From 02c7c2153dcdeb816784b9c63f0055510a4d7dde Mon Sep 17 00:00:00 2001 From: Mikita Belahlazau Date: Mon, 17 Apr 2023 13:45:39 -0700 Subject: [PATCH] feat(typescript): emit marked sources for functions, classes and enums (#5578) Co-authored-by: Shahms King --- kythe/typescript/indexer.ts | 71 +++++++++++++++-- .../testdata/marked_source/class.ts | 77 +++++++++++++++++++ .../typescript/testdata/marked_source/enum.ts | 33 ++++++++ .../testdata/marked_source/function.ts | 56 ++++++++++++++ 4 files changed, 229 insertions(+), 8 deletions(-) create mode 100644 kythe/typescript/testdata/marked_source/class.ts create mode 100644 kythe/typescript/testdata/marked_source/enum.ts create mode 100644 kythe/typescript/testdata/marked_source/function.ts diff --git a/kythe/typescript/indexer.ts b/kythe/typescript/indexer.ts index 5b434b3c13..3be7c54bd3 100644 --- a/kythe/typescript/indexer.ts +++ b/kythe/typescript/indexer.ts @@ -1108,6 +1108,7 @@ class Visitor { this.emitNode(kType, NodeKind.INTERFACE); this.emitEdge(this.newAnchor(decl.name), EdgeKind.DEFINES_BINDING, kType); this.visitJSDoc(decl, kType); + this.emitMarkedSourceForClasslikeDeclaration(decl, kType); } if (decl.typeParameters) @@ -1780,7 +1781,7 @@ class Visitor { ts.isPropertyDeclaration(decl) || ts.isBindingElement(decl) || ts.isShorthandPropertyAssignment(decl) || ts.isPropertySignature(decl) || ts.isJsxAttribute(decl)) { - this.emitDeclarationCode(decl, vname); + this.emitMarkedSourceForVariable(decl, vname); } else { todo(this.sourceRoot, decl, 'Emit variable delaration code'); } @@ -1817,10 +1818,10 @@ class Visitor { * ((property)|(local var)|const|let) : ( = )? * where `(local var)` is the declaration of a variable in a catch clause. */ - emitDeclarationCode( + emitMarkedSourceForVariable( decl: ts.VariableDeclaration|ts.PropertyAssignment| ts.PropertyDeclaration|ts.BindingElement|ts.ShorthandPropertyAssignment| - ts.PropertySignature|ts.JsxAttribute, + ts.PropertySignature|ts.JsxAttribute|ts.ParameterDeclaration|ts.EnumMember, declVName: VName) { const codeParts: JSONMarkedSource[] = []; const initializerList = decl.parent; @@ -1854,17 +1855,24 @@ class Visitor { '(local var)' : initializerList.flags & ts.NodeFlags.Const ? 'const' : 'let'; + } else if (ts.isParameter(varDecl)) { + declKw = '(parameter)'; + } else if (ts.isEnumMember(varDecl)) { + declKw = '(enum member)'; } else { declKw = '(property)'; } - const ty = this.typeChecker.getTypeAtLocation(decl); - const tyStr = this.typeChecker.typeToString(ty, decl); codeParts.push({kind: MarkedSourceKind.CONTEXT, pre_text: fmtMarkedSource(declKw)}); codeParts.push({kind: MarkedSourceKind.BOX, pre_text: ' '}); codeParts.push( {kind: MarkedSourceKind.IDENTIFIER, pre_text: fmtMarkedSource(decl.name.getText())}); - codeParts.push( - {kind: MarkedSourceKind.TYPE, pre_text: ': ', post_text: fmtMarkedSource(tyStr)}); + if (!ts.isEnumMember(varDecl)) { + const ty = this.typeChecker.getTypeAtLocation(decl); + const tyStr = this.typeChecker.typeToString(ty, decl); + codeParts.push( + {kind: MarkedSourceKind.TYPE, pre_text: ': ', post_text: fmtMarkedSource(tyStr)}); + + } if ('initializer' in varDecl && varDecl.initializer) { let init: ts.Node = varDecl.initializer; @@ -1880,7 +1888,49 @@ class Visitor { {kind: MarkedSourceKind.INITIALIZER, pre_text: fmtMarkedSource(init.getText())}); } - const markedSource = ({kind: MarkedSourceKind.BOX, child: codeParts}); + const markedSource = {kind: MarkedSourceKind.BOX, child: codeParts}; + this.emitFact(declVName, FactName.CODE_JSON, JSON.stringify(markedSource)); + } + + /** + * Emits a code fact for a class specifying how the declaration should be presented to users. + */ + emitMarkedSourceForClasslikeDeclaration( + decl: ts.ClassLikeDeclaration|ts.InterfaceDeclaration|ts.EnumDeclaration, declVName: VName) { + const markedSource: JSONMarkedSource = + {kind: MarkedSourceKind.IDENTIFIER, pre_text: decl.name?.getText() ?? 'anonymous'}; + this.emitFact(declVName, FactName.CODE_JSON, JSON.stringify(markedSource)); + } + + /** + * Emits a code fact for a function specifying how the declaration should be presented to users. + */ + emitMarkedSourceForFunction(decl: ts.FunctionLikeDeclaration, declVName: VName) { + const codeParts: JSONMarkedSource[] = []; + const context = ts.isMethodDeclaration(decl) ? '(method)' : 'function'; + codeParts.push({kind: MarkedSourceKind.CONTEXT, pre_text: context}); + codeParts.push({kind: MarkedSourceKind.BOX, pre_text: ' '}); + codeParts.push({ + kind: MarkedSourceKind.IDENTIFIER, + pre_text: fmtMarkedSource(decl.name?.getText() ?? 'anonymous'), + }); + codeParts.push({ + kind: MarkedSourceKind.PARAMETER_LOOKUP_BY_PARAM, + pre_text: '(', + post_child_text: ', ', + post_text: ')' + }); + const signature = this.typeChecker.getTypeAtLocation(decl).getCallSignatures()[0]; + if (signature) { + const returnType = signature.getReturnType(); + const returnTypeStr = this.typeChecker.typeToString(returnType, decl); + codeParts.push({ + kind: MarkedSourceKind.TYPE, + pre_text: ': ', + post_text: fmtMarkedSource(returnTypeStr), + }); + } + const markedSource = {kind: MarkedSourceKind.BOX, child: codeParts}; this.emitFact(declVName, FactName.CODE_JSON, JSON.stringify(markedSource)); } @@ -2147,6 +2197,7 @@ class Visitor { } else { this.emitFact(vname, FactName.COMPLETE, 'incomplete'); } + this.emitMarkedSourceForFunction(decl, vname); } /** @@ -2199,6 +2250,7 @@ class Visitor { this.emitEdge( this.newAnchor(param.name), EdgeKind.DEFINES_BINDING, kParam); + this.emitMarkedSourceForVariable(param, kParam); break; case ts.SyntaxKind.ObjectBindingPattern: case ts.SyntaxKind.ArrayBindingPattern: @@ -2324,6 +2376,7 @@ class Visitor { } this.visitJSDoc(decl, kClass); + this.emitMarkedSourceForClasslikeDeclaration(decl, kClass); } if (decl.typeParameters) this.visitTypeParameters(kClass, decl.typeParameters); @@ -2349,6 +2402,7 @@ class Visitor { for (const member of decl.members) { this.visit(member); } + this.emitMarkedSourceForClasslikeDeclaration(decl, kValue); } visitEnumMember(decl: ts.EnumMember) { @@ -2358,6 +2412,7 @@ class Visitor { if (!kMember) return; this.emitNode(kMember, NodeKind.CONSTANT); this.emitEdge(this.newAnchor(decl.name), EdgeKind.DEFINES_BINDING, kMember); + this.emitMarkedSourceForVariable(decl, kMember); } visitExpressionMember(node: ts.Node) { diff --git a/kythe/typescript/testdata/marked_source/class.ts b/kythe/typescript/testdata/marked_source/class.ts new file mode 100644 index 0000000000..b4f8e5906c --- /dev/null +++ b/kythe/typescript/testdata/marked_source/class.ts @@ -0,0 +1,77 @@ + +//- @MyClass defines/binding MyClass +//- +//- MyClass code MyClassCode +//- MyClassCode.kind "IDENTIFIER" +//- MyClassCode.pre_text "MyClass" +class MyClass { + constructor(arg: string) {} + + //- @myMethod defines/binding MyMethod + //- + //- MyMethod code MyMethodCode + //- MyMethodCode.kind "BOX" + //- + //- MyMethodCode child.0 MyMethodContext + //- MyMethodContext.kind "CONTEXT" + //- MyMethodContext.pre_text "(method)" + //- + //- MyMethodCode child.1 MyMethodSpace + //- MyMethodSpace.pre_text " " + //- + //- MyMethodCode child.2 MyMethodCodeId + //- MyMethodCodeId.kind "IDENTIFIER" + //- MyMethodCodeId.pre_text "myMethod" + //- + //- MyMethodCode child.3 MyMethodParams + //- MyMethodParams.kind "PARAMETER_LOOKUP_BY_PARAM" + //- MyMethodParams.pre_text "(" + //- MyMethodParams.post_text ")" + //- MyMethodParams.post_child_text ", " + //- + //- MyMethodCode child.4 MyMethodReturnType + //- MyMethodReturnType.kind "TYPE" + //- MyMethodReturnType.pre_text ": " + //- MyMethodReturnType.post_text "MyClass" + //- + //- @arg defines/binding Arg + //- Arg code ArgCode + //- ArgCode.kind "BOX" + //- + //- ArgCode child.0 ArgCodeContext + //- ArgCodeContext.kind "CONTEXT" + //- ArgCodeContext.pre_text "(parameter)" + //- + //- ArgCode child.1 ArgCodeSpace + //- ArgCodeSpace.pre_text " " + //- + //- ArgCode child.2 ArgCodeId + //- ArgCodeId.kind "IDENTIFIER" + //- ArgCodeId.pre_text "arg" + //- + //- ArgCode child.3 ArgCodeType + //- ArgCodeType.kind "TYPE" + //- ArgCodeType.pre_text ": " + //- ArgCodeType.post_text "number" + myMethod(arg: number): MyClass { + return this; + } + + // Test that return type is inferred. + //- @returnNumber defines/binding ReturnNumber + //- ReturnNumber code ReturnNumberCode + //- ReturnNumberCode child.4 ReturnNumberType + //- ReturnNumberType.kind "TYPE" + //- ReturnNumberType.pre_text ": " + //- ReturnNumberType.post_text "number" + returnNumber() { + return 42; + } +} + +//- @MyInterface defines/binding MyInterface +//- +//- MyInterface code MyInterfaceCode +//- MyInterfaceCode.kind "IDENTIFIER" +//- MyInterfaceCode.pre_text "MyInterface" +interface MyInterface {} \ No newline at end of file diff --git a/kythe/typescript/testdata/marked_source/enum.ts b/kythe/typescript/testdata/marked_source/enum.ts new file mode 100644 index 0000000000..40864f0d45 --- /dev/null +++ b/kythe/typescript/testdata/marked_source/enum.ts @@ -0,0 +1,33 @@ + +//- @MyEnum defines/binding MyEnum +//- +//- MyEnum code MyEnumCode +//- MyEnumCode.kind "IDENTIFIER" +//- MyEnumCode.pre_text "MyEnum" +enum MyEnum { + + //- @MY_VALUE defines/binding MyValue + //- + //- MyValue code MyValueCode + //- MyValueCode.kind "BOX" + //- + //- MyValueCode child.0 MyValueContext + //- MyValueContext.kind "CONTEXT" + //- MyValueContext.pre_text "(enum member)" + //- + //- MyValueCode child.1 MyValueSpace + //- MyValueSpace.pre_text " " + //- + //- MyValueCode child.2 MyValueId + //- MyValueId.kind "IDENTIFIER" + //- MyValueId.pre_text "MY_VALUE" + //- + //- MyValueCode child.3 MyValueEqual + //- MyValueEqual.kind "BOX" + //- MyValueEqual.pre_text " = " + //- + //- MyValueCode child.4 MyValueInitializer + //- MyValueInitializer.kind "INITIALIZER" + //- MyValueInitializer.pre_text "123" + MY_VALUE = 123, +} \ No newline at end of file diff --git a/kythe/typescript/testdata/marked_source/function.ts b/kythe/typescript/testdata/marked_source/function.ts new file mode 100644 index 0000000000..3c20a8417a --- /dev/null +++ b/kythe/typescript/testdata/marked_source/function.ts @@ -0,0 +1,56 @@ + +//- @myFunction defines/binding MyFunction +//- MyFunction code MyFunctionCode +//- +//- MyFunctionCode child.0 MyFunctionContext +//- MyFunctionContext.kind "CONTEXT" +//- MyFunctionContext.pre_text "function" +//- +//- MyFunctionCode child.1 MyFunctionSpace +//- MyFunctionSpace.pre_text " " +//- +//- MyFunctionCode child.2 MyFunctionName +//- MyFunctionName.kind "IDENTIFIER" +//- MyFunctionName.pre_text "myFunction" +//- +//- MyFunctionCode child.3 MyFunctionParams +//- MyFunctionParams.kind "PARAMETER_LOOKUP_BY_PARAM" +//- MyFunctionParams.pre_text "(" +//- MyFunctionParams.post_text ")" +//- MyFunctionParams.post_child_text ", " +//- +//- MyFunctionCode child.4 MyFunctionReturnType +//- MyFunctionReturnType.kind "TYPE" +//- MyFunctionReturnType.pre_text ": " +//- MyFunctionReturnType.post_text "number" +//- +//- @arg defines/binding Arg +//- Arg code ArgCode +//- ArgCode.kind "BOX" +//- +//- ArgCode child.0 ArgCodeContext +//- ArgCodeContext.kind "CONTEXT" +//- ArgCodeContext.pre_text "(parameter)" +//- +//- ArgCode child.1 ArgCodeSpace +//- ArgCodeSpace.pre_text " " +//- +//- ArgCode child.2 ArgCodeId +//- ArgCodeId.kind "IDENTIFIER" +//- ArgCodeId.pre_text "arg" +//- +//- ArgCode child.3 ArgCodeType +//- ArgCodeType.kind "TYPE" +//- ArgCodeType.pre_text ": " +//- ArgCodeType.post_text "string" +//- +//- ArgCode child.4 ArgCodeEqual +//- ArgCodeEqual.kind "BOX" +//- ArgCodeEqual.pre_text " = " +//- +//- ArgCode child.5 ArgCodeDefaultValue +//- ArgCodeDefaultValue.kind "INITIALIZER" +//- ArgCodeDefaultValue.pre_text "'0'" +function myFunction(arg: string = '0'): number { + return 0; +} \ No newline at end of file