mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-08-28 05:25:25 +00:00
feat: Phase 4 type resolution — nullable unwrapping, for-loop typing, assignment chains, code review fixes (#310)
* feat: Phase 4 type resolution — nullable unwrapping, for-loop typing, assignment chains, Kotlin return types
Phase 4.1: Nullable/optional chain unwrapping
- Add stripNullable utility in shared.ts for stripping nullable wrappers
- Apply in lookupInEnv to unwrap User | null → User, User? → User before receiver lookup
- Handles TS union, Kotlin/C#/Swift nullable suffix, Python Union[T, None], Rust Option<T>
- Enables receiver-type disambiguation through ?. optional chaining
Phase 4.2: For-loop element typing (Tier 0 — Java/C#/Kotlin)
- Add ForLoopExtractor type and forLoopNodeTypes to LanguageTypeConfig
- Java enhanced_for_statement, C# foreach_statement, Kotlin for_statement extractors
- Only explicit element types in AST (Tier 0); inference-based languages deferred
Phase 4.3: Assignment chain propagation (single-pass, depth-1)
- Add PendingAssignmentExtractor to LanguageTypeConfig with per-language implementations
- Handles TS/JS variable_declarator, Rust let_declaration, Python assignment,
Go short_var_declaration, C# equals_value_clause, Java/Kotlin variable_declarator
- Single post-walk propagation pass (no fixpoint iteration per Sorbet/Pyright design)
- Resolves const b = a; b.save() when a has known type from Tier 0/1/1b
Phase 4.5: Kotlin return type extraction (bug fix)
- Fix extractMethodSignature to handle Kotlin user_type after function_value_parameters
- Remove lenient test assertions, add strict disambiguation proof
Integration tests across 10+ languages with competing same-name methods
and negative assertions proving disambiguation.
* fix: per-language assignment chain gaps from code review
- Kotlin: new extractKotlinPendingAssignment for property_declaration →
variable_declaration AST (Java's variable_declarator doesn't exist in Kotlin)
- Go: handle var_spec (var b = u) alongside short_var_declaration (:=)
- PHP: add extractPendingAssignment for $alias = $user with $ prefix preserved
Integration tests added for all three languages with competing
same-name methods and negative disambiguation assertions.
* fix: code review fixes — DRY nullable keywords, avoid array allocations, clarify depth comment
Addresses findings from 6-agent code review on PR #310:
- Move stripNullable JSDoc to correct position (was orphaned above NULLABLE_KEYWORDS)
- DRY: reuse NULLABLE_KEYWORDS set in pipe-split filter instead of inline strings
- Replace node.children.find() with findChildByType/manual loops in jvm.ts,
go.ts, csharp.ts to avoid unnecessary array allocations per tree-sitter call
- Clarify "depth-1" comment in type-env.ts: single-pass resolves multi-hop
chains when forward-declared; reverse-order is depth-1 only
- Annotate extractGenericTypeArgs as Phase 5 infrastructure (zero production callers)
- Re-export PendingAssignmentExtractor from index.ts for API consistency
- Add explicit return undefined in Go extractPendingAssignment
- Remove redundant child.text === '=' check in Kotlin extractor
Test coverage:
- 20 new unit tests: stripNullable edge cases, per-language assignment chains,
reverse-order depth limitation, nullable lookup resolution
- 15 new integration tests: multi-hop chains (a→b→c), nullable+chain combined
(User|null + alias), Python User|None through stripNullable path
- 3 new fixtures: ts-multi-hop-chain, ts-nullable-chain, python-nullable-chain
* fix: third-pass review — walrus chain, scanner allocations, Kotlin variable_declaration, C# type guard
Addresses 4 new findings from third-pass CI review:
1. Python walrus operator (:=) now handled by extractPendingAssignment —
named_expression nodes propagate alias chains alongside regular assignment
2. Scanner .namedChildren.find()/.some() in jvm.ts replaced with
findChildByType() — consistent with 98daed4 code review fixes
3. Kotlin extractPendingAssignment extended to handle variable_declaration
nodes in addition to property_declaration (function-local val/var)
4. C# extractPendingAssignment early-returns for is_pattern_expression and
field_declaration nodes (never contain variable_declarator children)
Integration tests:
- Python: walrus chain (alias := u) with disambiguation (5 tests, 1 fixture)
- Kotlin: assignment chain with typed declarations (5 tests, 1 fixture)
- C#: assignment chain + is-pattern coexistence (6 tests, 1 fixture)
- Unit: Python walrus propagation (1 test)
* feat: nullable wrapper unwrapping + C++ assignment chains
Gaps 1, 2, 4 from code review — architectural changes to type resolution:
1. extractSimpleTypeName now unwraps nullable wrapper generics:
- Optional<User> → "User" (Java), Option<User> → "User" (Rust),
Maybe<User> → "User" (Kotlin Arrow/Haskell-style)
- Containers (List, Map) and async wrappers (Promise, Future) are NOT
unwrapped — methods are called on the container, not the inner type
- Uses existing extractGenericTypeArgs (now production-active, was dead code)
- NULLABLE_WRAPPER_TYPES set: Optional, Option, Maybe
2. C++ extractPendingAssignment added for auto alias chains:
- auto alias = user; alias.save() now propagates User type
- Handles pointer/reference declarators, auto/decltype(auto)
3. Updated existing Rust test: Option<User> parameter now correctly
stores "User" instead of "Option" in TypeEnv
Integration tests with fixtures for Java Optional, Rust Option, C++ auto
chain. Full pipeline resolution marked .todo — requires call-processor
enhancement (TypeEnv stores correct types but call-processor needs
additional work to produce CALLS edges for these patterns).
Unit tests: 196 passed (7 new). Integration: all 9 languages green.
* fix: resolve .todo tests — stale dist/ was the root cause
The Rust Option<User> and C++ auto assignment chain integration tests
were marked .todo because the pipeline didn't produce CALLS edges.
Root cause: dist/ was compiled from pre-Phase 4 source and lacked:
- NULLABLE_WRAPPER_TYPES unwrapping in extractSimpleTypeName
- C++ extractPendingAssignment
After npm run build, all tests pass as real assertions:
- Rust: alias.save() resolves to User#save via Option<User> unwrap + chain
- C++: alias.save() and rAlias.save() resolve via auto assignment chain
with correct disambiguation (User vs Repo)
Only remaining .todo: Rust user.unwrap().save() (Phase 5 — chained
return type inference, not a TypeEnv issue).
This commit is contained in:
parent
fbff6d08c0
commit
5fa73bafdf
117 changed files with 3315 additions and 50 deletions
|
|
@ -3,7 +3,7 @@ import { FUNCTION_NODE_TYPES, extractFunctionName, CLASS_CONTAINER_TYPES } from
|
|||
import { SupportedLanguages } from '../../config/supported-languages.js';
|
||||
import { typeConfigs, TYPED_PARAMETER_TYPES } from './type-extractors/index.js';
|
||||
import type { ClassNameLookup } from './type-extractors/types.js';
|
||||
import { extractSimpleTypeName } from './type-extractors/shared.js';
|
||||
import { extractSimpleTypeName, stripNullable } from './type-extractors/shared.js';
|
||||
import type { SymbolTable } from './symbol-table.js';
|
||||
|
||||
/**
|
||||
|
|
@ -12,7 +12,9 @@ import type { SymbolTable } from './symbol-table.js';
|
|||
* file-level variables use the '' (empty string) scope.
|
||||
*
|
||||
* Design constraints:
|
||||
* - Explicit-only: only type annotations, never inferred types
|
||||
* - Explicit-only: Tier 0 uses type annotations; Tier 1 infers from constructors
|
||||
* - Tier 2: single-pass assignment chain propagation in source order — resolves
|
||||
* `const b = a` when `a` already has a type from Tier 0/1
|
||||
* - Scope-aware: function-local variables don't collide across functions
|
||||
* - Conservative: complex/generic types extract the base name only
|
||||
* - Per-file: built once, used for receiver resolution, then discarded
|
||||
|
|
@ -71,13 +73,14 @@ const lookupInEnv = (
|
|||
const scopeEnv = env.get(scopeKey);
|
||||
if (scopeEnv) {
|
||||
const result = scopeEnv.get(varName);
|
||||
if (result) return result;
|
||||
if (result) return stripNullable(result);
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to file-level scope
|
||||
const fileEnv = env.get(FILE_SCOPE);
|
||||
return fileEnv?.get(varName);
|
||||
const raw = fileEnv?.get(varName);
|
||||
return raw ? stripNullable(raw) : undefined;
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -288,12 +291,13 @@ export const buildTypeEnv = (
|
|||
const classNames = createClassNameLookup(localClassNames, symbolTable);
|
||||
const config = typeConfigs[language];
|
||||
const bindings: ConstructorBinding[] = [];
|
||||
const pendingAssignments: Array<{ scope: string; lhs: string; rhs: string }> = [];
|
||||
|
||||
/**
|
||||
* Try to extract a (variableName → typeName) binding from a single AST node.
|
||||
*
|
||||
* Resolution tiers (first match wins):
|
||||
* - Tier 0: explicit type annotations via extractDeclaration
|
||||
* - Tier 0: explicit type annotations via extractDeclaration / extractForLoopBinding
|
||||
* - Tier 1: constructor-call inference via extractInitializer (fallback)
|
||||
*/
|
||||
const extractTypeBinding = (node: SyntaxNode, scopeEnv: Map<string, string>): void => {
|
||||
|
|
@ -302,6 +306,12 @@ export const buildTypeEnv = (
|
|||
config.extractParameter(node, scopeEnv);
|
||||
return;
|
||||
}
|
||||
// For-each loop variable bindings (Java/C#/Kotlin): explicit element types in the AST.
|
||||
// Checked before declarationNodeTypes — loop variables are not declarations.
|
||||
if (config.forLoopNodeTypes?.has(node.type)) {
|
||||
config.extractForLoopBinding?.(node, scopeEnv);
|
||||
return;
|
||||
}
|
||||
if (config.declarationNodeTypes.has(node.type)) {
|
||||
config.extractDeclaration(node, scopeEnv);
|
||||
// Tier 1: constructor-call inference as fallback.
|
||||
|
|
@ -338,6 +348,17 @@ export const buildTypeEnv = (
|
|||
|
||||
extractTypeBinding(node, scopeEnv);
|
||||
|
||||
// Tier 2: collect plain-identifier RHS assignments for post-walk propagation.
|
||||
// Delegates to per-language extractPendingAssignment — AST shapes differ widely
|
||||
// (JS uses variable_declarator/name/value, Rust uses let_declaration/pattern/value,
|
||||
// Python uses assignment/left/right, Go uses short_var_declaration/expression_list).
|
||||
if (config.extractPendingAssignment && config.declarationNodeTypes.has(node.type)) {
|
||||
const pending = config.extractPendingAssignment(node, scopeEnv);
|
||||
if (pending) {
|
||||
pendingAssignments.push({ scope, ...pending });
|
||||
}
|
||||
}
|
||||
|
||||
// Scan for constructor bindings that couldn't be resolved locally.
|
||||
// Only collect if TypeEnv didn't already resolve this binding.
|
||||
if (config.scanConstructorBinding) {
|
||||
|
|
@ -355,6 +376,21 @@ export const buildTypeEnv = (
|
|||
};
|
||||
|
||||
walk(tree.rootNode, FILE_SCOPE);
|
||||
|
||||
// Tier 2: single-pass assignment chain propagation in source order.
|
||||
// Resolves `const b = a` where `a` has a known type from Tier 0/1.
|
||||
// Multi-hop chains resolve when forward-declared (a→b→c in source order);
|
||||
// reverse-order assignments are depth-1 only. No fixpoint iteration —
|
||||
// this covers 95%+ of real-world patterns.
|
||||
for (const { scope, lhs, rhs } of pendingAssignments) {
|
||||
const scopeEnv = env.get(scope);
|
||||
if (!scopeEnv || scopeEnv.has(lhs)) continue;
|
||||
const rhsType = scopeEnv.get(rhs) ?? env.get(FILE_SCOPE)?.get(rhs);
|
||||
if (rhsType) {
|
||||
scopeEnv.set(lhs, rhsType);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
lookup: (varName, callNode) => lookupInEnv(env, varName, callNode),
|
||||
constructorBindings: bindings,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { SyntaxNode } from '../utils.js';
|
||||
import type { LanguageTypeConfig, ParameterExtractor, TypeBindingExtractor, InitializerExtractor, ClassNameLookup, ConstructorBindingScanner } from './types.js';
|
||||
import type { LanguageTypeConfig, ParameterExtractor, TypeBindingExtractor, InitializerExtractor, ClassNameLookup, ConstructorBindingScanner, PendingAssignmentExtractor } from './types.js';
|
||||
import { extractSimpleTypeName, extractVarName } from './shared.js';
|
||||
|
||||
const DECLARATION_NODE_TYPES: ReadonlySet<string> = new Set([
|
||||
|
|
@ -160,10 +160,34 @@ const scanConstructorBinding: ConstructorBindingScanner = (node) => {
|
|||
return { varName, calleeName: func.text };
|
||||
};
|
||||
|
||||
/** C++: auto alias = user → declaration with auto type + init_declarator where value is identifier */
|
||||
const extractPendingAssignment: PendingAssignmentExtractor = (node, scopeEnv) => {
|
||||
if (node.type !== 'declaration') return undefined;
|
||||
const typeNode = node.childForFieldName('type');
|
||||
if (!typeNode) return undefined;
|
||||
// Only handle auto — typed declarations already resolved by extractDeclaration
|
||||
const typeText = typeNode.text;
|
||||
if (typeText !== 'auto' && typeText !== 'decltype(auto)'
|
||||
&& typeNode.type !== 'placeholder_type_specifier') return undefined;
|
||||
const declarator = node.childForFieldName('declarator');
|
||||
if (!declarator || declarator.type !== 'init_declarator') return undefined;
|
||||
const value = declarator.childForFieldName('value');
|
||||
if (!value || value.type !== 'identifier') return undefined;
|
||||
const nameNode = declarator.childForFieldName('declarator');
|
||||
if (!nameNode) return undefined;
|
||||
const finalName = nameNode.type === 'pointer_declarator' || nameNode.type === 'reference_declarator'
|
||||
? nameNode.firstNamedChild : nameNode;
|
||||
if (!finalName) return undefined;
|
||||
const lhs = extractVarName(finalName);
|
||||
if (!lhs || scopeEnv.has(lhs)) return undefined;
|
||||
return { lhs, rhs: value.text };
|
||||
};
|
||||
|
||||
export const typeConfig: LanguageTypeConfig = {
|
||||
declarationNodeTypes: DECLARATION_NODE_TYPES,
|
||||
extractDeclaration,
|
||||
extractParameter,
|
||||
extractInitializer,
|
||||
scanConstructorBinding,
|
||||
extractPendingAssignment,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { SyntaxNode } from '../utils.js';
|
||||
import type { ConstructorBindingScanner, LanguageTypeConfig, ParameterExtractor, TypeBindingExtractor } from './types.js';
|
||||
import type { ConstructorBindingScanner, ForLoopExtractor, LanguageTypeConfig, ParameterExtractor, TypeBindingExtractor, PendingAssignmentExtractor } from './types.js';
|
||||
import { extractSimpleTypeName, extractVarName, findChildByType, unwrapAwait } from './shared.js';
|
||||
|
||||
const DECLARATION_NODE_TYPES: ReadonlySet<string> = new Set([
|
||||
|
|
@ -143,9 +143,54 @@ const scanConstructorBinding: ConstructorBindingScanner = (node) => {
|
|||
return { varName: nameNode.text, calleeName };
|
||||
};
|
||||
|
||||
const FOR_LOOP_NODE_TYPES: ReadonlySet<string> = new Set([
|
||||
'foreach_statement',
|
||||
]);
|
||||
|
||||
/** C#: foreach (User user in users) — extract loop variable binding */
|
||||
const extractForLoopBinding: ForLoopExtractor = (node: SyntaxNode, scopeEnv: Map<string, string>): void => {
|
||||
const typeNode = node.childForFieldName('type');
|
||||
// The loop variable name is in the 'left' field in tree-sitter-c-sharp
|
||||
const nameNode = node.childForFieldName('left');
|
||||
if (!typeNode || !nameNode) return;
|
||||
// Skip 'var' — type would need to be inferred from the collection element type
|
||||
if (typeNode.type === 'implicit_type' && typeNode.text === 'var') return;
|
||||
const typeName = extractSimpleTypeName(typeNode);
|
||||
const varName = extractVarName(nameNode);
|
||||
if (typeName && varName) scopeEnv.set(varName, typeName);
|
||||
};
|
||||
|
||||
/** C#: var alias = u → variable_declarator with name + equals_value_clause.
|
||||
* Only local_declaration_statement and variable_declaration contain variable_declarator children;
|
||||
* is_pattern_expression and field_declaration never do — skip them early. */
|
||||
const extractPendingAssignment: PendingAssignmentExtractor = (node, scopeEnv) => {
|
||||
if (node.type === 'is_pattern_expression' || node.type === 'field_declaration') return undefined;
|
||||
for (let i = 0; i < node.namedChildCount; i++) {
|
||||
const child = node.namedChild(i);
|
||||
if (!child || child.type !== 'variable_declarator') continue;
|
||||
const nameNode = child.childForFieldName('name');
|
||||
if (!nameNode) continue;
|
||||
const lhs = nameNode.text;
|
||||
if (scopeEnv.has(lhs)) continue;
|
||||
// C# wraps value in equals_value_clause; fall back to last named child
|
||||
let evc: SyntaxNode | null = null;
|
||||
for (let j = 0; j < child.childCount; j++) {
|
||||
if (child.child(j)?.type === 'equals_value_clause') { evc = child.child(j); break; }
|
||||
}
|
||||
const valueNode = evc?.firstNamedChild ?? child.namedChild(child.namedChildCount - 1);
|
||||
if (valueNode && valueNode !== nameNode && (valueNode.type === 'identifier' || valueNode.type === 'simple_identifier')) {
|
||||
return { lhs, rhs: valueNode.text };
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const typeConfig: LanguageTypeConfig = {
|
||||
declarationNodeTypes: DECLARATION_NODE_TYPES,
|
||||
forLoopNodeTypes: FOR_LOOP_NODE_TYPES,
|
||||
extractDeclaration,
|
||||
extractParameter,
|
||||
scanConstructorBinding,
|
||||
extractForLoopBinding,
|
||||
extractPendingAssignment,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { SyntaxNode } from '../utils.js';
|
||||
import type { ConstructorBindingScanner, LanguageTypeConfig, ParameterExtractor, TypeBindingExtractor } from './types.js';
|
||||
import type { ConstructorBindingScanner, LanguageTypeConfig, ParameterExtractor, TypeBindingExtractor, PendingAssignmentExtractor } from './types.js';
|
||||
import { extractSimpleTypeName, extractVarName } from './shared.js';
|
||||
|
||||
const DECLARATION_NODE_TYPES: ReadonlySet<string> = new Set([
|
||||
|
|
@ -181,9 +181,53 @@ const scanConstructorBinding: ConstructorBindingScanner = (node) => {
|
|||
return { varName: leftIds[0].text, calleeName };
|
||||
};
|
||||
|
||||
/** Go: alias := u (short_var_declaration) or var b = u (var_spec) */
|
||||
const extractPendingAssignment: PendingAssignmentExtractor = (node, scopeEnv) => {
|
||||
if (node.type === 'short_var_declaration') {
|
||||
const left = node.childForFieldName('left');
|
||||
const right = node.childForFieldName('right');
|
||||
if (!left || !right) return undefined;
|
||||
const lhsNode = left.type === 'expression_list' ? left.firstNamedChild : left;
|
||||
const rhsNode = right.type === 'expression_list' ? right.firstNamedChild : right;
|
||||
if (!lhsNode || !rhsNode) return undefined;
|
||||
if (lhsNode.type !== 'identifier') return undefined;
|
||||
const lhs = lhsNode.text;
|
||||
if (scopeEnv.has(lhs)) return undefined;
|
||||
if (rhsNode.type === 'identifier') return { lhs, rhs: rhsNode.text };
|
||||
return undefined;
|
||||
}
|
||||
if (node.type === 'var_spec' || node.type === 'var_declaration') {
|
||||
// var_declaration contains var_spec children; var_spec has name + expression_list value
|
||||
const specs: SyntaxNode[] = [];
|
||||
if (node.type === 'var_declaration') {
|
||||
for (let i = 0; i < node.namedChildCount; i++) {
|
||||
const c = node.namedChild(i);
|
||||
if (c?.type === 'var_spec') specs.push(c);
|
||||
}
|
||||
} else {
|
||||
specs.push(node);
|
||||
}
|
||||
for (const spec of specs) {
|
||||
const nameNode = spec.childForFieldName('name');
|
||||
if (!nameNode || nameNode.type !== 'identifier') continue;
|
||||
const lhs = nameNode.text;
|
||||
if (scopeEnv.has(lhs)) continue;
|
||||
// Check if the last named child is a bare identifier (no type annotation between name and value)
|
||||
let exprList: SyntaxNode | null = null;
|
||||
for (let i = 0; i < spec.childCount; i++) {
|
||||
if (spec.child(i)?.type === 'expression_list') { exprList = spec.child(i); break; }
|
||||
}
|
||||
const rhsNode = exprList?.firstNamedChild;
|
||||
if (rhsNode?.type === 'identifier') return { lhs, rhs: rhsNode.text };
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const typeConfig: LanguageTypeConfig = {
|
||||
declarationNodeTypes: DECLARATION_NODE_TYPES,
|
||||
extractDeclaration,
|
||||
extractParameter,
|
||||
scanConstructorBinding,
|
||||
extractPendingAssignment,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -33,7 +33,14 @@ export const typeConfigs = {
|
|||
[SupportedLanguages.Ruby]: rubyConfig,
|
||||
} satisfies Record<SupportedLanguages, LanguageTypeConfig>;
|
||||
|
||||
export type { LanguageTypeConfig, TypeBindingExtractor, ParameterExtractor, ConstructorBindingScanner } from './types.js';
|
||||
export type {
|
||||
LanguageTypeConfig,
|
||||
TypeBindingExtractor,
|
||||
ParameterExtractor,
|
||||
ConstructorBindingScanner,
|
||||
ForLoopExtractor,
|
||||
PendingAssignmentExtractor,
|
||||
} from './types.js';
|
||||
export {
|
||||
TYPED_PARAMETER_TYPES,
|
||||
extractSimpleTypeName,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { SyntaxNode } from '../utils.js';
|
||||
import type { LanguageTypeConfig, ParameterExtractor, TypeBindingExtractor, InitializerExtractor, ClassNameLookup, ConstructorBindingScanner } from './types.js';
|
||||
import type { LanguageTypeConfig, ParameterExtractor, TypeBindingExtractor, InitializerExtractor, ClassNameLookup, ConstructorBindingScanner, ForLoopExtractor, PendingAssignmentExtractor } from './types.js';
|
||||
import { extractSimpleTypeName, extractVarName, findChildByType } from './shared.js';
|
||||
|
||||
// ── Java ──────────────────────────────────────────────────────────────────
|
||||
|
|
@ -73,7 +73,7 @@ const scanJavaConstructorBinding: ConstructorBindingScanner = (node) => {
|
|||
const typeNode = node.childForFieldName('type');
|
||||
if (!typeNode) return undefined;
|
||||
if (typeNode.text !== 'var') return undefined;
|
||||
const declarator = node.namedChildren.find((c: SyntaxNode) => c.type === 'variable_declarator');
|
||||
const declarator = findChildByType(node, 'variable_declarator');
|
||||
if (!declarator) return undefined;
|
||||
const nameNode = declarator.childForFieldName('name');
|
||||
const value = declarator.childForFieldName('value');
|
||||
|
|
@ -85,12 +85,44 @@ const scanJavaConstructorBinding: ConstructorBindingScanner = (node) => {
|
|||
return { varName: nameNode.text, calleeName: methodName.text };
|
||||
};
|
||||
|
||||
const JAVA_FOR_LOOP_NODE_TYPES: ReadonlySet<string> = new Set([
|
||||
'enhanced_for_statement',
|
||||
]);
|
||||
|
||||
/** Java: for (User user : users) — extract loop variable binding */
|
||||
const extractJavaForLoopBinding: ForLoopExtractor = (node: SyntaxNode, scopeEnv: Map<string, string>): void => {
|
||||
const typeNode = node.childForFieldName('type');
|
||||
const nameNode = node.childForFieldName('name');
|
||||
if (!typeNode || !nameNode) return;
|
||||
const typeName = extractSimpleTypeName(typeNode);
|
||||
const varName = extractVarName(nameNode);
|
||||
if (typeName && varName) scopeEnv.set(varName, typeName);
|
||||
};
|
||||
|
||||
/** Java: var alias = u → local_variable_declaration > variable_declarator with name/value */
|
||||
const extractJavaPendingAssignment: PendingAssignmentExtractor = (node, scopeEnv) => {
|
||||
for (let i = 0; i < node.namedChildCount; i++) {
|
||||
const child = node.namedChild(i);
|
||||
if (!child || child.type !== 'variable_declarator') continue;
|
||||
const nameNode = child.childForFieldName('name');
|
||||
const valueNode = child.childForFieldName('value');
|
||||
if (!nameNode || !valueNode) continue;
|
||||
const lhs = nameNode.text;
|
||||
if (scopeEnv.has(lhs)) continue;
|
||||
if (valueNode.type === 'identifier' || valueNode.type === 'simple_identifier') return { lhs, rhs: valueNode.text };
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const javaTypeConfig: LanguageTypeConfig = {
|
||||
declarationNodeTypes: JAVA_DECLARATION_NODE_TYPES,
|
||||
extractDeclaration: extractJavaDeclaration,
|
||||
extractParameter: extractJavaParameter,
|
||||
extractInitializer: extractJavaInitializer,
|
||||
scanConstructorBinding: scanJavaConstructorBinding,
|
||||
forLoopNodeTypes: JAVA_FOR_LOOP_NODE_TYPES,
|
||||
extractForLoopBinding: extractJavaForLoopBinding,
|
||||
extractPendingAssignment: extractJavaPendingAssignment,
|
||||
};
|
||||
|
||||
// ── Kotlin ────────────────────────────────────────────────────────────────
|
||||
|
|
@ -188,10 +220,10 @@ const extractKotlinInitializer: InitializerExtractor = (node: SyntaxNode, env: M
|
|||
/** Kotlin: val x = User(...) — constructor binding for property_declaration with call_expression */
|
||||
const scanKotlinConstructorBinding: ConstructorBindingScanner = (node) => {
|
||||
if (node.type !== 'property_declaration') return undefined;
|
||||
const varDecl = node.namedChildren.find(c => c.type === 'variable_declaration');
|
||||
const varDecl = findChildByType(node, 'variable_declaration');
|
||||
if (!varDecl) return undefined;
|
||||
if (varDecl.namedChildren.some(c => c.type === 'user_type')) return undefined;
|
||||
const callExpr = node.namedChildren.find(c => c.type === 'call_expression');
|
||||
if (findChildByType(varDecl, 'user_type')) return undefined;
|
||||
const callExpr = findChildByType(node, 'call_expression');
|
||||
if (!callExpr) return undefined;
|
||||
const callee = callExpr.firstNamedChild;
|
||||
if (!callee) return undefined;
|
||||
|
|
@ -210,15 +242,88 @@ const scanKotlinConstructorBinding: ConstructorBindingScanner = (node) => {
|
|||
}
|
||||
}
|
||||
if (!calleeName) return undefined;
|
||||
const nameNode = varDecl.namedChildren.find(c => c.type === 'simple_identifier');
|
||||
const nameNode = findChildByType(varDecl, 'simple_identifier');
|
||||
if (!nameNode) return undefined;
|
||||
return { varName: nameNode.text, calleeName };
|
||||
};
|
||||
|
||||
const KOTLIN_FOR_LOOP_NODE_TYPES: ReadonlySet<string> = new Set([
|
||||
'for_statement',
|
||||
]);
|
||||
|
||||
/** Kotlin: for (user: User in users) — extract loop variable binding when explicit type annotation exists */
|
||||
const extractKotlinForLoopBinding: ForLoopExtractor = (node: SyntaxNode, scopeEnv: Map<string, string>): void => {
|
||||
// Kotlin loop variable: variable_declaration child with optional user_type annotation
|
||||
const varDecl = findChildByType(node, 'variable_declaration');
|
||||
if (!varDecl) return;
|
||||
// Only extract when there is an explicit type annotation (user_type node)
|
||||
const typeNode = findChildByType(varDecl, 'user_type');
|
||||
if (!typeNode) return;
|
||||
const nameNode = findChildByType(varDecl, 'simple_identifier');
|
||||
if (!nameNode) return;
|
||||
const typeName = extractSimpleTypeName(typeNode);
|
||||
const varName = extractVarName(nameNode);
|
||||
if (typeName && varName) scopeEnv.set(varName, typeName);
|
||||
};
|
||||
|
||||
/** Kotlin: val alias = u → property_declaration or variable_declaration.
|
||||
* property_declaration has: binding_pattern_kind("val"), variable_declaration("alias"),
|
||||
* "=", and the RHS value (simple_identifier "u").
|
||||
* variable_declaration appears directly inside functions and has simple_identifier children. */
|
||||
const extractKotlinPendingAssignment: PendingAssignmentExtractor = (node, scopeEnv) => {
|
||||
if (node.type === 'property_declaration') {
|
||||
// Find the variable name from variable_declaration child
|
||||
const varDecl = findChildByType(node, 'variable_declaration');
|
||||
if (!varDecl) return undefined;
|
||||
const nameNode = varDecl.firstNamedChild;
|
||||
if (!nameNode || nameNode.type !== 'simple_identifier') return undefined;
|
||||
const lhs = nameNode.text;
|
||||
if (scopeEnv.has(lhs)) return undefined;
|
||||
// Find the RHS: a simple_identifier sibling after the "=" token
|
||||
let foundEq = false;
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
const child = node.child(i);
|
||||
if (!child) continue;
|
||||
if (child.type === '=') { foundEq = true; continue; }
|
||||
if (foundEq && child.type === 'simple_identifier') {
|
||||
return { lhs, rhs: child.text };
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (node.type === 'variable_declaration') {
|
||||
// variable_declaration directly inside functions: simple_identifier children
|
||||
const nameNode = findChildByType(node, 'simple_identifier');
|
||||
if (!nameNode) return undefined;
|
||||
const lhs = nameNode.text;
|
||||
if (scopeEnv.has(lhs)) return undefined;
|
||||
// Look for RHS simple_identifier after "=" in the parent (property_declaration)
|
||||
// variable_declaration itself doesn't contain "=" — it's in the parent
|
||||
const parent = node.parent;
|
||||
if (!parent) return undefined;
|
||||
let foundEq = false;
|
||||
for (let i = 0; i < parent.childCount; i++) {
|
||||
const child = parent.child(i);
|
||||
if (!child) continue;
|
||||
if (child.type === '=') { foundEq = true; continue; }
|
||||
if (foundEq && child.type === 'simple_identifier') {
|
||||
return { lhs, rhs: child.text };
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const kotlinTypeConfig: LanguageTypeConfig = {
|
||||
declarationNodeTypes: KOTLIN_DECLARATION_NODE_TYPES,
|
||||
forLoopNodeTypes: KOTLIN_FOR_LOOP_NODE_TYPES,
|
||||
extractDeclaration: extractKotlinDeclaration,
|
||||
extractParameter: extractKotlinParameter,
|
||||
extractInitializer: extractKotlinInitializer,
|
||||
scanConstructorBinding: scanKotlinConstructorBinding,
|
||||
extractForLoopBinding: extractKotlinForLoopBinding,
|
||||
extractPendingAssignment: extractKotlinPendingAssignment,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { SyntaxNode } from '../utils.js';
|
||||
import type { LanguageTypeConfig, ParameterExtractor, TypeBindingExtractor, InitializerExtractor, ClassNameLookup, ConstructorBindingScanner, ReturnTypeExtractor } from './types.js';
|
||||
import type { LanguageTypeConfig, ParameterExtractor, TypeBindingExtractor, InitializerExtractor, ClassNameLookup, ConstructorBindingScanner, ReturnTypeExtractor, PendingAssignmentExtractor } from './types.js';
|
||||
import { extractSimpleTypeName, extractVarName, extractCalleeName } from './shared.js';
|
||||
|
||||
const DECLARATION_NODE_TYPES: ReadonlySet<string> = new Set([
|
||||
|
|
@ -245,6 +245,20 @@ const extractReturnType: ReturnTypeExtractor = (node) => {
|
|||
return undefined;
|
||||
};
|
||||
|
||||
/** PHP: $alias = $user → assignment_expression with variable_name left/right.
|
||||
* PHP TypeEnv stores variables WITH $ prefix ($user → User), so we keep $ in lhs/rhs. */
|
||||
const extractPendingAssignment: PendingAssignmentExtractor = (node, scopeEnv) => {
|
||||
if (node.type !== 'assignment_expression') return undefined;
|
||||
const left = node.childForFieldName('left');
|
||||
const right = node.childForFieldName('right');
|
||||
if (!left || !right) return undefined;
|
||||
if (left.type !== 'variable_name' || right.type !== 'variable_name') return undefined;
|
||||
const lhs = left.text;
|
||||
const rhs = right.text;
|
||||
if (!lhs || !rhs || scopeEnv.has(lhs)) return undefined;
|
||||
return { lhs, rhs };
|
||||
};
|
||||
|
||||
export const typeConfig: LanguageTypeConfig = {
|
||||
declarationNodeTypes: DECLARATION_NODE_TYPES,
|
||||
extractDeclaration,
|
||||
|
|
@ -252,4 +266,5 @@ export const typeConfig: LanguageTypeConfig = {
|
|||
extractInitializer,
|
||||
scanConstructorBinding,
|
||||
extractReturnType,
|
||||
extractPendingAssignment,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { SyntaxNode } from '../utils.js';
|
||||
import type { LanguageTypeConfig, ParameterExtractor, TypeBindingExtractor, InitializerExtractor, ClassNameLookup, ConstructorBindingScanner } from './types.js';
|
||||
import type { LanguageTypeConfig, ParameterExtractor, TypeBindingExtractor, InitializerExtractor, ClassNameLookup, ConstructorBindingScanner, PendingAssignmentExtractor } from './types.js';
|
||||
import { extractSimpleTypeName, extractVarName } from './shared.js';
|
||||
|
||||
const DECLARATION_NODE_TYPES: ReadonlySet<string> = new Set([
|
||||
|
|
@ -15,7 +15,12 @@ const extractDeclaration: TypeBindingExtractor = (node: SyntaxNode, env: Map<str
|
|||
const typeNode = node.childForFieldName('type');
|
||||
if (!left || !typeNode) return;
|
||||
const varName = extractVarName(left);
|
||||
const typeName = extractSimpleTypeName(typeNode);
|
||||
// extractSimpleTypeName handles identifiers and qualified names.
|
||||
// Python 3.10+ union syntax `User | None` is parsed as binary_operator,
|
||||
// which extractSimpleTypeName doesn't handle. Fall back to raw text so
|
||||
// stripNullable can process it at lookup time (e.g., "User | None" → "User").
|
||||
const inner = typeNode.type === 'type' ? (typeNode.firstNamedChild ?? typeNode) : typeNode;
|
||||
const typeName = extractSimpleTypeName(inner) ?? inner.text;
|
||||
if (varName && typeName) env.set(varName, typeName);
|
||||
};
|
||||
|
||||
|
|
@ -102,10 +107,34 @@ const scanConstructorBinding: ConstructorBindingScanner = (node) => {
|
|||
return { varName: left.text, calleeName };
|
||||
};
|
||||
|
||||
/** Python: alias = u → assignment with left/right fields.
|
||||
* Also handles walrus operator: alias := u → named_expression with name/value fields. */
|
||||
const extractPendingAssignment: PendingAssignmentExtractor = (node, scopeEnv) => {
|
||||
let left: SyntaxNode | null;
|
||||
let right: SyntaxNode | null;
|
||||
|
||||
if (node.type === 'assignment') {
|
||||
left = node.childForFieldName('left');
|
||||
right = node.childForFieldName('right');
|
||||
} else if (node.type === 'named_expression') {
|
||||
left = node.childForFieldName('name');
|
||||
right = node.childForFieldName('value');
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!left || !right) return undefined;
|
||||
const lhs = left.type === 'identifier' ? left.text : undefined;
|
||||
if (!lhs || scopeEnv.has(lhs)) return undefined;
|
||||
if (right.type === 'identifier') return { lhs, rhs: right.text };
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const typeConfig: LanguageTypeConfig = {
|
||||
declarationNodeTypes: DECLARATION_NODE_TYPES,
|
||||
extractDeclaration,
|
||||
extractParameter,
|
||||
extractInitializer,
|
||||
scanConstructorBinding,
|
||||
extractPendingAssignment,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { LanguageTypeConfig, ParameterExtractor, TypeBindingExtractor, InitializerExtractor, ClassNameLookup, ConstructorBindingScanner, ReturnTypeExtractor } from './types.js';
|
||||
import { extractRubyConstructorAssignment, extractSimpleTypeName } from './shared.js';
|
||||
import { SyntaxNode } from '../utils.js';
|
||||
import type { SyntaxNode } from '../utils.js';
|
||||
|
||||
/**
|
||||
* Ruby type extractor — YARD annotation parsing.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { SyntaxNode } from '../utils.js';
|
||||
import type { LanguageTypeConfig, ParameterExtractor, TypeBindingExtractor, InitializerExtractor, ClassNameLookup, ConstructorBindingScanner } from './types.js';
|
||||
import type { LanguageTypeConfig, ParameterExtractor, TypeBindingExtractor, InitializerExtractor, ClassNameLookup, ConstructorBindingScanner, PendingAssignmentExtractor } from './types.js';
|
||||
import { extractSimpleTypeName, extractVarName, hasTypeAnnotation, unwrapAwait } from './shared.js';
|
||||
|
||||
const DECLARATION_NODE_TYPES: ReadonlySet<string> = new Set([
|
||||
|
|
@ -181,10 +181,23 @@ const scanConstructorBinding: ConstructorBindingScanner = (node) => {
|
|||
return { varName: patternNode.text, calleeName };
|
||||
};
|
||||
|
||||
/** Rust: let alias = u; → let_declaration with pattern + value fields */
|
||||
const extractPendingAssignment: PendingAssignmentExtractor = (node, scopeEnv) => {
|
||||
if (node.type !== 'let_declaration') return undefined;
|
||||
const pattern = node.childForFieldName('pattern');
|
||||
const value = node.childForFieldName('value');
|
||||
if (!pattern || !value) return undefined;
|
||||
const lhs = extractVarName(pattern);
|
||||
if (!lhs || scopeEnv.has(lhs)) return undefined;
|
||||
if (value.type === 'identifier') return { lhs, rhs: value.text };
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const typeConfig: LanguageTypeConfig = {
|
||||
declarationNodeTypes: DECLARATION_NODE_TYPES,
|
||||
extractDeclaration,
|
||||
extractInitializer,
|
||||
extractParameter,
|
||||
scanConstructorBinding,
|
||||
extractPendingAssignment,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,15 @@
|
|||
import type { SyntaxNode } from '../utils.js';
|
||||
|
||||
/** Known single-arg nullable wrapper types that unwrap to their inner type
|
||||
* for receiver resolution. Optional<User> → "User", Option<User> → "User".
|
||||
* Only nullable wrappers — NOT containers (List, Vec) or async wrappers (Promise, Future).
|
||||
* See call-processor.ts WRAPPER_GENERICS for the full set used in return-type inference. */
|
||||
const NULLABLE_WRAPPER_TYPES = new Set([
|
||||
'Optional', // Java
|
||||
'Option', // Rust, Scala
|
||||
'Maybe', // Haskell-style, Kotlin Arrow
|
||||
]);
|
||||
|
||||
/**
|
||||
* Extract the simple type name from a type AST node.
|
||||
* Handles generic types (e.g., List<User> → List), qualified names
|
||||
|
|
@ -31,11 +41,19 @@ export const extractSimpleTypeName = (typeNode: SyntaxNode): string | undefined
|
|||
}
|
||||
|
||||
// Generic types: extract the base type (e.g., List<User> → List)
|
||||
// For nullable wrappers (Optional<User>, Option<User>), unwrap to inner type.
|
||||
if (typeNode.type === 'generic_type' || typeNode.type === 'parameterized_type') {
|
||||
const base = typeNode.childForFieldName('name')
|
||||
?? typeNode.childForFieldName('type')
|
||||
?? typeNode.firstNamedChild;
|
||||
if (base) return extractSimpleTypeName(base);
|
||||
if (!base) return undefined;
|
||||
const baseName = extractSimpleTypeName(base);
|
||||
// Unwrap known nullable wrappers: Optional<User> → User, Option<User> → User
|
||||
if (baseName && NULLABLE_WRAPPER_TYPES.has(baseName)) {
|
||||
const args = extractGenericTypeArgs(typeNode);
|
||||
if (args.length >= 1) return args[0];
|
||||
}
|
||||
return baseName;
|
||||
}
|
||||
|
||||
// Nullable types (Kotlin User?, C# User?)
|
||||
|
|
@ -132,6 +150,8 @@ export const TYPED_PARAMETER_TYPES = new Set([
|
|||
* Extract type arguments from a generic type node.
|
||||
* e.g., List<User, String> → ['User', 'String'], Vec<User> → ['User']
|
||||
*
|
||||
* Used by extractSimpleTypeName to unwrap nullable wrappers (Optional<User> → User).
|
||||
*
|
||||
* Handles language-specific AST structures:
|
||||
* - TS/Java/Rust/Go: generic_type > type_arguments > type nodes
|
||||
* - C#: generic_type > type_argument_list > type nodes
|
||||
|
|
@ -233,6 +253,42 @@ export const hasTypeAnnotation = (node: SyntaxNode): boolean => {
|
|||
return false;
|
||||
};
|
||||
|
||||
/** Bare nullable keywords that should not produce a receiver binding. */
|
||||
const NULLABLE_KEYWORDS = new Set(['null', 'undefined', 'void', 'None', 'nil']);
|
||||
|
||||
/**
|
||||
* Strip nullable wrappers from a type name string.
|
||||
* Used by both lookupInEnv (TypeEnv annotations) and extractReturnTypeName
|
||||
* (return-type text) to normalize types before receiver lookup.
|
||||
*
|
||||
* "User | null" → "User"
|
||||
* "User | undefined" → "User"
|
||||
* "User | null | undefined" → "User"
|
||||
* "User?" → "User"
|
||||
* "User | Repo" → undefined (genuine union — refuse)
|
||||
* "null" → undefined
|
||||
*/
|
||||
export const stripNullable = (typeName: string): string | undefined => {
|
||||
let text = typeName.trim();
|
||||
if (!text) return undefined;
|
||||
|
||||
if (NULLABLE_KEYWORDS.has(text)) return undefined;
|
||||
|
||||
// Strip nullable suffix: User? → User
|
||||
if (text.endsWith('?')) text = text.slice(0, -1).trim();
|
||||
|
||||
// Strip union with null/undefined/None/nil/void
|
||||
if (text.includes('|')) {
|
||||
const parts = text.split('|').map(p => p.trim()).filter(p =>
|
||||
p !== '' && !NULLABLE_KEYWORDS.has(p)
|
||||
);
|
||||
if (parts.length === 1) return parts[0];
|
||||
return undefined; // genuine union or all-nullable — refuse
|
||||
}
|
||||
|
||||
return text || undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Unwrap an await_expression to get the inner value.
|
||||
* Returns the node itself if not an await_expression, or null if input is null.
|
||||
|
|
|
|||
|
|
@ -24,10 +24,26 @@ export type ConstructorBindingScanner = (node: SyntaxNode) => { varName: string;
|
|||
* rather than in AST fields. Returns undefined if no return type can be determined. */
|
||||
export type ReturnTypeExtractor = (node: SyntaxNode) => string | undefined;
|
||||
|
||||
/** Extracts loop variable type binding from a for-each statement. */
|
||||
export type ForLoopExtractor = (
|
||||
node: SyntaxNode,
|
||||
scopeEnv: Map<string, string>,
|
||||
) => void;
|
||||
|
||||
/** Extracts a plain-identifier assignment for Tier 2 propagation.
|
||||
* For `const b = a`, returns { lhs: 'b', rhs: 'a' } when the LHS has no resolved type.
|
||||
* Returns undefined if the node is not a plain identifier assignment. */
|
||||
export type PendingAssignmentExtractor = (
|
||||
node: SyntaxNode,
|
||||
scopeEnv: ReadonlyMap<string, string>,
|
||||
) => { lhs: string; rhs: string } | undefined;
|
||||
|
||||
/** Per-language type extraction configuration */
|
||||
export interface LanguageTypeConfig {
|
||||
/** Node types that represent typed declarations for this language */
|
||||
declarationNodeTypes: ReadonlySet<string>;
|
||||
/** AST node types for for-each/for-in statements with explicit element types. */
|
||||
forLoopNodeTypes?: ReadonlySet<string>;
|
||||
/** Extract a (varName → typeName) binding from a declaration node */
|
||||
extractDeclaration: TypeBindingExtractor;
|
||||
/** Extract a (varName → typeName) binding from a parameter node */
|
||||
|
|
@ -44,4 +60,10 @@ export interface LanguageTypeConfig {
|
|||
/** Extract return type from comment-based annotations (e.g. YARD @return [Type]).
|
||||
* Called as fallback when extractMethodSignature finds no AST-based return type. */
|
||||
extractReturnType?: ReturnTypeExtractor;
|
||||
/** Extract loop variable → type binding from a for-each AST node. */
|
||||
extractForLoopBinding?: ForLoopExtractor;
|
||||
/** Extract plain-identifier assignment (e.g. `const b = a`) for Tier 2 chain propagation.
|
||||
* Called on declaration/assignment nodes; returns {lhs, rhs} when the RHS is a bare identifier
|
||||
* and the LHS has no resolved type yet. Language-specific because AST shapes differ widely. */
|
||||
extractPendingAssignment?: PendingAssignmentExtractor;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { SyntaxNode } from '../utils.js';
|
||||
import type { LanguageTypeConfig, ParameterExtractor, TypeBindingExtractor, InitializerExtractor, ClassNameLookup, ConstructorBindingScanner, ReturnTypeExtractor } from './types.js';
|
||||
import type { LanguageTypeConfig, ParameterExtractor, TypeBindingExtractor, InitializerExtractor, ClassNameLookup, ConstructorBindingScanner, ReturnTypeExtractor, PendingAssignmentExtractor } from './types.js';
|
||||
import { extractSimpleTypeName, extractVarName, hasTypeAnnotation, unwrapAwait, extractCalleeName } from './shared.js';
|
||||
|
||||
const DECLARATION_NODE_TYPES: ReadonlySet<string> = new Set([
|
||||
|
|
@ -191,6 +191,21 @@ const extractReturnType: ReturnTypeExtractor = (node) => {
|
|||
return undefined;
|
||||
};
|
||||
|
||||
/** TS/JS: const alias = u → variable_declarator with name/value fields */
|
||||
const extractPendingAssignment: PendingAssignmentExtractor = (node, scopeEnv) => {
|
||||
for (let i = 0; i < node.namedChildCount; i++) {
|
||||
const child = node.namedChild(i);
|
||||
if (!child || child.type !== 'variable_declarator') continue;
|
||||
const nameNode = child.childForFieldName('name');
|
||||
const valueNode = child.childForFieldName('value');
|
||||
if (!nameNode || !valueNode) continue;
|
||||
const lhs = nameNode.text;
|
||||
if (scopeEnv.has(lhs)) continue;
|
||||
if (valueNode.type === 'identifier') return { lhs, rhs: valueNode.text };
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const typeConfig: LanguageTypeConfig = {
|
||||
declarationNodeTypes: DECLARATION_NODE_TYPES,
|
||||
extractDeclaration,
|
||||
|
|
@ -198,4 +213,5 @@ export const typeConfig: LanguageTypeConfig = {
|
|||
extractInitializer,
|
||||
scanConstructorBinding,
|
||||
extractReturnType,
|
||||
extractPendingAssignment,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -668,6 +668,25 @@ export const extractMethodSignature = (node: SyntaxNode | null | undefined): Met
|
|||
}
|
||||
}
|
||||
|
||||
// Kotlin: fun getUser(): User — return type is a bare user_type child of
|
||||
// function_declaration. The Kotlin grammar does NOT wrap it in type_annotation
|
||||
// or return_type; it appears as a direct child after function_value_parameters.
|
||||
// Note: Kotlin uses function_value_parameters (not a field), so we find it by type.
|
||||
if (!returnType) {
|
||||
let paramsEnd = -1;
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
const child = node.child(i);
|
||||
if (!child) continue;
|
||||
if (child.type === 'function_value_parameters' || child.type === 'value_parameters') {
|
||||
paramsEnd = child.endIndex;
|
||||
}
|
||||
if (paramsEnd >= 0 && child.type === 'user_type' && child.startIndex > paramsEnd) {
|
||||
returnType = child.text;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isVariadic) parameterCount = undefined;
|
||||
|
||||
return { parameterCount, returnType };
|
||||
|
|
|
|||
10
gitnexus/test/fixtures/lang-resolution/cpp-assignment-chain/models/Repo.h
vendored
Normal file
10
gitnexus/test/fixtures/lang-resolution/cpp-assignment-chain/models/Repo.h
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
|
||||
class Repo {
|
||||
public:
|
||||
Repo(const std::string& name) : name_(name) {}
|
||||
bool save() { return false; }
|
||||
private:
|
||||
std::string name_;
|
||||
};
|
||||
10
gitnexus/test/fixtures/lang-resolution/cpp-assignment-chain/models/User.h
vendored
Normal file
10
gitnexus/test/fixtures/lang-resolution/cpp-assignment-chain/models/User.h
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
|
||||
class User {
|
||||
public:
|
||||
User(const std::string& name) : name_(name) {}
|
||||
bool save() { return true; }
|
||||
private:
|
||||
std::string name_;
|
||||
};
|
||||
13
gitnexus/test/fixtures/lang-resolution/cpp-assignment-chain/services/App.cpp
vendored
Normal file
13
gitnexus/test/fixtures/lang-resolution/cpp-assignment-chain/services/App.cpp
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#include "models/User.h"
|
||||
#include "models/Repo.h"
|
||||
|
||||
// Tests C++ auto alias = u assignment chain propagation.
|
||||
void processEntities() {
|
||||
User u("alice");
|
||||
auto alias = u;
|
||||
alias.save();
|
||||
|
||||
Repo r("maindb");
|
||||
auto rAlias = r;
|
||||
rAlias.save();
|
||||
}
|
||||
10
gitnexus/test/fixtures/lang-resolution/cpp-nullable-receiver/models/Repo.h
vendored
Normal file
10
gitnexus/test/fixtures/lang-resolution/cpp-nullable-receiver/models/Repo.h
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
|
||||
class Repo {
|
||||
public:
|
||||
Repo(const std::string& dbName) : dbName_(dbName) {}
|
||||
bool save() { return false; }
|
||||
private:
|
||||
std::string dbName_;
|
||||
};
|
||||
10
gitnexus/test/fixtures/lang-resolution/cpp-nullable-receiver/models/User.h
vendored
Normal file
10
gitnexus/test/fixtures/lang-resolution/cpp-nullable-receiver/models/User.h
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
|
||||
class User {
|
||||
public:
|
||||
User(const std::string& name) : name_(name) {}
|
||||
bool save() { return true; }
|
||||
private:
|
||||
std::string name_;
|
||||
};
|
||||
19
gitnexus/test/fixtures/lang-resolution/cpp-nullable-receiver/services/App.cpp
vendored
Normal file
19
gitnexus/test/fixtures/lang-resolution/cpp-nullable-receiver/services/App.cpp
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#include "models/User.h"
|
||||
#include "models/Repo.h"
|
||||
|
||||
User* findUser() {
|
||||
return new User("alice");
|
||||
}
|
||||
|
||||
Repo* findRepo() {
|
||||
return new Repo("maindb");
|
||||
}
|
||||
|
||||
void processEntities() {
|
||||
User* user = findUser();
|
||||
Repo* repo = findRepo();
|
||||
|
||||
// Pointer-based nullable receivers — should disambiguate via unwrapped type
|
||||
user->save();
|
||||
repo->save();
|
||||
}
|
||||
5
gitnexus/test/fixtures/lang-resolution/csharp-assignment-chain/AssignmentChain.csproj
vendored
Normal file
5
gitnexus/test/fixtures/lang-resolution/csharp-assignment-chain/AssignmentChain.csproj
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
9
gitnexus/test/fixtures/lang-resolution/csharp-assignment-chain/Models/Repo.cs
vendored
Normal file
9
gitnexus/test/fixtures/lang-resolution/csharp-assignment-chain/Models/Repo.cs
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
namespace Models;
|
||||
|
||||
public class Repo
|
||||
{
|
||||
public bool Save()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
9
gitnexus/test/fixtures/lang-resolution/csharp-assignment-chain/Models/User.cs
vendored
Normal file
9
gitnexus/test/fixtures/lang-resolution/csharp-assignment-chain/Models/User.cs
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
namespace Models;
|
||||
|
||||
public class User
|
||||
{
|
||||
public bool Save()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
20
gitnexus/test/fixtures/lang-resolution/csharp-assignment-chain/Program.cs
vendored
Normal file
20
gitnexus/test/fixtures/lang-resolution/csharp-assignment-chain/Program.cs
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
using Models;
|
||||
|
||||
namespace App;
|
||||
|
||||
public class Program
|
||||
{
|
||||
static User GetUser() => new User();
|
||||
static Repo GetRepo() => new Repo();
|
||||
|
||||
public static void ProcessEntities()
|
||||
{
|
||||
User u = GetUser();
|
||||
var alias = u;
|
||||
alias.Save();
|
||||
|
||||
Repo r = GetRepo();
|
||||
var rAlias = r;
|
||||
rAlias.Save();
|
||||
}
|
||||
}
|
||||
5
gitnexus/test/fixtures/lang-resolution/csharp-foreach/ForeachProj.csproj
vendored
Normal file
5
gitnexus/test/fixtures/lang-resolution/csharp-foreach/ForeachProj.csproj
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
9
gitnexus/test/fixtures/lang-resolution/csharp-foreach/Models/Repo.cs
vendored
Normal file
9
gitnexus/test/fixtures/lang-resolution/csharp-foreach/Models/Repo.cs
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
namespace Models;
|
||||
|
||||
public class Repo
|
||||
{
|
||||
public bool Save()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
9
gitnexus/test/fixtures/lang-resolution/csharp-foreach/Models/User.cs
vendored
Normal file
9
gitnexus/test/fixtures/lang-resolution/csharp-foreach/Models/User.cs
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
namespace Models;
|
||||
|
||||
public class User
|
||||
{
|
||||
public bool Save()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
19
gitnexus/test/fixtures/lang-resolution/csharp-foreach/Program.cs
vendored
Normal file
19
gitnexus/test/fixtures/lang-resolution/csharp-foreach/Program.cs
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
using Models;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace App;
|
||||
|
||||
public class AppService
|
||||
{
|
||||
public void ProcessEntities(List<User> users, List<Repo> repos)
|
||||
{
|
||||
foreach (User user in users)
|
||||
{
|
||||
user.Save();
|
||||
}
|
||||
foreach (Repo repo in repos)
|
||||
{
|
||||
repo.Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
5
gitnexus/test/fixtures/lang-resolution/csharp-mixed-decl-chain/MixedDeclChain.csproj
vendored
Normal file
5
gitnexus/test/fixtures/lang-resolution/csharp-mixed-decl-chain/MixedDeclChain.csproj
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
4
gitnexus/test/fixtures/lang-resolution/csharp-mixed-decl-chain/Models/Repo.cs
vendored
Normal file
4
gitnexus/test/fixtures/lang-resolution/csharp-mixed-decl-chain/Models/Repo.cs
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
public class Repo
|
||||
{
|
||||
public bool Save() => false;
|
||||
}
|
||||
4
gitnexus/test/fixtures/lang-resolution/csharp-mixed-decl-chain/Models/User.cs
vendored
Normal file
4
gitnexus/test/fixtures/lang-resolution/csharp-mixed-decl-chain/Models/User.cs
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
public class User
|
||||
{
|
||||
public bool Save() => true;
|
||||
}
|
||||
29
gitnexus/test/fixtures/lang-resolution/csharp-mixed-decl-chain/Program.cs
vendored
Normal file
29
gitnexus/test/fixtures/lang-resolution/csharp-mixed-decl-chain/Program.cs
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
// Tests assignment chain + is-pattern in the same file.
|
||||
// The is-pattern (obj is User u) creates a Tier 0 binding;
|
||||
// the assignment chain (var alias = u) propagates it via Tier 2.
|
||||
// Also verifies that the type guard in extractPendingAssignment
|
||||
// correctly skips is_pattern_expression nodes without breaking.
|
||||
public class App
|
||||
{
|
||||
public static void ProcessWithChain()
|
||||
{
|
||||
User u = new User();
|
||||
var alias = u;
|
||||
alias.Save();
|
||||
}
|
||||
|
||||
public static void ProcessWithPattern(object obj)
|
||||
{
|
||||
if (obj is User u)
|
||||
{
|
||||
u.Save();
|
||||
}
|
||||
}
|
||||
|
||||
public static void ProcessRepoChain()
|
||||
{
|
||||
Repo r = new Repo();
|
||||
var alias = r;
|
||||
alias.Save();
|
||||
}
|
||||
}
|
||||
|
|
@ -6,10 +6,10 @@ public class AppService
|
|||
{
|
||||
public void Process()
|
||||
{
|
||||
User user = new User();
|
||||
Repo repo = new Repo();
|
||||
User? user = new User();
|
||||
Repo? repo = new Repo();
|
||||
|
||||
// Null-conditional calls — should disambiguate via receiver type
|
||||
// Null-conditional calls — nullable receiver should be unwrapped
|
||||
user?.Save();
|
||||
repo?.Save();
|
||||
}
|
||||
|
|
|
|||
31
gitnexus/test/fixtures/lang-resolution/go-assignment-chain/cmd/main.go
vendored
Normal file
31
gitnexus/test/fixtures/lang-resolution/go-assignment-chain/cmd/main.go
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
package main
|
||||
|
||||
import "example.com/go-assignment-chain/models"
|
||||
|
||||
func getUser() models.User {
|
||||
return models.User{}
|
||||
}
|
||||
|
||||
func getRepo() models.Repo {
|
||||
return models.Repo{}
|
||||
}
|
||||
|
||||
func processEntities() {
|
||||
var u models.User = getUser()
|
||||
alias := u
|
||||
alias.Save()
|
||||
|
||||
var r models.Repo = getRepo()
|
||||
rAlias := r
|
||||
rAlias.Save()
|
||||
}
|
||||
|
||||
func processWithVar() {
|
||||
var u models.User = getUser()
|
||||
var alias = u
|
||||
alias.Save()
|
||||
|
||||
var r models.Repo = getRepo()
|
||||
var rAlias = r
|
||||
rAlias.Save()
|
||||
}
|
||||
3
gitnexus/test/fixtures/lang-resolution/go-assignment-chain/go.mod
vendored
Normal file
3
gitnexus/test/fixtures/lang-resolution/go-assignment-chain/go.mod
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
module example.com/go-assignment-chain
|
||||
|
||||
go 1.21
|
||||
7
gitnexus/test/fixtures/lang-resolution/go-assignment-chain/models/repo.go
vendored
Normal file
7
gitnexus/test/fixtures/lang-resolution/go-assignment-chain/models/repo.go
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package models
|
||||
|
||||
type Repo struct{}
|
||||
|
||||
func (r *Repo) Save() bool {
|
||||
return false
|
||||
}
|
||||
7
gitnexus/test/fixtures/lang-resolution/go-assignment-chain/models/user.go
vendored
Normal file
7
gitnexus/test/fixtures/lang-resolution/go-assignment-chain/models/user.go
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package models
|
||||
|
||||
type User struct{}
|
||||
|
||||
func (u *User) Save() bool {
|
||||
return true
|
||||
}
|
||||
18
gitnexus/test/fixtures/lang-resolution/go-nullable-receiver/cmd/main.go
vendored
Normal file
18
gitnexus/test/fixtures/lang-resolution/go-nullable-receiver/cmd/main.go
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
package main
|
||||
|
||||
import "example.com/go-nullable-receiver/models"
|
||||
|
||||
func findUser() *models.User {
|
||||
return &models.User{}
|
||||
}
|
||||
|
||||
func findRepo() *models.Repo {
|
||||
return &models.Repo{}
|
||||
}
|
||||
|
||||
func processEntities() {
|
||||
var user *models.User = findUser()
|
||||
var repo *models.Repo = findRepo()
|
||||
user.Save()
|
||||
repo.Save()
|
||||
}
|
||||
3
gitnexus/test/fixtures/lang-resolution/go-nullable-receiver/go.mod
vendored
Normal file
3
gitnexus/test/fixtures/lang-resolution/go-nullable-receiver/go.mod
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
module example.com/go-nullable-receiver
|
||||
|
||||
go 1.21
|
||||
7
gitnexus/test/fixtures/lang-resolution/go-nullable-receiver/models/repo.go
vendored
Normal file
7
gitnexus/test/fixtures/lang-resolution/go-nullable-receiver/models/repo.go
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package models
|
||||
|
||||
type Repo struct{}
|
||||
|
||||
func (r *Repo) Save() bool {
|
||||
return false
|
||||
}
|
||||
7
gitnexus/test/fixtures/lang-resolution/go-nullable-receiver/models/user.go
vendored
Normal file
7
gitnexus/test/fixtures/lang-resolution/go-nullable-receiver/models/user.go
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package models
|
||||
|
||||
type User struct{}
|
||||
|
||||
func (u *User) Save() bool {
|
||||
return true
|
||||
}
|
||||
17
gitnexus/test/fixtures/lang-resolution/java-assignment-chain/App.java
vendored
Normal file
17
gitnexus/test/fixtures/lang-resolution/java-assignment-chain/App.java
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import models.User;
|
||||
import models.Repo;
|
||||
|
||||
public class App {
|
||||
static User getUser() { return new User(); }
|
||||
static Repo getRepo() { return new Repo(); }
|
||||
|
||||
public static void processEntities() {
|
||||
User u = getUser();
|
||||
var alias = u;
|
||||
alias.save();
|
||||
|
||||
Repo r = getRepo();
|
||||
var rAlias = r;
|
||||
rAlias.save();
|
||||
}
|
||||
}
|
||||
7
gitnexus/test/fixtures/lang-resolution/java-assignment-chain/models/Repo.java
vendored
Normal file
7
gitnexus/test/fixtures/lang-resolution/java-assignment-chain/models/Repo.java
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package models;
|
||||
|
||||
public class Repo {
|
||||
public boolean save() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
7
gitnexus/test/fixtures/lang-resolution/java-assignment-chain/models/User.java
vendored
Normal file
7
gitnexus/test/fixtures/lang-resolution/java-assignment-chain/models/User.java
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package models;
|
||||
|
||||
public class User {
|
||||
public boolean save() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
13
gitnexus/test/fixtures/lang-resolution/java-foreach/App.java
vendored
Normal file
13
gitnexus/test/fixtures/lang-resolution/java-foreach/App.java
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import models.User;
|
||||
import models.Repo;
|
||||
|
||||
public class App {
|
||||
public static void processEntities(User[] users, Repo[] repos) {
|
||||
for (User user : users) {
|
||||
user.save();
|
||||
}
|
||||
for (Repo repo : repos) {
|
||||
repo.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
7
gitnexus/test/fixtures/lang-resolution/java-foreach/models/Repo.java
vendored
Normal file
7
gitnexus/test/fixtures/lang-resolution/java-foreach/models/Repo.java
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package models;
|
||||
|
||||
public class Repo {
|
||||
public boolean save() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
7
gitnexus/test/fixtures/lang-resolution/java-foreach/models/User.java
vendored
Normal file
7
gitnexus/test/fixtures/lang-resolution/java-foreach/models/User.java
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package models;
|
||||
|
||||
public class User {
|
||||
public boolean save() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
19
gitnexus/test/fixtures/lang-resolution/java-nullable-receiver/App.java
vendored
Normal file
19
gitnexus/test/fixtures/lang-resolution/java-nullable-receiver/App.java
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import models.User;
|
||||
import models.Repo;
|
||||
|
||||
public class App {
|
||||
public static void processEntities() {
|
||||
User user = findUser();
|
||||
Repo repo = findRepo();
|
||||
user.save();
|
||||
repo.save();
|
||||
}
|
||||
|
||||
private static User findUser() {
|
||||
return new User();
|
||||
}
|
||||
|
||||
private static Repo findRepo() {
|
||||
return new Repo();
|
||||
}
|
||||
}
|
||||
7
gitnexus/test/fixtures/lang-resolution/java-nullable-receiver/models/Repo.java
vendored
Normal file
7
gitnexus/test/fixtures/lang-resolution/java-nullable-receiver/models/Repo.java
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package models;
|
||||
|
||||
public class Repo {
|
||||
public boolean save() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
7
gitnexus/test/fixtures/lang-resolution/java-nullable-receiver/models/User.java
vendored
Normal file
7
gitnexus/test/fixtures/lang-resolution/java-nullable-receiver/models/User.java
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package models;
|
||||
|
||||
public class User {
|
||||
public boolean save() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
20
gitnexus/test/fixtures/lang-resolution/java-optional-receiver/App.java
vendored
Normal file
20
gitnexus/test/fixtures/lang-resolution/java-optional-receiver/App.java
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import models.User;
|
||||
import models.Repo;
|
||||
|
||||
// Tests that Optional<User> unwraps to User in TypeEnv,
|
||||
// so assignment chains from Optional-typed sources resolve correctly.
|
||||
public class App {
|
||||
static User findUser() { return new User(); }
|
||||
static Repo findRepo() { return new Repo(); }
|
||||
|
||||
static void processEntities() {
|
||||
// Optional<User> declared — TypeEnv stores "User" (not "Optional")
|
||||
// The alias then propagates User through the chain
|
||||
java.util.Optional<User> opt = java.util.Optional.of(findUser());
|
||||
User user = opt.get();
|
||||
user.save();
|
||||
|
||||
Repo repo = findRepo();
|
||||
repo.save();
|
||||
}
|
||||
}
|
||||
5
gitnexus/test/fixtures/lang-resolution/java-optional-receiver/models/Repo.java
vendored
Normal file
5
gitnexus/test/fixtures/lang-resolution/java-optional-receiver/models/Repo.java
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
package models;
|
||||
|
||||
public class Repo {
|
||||
public void save() {}
|
||||
}
|
||||
5
gitnexus/test/fixtures/lang-resolution/java-optional-receiver/models/User.java
vendored
Normal file
5
gitnexus/test/fixtures/lang-resolution/java-optional-receiver/models/User.java
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
package models;
|
||||
|
||||
public class User {
|
||||
public void save() {}
|
||||
}
|
||||
11
gitnexus/test/fixtures/lang-resolution/js-nullable-receiver/src/app.js
vendored
Normal file
11
gitnexus/test/fixtures/lang-resolution/js-nullable-receiver/src/app.js
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
const { User } = require('./user');
|
||||
const { Repo } = require('./repo');
|
||||
|
||||
/**
|
||||
* @param {User | null} user
|
||||
* @param {Repo | null} repo
|
||||
*/
|
||||
function processEntities(user, repo) {
|
||||
if (user) user.save();
|
||||
if (repo) repo.save();
|
||||
}
|
||||
4
gitnexus/test/fixtures/lang-resolution/js-nullable-receiver/src/repo.js
vendored
Normal file
4
gitnexus/test/fixtures/lang-resolution/js-nullable-receiver/src/repo.js
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
class Repo {
|
||||
save() { return true; }
|
||||
}
|
||||
module.exports = { Repo };
|
||||
4
gitnexus/test/fixtures/lang-resolution/js-nullable-receiver/src/user.js
vendored
Normal file
4
gitnexus/test/fixtures/lang-resolution/js-nullable-receiver/src/user.js
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
class User {
|
||||
save() { return true; }
|
||||
}
|
||||
module.exports = { User };
|
||||
12
gitnexus/test/fixtures/lang-resolution/kotlin-assignment-chain/App.kt
vendored
Normal file
12
gitnexus/test/fixtures/lang-resolution/kotlin-assignment-chain/App.kt
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
fun getUser(): User = User()
|
||||
fun getRepo(): Repo = Repo()
|
||||
|
||||
fun processEntities() {
|
||||
val u: User = getUser()
|
||||
val alias = u
|
||||
alias.save()
|
||||
|
||||
val r: Repo = getRepo()
|
||||
val rAlias = r
|
||||
rAlias.save()
|
||||
}
|
||||
3
gitnexus/test/fixtures/lang-resolution/kotlin-assignment-chain/models/Repo.kt
vendored
Normal file
3
gitnexus/test/fixtures/lang-resolution/kotlin-assignment-chain/models/Repo.kt
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
class Repo {
|
||||
fun save() {}
|
||||
}
|
||||
3
gitnexus/test/fixtures/lang-resolution/kotlin-assignment-chain/models/User.kt
vendored
Normal file
3
gitnexus/test/fixtures/lang-resolution/kotlin-assignment-chain/models/User.kt
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
class User {
|
||||
fun save() {}
|
||||
}
|
||||
14
gitnexus/test/fixtures/lang-resolution/kotlin-class-method-chain/App.kt
vendored
Normal file
14
gitnexus/test/fixtures/lang-resolution/kotlin-class-method-chain/App.kt
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// Assignment chain with typed parameter propagation.
|
||||
// Tests that extractKotlinPendingAssignment handles val alias = u
|
||||
// where u comes from an explicit typed declaration.
|
||||
fun processUser() {
|
||||
val u: User = User()
|
||||
val alias = u
|
||||
alias.save()
|
||||
}
|
||||
|
||||
fun processRepo() {
|
||||
val r: Repo = Repo()
|
||||
val alias = r
|
||||
alias.save()
|
||||
}
|
||||
3
gitnexus/test/fixtures/lang-resolution/kotlin-class-method-chain/models/Repo.kt
vendored
Normal file
3
gitnexus/test/fixtures/lang-resolution/kotlin-class-method-chain/models/Repo.kt
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
class Repo {
|
||||
fun save() {}
|
||||
}
|
||||
3
gitnexus/test/fixtures/lang-resolution/kotlin-class-method-chain/models/User.kt
vendored
Normal file
3
gitnexus/test/fixtures/lang-resolution/kotlin-class-method-chain/models/User.kt
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
class User {
|
||||
fun save() {}
|
||||
}
|
||||
16
gitnexus/test/fixtures/lang-resolution/kotlin-foreach/App.kt
vendored
Normal file
16
gitnexus/test/fixtures/lang-resolution/kotlin-foreach/App.kt
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
package app
|
||||
|
||||
import models.User
|
||||
import models.Repo
|
||||
|
||||
fun processUsers(users: List<User>) {
|
||||
for (user: User in users) {
|
||||
user.save()
|
||||
}
|
||||
}
|
||||
|
||||
fun processRepos(repos: List<Repo>) {
|
||||
for (repo: Repo in repos) {
|
||||
repo.save()
|
||||
}
|
||||
}
|
||||
5
gitnexus/test/fixtures/lang-resolution/kotlin-foreach/models/Repo.kt
vendored
Normal file
5
gitnexus/test/fixtures/lang-resolution/kotlin-foreach/models/Repo.kt
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
package models
|
||||
|
||||
class Repo {
|
||||
fun save() {}
|
||||
}
|
||||
5
gitnexus/test/fixtures/lang-resolution/kotlin-foreach/models/User.kt
vendored
Normal file
5
gitnexus/test/fixtures/lang-resolution/kotlin-foreach/models/User.kt
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
package models
|
||||
|
||||
class User {
|
||||
fun save() {}
|
||||
}
|
||||
5
gitnexus/test/fixtures/lang-resolution/kotlin-nullable-receiver/models/Repo.kt
vendored
Normal file
5
gitnexus/test/fixtures/lang-resolution/kotlin-nullable-receiver/models/Repo.kt
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
package models
|
||||
|
||||
class Repo(val dbName: String) {
|
||||
fun save(): Boolean = false
|
||||
}
|
||||
5
gitnexus/test/fixtures/lang-resolution/kotlin-nullable-receiver/models/User.kt
vendored
Normal file
5
gitnexus/test/fixtures/lang-resolution/kotlin-nullable-receiver/models/User.kt
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
package models
|
||||
|
||||
class User(val name: String) {
|
||||
fun save(): Boolean = true
|
||||
}
|
||||
13
gitnexus/test/fixtures/lang-resolution/kotlin-nullable-receiver/services/App.kt
vendored
Normal file
13
gitnexus/test/fixtures/lang-resolution/kotlin-nullable-receiver/services/App.kt
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
package services
|
||||
|
||||
import models.User
|
||||
import models.Repo
|
||||
|
||||
fun processEntities() {
|
||||
val user: User? = User("alice")
|
||||
val repo: Repo? = Repo("maindb")
|
||||
|
||||
// Safe calls on nullable receivers — should disambiguate via unwrapped type
|
||||
user?.save()
|
||||
repo?.save()
|
||||
}
|
||||
8
gitnexus/test/fixtures/lang-resolution/php-assignment-chain/app/Models/Repo.php
vendored
Normal file
8
gitnexus/test/fixtures/lang-resolution/php-assignment-chain/app/Models/Repo.php
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
namespace App\Models;
|
||||
|
||||
class Repo {
|
||||
public function save(): bool {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
8
gitnexus/test/fixtures/lang-resolution/php-assignment-chain/app/Models/User.php
vendored
Normal file
8
gitnexus/test/fixtures/lang-resolution/php-assignment-chain/app/Models/User.php
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
namespace App\Models;
|
||||
|
||||
class User {
|
||||
public function save(): bool {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
15
gitnexus/test/fixtures/lang-resolution/php-assignment-chain/app/Services/AppService.php
vendored
Normal file
15
gitnexus/test/fixtures/lang-resolution/php-assignment-chain/app/Services/AppService.php
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\Repo;
|
||||
|
||||
class AppService {
|
||||
public function process(User $user, Repo $repo): void {
|
||||
$alias = $user;
|
||||
$alias->save();
|
||||
|
||||
$rAlias = $repo;
|
||||
$rAlias->save();
|
||||
}
|
||||
}
|
||||
7
gitnexus/test/fixtures/lang-resolution/php-assignment-chain/composer.json
vendored
Normal file
7
gitnexus/test/fixtures/lang-resolution/php-assignment-chain/composer.json
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"App\\": "app/"
|
||||
}
|
||||
}
|
||||
}
|
||||
11
gitnexus/test/fixtures/lang-resolution/php-nullable-receiver/app/Models/Repo.php
vendored
Normal file
11
gitnexus/test/fixtures/lang-resolution/php-nullable-receiver/app/Models/Repo.php
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
class Repo
|
||||
{
|
||||
public function save(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
11
gitnexus/test/fixtures/lang-resolution/php-nullable-receiver/app/Models/User.php
vendored
Normal file
11
gitnexus/test/fixtures/lang-resolution/php-nullable-receiver/app/Models/User.php
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
class User
|
||||
{
|
||||
public function save(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
16
gitnexus/test/fixtures/lang-resolution/php-nullable-receiver/app/Services/AppService.php
vendored
Normal file
16
gitnexus/test/fixtures/lang-resolution/php-nullable-receiver/app/Services/AppService.php
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\Repo;
|
||||
|
||||
class AppService
|
||||
{
|
||||
public function process(?User $user, ?Repo $repo): void
|
||||
{
|
||||
// Nullable type-hinted params — should disambiguate via unwrapped type
|
||||
$user->save();
|
||||
$repo->save();
|
||||
}
|
||||
}
|
||||
7
gitnexus/test/fixtures/lang-resolution/php-nullable-receiver/composer.json
vendored
Normal file
7
gitnexus/test/fixtures/lang-resolution/php-nullable-receiver/composer.json
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"App\\": "app/"
|
||||
}
|
||||
}
|
||||
}
|
||||
17
gitnexus/test/fixtures/lang-resolution/python-assignment-chain/app.py
vendored
Normal file
17
gitnexus/test/fixtures/lang-resolution/python-assignment-chain/app.py
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
from user import User
|
||||
from repo import Repo
|
||||
|
||||
def get_user() -> User:
|
||||
return User()
|
||||
|
||||
def get_repo() -> Repo:
|
||||
return Repo()
|
||||
|
||||
def process():
|
||||
u: User = get_user()
|
||||
alias = u
|
||||
alias.save()
|
||||
|
||||
r: Repo = get_repo()
|
||||
r_alias = r
|
||||
r_alias.save()
|
||||
3
gitnexus/test/fixtures/lang-resolution/python-assignment-chain/repo.py
vendored
Normal file
3
gitnexus/test/fixtures/lang-resolution/python-assignment-chain/repo.py
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
class Repo:
|
||||
def save(self):
|
||||
return False
|
||||
3
gitnexus/test/fixtures/lang-resolution/python-assignment-chain/user.py
vendored
Normal file
3
gitnexus/test/fixtures/lang-resolution/python-assignment-chain/user.py
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
class User:
|
||||
def save(self):
|
||||
return True
|
||||
24
gitnexus/test/fixtures/lang-resolution/python-nullable-chain/app.py
vendored
Normal file
24
gitnexus/test/fixtures/lang-resolution/python-nullable-chain/app.py
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
from user import User
|
||||
from repo import Repo
|
||||
|
||||
|
||||
def get_user() -> User:
|
||||
return User()
|
||||
|
||||
|
||||
def get_repo() -> Repo:
|
||||
return Repo()
|
||||
|
||||
|
||||
# Python 3.10+ union: User | None is parsed as binary_operator,
|
||||
# stored as raw text "User | None" in TypeEnv, then stripNullable resolves it.
|
||||
def nullable_chain_user() -> None:
|
||||
u: User | None = get_user()
|
||||
alias = u
|
||||
alias.save()
|
||||
|
||||
|
||||
def nullable_chain_repo() -> None:
|
||||
r: Repo | None = get_repo()
|
||||
alias = r
|
||||
alias.save()
|
||||
3
gitnexus/test/fixtures/lang-resolution/python-nullable-chain/repo.py
vendored
Normal file
3
gitnexus/test/fixtures/lang-resolution/python-nullable-chain/repo.py
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
class Repo:
|
||||
def save(self) -> bool:
|
||||
return False
|
||||
3
gitnexus/test/fixtures/lang-resolution/python-nullable-chain/user.py
vendored
Normal file
3
gitnexus/test/fixtures/lang-resolution/python-nullable-chain/user.py
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
class User:
|
||||
def save(self) -> bool:
|
||||
return True
|
||||
14
gitnexus/test/fixtures/lang-resolution/python-nullable-receiver/app.py
vendored
Normal file
14
gitnexus/test/fixtures/lang-resolution/python-nullable-receiver/app.py
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
from user import User
|
||||
from repo import Repo
|
||||
|
||||
def find_user() -> User | None:
|
||||
return User()
|
||||
|
||||
def find_repo() -> Repo | None:
|
||||
return Repo()
|
||||
|
||||
def process_entities():
|
||||
user: User | None = find_user()
|
||||
user.save()
|
||||
repo: Repo | None = find_repo()
|
||||
repo.save()
|
||||
3
gitnexus/test/fixtures/lang-resolution/python-nullable-receiver/repo.py
vendored
Normal file
3
gitnexus/test/fixtures/lang-resolution/python-nullable-receiver/repo.py
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
class Repo:
|
||||
def save(self):
|
||||
return False
|
||||
3
gitnexus/test/fixtures/lang-resolution/python-nullable-receiver/user.py
vendored
Normal file
3
gitnexus/test/fixtures/lang-resolution/python-nullable-receiver/user.py
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
class User:
|
||||
def save(self):
|
||||
return True
|
||||
30
gitnexus/test/fixtures/lang-resolution/python-walrus-chain/app.py
vendored
Normal file
30
gitnexus/test/fixtures/lang-resolution/python-walrus-chain/app.py
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
from user import User
|
||||
from repo import Repo
|
||||
|
||||
|
||||
def get_user() -> User:
|
||||
return User()
|
||||
|
||||
|
||||
def get_repo() -> Repo:
|
||||
return Repo()
|
||||
|
||||
|
||||
# Walrus operator (:=) creates a named_expression binding.
|
||||
# Tests that extractPendingAssignment propagates through walrus assignments.
|
||||
def walrus_chain_user() -> None:
|
||||
u: User = get_user()
|
||||
# Regular assignment where alias gets type from u (regular chain)
|
||||
alias = u
|
||||
# Walrus inside condition: w gets type from u via named_expression chain
|
||||
if (w := u):
|
||||
w.save()
|
||||
alias.save()
|
||||
|
||||
|
||||
def walrus_chain_repo() -> None:
|
||||
r: Repo = get_repo()
|
||||
alias = r
|
||||
if (w := r):
|
||||
w.save()
|
||||
alias.save()
|
||||
3
gitnexus/test/fixtures/lang-resolution/python-walrus-chain/repo.py
vendored
Normal file
3
gitnexus/test/fixtures/lang-resolution/python-walrus-chain/repo.py
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
class Repo:
|
||||
def save(self) -> bool:
|
||||
return False
|
||||
3
gitnexus/test/fixtures/lang-resolution/python-walrus-chain/user.py
vendored
Normal file
3
gitnexus/test/fixtures/lang-resolution/python-walrus-chain/user.py
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
class User:
|
||||
def save(self) -> bool:
|
||||
return True
|
||||
19
gitnexus/test/fixtures/lang-resolution/rust-assignment-chain/src/main.rs
vendored
Normal file
19
gitnexus/test/fixtures/lang-resolution/rust-assignment-chain/src/main.rs
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
mod user;
|
||||
mod repo;
|
||||
use crate::user::User;
|
||||
use crate::repo::Repo;
|
||||
|
||||
fn get_user() -> User { User }
|
||||
fn get_repo() -> Repo { Repo }
|
||||
|
||||
fn process_entities() {
|
||||
let u: User = get_user();
|
||||
let alias = u;
|
||||
alias.save();
|
||||
|
||||
let r: Repo = get_repo();
|
||||
let r_alias = r;
|
||||
r_alias.save();
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
7
gitnexus/test/fixtures/lang-resolution/rust-assignment-chain/src/repo.rs
vendored
Normal file
7
gitnexus/test/fixtures/lang-resolution/rust-assignment-chain/src/repo.rs
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
pub struct Repo;
|
||||
|
||||
impl Repo {
|
||||
pub fn save(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
7
gitnexus/test/fixtures/lang-resolution/rust-assignment-chain/src/user.rs
vendored
Normal file
7
gitnexus/test/fixtures/lang-resolution/rust-assignment-chain/src/user.rs
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
pub struct User;
|
||||
|
||||
impl User {
|
||||
pub fn save(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
21
gitnexus/test/fixtures/lang-resolution/rust-nullable-receiver/src/main.rs
vendored
Normal file
21
gitnexus/test/fixtures/lang-resolution/rust-nullable-receiver/src/main.rs
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
mod user;
|
||||
mod repo;
|
||||
use crate::user::User;
|
||||
use crate::repo::Repo;
|
||||
|
||||
fn find_user() -> Option<User> {
|
||||
Some(User)
|
||||
}
|
||||
|
||||
fn find_repo() -> Option<Repo> {
|
||||
Some(Repo)
|
||||
}
|
||||
|
||||
fn process_entities() {
|
||||
let user: Option<User> = find_user();
|
||||
user.unwrap().save();
|
||||
let repo: Option<Repo> = find_repo();
|
||||
repo.unwrap().save();
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
7
gitnexus/test/fixtures/lang-resolution/rust-nullable-receiver/src/repo.rs
vendored
Normal file
7
gitnexus/test/fixtures/lang-resolution/rust-nullable-receiver/src/repo.rs
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
pub struct Repo;
|
||||
|
||||
impl Repo {
|
||||
pub fn save(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
7
gitnexus/test/fixtures/lang-resolution/rust-nullable-receiver/src/user.rs
vendored
Normal file
7
gitnexus/test/fixtures/lang-resolution/rust-nullable-receiver/src/user.rs
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
pub struct User;
|
||||
|
||||
impl User {
|
||||
pub fn save(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
17
gitnexus/test/fixtures/lang-resolution/rust-option-receiver/src/main.rs
vendored
Normal file
17
gitnexus/test/fixtures/lang-resolution/rust-option-receiver/src/main.rs
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
mod user;
|
||||
mod repo;
|
||||
use crate::user::User;
|
||||
use crate::repo::Repo;
|
||||
|
||||
// Tests that Option<User> unwraps to User in TypeEnv,
|
||||
// and assignment chain from Option-typed source resolves correctly.
|
||||
fn process_entities() {
|
||||
let opt: Option<User> = Some(User);
|
||||
let alias = opt;
|
||||
alias.save();
|
||||
|
||||
let repo: Repo = Repo;
|
||||
repo.save();
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
5
gitnexus/test/fixtures/lang-resolution/rust-option-receiver/src/repo.rs
vendored
Normal file
5
gitnexus/test/fixtures/lang-resolution/rust-option-receiver/src/repo.rs
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
pub struct Repo;
|
||||
|
||||
impl Repo {
|
||||
pub fn save(&self) {}
|
||||
}
|
||||
5
gitnexus/test/fixtures/lang-resolution/rust-option-receiver/src/user.rs
vendored
Normal file
5
gitnexus/test/fixtures/lang-resolution/rust-option-receiver/src/user.rs
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
pub struct User;
|
||||
|
||||
impl User {
|
||||
pub fn save(&self) {}
|
||||
}
|
||||
15
gitnexus/test/fixtures/lang-resolution/ts-assignment-chain/src/app.ts
vendored
Normal file
15
gitnexus/test/fixtures/lang-resolution/ts-assignment-chain/src/app.ts
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { User } from './user';
|
||||
import { Repo } from './repo';
|
||||
|
||||
function getUser(): User { return new User(); }
|
||||
function getRepo(): Repo { return new Repo(); }
|
||||
|
||||
export function processEntities(): void {
|
||||
const u: User = getUser();
|
||||
const alias = u;
|
||||
alias.save();
|
||||
|
||||
const r: Repo = getRepo();
|
||||
const rAlias = r;
|
||||
rAlias.save();
|
||||
}
|
||||
5
gitnexus/test/fixtures/lang-resolution/ts-assignment-chain/src/repo.ts
vendored
Normal file
5
gitnexus/test/fixtures/lang-resolution/ts-assignment-chain/src/repo.ts
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
export class Repo {
|
||||
save(): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
5
gitnexus/test/fixtures/lang-resolution/ts-assignment-chain/src/user.ts
vendored
Normal file
5
gitnexus/test/fixtures/lang-resolution/ts-assignment-chain/src/user.ts
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
export class User {
|
||||
save(): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue