diff --git a/snapshots/input/syntax/src/symbol-kinds.ts b/snapshots/input/syntax/src/symbol-kinds.ts new file mode 100644 index 00000000..f1220d74 --- /dev/null +++ b/snapshots/input/syntax/src/symbol-kinds.ts @@ -0,0 +1,54 @@ +// format-options: showKinds +export const constant = 1 +export let mutable = 2 +export var legacy = 3 + +export type Alias = { value: T } + +export interface Contract { + property: T + method(value: T): T +} + +export enum Choice { + First, +} + +export namespace Space { + export const nested = 1 +} + +export class Example implements Contract { + static staticProperty = 1 + property: T + + constructor(property: T) { + this.property = property + } + + method(value: T): T { + return value + } + + static staticMethod(value: number): number { + return value + } + + get accessor(): T { + return this.property + } + + set accessor(value: T) { + this.property = value + } + + explicitThis(this: Example, value: T): T { + return value + } +} + +export function identity(value: T): T { + const local = value + let mutableLocal = local + return mutableLocal +} diff --git a/snapshots/output/syntax/src/symbol-kinds.ts b/snapshots/output/syntax/src/symbol-kinds.ts new file mode 100644 index 00000000..5bfbd942 --- /dev/null +++ b/snapshots/output/syntax/src/symbol-kinds.ts @@ -0,0 +1,162 @@ +// < definition syntax 1.0.0 src/`symbol-kinds.ts`/ +//kind File + +// format-options: showKinds +export const constant = 1 +// ^^^^^^^^ definition syntax 1.0.0 src/`symbol-kinds.ts`/constant. +// kind Constant +export let mutable = 2 +// ^^^^^^^ definition syntax 1.0.0 src/`symbol-kinds.ts`/mutable. +// kind Variable +export var legacy = 3 +// ^^^^^^ definition syntax 1.0.0 src/`symbol-kinds.ts`/legacy. +// kind Variable + +export type Alias = { value: T } +// ^^^^^ definition syntax 1.0.0 src/`symbol-kinds.ts`/Alias# +// kind TypeAlias +// ^ definition syntax 1.0.0 src/`symbol-kinds.ts`/Alias#[T] +// kind TypeParameter +// ^^^^^ definition syntax 1.0.0 src/`symbol-kinds.ts`/Alias#typeLiteral0:value. +// kind Property +// ^ reference syntax 1.0.0 src/`symbol-kinds.ts`/Alias#[T] + +export interface Contract { +// ^^^^^^^^ definition syntax 1.0.0 src/`symbol-kinds.ts`/Contract# +// kind Interface +// ^ definition syntax 1.0.0 src/`symbol-kinds.ts`/Contract#[T] +// kind TypeParameter + property: T +//^^^^^^^^ definition syntax 1.0.0 src/`symbol-kinds.ts`/Contract#property. +//kind Property +// ^ reference syntax 1.0.0 src/`symbol-kinds.ts`/Contract#[T] + method(value: T): T +//^^^^^^ definition syntax 1.0.0 src/`symbol-kinds.ts`/Contract#method(). +//kind Method +// ^^^^^ definition syntax 1.0.0 src/`symbol-kinds.ts`/Contract#method().(value) +// kind Parameter +// ^ reference syntax 1.0.0 src/`symbol-kinds.ts`/Contract#[T] +// ^ reference syntax 1.0.0 src/`symbol-kinds.ts`/Contract#[T] +} + +export enum Choice { +// ^^^^^^ definition syntax 1.0.0 src/`symbol-kinds.ts`/Choice# +// kind Enum + First, +//^^^^^ definition syntax 1.0.0 src/`symbol-kinds.ts`/Choice#First. +//kind EnumMember +} + +export namespace Space { +// ^^^^^ definition syntax 1.0.0 src/`symbol-kinds.ts`/Space/ +// kind Namespace + export const nested = 1 +// ^^^^^^ definition syntax 1.0.0 src/`symbol-kinds.ts`/Space/nested. +// kind Constant +} + +export class Example implements Contract { +// ^^^^^^^ definition syntax 1.0.0 src/`symbol-kinds.ts`/Example# +// kind Class +// relationship implementation syntax 1.0.0 src/`symbol-kinds.ts`/Contract# +// ^ definition syntax 1.0.0 src/`symbol-kinds.ts`/Example#[T] +// kind TypeParameter +// ^^^^^^^^ reference syntax 1.0.0 src/`symbol-kinds.ts`/Contract# +// ^ reference syntax 1.0.0 src/`symbol-kinds.ts`/Example#[T] + static staticProperty = 1 +// ^^^^^^^^^^^^^^ definition syntax 1.0.0 src/`symbol-kinds.ts`/Example#staticProperty. +// kind StaticProperty + property: T +//^^^^^^^^ definition syntax 1.0.0 src/`symbol-kinds.ts`/Example#property. +//kind Property +//relationship implementation reference syntax 1.0.0 src/`symbol-kinds.ts`/Contract#property. +// ^ reference syntax 1.0.0 src/`symbol-kinds.ts`/Example#[T] + + constructor(property: T) { +//^^^^^^^^^^^ definition syntax 1.0.0 src/`symbol-kinds.ts`/Example#``(). +//kind Constructor +// ^^^^^^^^ definition syntax 1.0.0 src/`symbol-kinds.ts`/Example#``().(property) +// kind Parameter +// ^ reference syntax 1.0.0 src/`symbol-kinds.ts`/Example#[T] + this.property = property +// ^^^^^^^^ reference syntax 1.0.0 src/`symbol-kinds.ts`/Example#property. +// ^^^^^^^^ reference syntax 1.0.0 src/`symbol-kinds.ts`/Example#``().(property) + } + + method(value: T): T { +//^^^^^^ definition syntax 1.0.0 src/`symbol-kinds.ts`/Example#method(). +//kind Method +//relationship implementation reference syntax 1.0.0 src/`symbol-kinds.ts`/Contract#method(). +// ^^^^^ definition syntax 1.0.0 src/`symbol-kinds.ts`/Example#method().(value) +// kind Parameter +// ^ reference syntax 1.0.0 src/`symbol-kinds.ts`/Example#[T] +// ^ reference syntax 1.0.0 src/`symbol-kinds.ts`/Example#[T] + return value +// ^^^^^ reference syntax 1.0.0 src/`symbol-kinds.ts`/Example#method().(value) + } + + static staticMethod(value: number): number { +// ^^^^^^^^^^^^ definition syntax 1.0.0 src/`symbol-kinds.ts`/Example#staticMethod(). +// kind StaticMethod +// ^^^^^ definition syntax 1.0.0 src/`symbol-kinds.ts`/Example#staticMethod().(value) +// kind Parameter + return value +// ^^^^^ reference syntax 1.0.0 src/`symbol-kinds.ts`/Example#staticMethod().(value) + } + + get accessor(): T { +// ^^^^^^^^ definition syntax 1.0.0 src/`symbol-kinds.ts`/Example#`accessor`(). +// kind Getter +// ^ reference syntax 1.0.0 src/`symbol-kinds.ts`/Example#[T] + return this.property +// ^^^^^^^^ reference syntax 1.0.0 src/`symbol-kinds.ts`/Example#property. + } + + set accessor(value: T) { +// ^^^^^^^^ definition syntax 1.0.0 src/`symbol-kinds.ts`/Example#`accessor`(). +// kind Setter +// ^^^^^ definition syntax 1.0.0 src/`symbol-kinds.ts`/Example#`accessor`().(value) +// kind Parameter +// ^ reference syntax 1.0.0 src/`symbol-kinds.ts`/Example#[T] + this.property = value +// ^^^^^^^^ reference syntax 1.0.0 src/`symbol-kinds.ts`/Example#property. +// ^^^^^ reference syntax 1.0.0 src/`symbol-kinds.ts`/Example#`accessor`().(value) + } + + explicitThis(this: Example, value: T): T { +//^^^^^^^^^^^^ definition syntax 1.0.0 src/`symbol-kinds.ts`/Example#explicitThis(). +//kind Method +// ^^^^ definition syntax 1.0.0 src/`symbol-kinds.ts`/Example#explicitThis().(this) +// kind ThisParameter +// ^^^^^^^ reference syntax 1.0.0 src/`symbol-kinds.ts`/Example# +// ^ reference syntax 1.0.0 src/`symbol-kinds.ts`/Example#[T] +// ^^^^^ definition syntax 1.0.0 src/`symbol-kinds.ts`/Example#explicitThis().(value) +// kind Parameter +// ^ reference syntax 1.0.0 src/`symbol-kinds.ts`/Example#[T] +// ^ reference syntax 1.0.0 src/`symbol-kinds.ts`/Example#[T] + return value +// ^^^^^ reference syntax 1.0.0 src/`symbol-kinds.ts`/Example#explicitThis().(value) + } +} + +export function identity(value: T): T { +// ^^^^^^^^ definition syntax 1.0.0 src/`symbol-kinds.ts`/identity(). +// kind Function +// ^ definition syntax 1.0.0 src/`symbol-kinds.ts`/identity().[T] +// kind TypeParameter +// ^^^^^ definition syntax 1.0.0 src/`symbol-kinds.ts`/identity().(value) +// kind Parameter +// ^ reference syntax 1.0.0 src/`symbol-kinds.ts`/identity().[T] +// ^ reference syntax 1.0.0 src/`symbol-kinds.ts`/identity().[T] + const local = value +// ^^^^^ definition local 3 +// kind Constant +// ^^^^^ reference syntax 1.0.0 src/`symbol-kinds.ts`/identity().(value) + let mutableLocal = local +// ^^^^^^^^^^^^ definition local 6 +// kind Variable +// ^^^^^ reference local 3 + return mutableLocal +// ^^^^^^^^^^^^ reference local 6 +} + diff --git a/src/FileIndexer.ts b/src/FileIndexer.ts index 991ce9dc..2cadbeca 100644 --- a/src/FileIndexer.ts +++ b/src/FileIndexer.ts @@ -82,6 +82,7 @@ export class FileIndexer { new scip.scip.SymbolInformation({ symbol: symbol.value, documentation: ['```ts\nmodule "' + moduleName + '"\n```'], + kind: scip.scip.SymbolInformation.Kind.File, }) ) } @@ -351,6 +352,7 @@ export class FileIndexer { symbol: symbol.value, documentation, relationships: this.relationships(declaration, symbol), + kind: symbolKind(declaration, sym), }) ) } @@ -822,6 +824,135 @@ function scriptElementKind( return ts.ScriptElementKind.unknown } +function symbolKind( + declaration: ts.Node, + sym: ts.Symbol +): scip.scip.SymbolInformation.Kind { + const Kind = scip.scip.SymbolInformation.Kind + + if (ts.isClassLike(declaration)) { + return Kind.Class + } + if (ts.isInterfaceDeclaration(declaration)) { + return Kind.Interface + } + if (ts.isTypeAliasDeclaration(declaration)) { + return Kind.TypeAlias + } + if (ts.isEnumDeclaration(declaration)) { + return Kind.Enum + } + if (ts.isEnumMember(declaration)) { + return Kind.EnumMember + } + if (ts.isConstructorDeclaration(declaration)) { + return Kind.Constructor + } + if (ts.isGetAccessorDeclaration(declaration)) { + return Kind.Getter + } + if (ts.isSetAccessorDeclaration(declaration)) { + return Kind.Setter + } + if ( + ts.isMethodDeclaration(declaration) || + ts.isMethodSignature(declaration) + ) { + return hasStaticModifier(declaration) ? Kind.StaticMethod : Kind.Method + } + if (ts.isFunctionDeclaration(declaration)) { + return Kind.Function + } + if ( + ts.isPropertyDeclaration(declaration) || + ts.isPropertySignature(declaration) || + ts.isPropertyAssignment(declaration) || + ts.isShorthandPropertyAssignment(declaration) + ) { + return hasStaticModifier(declaration) ? Kind.StaticProperty : Kind.Property + } + if (ts.isParameter(declaration)) { + return declaration.name.getText() === 'this' + ? Kind.ThisParameter + : Kind.Parameter + } + if (ts.isTypeParameterDeclaration(declaration)) { + return Kind.TypeParameter + } + if (ts.isModuleDeclaration(declaration)) { + return ts.isStringLiteral(declaration.name) ? Kind.Module : Kind.Namespace + } + if (ts.isVariableDeclaration(declaration)) { + return variableKind(declaration) + } + if (ts.isBindingElement(declaration)) { + return bindingElementKind(declaration) + } + + const flags = sym.getFlags() + if (flags & ts.SymbolFlags.TypeAlias) return Kind.TypeAlias + if (flags & ts.SymbolFlags.Class) return Kind.Class + if (flags & ts.SymbolFlags.Interface) return Kind.Interface + if (flags & ts.SymbolFlags.Enum) return Kind.Enum + if (flags & ts.SymbolFlags.EnumMember) return Kind.EnumMember + if (flags & ts.SymbolFlags.Constructor) return Kind.Constructor + if (flags & ts.SymbolFlags.GetAccessor) return Kind.Getter + if (flags & ts.SymbolFlags.SetAccessor) return Kind.Setter + if (flags & ts.SymbolFlags.Method) return Kind.Method + if (flags & ts.SymbolFlags.Function) return Kind.Function + if (flags & ts.SymbolFlags.TypeParameter) return Kind.TypeParameter + if (flags & ts.SymbolFlags.NamespaceModule) return Kind.Namespace + if (flags & ts.SymbolFlags.Property) return Kind.Property + if (flags & ts.SymbolFlags.Variable) { + return ts_inline.isParameter(sym) ? Kind.Parameter : Kind.Variable + } + return Kind.UnspecifiedKind +} + +function hasStaticModifier(node: ts.Node): boolean { + return Boolean( + ts.canHaveModifiers(node) && + ts + .getModifiers(node) + ?.some(modifier => modifier.kind === ts.SyntaxKind.StaticKeyword) + ) +} + +function variableKind( + declaration: ts.VariableDeclaration +): scip.scip.SymbolInformation.Kind { + const Kind = scip.scip.SymbolInformation.Kind + const parent = declaration.parent + if ( + ts.isVariableDeclarationList(parent) && + parent.flags & (ts.NodeFlags.Const | ts.NodeFlags.Using) + ) { + return Kind.Constant + } + return Kind.Variable +} + +function bindingElementKind( + declaration: ts.BindingElement +): scip.scip.SymbolInformation.Kind { + const Kind = scip.scip.SymbolInformation.Kind + let owner: ts.Node = declaration.parent + while ( + ts.isBindingElement(owner) || + ts.isArrayBindingPattern(owner) || + ts.isObjectBindingPattern(owner) + ) { + owner = owner.parent + } + if (ts.isParameter(owner)) { + return Kind.Parameter + } + if (ts.isVariableDeclaration(owner)) { + return variableKind(owner) + } + return Kind.Variable +} + function isEqualOccurrence( a: scip.scip.Occurrence, b: scip.scip.Occurrence diff --git a/src/SnapshotTesting.ts b/src/SnapshotTesting.ts index 47bf898b..89e5c300 100644 --- a/src/SnapshotTesting.ts +++ b/src/SnapshotTesting.ts @@ -18,10 +18,12 @@ function getSymbolTable( function parseOptions(lines: string[]): { showDocs: boolean + showKinds: boolean showRanges: boolean } { const formatOptions = { showDocs: false, + showKinds: false, showRanges: false, } @@ -172,13 +174,23 @@ export function formatSnapshot( } } + const pushKind = (kind: scip.SymbolInformation.Kind): void => { + if (!formatOptions.showKinds) { + return + } + out.push(prefix) + out.push(`kind ${scip.SymbolInformation.Kind[kind]}`) + } + const externalSymbol = externalSymbolTable.get(symbol) if (externalSymbol) { + pushKind(externalSymbol.kind) pushOneDoc(externalSymbol.documentation, true) pushOneRelationship(externalSymbol.relationships) } else { const info = symbolTable.get(symbol) if (info) { + pushKind(info.kind) pushOneDoc(info.documentation, false) pushOneRelationship(info.relationships) } diff --git a/src/main.test.ts b/src/main.test.ts index 17b26125..23b8b263 100644 --- a/src/main.test.ts +++ b/src/main.test.ts @@ -5,6 +5,7 @@ import * as process from 'process' import * as Diff from 'diff' import { test } from 'uvu' +import * as assert from 'uvu/assert' import { Input } from './Input' import { indexCommand } from './main' @@ -74,6 +75,18 @@ for (const snapshotDirectory of snapshotDirectories) { throw new Error('empty LSIF index') } for (const document of index.documents) { + if (document.relative_path === 'src/symbol-kinds.ts') { + assert.equal( + document.symbols + .filter( + symbol => + symbol.kind === scip.scip.SymbolInformation.Kind.UnspecifiedKind + ) + .map(symbol => symbol.symbol), + [], + 'all symbols in the symbol-kind fixture should have a SCIP kind' + ) + } const inputPath = path.join(inputRoot, document.relative_path) const relativeToInputDirectory = path.relative(inputDirectory, inputPath) const outputPath = path.resolve(outputDirectory, relativeToInputDirectory)