mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-09 03:17:54 +00:00
fix: sanitizeReturnType dot corruption, JS test assertions, Ruby constant receiver
- Remove redundant dot-path stripping from sanitizeReturnType that corrupted qualified names inside generics (e.g. Promise<models.User> → User>) - Split JS async fixture into separate files and add negative assertions to properly verify disambiguation (mirroring PHP test pattern) - Accept 'constant' node type in Ruby scanConstructorBinding for factory call assignments (SERVICE = build_service()) - Add 'constant' to SIMPLE_RECEIVER_TYPES so extractReceiverName handles Ruby constant receivers (SERVICE.process)
This commit is contained in:
parent
fda19a915c
commit
9fbbe2da8d
13 changed files with 155 additions and 28 deletions
|
|
@ -226,7 +226,7 @@ const scanConstructorBinding: ConstructorBindingScanner = (node) => {
|
|||
const left = node.childForFieldName('left');
|
||||
const right = node.childForFieldName('right');
|
||||
if (!left || !right) return undefined;
|
||||
if (left.type !== 'identifier') return undefined;
|
||||
if (left.type !== 'identifier' && left.type !== 'constant') return undefined;
|
||||
if (right.type !== 'call') return undefined;
|
||||
const method = right.childForFieldName('method');
|
||||
if (!method) return undefined;
|
||||
|
|
|
|||
|
|
@ -168,9 +168,6 @@ const sanitizeReturnType = (raw: string): string | undefined => {
|
|||
if (type.startsWith('?') || type.startsWith('!')) type = type.slice(1);
|
||||
// Strip module: prefix — module:models.User → models.User
|
||||
if (type.startsWith('module:')) type = type.slice(7);
|
||||
// Take last segment of dotted path: models.User → User
|
||||
const dotIdx = type.lastIndexOf('.');
|
||||
if (dotIdx >= 0) type = type.slice(dotIdx + 1);
|
||||
// Reject unions (ambiguous)
|
||||
if (type.includes('|')) return undefined;
|
||||
if (!type) return undefined;
|
||||
|
|
|
|||
|
|
@ -804,6 +804,7 @@ const SIMPLE_RECEIVER_TYPES = new Set([
|
|||
'super_expression', // Kotlin wraps super in super_expression
|
||||
'base', // C# base.Method()
|
||||
'parent', // PHP parent::method()
|
||||
'constant', // Ruby CONSTANT.method() (uppercase identifiers)
|
||||
]);
|
||||
|
||||
export const extractReceiverName = (
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
const { User, Repo } = require('./models');
|
||||
const { User } = require('./user');
|
||||
const { Repo } = require('./repo');
|
||||
|
||||
/**
|
||||
* @returns {Promise<User>}
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
class User {
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
save() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class Repo {
|
||||
constructor(path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
save() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { User, Repo };
|
||||
11
gitnexus/test/fixtures/lang-resolution/js-jsdoc-async-return-type/repo.js
vendored
Normal file
11
gitnexus/test/fixtures/lang-resolution/js-jsdoc-async-return-type/repo.js
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
class Repo {
|
||||
constructor(path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
save() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { Repo };
|
||||
11
gitnexus/test/fixtures/lang-resolution/js-jsdoc-async-return-type/user.js
vendored
Normal file
11
gitnexus/test/fixtures/lang-resolution/js-jsdoc-async-return-type/user.js
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
class User {
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
save() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { User };
|
||||
13
gitnexus/test/fixtures/lang-resolution/js-jsdoc-qualified-return-type/app.js
vendored
Normal file
13
gitnexus/test/fixtures/lang-resolution/js-jsdoc-qualified-return-type/app.js
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
const { User } = require('./user');
|
||||
|
||||
/**
|
||||
* @returns {Promise<models.User>}
|
||||
*/
|
||||
async function fetchUser(name) {
|
||||
return new User(name);
|
||||
}
|
||||
|
||||
async function processUser() {
|
||||
const user = await fetchUser('alice');
|
||||
user.save();
|
||||
}
|
||||
11
gitnexus/test/fixtures/lang-resolution/js-jsdoc-qualified-return-type/user.js
vendored
Normal file
11
gitnexus/test/fixtures/lang-resolution/js-jsdoc-qualified-return-type/user.js
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
class User {
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
save() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { User };
|
||||
10
gitnexus/test/fixtures/lang-resolution/ruby-constant-factory-call/app.rb
vendored
Normal file
10
gitnexus/test/fixtures/lang-resolution/ruby-constant-factory-call/app.rb
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
require_relative 'user_service'
|
||||
|
||||
# @return [UserService]
|
||||
def build_service
|
||||
UserService.new
|
||||
end
|
||||
|
||||
SERVICE = build_service()
|
||||
SERVICE.process
|
||||
SERVICE.validate
|
||||
19
gitnexus/test/fixtures/lang-resolution/ruby-constant-factory-call/user_service.rb
vendored
Normal file
19
gitnexus/test/fixtures/lang-resolution/ruby-constant-factory-call/user_service.rb
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
class UserService
|
||||
def process
|
||||
true
|
||||
end
|
||||
|
||||
def validate
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
class AdminService
|
||||
def process
|
||||
true
|
||||
end
|
||||
|
||||
def validate
|
||||
true
|
||||
end
|
||||
end
|
||||
|
|
@ -641,3 +641,43 @@ describe('Ruby return type inference via function call', () => {
|
|||
expect(saveCall).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Ruby constant LHS factory call: SERVICE = build_service() with YARD @return
|
||||
// Verifies that constant assignments (uppercase LHS) from plain function calls
|
||||
// are captured by scanConstructorBinding, not just identifier assignments.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Ruby constant factory call resolution (SERVICE = build_service())', () => {
|
||||
let result: PipelineResult;
|
||||
|
||||
beforeAll(async () => {
|
||||
result = await runPipelineFromRepo(
|
||||
path.join(FIXTURES, 'ruby-constant-factory-call'),
|
||||
() => {},
|
||||
);
|
||||
}, 60000);
|
||||
|
||||
it('detects UserService and AdminService classes with process and validate methods', () => {
|
||||
expect(getNodesByLabel(result, 'Class')).toContain('UserService');
|
||||
expect(getNodesByLabel(result, 'Class')).toContain('AdminService');
|
||||
expect(getNodesByLabel(result, 'Method')).toContain('process');
|
||||
expect(getNodesByLabel(result, 'Method')).toContain('validate');
|
||||
});
|
||||
|
||||
it('resolves SERVICE.process() to UserService#process via constant factory call', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const processCall = calls.find(c =>
|
||||
c.target === 'process' && c.targetFilePath.includes('user_service.rb'),
|
||||
);
|
||||
expect(processCall).toBeDefined();
|
||||
});
|
||||
|
||||
it('resolves SERVICE.validate() to UserService#validate via constant factory call', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const validateCall = calls.find(c =>
|
||||
c.target === 'validate' && c.targetFilePath.includes('user_service.rb'),
|
||||
);
|
||||
expect(validateCall).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -968,15 +968,49 @@ describe('JavaScript async return type inference via JSDoc @returns {Promise<Use
|
|||
it('resolves user.save() to User#save via @returns {Promise<User>} unwrapping', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const saveCall = calls.find(c =>
|
||||
c.target === 'save' && c.source === 'processUser' && c.targetFilePath.includes('models.js'),
|
||||
c.target === 'save' && c.source === 'processUser' && c.targetFilePath.includes('user.js'),
|
||||
);
|
||||
expect(saveCall).toBeDefined();
|
||||
// Negative: must NOT resolve to Repo#save
|
||||
const wrongCall = calls.find(c =>
|
||||
c.target === 'save' && c.source === 'processUser' && c.targetFilePath.includes('repo.js'),
|
||||
);
|
||||
expect(wrongCall).toBeUndefined();
|
||||
});
|
||||
|
||||
it('resolves repo.save() to Repo#save via @returns {Promise<Repo>} unwrapping', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const saveCall = calls.find(c =>
|
||||
c.target === 'save' && c.source === 'processRepo' && c.targetFilePath.includes('models.js'),
|
||||
c.target === 'save' && c.source === 'processRepo' && c.targetFilePath.includes('repo.js'),
|
||||
);
|
||||
expect(saveCall).toBeDefined();
|
||||
// Negative: must NOT resolve to User#save
|
||||
const wrongCall = calls.find(c =>
|
||||
c.target === 'save' && c.source === 'processRepo' && c.targetFilePath.includes('user.js'),
|
||||
);
|
||||
expect(wrongCall).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// JavaScript qualified return type: @returns {Promise<models.User>}
|
||||
// Verifies that dot-qualified names inside generics are not corrupted.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('JavaScript qualified return type via JSDoc @returns {Promise<models.User>}', () => {
|
||||
let result: PipelineResult;
|
||||
|
||||
beforeAll(async () => {
|
||||
result = await runPipelineFromRepo(
|
||||
path.join(FIXTURES, 'js-jsdoc-qualified-return-type'),
|
||||
() => {},
|
||||
);
|
||||
}, 60000);
|
||||
|
||||
it('resolves user.save() to User#save despite qualified return type', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const saveCall = calls.find(c =>
|
||||
c.target === 'save' && c.source === 'processUser' && c.targetFilePath.includes('user.js'),
|
||||
);
|
||||
expect(saveCall).toBeDefined();
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue