This repository has been archived by the owner on Jul 15, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 199
/
noUnexternalizedStringsRule.ts
126 lines (114 loc) · 5.22 KB
/
noUnexternalizedStringsRule.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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import * as ts from 'typescript';
import * as Lint from 'tslint';
import {ErrorTolerantWalker} from './utils/ErrorTolerantWalker';
import {ExtendedMetadata} from './utils/ExtendedMetadata';
/**
* Implementation of the no-unexternalized-strings rule.
*/
export class Rule extends Lint.Rules.AbstractRule {
public static metadata: ExtendedMetadata = {
ruleName: 'no-unexternalized-strings',
type: 'maintainability',
description: 'Ensures that double quoted strings are passed to a localize call to provide proper strings for different locales',
options: null,
optionsDescription: '',
typescriptOnly: true,
issueClass: 'Ignored',
issueType: 'Warning',
severity: 'Low',
level: 'Opportunity for Excellence',
group: 'Configurable',
recommendation: 'false, // the VS Code team has a specific localization process that this rule enforces'
};
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithWalker(new NoUnexternalizedStringsRuleWalker(sourceFile, this.getOptions()));
}
}
interface Map<V> {
[key: string]: V;
}
interface UnexternalizedStringsOptions {
signatures?: string[];
messageIndex?: number;
ignores?: string[];
}
class NoUnexternalizedStringsRuleWalker extends ErrorTolerantWalker {
private static SINGLE_QUOTE: string = '\'';
private signatures: Map<boolean>;
private messageIndex: number | undefined;
private ignores: Map<boolean>;
constructor(sourceFile: ts.SourceFile, opt: Lint.IOptions) {
super(sourceFile, opt);
this.signatures = Object.create(null);
this.ignores = Object.create(null);
const options: any[] = this.getOptions();
const first: UnexternalizedStringsOptions = options && options.length > 0 ? options[0] : null;
if (first) {
if (Array.isArray(first.signatures)) {
first.signatures.forEach((signature: string) => this.signatures[signature] = true);
}
if (Array.isArray(first.ignores)) {
first.ignores.forEach((ignore: string) => this.ignores[ignore] = true);
}
if (first.messageIndex !== undefined) {
this.messageIndex = first.messageIndex;
}
}
}
protected visitStringLiteral(node: ts.StringLiteral): void {
this.checkStringLiteral(node);
super.visitStringLiteral(node);
}
private checkStringLiteral(node: ts.StringLiteral): void {
const text = node.getText();
// The string literal is enclosed in single quotes. Treat as OK.
if (text.length >= 2 && text[0] === NoUnexternalizedStringsRuleWalker.SINGLE_QUOTE
&& text[text.length - 1] === NoUnexternalizedStringsRuleWalker.SINGLE_QUOTE) {
return;
}
const info = this.findDescribingParent(node);
// Ignore strings in import and export nodes.
if (info && info.ignoreUsage) {
return;
}
const callInfo = info ? info.callInfo : null;
if (callInfo && this.ignores[callInfo.callExpression.expression.getText()]) {
return;
}
if (!callInfo || callInfo.argIndex === -1 || !this.signatures[callInfo.callExpression.expression.getText()]) {
this.addFailureAt(node.getStart(), node.getWidth(), `Unexternalized string found: ${node.getText()}`);
return;
}
// We have a string that is a direct argument into the localize call.
const messageArg = callInfo.argIndex === this.messageIndex
? callInfo.callExpression.arguments[this.messageIndex]
: null;
if (messageArg && messageArg !== node) {
this.addFailureAt(
node.getStart(), node.getWidth(),
`Message argument to '${callInfo.callExpression.expression.getText()}' must be a string literal.`);
return;
}
}
private findDescribingParent(node: ts.Node):
{ callInfo?: { callExpression: ts.CallExpression, argIndex: number }, ignoreUsage?: boolean; } | null {
const kinds = ts.SyntaxKind;
while ((node.parent != null)) {
const parent: ts.Node = node.parent;
const kind = parent.kind;
if (kind === kinds.CallExpression) {
const callExpression = <ts.CallExpression>parent;
return { callInfo: { callExpression: callExpression, argIndex: callExpression.arguments.indexOf(<any>node) }};
} else if (kind === kinds.ImportEqualsDeclaration || kind === kinds.ImportDeclaration || kind === kinds.ExportDeclaration) {
return { ignoreUsage: true };
} else if (kind === kinds.VariableDeclaration || kind === kinds.FunctionDeclaration || kind === kinds.PropertyDeclaration
|| kind === kinds.MethodDeclaration || kind === kinds.VariableDeclarationList || kind === kinds.InterfaceDeclaration
|| kind === kinds.ClassDeclaration || kind === kinds.EnumDeclaration || kind === kinds.ModuleDeclaration
|| kind === kinds.TypeAliasDeclaration || kind === kinds.SourceFile) {
return null;
}
node = parent;
}
return null;
}
}