-
Notifications
You must be signed in to change notification settings - Fork 0
/
noClosuresRule.ts
105 lines (92 loc) · 4 KB
/
noClosuresRule.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
import * as ts from "typescript";
import * as Lint from "tslint";
declare var global: any;
export class Rule extends Lint.Rules.TypedRule {
/* tslint:disable:object-literal-sort-keys */
public static metadata: Lint.IRuleMetadata = {
ruleName: "no-closures",
description: "Disallows usage of variables outside of their declaration function.",
descriptionDetails: Lint.Utils.dedent`
This rule is primarily useful to avoid memory leaks
and closure related bugs.`,
optionsDescription: "Not configurable.",
options: null,
optionExamples: [true],
type: "functionality",
typescriptOnly: false,
requiresTypeInfo: false
};
/* tslint:enable:object-literal-sort-keys */
public static FAILURE_STRING(name: string) {
return `variable '${name}' used as closure`;
}
public applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] {
return this.applyWithFunction(sourceFile, (ctx) => walk(ctx, program.getTypeChecker()));
}
}
function walk(ctx: Lint.WalkContext<void>, checker: ts.TypeChecker): void {
return ts.forEachChild(ctx.sourceFile, function recur(node: ts.Node): void {
switch (node.kind) {
case ts.SyntaxKind.TypeReference:
// Ignore types.
return;
case ts.SyntaxKind.PropertyAccessExpression:
let expression = (node as ts.PropertyAccessExpression).expression;
if (expression.kind === ts.SyntaxKind.ThisKeyword) {
// this keyword is fine
} else if (expression.kind === ts.SyntaxKind.Identifier) {
checkProperty(expression);
}
case ts.SyntaxKind.Identifier:
return checkIdentifier(node as ts.Identifier, checker.getSymbolAtLocation(node));
case ts.SyntaxKind.ExportSpecifier:
return checkIdentifier(
(node as ts.ExportSpecifier).name,
checker.getExportSpecifierLocalTargetSymbol(node as ts.ExportSpecifier));
default:
return ts.forEachChild(node, recur);
}
});
function checkProperty(node: any): void {
if (node.text === "arguments") {
return;
}
if (node.text === "global") {
return;
}
let crawl = node;
while (crawl.parent) {
if (crawl.kind === ts.SyntaxKind.ArrowFunction) {
if (!doesFunctionDefine(node.text, crawl)) {
ctx.addFailureAtNode(node, Rule.FAILURE_STRING(node.text));
}
return;
} else if (crawl.kind === ts.SyntaxKind.MethodDeclaration) {
if (!doesFunctionDefine(node.text, crawl)) {
ctx.addFailureAtNode(node, Rule.FAILURE_STRING(node.text));
}
return;
} else if (crawl.kind === ts.SyntaxKind.FunctionDeclaration) {
if (!doesFunctionDefine(node.text, crawl)) {
ctx.addFailureAtNode(node, Rule.FAILURE_STRING(node.text));
}
return;
}
crawl = crawl.parent;
}
}
function doesFunctionDefine(variable: string, functionNode: ts.MethodDeclaration | ts.FunctionDeclaration | ts.ConstructorDeclaration): boolean {
return (functionNode as any).locals.has(variable);
}
function checkIdentifier(node: ts.Identifier, symbol: ts.Symbol | undefined): void {
if (symbol !== undefined) {
if (symbol.declarations[0].kind === ts.SyntaxKind.VariableDeclaration &&
node.parent && node.parent.kind !== ts.SyntaxKind.VariableDeclaration) {
checkProperty(node);
} else if (symbol.declarations[0].kind === ts.SyntaxKind.Parameter &&
node.parent && node.parent.kind !== ts.SyntaxKind.Parameter) {
checkProperty(node);
}
}
}
}