mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-24 06:53:52 +08:00
fix(ts): a type a module doesn't import is never another module's (#2736)
A TypeScript module reaches another module's type only through an import, so a type it names without importing it is its own or a global one: a script's top-level declaration or one in a declare global block. #2726 held a supertype to that. A type annotation, a type argument and a type alias's value went to the name matcher like any other name, which handed them a project type of that name: vscode's Omit<...> annotations reached quickInput.ts's export type Omit instead of TypeScript's own, and type parameters and mapped keys, like excalidraw's <Point extends ...> and frontend's <T>, reached other modules' types and enum members. Both name-match strategies now leave out another module's non-global type for a TS or JS module's type reference to a name the file doesn't import, ahead of the ranking, so a declare global type still wins over a nearer module's namesake. Left as they were: the last name of a qualified type, vscode.TextDocument, written after a dot; a name inside a declare module 'x' block, which is module x's own; and a name an import X = ns.X alias binds, which the file's import list doesn't hold. Globals come from declaration files and declare global blocks. A script .ts's top level is a sample's or a fixture's, global to its own program only (vscode's colorize fixtures declare type T), and a test's or fixture's globals reach its tests only, as its declare module blocks do. That holds for supertypes too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
0c28480576
commit
458ad51921
@@ -263,6 +263,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
- In C++, a call written through a namespace, like protocolbuffers/protobuf's `absl::StripPrefix(…)` or fmtlib/fmt's `fmt::ptr(p)`, no longer links to a class's method that only shares the function's name, like protobuf's `MultiCasePrefixStripper::StripPrefix` or a test scanner's `ptr()`: a namespace holds functions and types, not methods. A call through a namespace a macro opens now links to what that namespace declares, so fmt's `fmt::ptr(p)` reaches fmt's own `ptr` function and `fmt::day(…)` its `day` type, while a call into a library outside your project, like abseil, links to nothing. A call through a lowercase class, or an alias or typedef of one, links as before. Re-index C++ projects after upgrading.
|
||||
- In TypeScript and JavaScript, a call to a function's own parameter, like `isAvailable(f)` on a parameter typed `(f: string) => boolean` or `compare(a, b)` on a comparator parameter, no longer links to a function of the same name in another file, or to the file's own import of that name. Before, a parameter whose type or default value contained parentheses, as a function type does, wasn't recognized as one. A parameter or local stands in for an import of its name only where it is in scope, so a Promise executor's `resolve(…)` no longer links to an imported `path.resolve`, while a `resolve(file)` after the executor still does. Re-index TypeScript and JavaScript projects after upgrading.
|
||||
- In TypeScript and JavaScript, a name ending in `$`, like `syncState$`, is no longer read as a declaration of `syncState`, so a call to an imported `syncState(…)` in the same function keeps its link, and a file's own `function $()` or `const $ = …` now counts as declaring `$`.
|
||||
- In TypeScript, a type a file uses without importing it, like `Omit<T, K>`, `Record<K, V>`, the DOM's `Event`, or a generic's own `T`, no longer links to a type of that name another module of your project declares, like a local `export type Omit` or an enum member `T`: it links to the file's own type or a global one, declared in a script `.d.ts` or a `declare global` block. A type you import, write qualified (`vscode.TextDocument`), bring in with `import X = ns.X`, or name inside a `declare module` block links as before. Re-index TypeScript projects after upgrading.
|
||||
|
||||
## [1.6.2] - 2026-10-03
|
||||
|
||||
|
||||
@@ -7011,13 +7011,15 @@ describe('Cross-language type/import gate (RN name collisions)', () => {
|
||||
// TS name-match — so a TS ref to `TestRunner` crossed web→jvm. The ref here
|
||||
// is intentionally NOT imported: a clean relative import would mask the bug
|
||||
// by resolving via the import map before the framework strategy can win.
|
||||
// So the TS type is a global, declared in a script `.d.ts`: the one kind of
|
||||
// type a module names without importing it.
|
||||
fs.writeFileSync(
|
||||
path.join(tempDir, 'package.json'),
|
||||
JSON.stringify({ dependencies: { 'react-native': '*' } })
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(tempDir, 'useTests.ts'),
|
||||
`export type TestRunner = { run: () => void };\n`
|
||||
path.join(tempDir, 'useTests.d.ts'),
|
||||
`type TestRunner = { run: () => void };\n`
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(tempDir, 'basic.tsx'),
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
* A type an ES module names without importing it is the module's own or a
|
||||
* global one — never another module's type of that name.
|
||||
*
|
||||
* // quickInput.ts
|
||||
* export type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
|
||||
* // picker.ts
|
||||
* export function pick(options: Omit<IPickOptions, 'canPickMany'>) {}
|
||||
*
|
||||
* picker.ts's `Omit` is TypeScript's own, yet a type annotation's name went
|
||||
* to the name matcher like any other, which handed it the one project type
|
||||
* of that name: 336 of vscode's annotations reached quickInput.ts's `export
|
||||
* type Omit`. #2726 held a supertype to this rule; a type annotation, a type
|
||||
* argument and a type alias's value now follow it too. A type the file
|
||||
* declares, one a script's top level or a `declare global` block declares,
|
||||
* and the last name of a qualified type (`qi.IPickOptions`) link as before.
|
||||
*/
|
||||
import { describe, it, expect, afterAll, beforeAll } from 'vitest';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { CodeGraph } from '../src';
|
||||
|
||||
let root = '';
|
||||
let cg: CodeGraph;
|
||||
|
||||
beforeAll(async () => {
|
||||
root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-ts-type-scope-'));
|
||||
const files: Record<string, string> = {
|
||||
'src/quickInput.ts': `export type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
|
||||
export interface IPickOptions { canPickMany?: boolean; title?: string }
|
||||
`,
|
||||
'src/events.ts': 'export class Event { constructor(readonly type: string) {} }\n',
|
||||
'src/picker.ts': `import { IPickOptions } from './quickInput';
|
||||
type Local = { id: string };
|
||||
export function pick(options: Omit<IPickOptions, 'canPickMany'>): Local {
|
||||
return { id: options.title ?? '' };
|
||||
}
|
||||
export function onClick(handler: (e: Event) => void): void {
|
||||
void handler;
|
||||
}
|
||||
export type Picked = Omit<IPickOptions, 'title'>;
|
||||
`,
|
||||
// An augmentation's names are the augmented module's own, imported or not.
|
||||
'src/react/reactInterfaces.ts': 'export type FCReactive<P, P2> = P & P2;\nexport interface IReactive { base?: string }\n',
|
||||
'src/types/reactive-native.d.ts': `import type { IReactive } from '../react/reactInterfaces';
|
||||
declare module '../react/reactInterfaces' {
|
||||
interface IReactive {
|
||||
View: FCReactive<IReactive, { style?: string }>;
|
||||
}
|
||||
}
|
||||
`,
|
||||
// An `import X = ns.X` alias imports its name, as bitwarden's desktop code does napi's classes.
|
||||
'src/napi.d.ts': 'export declare namespace autofill {\n export class IpcServer { listen(): void }\n}\n',
|
||||
'src/alias.ts': `import { autofill } from './napi';
|
||||
import IpcServer = autofill.IpcServer;
|
||||
export function serve(server: IpcServer): void {
|
||||
void server;
|
||||
}
|
||||
`,
|
||||
// Only the namespace is imported: `IPickOptions` is recorded alone, written qualified.
|
||||
'src/qualified.ts': `import * as qi from './quickInput';
|
||||
export function qualified(options: qi.IPickOptions): void {
|
||||
void options;
|
||||
}
|
||||
`,
|
||||
// A script declaration file (no import or export): its declarations are global.
|
||||
'src/typings/globals.d.ts': 'interface GlobalThing { id: string }\n',
|
||||
// A module's `declare global` block, and a module-scoped namesake nearer the user.
|
||||
'src/env.ts': `export {};
|
||||
declare global {
|
||||
interface AppConfig { debug: boolean }
|
||||
}
|
||||
`,
|
||||
'src/app/config.ts': 'export interface AppConfig { port: number }\n',
|
||||
'src/app/use.ts': `export function useGlobal(thing: GlobalThing): string {
|
||||
return thing.id;
|
||||
}
|
||||
export function useConfig(config: AppConfig): boolean {
|
||||
return config.debug;
|
||||
}
|
||||
export function useScripts(thing: LegacyThing, fixture: FixtureGlobal): number {
|
||||
return thing.id + fixture.x;
|
||||
}
|
||||
export const firstOf = <T>(items: T[]): T | undefined => items[0];
|
||||
export function useShared(shape: SharedShape): string {
|
||||
return shape.id;
|
||||
}
|
||||
`,
|
||||
// A global both the project and a nearer fixture declare: the project's wins, ahead of the ranking.
|
||||
'types/shared.d.ts': 'interface SharedShape { id: string }\n',
|
||||
'src/app/__tests__/shared-fixture.d.ts': 'interface SharedShape { id: string; fixture: true }\n',
|
||||
// A script .ts is no declaration file: its top level is global to its own program only.
|
||||
'src/legacy/globals.ts': 'interface LegacyThing { id: number }\n',
|
||||
// A fixture's globals are its tests' — a declaration file's, and a colorize-style script's `type T`.
|
||||
'test/fixtures/fixture.d.ts': 'interface FixtureGlobal { x: number }\n',
|
||||
'test/fixtures/colorize.ts': 'const n = null;\ntype T = null | undefined;\n',
|
||||
'test/use-fixture.test.ts': `export function check(fixture: FixtureGlobal): number {
|
||||
return fixture.x;
|
||||
}
|
||||
`,
|
||||
};
|
||||
for (const [rel, content] of Object.entries(files)) {
|
||||
fs.mkdirSync(path.dirname(path.join(root, rel)), { recursive: true });
|
||||
fs.writeFileSync(path.join(root, rel), content);
|
||||
}
|
||||
cg = await CodeGraph.init(root, { index: true });
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
cg?.close();
|
||||
if (root) fs.rmSync(root, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
/** `<file>:<qualified name>` of every type the named symbol in `file` references. */
|
||||
const referencesFrom = (file: string, name: string): string[] => {
|
||||
const node = cg.getNodesInFile(file).find((n) => n.name === name && n.kind !== 'file')!;
|
||||
expect(node, `${file} ${name}`).toBeDefined();
|
||||
return cg.getOutgoingEdges(node.id).filter((e) => e.kind === 'references').map((e) => {
|
||||
const target = cg.getNode(e.target)!;
|
||||
return `${target.filePath}:${target.qualifiedName}`;
|
||||
});
|
||||
};
|
||||
|
||||
describe('a type a TS module names without importing it', () => {
|
||||
it('is no other module’s type of that name: TypeScript’s Omit, the DOM’s Event', () => {
|
||||
const picked = referencesFrom('src/picker.ts', 'pick');
|
||||
expect(picked).not.toContain('src/quickInput.ts:Omit');
|
||||
expect(referencesFrom('src/picker.ts', 'onClick')).not.toContain('src/events.ts:Event');
|
||||
expect(referencesFrom('src/picker.ts', 'Picked')).not.toContain('src/quickInput.ts:Omit');
|
||||
});
|
||||
|
||||
it('is the module’s own type, and an imported one still links through its import', () => {
|
||||
const picked = referencesFrom('src/picker.ts', 'pick');
|
||||
expect(picked).toContain('src/picker.ts:Local');
|
||||
expect(picked).toContain('src/quickInput.ts:IPickOptions');
|
||||
expect(referencesFrom('src/picker.ts', 'Picked')).toContain('src/quickInput.ts:IPickOptions');
|
||||
});
|
||||
|
||||
it('is a global: a script’s top level or a declare global block, ahead of a nearer module’s namesake', () => {
|
||||
expect(referencesFrom('src/app/use.ts', 'useGlobal')).toContain('src/typings/globals.d.ts:GlobalThing');
|
||||
const config = referencesFrom('src/app/use.ts', 'useConfig');
|
||||
expect(config).toContain('src/env.ts:AppConfig');
|
||||
expect(config).not.toContain('src/app/config.ts:AppConfig');
|
||||
});
|
||||
|
||||
it('leaves the last name of a qualified type to link as before', () => {
|
||||
expect(referencesFrom('src/qualified.ts', 'qualified')).toContain('src/quickInput.ts:IPickOptions');
|
||||
});
|
||||
|
||||
it('leaves a name inside a declare module block to the module it declares', () => {
|
||||
expect(referencesFrom('src/types/reactive-native.d.ts', 'View')).toContain('src/react/reactInterfaces.ts:FCReactive');
|
||||
});
|
||||
|
||||
it('is no script .ts’s top-level type, nor a fixture’s outside the tests', () => {
|
||||
expect(referencesFrom('src/app/use.ts', 'useScripts')).toEqual([]);
|
||||
expect(referencesFrom('src/app/use.ts', 'firstOf')).not.toContain('test/fixtures/colorize.ts:T');
|
||||
expect(referencesFrom('test/use-fixture.test.ts', 'check')).toContain('test/fixtures/fixture.d.ts:FixtureGlobal');
|
||||
expect(referencesFrom('src/app/use.ts', 'useShared')).toEqual(['types/shared.d.ts:SharedShape']);
|
||||
});
|
||||
|
||||
it('counts an import X = ns.X alias as an import of its name', () => {
|
||||
expect(referencesFrom('src/alias.ts', 'serve').some((t) => t.startsWith('src/napi.d.ts:') && t.endsWith('IpcServer'))).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -235,6 +235,10 @@ export interface GlobalScopeScan {
|
||||
* `abstract`, `enum`, `function`, `const`, …).
|
||||
*/
|
||||
globalWords: Set<string>;
|
||||
/** The file's `declare module '<name>' { … }` blocks, whose names are the declared module's. */
|
||||
moduleBlocks: readonly AmbientModuleBlock[];
|
||||
/** Names an `import X = …` declaration binds: `import AutofillIpcServer = autofill.AutofillIpcServer`. */
|
||||
importAliases: Set<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -244,11 +248,22 @@ export interface GlobalScopeScan {
|
||||
* not in it.
|
||||
*/
|
||||
export function scanGlobalScope(source: string): GlobalScopeScan {
|
||||
const { script } = scanAmbientModules(source);
|
||||
const { script, blocks: moduleBlocks } = scanAmbientModules(source);
|
||||
const globalWords = new Set<string>();
|
||||
const importAliases = new Set<string>();
|
||||
let declare = 0; // 1: `declare`, 2: `declare global`
|
||||
let inGlobal = false;
|
||||
let alias = 0; // 1: `import` (or `import type`), 2: `import X`
|
||||
let aliasName = '';
|
||||
tokenize(source, (kind, text, line, column, depth) => {
|
||||
if (kind === Kind.Word && text === 'import') alias = 1;
|
||||
else if (alias === 1 && kind === Kind.Word && text !== 'type') {
|
||||
alias = 2;
|
||||
aliasName = text;
|
||||
} else if (alias === 2 && kind === Kind.Punct && text === '=') {
|
||||
importAliases.add(aliasName);
|
||||
alias = 0;
|
||||
} else if (!(alias === 1 && kind === Kind.Word)) alias = 0;
|
||||
if (inGlobal) {
|
||||
if (kind === Kind.Punct && text === '}' && depth === 0) inGlobal = false;
|
||||
else if (kind === Kind.Word && depth === 1) globalWords.add(`${line}:${column}`);
|
||||
@@ -266,7 +281,7 @@ export function scanGlobalScope(source: string): GlobalScopeScan {
|
||||
if (kind === Kind.Punct && text === '{' && declare === 2) inGlobal = true;
|
||||
declare = 0;
|
||||
});
|
||||
return { script, globalWords };
|
||||
return { script, globalWords, moduleBlocks, importAliases };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -519,7 +534,7 @@ function scanned(context: ResolutionContext): NonNullable<Memo['scanned']> {
|
||||
const DECLARATION_KINDS = new Set<Node['kind']>(['function', 'class', 'interface', 'type_alias', 'enum', 'constant', 'variable', 'component']);
|
||||
|
||||
/** Whether `node` starts inside the block, by position. */
|
||||
function inBlock(node: Pick<Node, 'startLine' | 'startColumn'>, block: AmbientModuleBlock): boolean {
|
||||
export function inBlock(node: Pick<Node, 'startLine' | 'startColumn'>, block: AmbientModuleBlock): boolean {
|
||||
const afterStart = node.startLine > block.startLine || (node.startLine === block.startLine && node.startColumn >= block.startColumn);
|
||||
const beforeEnd = node.startLine < block.endLine || (node.startLine === block.endLine && node.startColumn <= block.endColumn);
|
||||
return afterStart && beforeEnd;
|
||||
|
||||
@@ -20,8 +20,8 @@ import { clearCppRawStringMemos, cppLiteralsBlanked, cppNonCodeLines } from './c
|
||||
import { cppClassWritten, type CppNameSite } from './cpp-supertypes';
|
||||
import { cppIncludedFile, cppIncludedHeadersBeside, cppIncluders } from './cpp-includers';
|
||||
import { isCFeatureVariant, isOffByDefault } from './c-feature-variants';
|
||||
import { isTestPath } from '../search/query-utils';
|
||||
import { isMergedAmbientDeclaration, scanGlobalScope, type GlobalScopeScan } from './ambient-modules';
|
||||
import { isTestFile, isTestPath } from '../search/query-utils';
|
||||
import { inBlock, isDeclarationFile, isMergedAmbientDeclaration, scanGlobalScope, type GlobalScopeScan } from './ambient-modules';
|
||||
import { isMinifiedContent } from '../extraction/generated-detection';
|
||||
import { getCargoWorkspaceCrateMap } from './frameworks/cargo-workspace';
|
||||
import { readRustScopes, rustBindingAt, rustInnerBlockAt, rustBoundsOf, rustFnAt, rustImplAt, splitRustTopLevel, type RustBinding, type RustPatternSite, type RustScopes } from './rust-scopes';
|
||||
@@ -1547,35 +1547,71 @@ function tsGlobalScope(file: string, context: ResolutionContext): GlobalScopeSca
|
||||
if (scope === undefined) {
|
||||
const source = context.readFile(file);
|
||||
scope = source === null ? null : scanGlobalScope(source);
|
||||
if (scope?.script && HAS_CJS_EXPORT.test(source!)) scope = { script: false, globalWords: new Set() };
|
||||
if (scope?.script && HAS_CJS_EXPORT.test(source!)) scope = { ...scope, script: false, globalWords: new Set() };
|
||||
memo.set(file, scope);
|
||||
}
|
||||
return scope;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a supertype written as a bare name the file doesn't import, in an
|
||||
* ES module, may be `n`: a declaration of the file's own, or a global one —
|
||||
* at a script's top level (a file with no import or export, a script `.d.ts`)
|
||||
* or directly inside `declare global`. A module's own declaration, exported
|
||||
* or not, is reached only through an import, which the import resolver has
|
||||
* followed already, and a namespace's member is no global. So an interface's
|
||||
* `extends Omit<…>` is TypeScript's `Omit`, never vscode's `export type Omit`
|
||||
* in quickInput.ts, and `extends Function` never terminal-suggest's
|
||||
* `Fig.Function`.
|
||||
* Whether a type written as a bare name the file doesn't import, in an ES
|
||||
* module, may be `n`: a declaration of the file's own, or a global one — at a
|
||||
* script's top level (a file with no import or export, a script `.d.ts`) or
|
||||
* directly inside `declare global`. A module's own declaration, exported or
|
||||
* not, is reached only through an import, which the import resolver has
|
||||
* followed already, and a namespace's member is no global. So `extends
|
||||
* Omit<…>` and `options: Omit<IPickOptions, 'canPickMany'>` are TypeScript's
|
||||
* `Omit`, never vscode's `export type Omit` in quickInput.ts, and `extends
|
||||
* Function` never terminal-suggest's `Fig.Function`.
|
||||
*
|
||||
* The globals project code relies on come from declaration files and
|
||||
* `declare global` blocks. A script `.ts` is a sample or a fixture whose top
|
||||
* level is global only to its own program — vscode's colorize fixtures
|
||||
* declare `type T` and copilot's eslint fixtures `interface Node` — and a
|
||||
* test's or fixture's globals are its tests' only, as its `declare module`
|
||||
* blocks are (see ambient-modules.ts).
|
||||
*/
|
||||
function isTsSupertypeInScope(n: Node, ref: UnresolvedRef, context: ResolutionContext): boolean {
|
||||
function isTsTypeInScope(n: Node, ref: UnresolvedRef, context: ResolutionContext): boolean {
|
||||
if (n.filePath === ref.filePath || !TS_JS_FILE.test(n.filePath)) return true;
|
||||
const scope = tsGlobalScope(n.filePath, context);
|
||||
return !scope || scope.globalWords.has(`${n.startLine}:${n.startColumn}`);
|
||||
if (!scope) return true;
|
||||
if ((scope.script && !isDeclarationFile(n.filePath)) || (isTestFile(n.filePath) && !isTestFile(ref.filePath))) return false;
|
||||
return scope.globalWords.has(`${n.startLine}:${n.startColumn}`);
|
||||
}
|
||||
|
||||
/** Whether `ref` is a supertype written as a bare name its ES module doesn't import (isTsSupertypeInScope). */
|
||||
function isTsBareSupertype(ref: UnresolvedRef, context: ResolutionContext): boolean {
|
||||
return isInheritanceRef(ref) && JS_FAMILY.has(ref.language) && TS_JS_FILE.test(ref.filePath) &&
|
||||
/^[A-Za-z_$][\w$]*$/.test(ref.referenceName) &&
|
||||
!context.getImportMappings(ref.filePath, ref.language).some((m) => m.localName === ref.referenceName) &&
|
||||
tsGlobalScope(ref.filePath, context)?.script === false;
|
||||
/**
|
||||
* Whether `ref` names a type by a bare name its ES module doesn't import
|
||||
* (isTsTypeInScope): a supertype, or a type annotation's or type argument's
|
||||
* name, which TS and JS files record as `references` and nothing else does.
|
||||
* The last name of a qualified type, `vscode.TextDocument`, is no bare name,
|
||||
* and neither is a name inside a `declare module 'x'` block, which is module
|
||||
* `x`'s own: legend-state's augmentation of `'@legendapp/state/react'` writes
|
||||
* that module's `FCReactive` without importing it. An `import X = ns.X`
|
||||
* alias imports its name.
|
||||
*/
|
||||
function isTsBareTypeName(ref: UnresolvedRef, context: ResolutionContext): boolean {
|
||||
const typeRef = ref.referenceKind === 'references';
|
||||
if (!(typeRef || isInheritanceRef(ref)) || !JS_FAMILY.has(ref.language) || !TS_JS_FILE.test(ref.filePath) ||
|
||||
!/^[A-Za-z_$][\w$]*$/.test(ref.referenceName) ||
|
||||
context.getImportMappings(ref.filePath, ref.language).some((m) => m.localName === ref.referenceName) ||
|
||||
(typeRef && isWrittenQualified(ref, context))) return false;
|
||||
const scope = tsGlobalScope(ref.filePath, context);
|
||||
const at = { startLine: ref.line, startColumn: ref.column };
|
||||
return scope?.script === false && !scope.importAliases.has(ref.referenceName) &&
|
||||
!scope.moduleBlocks.some((block) => inBlock(at, block));
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a name recorded alone was written after a `.`, as the last name of
|
||||
* `ns.Type` or `import('x').Type` is. A column that doesn't find the name on
|
||||
* its line counts as written so, which leaves the reference as it was.
|
||||
*/
|
||||
function isWrittenQualified(ref: UnresolvedRef, context: ResolutionContext): boolean {
|
||||
const line = context.getFileLines?.(ref.filePath)?.[ref.line - 1] ?? context.readFile(ref.filePath)?.split(/\r?\n/)[ref.line - 1] ?? '';
|
||||
if (!line.startsWith(ref.referenceName, ref.column)) return true;
|
||||
let i = ref.column - 1;
|
||||
while (i >= 0 && (line[i] === ' ' || line[i] === '\t')) i--;
|
||||
return line[i] === '.';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -11585,8 +11621,8 @@ export function matchByExactName(
|
||||
const importRef = ref.referenceKind === 'imports';
|
||||
const inheritanceRef = isInheritanceRef(ref);
|
||||
// Read once, and only for a candidate in another file: it scans the file.
|
||||
let bareSupertype: boolean | undefined;
|
||||
const tsBareSupertype = (): boolean => (bareSupertype ??= isTsBareSupertype(ref, context));
|
||||
let bareTypeName: boolean | undefined;
|
||||
const tsBareTypeName = (): boolean => (bareTypeName ??= isTsBareTypeName(ref, context));
|
||||
const sameName = context.getNodesByName(ref.referenceName);
|
||||
// `NAME(...)` where NAME is a function-like macro somewhere in the project is
|
||||
// an expansion or a call to a same-named function — never the macro itself
|
||||
@@ -11687,10 +11723,10 @@ export function matchByExactName(
|
||||
// single-match shortcut. Restricting the pool BEFORE ranking lets the
|
||||
// legitimate supertype win instead of merely dropping the false edge.
|
||||
(!inheritanceRef || isSupertypeTarget(n)) &&
|
||||
// And a supertype a TS/JS module names without importing it is its own or
|
||||
// a global, never another module's — ahead of the ranking, so a global
|
||||
// `interface Window` in a `declare global` still wins.
|
||||
!(inheritanceRef && n.filePath !== ref.filePath && tsBareSupertype() && !isTsSupertypeInScope(n, ref, context)) &&
|
||||
// And a supertype or type a TS/JS module names without importing it is
|
||||
// its own or a global, never another module's — ahead of the ranking, so
|
||||
// a global `interface Window` in a `declare global` still wins.
|
||||
!(n.filePath !== ref.filePath && tsBareTypeName() && !isTsTypeInScope(n, ref, context)) &&
|
||||
// Likewise for `imports`: a member that only exists inside a type is not
|
||||
// importable, so it is not a candidate. Without this a `path`/`id`/`url`
|
||||
// import resolved to some interface's same-named property.
|
||||
@@ -23890,7 +23926,7 @@ export function matchFuzzy(
|
||||
(TYPE_MEMBER_KINDS.has(finalCandidates[0]!.kind) ||
|
||||
(finalCandidates[0]!.filePath !== ref.filePath && isLocallyBoundJsName(ref.referenceName, ref.filePath, context)))) &&
|
||||
!(JS_FAMILY.has(ref.language) && isOutOfRepoBinding(ref.referenceName, ref, context)) &&
|
||||
!(finalCandidates[0]!.filePath !== ref.filePath && isTsBareSupertype(ref, context) && !isTsSupertypeInScope(finalCandidates[0]!, ref, context)) &&
|
||||
!(finalCandidates[0]!.filePath !== ref.filePath && isTsBareTypeName(ref, context) && !isTsTypeInScope(finalCandidates[0]!, ref, context)) &&
|
||||
!(finalCandidates[0]!.kind === 'method' && isBareGoCall(ref, context)) &&
|
||||
// A bare PHP call is a function call (case-insensitive, so fuzzy may find
|
||||
// one) — never the class `View` for `view(…)`, never a method.
|
||||
|
||||
Reference in New Issue
Block a user