JS: Extract types and signatures for functions

This commit is contained in:
Asger F
2019-09-18 14:49:21 +01:00
parent 999d10e1f0
commit b4f67f20af
11 changed files with 172 additions and 25 deletions

View File

@@ -22,6 +22,7 @@ export interface AugmentedNode extends ts.Node {
$symbol?: number;
$resolvedSignature?: number;
$overloadIndex?: number;
$declaredSignature?: number;
}
export type AugmentedPos = number;
@@ -263,6 +264,17 @@ export function augmentAst(ast: AugmentedSourceFile, code: string, project: Proj
namePart.$symbol = typeTable.getSymbolId(symbol);
}
}
if (ts.isFunctionLike(node)) {
let signature = typeChecker.getSignatureFromDeclaration(node);
if (signature != null) {
let kind = ts.isConstructSignatureDeclaration(node) || ts.isConstructorDeclaration(node)
? ts.SignatureKind.Construct : ts.SignatureKind.Call;
let id = typeTable.getSignatureId(kind, signature);
if (id != null) {
(node as AugmentedNode).$declaredSignature = id;
}
}
}
}
}
}
@@ -302,8 +314,10 @@ function isTypedNode(node: ts.Node): boolean {
case ts.SyntaxKind.BinaryExpression:
case ts.SyntaxKind.CallExpression:
case ts.SyntaxKind.ClassExpression:
case ts.SyntaxKind.ClassDeclaration:
case ts.SyntaxKind.CommaListExpression:
case ts.SyntaxKind.ConditionalExpression:
case ts.SyntaxKind.Constructor:
case ts.SyntaxKind.DeleteExpression:
case ts.SyntaxKind.ElementAccessExpression:
case ts.SyntaxKind.ExpressionStatement:
@@ -311,9 +325,13 @@ function isTypedNode(node: ts.Node): boolean {
case ts.SyntaxKind.FalseKeyword:
case ts.SyntaxKind.FunctionDeclaration:
case ts.SyntaxKind.FunctionExpression:
case ts.SyntaxKind.GetAccessor:
case ts.SyntaxKind.Identifier:
case ts.SyntaxKind.IndexSignature:
case ts.SyntaxKind.JsxExpression:
case ts.SyntaxKind.LiteralType:
case ts.SyntaxKind.MethodDeclaration:
case ts.SyntaxKind.MethodSignature:
case ts.SyntaxKind.NewExpression:
case ts.SyntaxKind.NonNullExpression:
case ts.SyntaxKind.NoSubstitutionTemplateLiteral:
@@ -327,6 +345,7 @@ function isTypedNode(node: ts.Node): boolean {
case ts.SyntaxKind.PrefixUnaryExpression:
case ts.SyntaxKind.PropertyAccessExpression:
case ts.SyntaxKind.RegularExpressionLiteral:
case ts.SyntaxKind.SetAccessor:
case ts.SyntaxKind.StringLiteral:
case ts.SyntaxKind.TaggedTemplateExpression:
case ts.SyntaxKind.TemplateExpression:

View File

@@ -12,6 +12,7 @@ import java.util.List;
public abstract class AFunctionExpression extends Expression implements IFunction {
private final AFunction<? extends Node> fn;
private int symbol = -1;
private int declaredSignature = -1;
public AFunctionExpression(
String type,
@@ -144,4 +145,14 @@ public abstract class AFunctionExpression extends Expression implements IFunctio
public void setSymbol(int symbol) {
this.symbol = symbol;
}
@Override
public int getDeclaredSignatureId() {
return declaredSignature;
}
@Override
public void setDeclaredSignatureId(int id) {
declaredSignature = id;
}
}

View File

@@ -19,6 +19,8 @@ public class FunctionDeclaration extends Statement implements IFunction {
private final AFunction<? extends Node> fn;
private final boolean hasDeclareKeyword;
private int symbol = -1;
private int staticType = -1;
private int declaredSignature = -1;
public FunctionDeclaration(
SourceLocation loc,
@@ -185,4 +187,24 @@ public class FunctionDeclaration extends Statement implements IFunction {
public void setSymbol(int symbol) {
this.symbol = symbol;
}
@Override
public int getStaticTypeId() {
return staticType;
}
@Override
public void setStaticTypeId(int id) {
staticType = id;
}
@Override
public int getDeclaredSignatureId() {
return declaredSignature;
}
@Override
public void setDeclaredSignatureId(int id) {
declaredSignature = id;
}
}

View File

@@ -3,11 +3,12 @@ package com.semmle.js.ast;
import com.semmle.ts.ast.DecoratorList;
import com.semmle.ts.ast.INodeWithSymbol;
import com.semmle.ts.ast.ITypeExpression;
import com.semmle.ts.ast.ITypedAstNode;
import com.semmle.ts.ast.TypeParameter;
import java.util.List;
/** A function declaration or expression. */
public interface IFunction extends IStatementContainer, INodeWithSymbol {
public interface IFunction extends IStatementContainer, INodeWithSymbol, ITypedAstNode {
/** The function name; may be null for function expressions. */
public Identifier getId();
@@ -63,4 +64,15 @@ public interface IFunction extends IStatementContainer, INodeWithSymbol {
public List<DecoratorList> getParameterDecorators();
public boolean hasDeclareKeyword();
/**
* Gets the type signature of this function as determined by the TypeScript compiler, or -1 if no
* call signature was extracted.
*
* <p>The ID refers to a signature in a table that is extracted on a per-project basis, and the
* meaning of this type ID is not available at the AST level.
*/
public int getDeclaredSignatureId();
public void setDeclaredSignatureId(int id);
}

View File

@@ -762,6 +762,7 @@ public class ASTExtractor {
trapwriter.addTuple("hasDeclareKeyword", key);
}
extractFunction(nd, key);
emitStaticType(nd, key);
return key;
}
@@ -833,7 +834,13 @@ public class ASTExtractor {
extractParameterDefaultsAndTypes(nd, key, i);
extractFunctionAttributes(nd, key);
// Extract associated symbol and signature
emitNodeSymbol(nd, key);
if (nd.getDeclaredSignatureId() != -1) {
Label signatureKey = trapwriter.globalID("signature;" + nd.getDeclaredSignatureId());
trapwriter.addTuple("declared_function_signature", key, signatureKey);
}
boolean oldIsStrict = isStrict;
isStrict = bodyIsStrict;

View File

@@ -42,6 +42,7 @@ import com.semmle.js.ast.ForOfStatement;
import com.semmle.js.ast.ForStatement;
import com.semmle.js.ast.FunctionDeclaration;
import com.semmle.js.ast.FunctionExpression;
import com.semmle.js.ast.IFunction;
import com.semmle.js.ast.INode;
import com.semmle.js.ast.IPattern;
import com.semmle.js.ast.Identifier;
@@ -670,6 +671,13 @@ public class TypeScriptASTConverter {
attachSymbolInformation(node, json);
}
/** Attached the declared call signature to a function. */
private void attachDeclaredSignature(IFunction node, JsonObject json) {
if (json.has("$declaredSignature")) {
node.setDeclaredSignatureId(json.get("$declaredSignature").getAsInt());
}
}
/**
* Convert the given array of TypeScript AST nodes into a list of JavaScript AST nodes, skipping
* any {@code null} elements.
@@ -786,15 +794,18 @@ public class TypeScriptASTConverter {
}
private Node convertArrowFunction(JsonObject node, SourceLocation loc) throws ParseError {
return new ArrowFunctionExpression(
loc,
convertParameters(node),
convertChild(node, "body"),
false,
hasModifier(node, "AsyncKeyword"),
convertChildrenNotNull(node, "typeParameters"),
convertParameterTypes(node),
convertChildAsType(node, "type"));
ArrowFunctionExpression function =
new ArrowFunctionExpression(
loc,
convertParameters(node),
convertChild(node, "body"),
false,
hasModifier(node, "AsyncKeyword"),
convertChildrenNotNull(node, "typeParameters"),
convertParameterTypes(node),
convertChildAsType(node, "type"));
attachDeclaredSignature(function, node);
return function;
}
private Node convertAwaitExpression(JsonObject node, SourceLocation loc) throws ParseError {
@@ -1044,6 +1055,8 @@ public class TypeScriptASTConverter {
null,
null);
attachSymbolInformation(value, node);
attachStaticType(value, node);
attachDeclaredSignature(value, node);
List<FieldDefinition> parameterFields = convertParameterFields(node);
return new MethodDefinition(loc, flags, methodKind, key, value, parameterFields);
}
@@ -1234,6 +1247,8 @@ public class TypeScriptASTConverter {
returnType,
thisParam);
attachSymbolInformation(function, node);
attachStaticType(function, node);
attachDeclaredSignature(function, node);
return fixExports(loc, function);
}
@@ -1247,18 +1262,22 @@ public class TypeScriptASTConverter {
List<DecoratorList> paramDecorators = convertParameterDecorators(node);
ITypeExpression returnType = convertChildAsType(node, "type");
ITypeExpression thisParam = convertThisParameterType(node);
return new FunctionExpression(
loc,
fnId,
params,
fnbody,
generator,
async,
convertChildrenNotNull(node, "typeParameters"),
paramTypes,
paramDecorators,
returnType,
thisParam);
FunctionExpression function =
new FunctionExpression(
loc,
fnId,
params,
fnbody,
generator,
async,
convertChildrenNotNull(node, "typeParameters"),
paramTypes,
paramDecorators,
returnType,
thisParam);
attachStaticType(function, node);
attachDeclaredSignature(function, node);
return function;
}
private Node convertFunctionType(JsonObject node, SourceLocation loc) throws ParseError {
@@ -1591,7 +1610,7 @@ public class TypeScriptASTConverter {
List<DecoratorList> paramDecorators = convertParameterDecorators(node);
List<TypeParameter> typeParameters = convertChildrenNotNull(node, "typeParameters");
ITypeExpression thisType = convertThisParameterType(node);
FunctionExpression method =
FunctionExpression function =
new FunctionExpression(
loc,
null,
@@ -1604,8 +1623,10 @@ public class TypeScriptASTConverter {
paramDecorators,
returnType,
thisType);
attachSymbolInformation(method, node);
return method;
attachSymbolInformation(function, node);
attachStaticType(function, node);
attachDeclaredSignature(function, node);
return function;
}
private Node convertNamespaceDeclaration(JsonObject node, SourceLocation loc) throws ParseError {

View File

@@ -416,6 +416,13 @@ class Function extends @function, Parameterized, TypeParameterized, StmtContaine
* This predicate is only populated for files extracted with full TypeScript extraction.
*/
CanonicalFunctionName getCanonicalName() { ast_node_symbol(this, result) }
/**
* Gets the call signature of this function, as determined by the TypeScript compiler, if any.
*/
CallSignatureType getCallSignature() {
declared_function_signature(this, result)
}
}
/**

View File

@@ -641,6 +641,11 @@ ast_node_type(
unique int node: @typed_ast_node ref,
int typ: @type ref);
declared_function_signature(
unique int node: @function ref,
int sig: @signature_type ref
);
invoke_expr_signature(
unique int node: @invokeexpr ref,
int sig: @signature_type ref

View File

@@ -6,9 +6,19 @@ test_ExprSignature
| tst.ts:12:8:12:8 | x | number |
| tst.ts:16:8:16:8 | x | number |
| tst.ts:17:8:17:8 | x | any |
| tst.ts:21:3:21:28 | method( ... string; | (x: number): string |
| tst.ts:21:10:21:10 | x | number |
| tst.ts:23:3:23:38 | overloa ... number; | (x: any): any |
| tst.ts:23:3:23:38 | overloa ... number; | (x: number): number |
| tst.ts:23:3:23:38 | overloa ... number; | (x: string): string |
| tst.ts:23:20:23:20 | x | number |
| tst.ts:24:3:24:38 | overloa ... string; | (x: any): any |
| tst.ts:24:3:24:38 | overloa ... string; | (x: number): number |
| tst.ts:24:3:24:38 | overloa ... string; | (x: string): string |
| tst.ts:24:20:24:20 | x | string |
| tst.ts:25:3:25:32 | overloa ... ): any; | (x: any): any |
| tst.ts:25:3:25:32 | overloa ... ): any; | (x: number): number |
| tst.ts:25:3:25:32 | overloa ... ): any; | (x: string): string |
| tst.ts:25:20:25:20 | x | any |
| tst.ts:28:5:28:5 | m | Method |
| tst.ts:29:1:29:1 | m | Method |
@@ -22,7 +32,9 @@ test_ExprSignature
| tst.ts:30:1:30:25 | m.overl ... ("foo") | string |
| tst.ts:30:20:30:24 | "foo" | "foo" |
| tst.ts:33:3:33:10 | callback | (x: number): string |
| tst.ts:33:13:33:33 | (x: num ... string | (x: number): string |
| tst.ts:33:14:33:14 | x | number |
| tst.ts:37:3:37:18 | method(x: T): T; | (x: T): T |
| tst.ts:37:10:37:10 | x | T |
| tst.ts:40:10:40:12 | foo | (g: Generic<string>): string |
| tst.ts:40:14:40:14 | g | Generic<string> |
@@ -30,6 +42,11 @@ test_ExprSignature
| tst.ts:41:10:41:17 | g.method | (x: string): string |
| tst.ts:41:10:41:24 | g.method("foo") | string |
| tst.ts:41:19:41:23 | "foo" | "foo" |
| tst.ts:44:15:44:15 | C | C |
| tst.ts:45:3:45:25 | constru ... tring); | any |
| tst.ts:45:15:45:15 | x | string |
| tst.ts:46:3:46:25 | constru ... umber); | any |
| tst.ts:46:15:46:15 | x | number |
test_TypeReferenceSig
| Callable | function | 0 | (x: number): string |
| Newable | constructor | 0 | new (x: number): any |
@@ -38,3 +55,20 @@ test_TypeReferenceSig
| OverloadedCallable | function | 2 | (x: any): any |
| OverloadedNewable | constructor | 0 | new (x: number): OverloadedNewable |
| OverloadedNewable | constructor | 1 | new (x: any): any |
test_FunctionCallSig
| tst.ts:2:3:2:22 | (x: number): string; | (x: number): string |
| tst.ts:6:3:6:22 | (x: number): number; | (x: number): number |
| tst.ts:7:3:7:22 | (x: string): string; | (x: string): string |
| tst.ts:8:3:8:16 | (x: any): any; | (x: any): any |
| tst.ts:12:3:12:23 | new (x: ... ): any; | new (x: number): any |
| tst.ts:16:3:16:37 | new (x: ... ewable; | new (x: number): OverloadedNewable |
| tst.ts:17:3:17:20 | new (x: any): any; | new (x: any): any |
| tst.ts:21:3:21:28 | method( ... string; | (x: number): string |
| tst.ts:23:3:23:38 | overloa ... number; | (x: number): number |
| tst.ts:24:3:24:38 | overloa ... string; | (x: string): string |
| tst.ts:25:3:25:32 | overloa ... ): any; | (x: any): any |
| tst.ts:33:13:33:33 | (x: num ... string | (x: number): string |
| tst.ts:37:3:37:18 | method(x: T): T; | (x: T): T |
| tst.ts:40:1:42:1 | functio ... oo");\\n} | (g: Generic<string>): string |
| tst.ts:45:3:45:25 | constru ... tring); | new (x: string): C |
| tst.ts:46:3:46:25 | constru ... umber); | new (x: number): C |

View File

@@ -16,3 +16,7 @@ query predicate test_ExprSignature(Expr expr, string type) {
query predicate test_TypeReferenceSig(TypeReference type, SignatureKind kind, int n, CallSignatureType sig) {
sig = type.getSignature(kind, n)
}
query predicate test_FunctionCallSig(Function f, CallSignatureType sig) {
sig = f.getCallSignature()
}

View File

@@ -40,3 +40,8 @@ interface Generic<T> {
function foo(g: Generic<string>) {
return g.method("foo");
}
declare class C {
constructor(x: string);
constructor(x: number);
}