-
Notifications
You must be signed in to change notification settings - Fork 21
Use unique declaration references #192
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -65,7 +65,21 @@ export class FileIndexer { | |
if (ts.isIdentifier(node) || ts.isStringLiteralLike(node)) { | ||
const sym = this.getTSSymbolAtLocation(node) | ||
if (sym) { | ||
this.visitSymbolOccurrence(node, sym) | ||
/** | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree it's not super clear why There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
* For example: | ||
* | ||
* const a = 1 | ||
* ^ node | ||
* ^ node.parent.name | ||
* ^^^^^ node.parent | ||
*/ | ||
const isDefinition = this.declarationName(node.parent) === node | ||
|
||
if (isDefinition) { | ||
return this.indexDefinition(node, sym) | ||
} | ||
|
||
this.indexReference(node, sym) | ||
valerybugakov marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
ts.forEachChild(node, node => this.visit(node)) | ||
|
@@ -97,42 +111,48 @@ export class FileIndexer { | |
return symbol | ||
} | ||
|
||
private visitSymbolOccurrence(node: ts.Node, sym: ts.Symbol): void { | ||
const range = Range.fromNode(node).toLsif() | ||
let role = 0 | ||
const isDefinition = this.declarationName(node.parent) === node | ||
if (isDefinition) { | ||
role |= scip.scip.SymbolRole.Definition | ||
private indexDefinition(node: ts.Node, sym: ts.Symbol): void { | ||
const [declaration] = sym?.declarations || [] | ||
const scipSymbol = this.scipSymbol(declaration) | ||
|
||
if (scipSymbol.isEmpty()) { | ||
return | ||
} | ||
for (const declaration of sym?.declarations || []) { | ||
const scipSymbol = this.scipSymbol(declaration) | ||
|
||
if (scipSymbol.isEmpty()) { | ||
// Skip empty symbols | ||
continue | ||
} | ||
const range = Range.fromNode(node).toLsif() | ||
|
||
this.document.occurrences.push( | ||
new scip.scip.Occurrence({ | ||
range, | ||
symbol: scipSymbol.value, | ||
symbol_roles: scip.scip.SymbolRole.Definition, | ||
}) | ||
) | ||
|
||
this.addSymbolInformation(node, sym, declaration, scipSymbol) | ||
this.handleShorthandPropertyDefinition(declaration, range) | ||
this.handleObjectBindingPattern(node, range) | ||
} | ||
|
||
private indexReference(node: ts.Node, sym: ts.Symbol): void { | ||
const range = Range.fromNode(node).toLsif() | ||
|
||
for (const symbol of this.uniqueDeclarationSymbolValues(sym)) { | ||
this.document.occurrences.push( | ||
new scip.scip.Occurrence({ | ||
range, | ||
symbol: scipSymbol.value, | ||
symbol_roles: role, | ||
symbol, | ||
symbol_roles: scip.scip.SymbolRole.UnspecifiedSymbolRole, | ||
}) | ||
) | ||
if (isDefinition) { | ||
this.addSymbolInformation(node, sym, declaration, scipSymbol) | ||
this.handleShorthandPropertyDefinition(declaration, range) | ||
this.handleObjectBindingPattern(node, range) | ||
// Only emit one symbol for definitions sites, see https://github.com/sourcegraph/lsif-typescript/issues/45 | ||
break | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Emits an additional definition occurrence when destructuring an object | ||
* pattern. For example: | ||
* ``` | ||
* interface Props { property: number} | ||
* interface Props { property: number } | ||
* const props: Props[] = [{ property: 42 }] | ||
* props.map(({property}) => property) = {a} | ||
* // ^^^^^^^^ references `Props.property` and defines a local parameter `property` | ||
|
@@ -148,15 +168,11 @@ export class FileIndexer { | |
} | ||
const tpe = this.checker.getTypeAtLocation(node.parent.parent) | ||
const property = tpe.getProperty(node.getText()) | ||
for (const declaration of property?.declarations || []) { | ||
const scipSymbol = this.scipSymbol(declaration) | ||
if (scipSymbol.isEmpty()) { | ||
continue | ||
} | ||
for (const symbol of this.uniqueDeclarationSymbolValues(property)) { | ||
this.document.occurrences.push( | ||
new scip.scip.Occurrence({ | ||
range, | ||
symbol: scipSymbol.value, | ||
symbol, | ||
}) | ||
) | ||
} | ||
|
@@ -233,6 +249,7 @@ export class FileIndexer { | |
): scip.scip.Relationship[] { | ||
const relationships: scip.scip.Relationship[] = [] | ||
const isAddedSymbol = new Set<string>() | ||
|
||
const pushImplementation = ( | ||
node: ts.NamedDeclaration, | ||
isReferences: boolean | ||
|
@@ -305,7 +322,7 @@ export class FileIndexer { | |
return undefined | ||
} | ||
|
||
private scipSymbol(node: ts.Node): ScipSymbol { | ||
private scipSymbol = (node: ts.Node): ScipSymbol => { | ||
valerybugakov marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const fromCache: ScipSymbol | undefined = | ||
this.globalSymbolTable.get(node) || this.localSymbolTable.get(node) | ||
if (fromCache) { | ||
|
@@ -361,7 +378,7 @@ export class FileIndexer { | |
return this.scipSymbol(decl) | ||
} | ||
} catch { | ||
// TODO: https://github.com/sourcegraph/lsif-typescript/issues/34 | ||
// TODO: https://github.com/sourcegraph/scip-typescript/issues/34 | ||
// continue regardless of error, the TypeScript compiler tends to | ||
// trigger stack overflows in getTypeOfSymbolAtLocation and we | ||
// don't know why yet. | ||
|
@@ -378,7 +395,11 @@ export class FileIndexer { | |
return this.cached(node, this.scipSymbol(node.parent)) | ||
} | ||
|
||
if (ts.isImportSpecifier(node) || ts.isImportClause(node)) { | ||
if ( | ||
ts.isImportSpecifier(node) || | ||
ts.isImportClause(node) || | ||
ts.isNamespaceImport(node) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this change related to de-duplication? It would be nice to isolate this change and have a diff in the snapshot tests to better understand the exact motivation for this change. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
) { | ||
const tpe = this.checker.getTypeAtLocation(node) | ||
for (const declaration of tpe.symbol?.declarations || []) { | ||
return this.scipSymbol(declaration) | ||
|
@@ -608,6 +629,25 @@ export class FileIndexer { | |
|
||
return this.checker.getTypeAtLocation(node) | ||
} | ||
|
||
/** | ||
* @param sym ts.Symbol with declarations to process. | ||
* @returns an array of unique non-empty SCIP symbol values extracted | ||
* from ts.Symbol declarations. | ||
*/ | ||
private uniqueDeclarationSymbolValues(sym?: ts.Symbol): string[] { | ||
varungandhi-src marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (!sym?.declarations) { | ||
return [] | ||
} | ||
|
||
return Array.from( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This expression is fairly expensive considering it's in a very hot loop and that we can achieve a similar results with a single if condition, as is done in It's very important that we try to keep performance parity with |
||
new Set( | ||
sym.declarations | ||
.map(declaration => this.scipSymbol(declaration).value) | ||
.filter(Boolean) | ||
) | ||
) | ||
varungandhi-src marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
|
||
function isAnonymousContainerOfSymbols(node: ts.Node): boolean { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added new
test-fast
andupdate-snapshots-fast
scripts. I prefer to get the type errors in the terminal because typechecking is reasonably fast in this codebase and debugging runtime exceptions is slower than debugging type errors.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good! To clarify the reasoning behind this: while iterating on changes locally, I often have type errors because I want to test some logic but don't want to invest time into making Typescript happy about it because there's no intent to commit it. Type-checking slows down the process of trying different ideas on existing tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm +1 to adding these scripts to package.json as long as we keep the typechecking variants as well. For sg/sg, I appreciate that
sg start
doesn't typecheck because it's a big codebase slowing down the edit/reload/debug feedback loop. In scip-typescript, typechecking has been fast enough for my needs.