1 |
|
2 | import * as ts from 'typescript';
|
3 | import * as Lint from 'tslint';
|
4 | import { ErrorTolerantWalker } from './ErrorTolerantWalker';
|
5 | import { AstUtils } from './AstUtils';
|
6 | import { Scope } from './Scope';
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 | export class ScopedSymbolTrackingWalker extends ErrorTolerantWalker {
|
14 | private typeChecker?: ts.TypeChecker;
|
15 | private scope: Scope;
|
16 |
|
17 | constructor(sourceFile: ts.SourceFile, options: Lint.IOptions, program?: ts.Program) {
|
18 | super(sourceFile, options);
|
19 |
|
20 | if (program) {
|
21 | this.typeChecker = program.getTypeChecker();
|
22 | }
|
23 | }
|
24 |
|
25 | protected isExpressionEvaluatingToFunction(expression: ts.Expression): boolean {
|
26 | if (expression.kind === ts.SyntaxKind.ArrowFunction || expression.kind === ts.SyntaxKind.FunctionExpression) {
|
27 | return true;
|
28 | }
|
29 |
|
30 | const isString =
|
31 | expression.kind === ts.SyntaxKind.StringLiteral ||
|
32 | expression.kind === ts.SyntaxKind.NoSubstitutionTemplateLiteral ||
|
33 | expression.kind === ts.SyntaxKind.TemplateExpression ||
|
34 | expression.kind === ts.SyntaxKind.TaggedTemplateExpression;
|
35 | if (isString || expression.kind === ts.SyntaxKind.BinaryExpression) {
|
36 | return false;
|
37 | }
|
38 |
|
39 |
|
40 | if (this.scope.isFunctionSymbol(expression.getText())) {
|
41 | return true;
|
42 | }
|
43 |
|
44 | if (expression.kind === ts.SyntaxKind.Identifier && this.typeChecker) {
|
45 | const tsSymbol = this.typeChecker.getSymbolAtLocation(expression);
|
46 | if (tsSymbol && tsSymbol.flags === ts.SymbolFlags.Function) {
|
47 | return true;
|
48 | }
|
49 | return false;
|
50 | }
|
51 |
|
52 | if (expression.kind === ts.SyntaxKind.CallExpression) {
|
53 |
|
54 | if ((<any>expression).expression.name && (<any>expression).expression.name.getText() === 'bind') {
|
55 | return true;
|
56 | }
|
57 |
|
58 | try {
|
59 |
|
60 | if (!this.typeChecker) {
|
61 | return true;
|
62 | }
|
63 |
|
64 | const signature: ts.Signature = this.typeChecker.getResolvedSignature(<ts.CallExpression>expression);
|
65 | const expressionType: ts.Type = this.typeChecker.getReturnTypeOfSignature(signature);
|
66 | return this.isFunctionType(expressionType, this.typeChecker);
|
67 | } catch (error) {
|
68 |
|
69 | return false;
|
70 | }
|
71 | }
|
72 |
|
73 | if (!this.typeChecker) {
|
74 | return true;
|
75 | }
|
76 |
|
77 | return this.isFunctionType(this.typeChecker.getTypeAtLocation(expression), this.typeChecker);
|
78 | }
|
79 |
|
80 | private isFunctionType(expressionType: ts.Type, typeChecker: ts.TypeChecker): boolean {
|
81 | const signatures: ts.Signature[] = typeChecker.getSignaturesOfType(expressionType, ts.SignatureKind.Call);
|
82 | if (signatures != null && signatures.length > 0) {
|
83 | const signatureDeclaration: ts.SignatureDeclaration = signatures[0].declaration;
|
84 | if (signatureDeclaration.kind === ts.SyntaxKind.FunctionType) {
|
85 | return true;
|
86 | }
|
87 | }
|
88 | return false;
|
89 | }
|
90 |
|
91 | protected visitArrowFunction(node: ts.ArrowFunction): void {
|
92 | this.scope = new Scope(this.scope);
|
93 | this.scope.addParameters(node.parameters);
|
94 | super.visitArrowFunction(node);
|
95 | this.scope = this.scope.parent;
|
96 | }
|
97 |
|
98 | protected visitClassDeclaration(node: ts.ClassDeclaration): void {
|
99 | this.scope = new Scope(this.scope);
|
100 | node.members.forEach(
|
101 | (element: ts.ClassElement): void => {
|
102 | const prefix: string = AstUtils.isStatic(element) ? node.name.getText() + '.' : 'this.';
|
103 |
|
104 | if (element.kind === ts.SyntaxKind.MethodDeclaration) {
|
105 |
|
106 | this.scope.addFunctionSymbol(prefix + (<ts.MethodDeclaration>element).name.getText());
|
107 | } else if (element.kind === ts.SyntaxKind.PropertyDeclaration) {
|
108 | const prop: ts.PropertyDeclaration = <ts.PropertyDeclaration>element;
|
109 |
|
110 | if (AstUtils.isDeclarationFunctionType(prop)) {
|
111 | this.scope.addFunctionSymbol(prefix + (<ts.MethodDeclaration>element).name.getText());
|
112 | } else {
|
113 | this.scope.addNonFunctionSymbol(prefix + (<ts.MethodDeclaration>element).name.getText());
|
114 | }
|
115 | }
|
116 | }
|
117 | );
|
118 | super.visitClassDeclaration(node);
|
119 | this.scope = this.scope.parent;
|
120 | }
|
121 |
|
122 | protected visitConstructorDeclaration(node: ts.ConstructorDeclaration): void {
|
123 | this.scope = new Scope(this.scope);
|
124 | this.scope.addParameters(node.parameters);
|
125 | super.visitConstructorDeclaration(node);
|
126 | this.scope = this.scope.parent;
|
127 | }
|
128 |
|
129 | protected visitFunctionDeclaration(node: ts.FunctionDeclaration): void {
|
130 | this.scope = new Scope(this.scope);
|
131 | this.scope.addParameters(node.parameters);
|
132 | super.visitFunctionDeclaration(node);
|
133 | this.scope = this.scope.parent;
|
134 | }
|
135 |
|
136 | protected visitFunctionExpression(node: ts.FunctionExpression): void {
|
137 | this.scope = new Scope(this.scope);
|
138 | this.scope.addParameters(node.parameters);
|
139 | super.visitFunctionExpression(node);
|
140 | this.scope = this.scope.parent;
|
141 | }
|
142 |
|
143 | protected visitMethodDeclaration(node: ts.MethodDeclaration): void {
|
144 | this.scope = new Scope(this.scope);
|
145 | this.scope.addParameters(node.parameters);
|
146 | super.visitMethodDeclaration(node);
|
147 | this.scope = this.scope.parent;
|
148 | }
|
149 |
|
150 | protected visitModuleDeclaration(node: ts.ModuleDeclaration): void {
|
151 | this.scope = new Scope(this.scope);
|
152 | this.scope.addGlobalScope(node.body, this.getSourceFile(), this.getOptions());
|
153 | super.visitModuleDeclaration(node);
|
154 | this.scope = this.scope.parent;
|
155 | }
|
156 |
|
157 | protected visitSetAccessor(node: ts.AccessorDeclaration): void {
|
158 | this.scope = new Scope(this.scope);
|
159 | this.scope.addParameters(node.parameters);
|
160 | super.visitSetAccessor(node);
|
161 | this.scope = this.scope.parent;
|
162 | }
|
163 |
|
164 | protected visitSourceFile(node: ts.SourceFile): void {
|
165 | this.scope = new Scope(null);
|
166 | this.scope.addGlobalScope(node, node, this.getOptions());
|
167 | super.visitSourceFile(node);
|
168 | this.scope = null;
|
169 | }
|
170 | }
|