|
| 1 | +/*--------------------------------------------------------------------------------------------- |
| 2 | + * Copyright (c) Microsoft Corporation. All rights reserved. |
| 3 | + * Licensed under the MIT License. See License.txt in the project root for license information. |
| 4 | + *--------------------------------------------------------------------------------------------*/ |
| 5 | +"use strict"; |
| 6 | +var __extends = (this && this.__extends) || function(d, b) { |
| 7 | + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; |
| 8 | + function __() { this.constructor = d; } |
| 9 | + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); |
| 10 | +}; |
| 11 | +var ts = require('typescript'); |
| 12 | +var Lint = require('tslint/lib/lint'); |
| 13 | +/** |
| 14 | + * Implementation of the no-unexternalized-strings rule. |
| 15 | + */ |
| 16 | +var Rule = (function(_super) { |
| 17 | + __extends(Rule, _super); |
| 18 | + function Rule() { |
| 19 | + _super.apply(this, arguments); |
| 20 | + } |
| 21 | + Rule.prototype.apply = function(sourceFile) { |
| 22 | + return this.applyWithWalker(new NoUnexternalizedStringsRuleWalker(sourceFile, this.getOptions())); |
| 23 | + }; |
| 24 | + return Rule; |
| 25 | +} (Lint.Rules.AbstractRule)); |
| 26 | +exports.Rule = Rule; |
| 27 | +function isStringLiteral(node) { |
| 28 | + return node && node.kind === ts.SyntaxKind.StringLiteral; |
| 29 | +} |
| 30 | +function isObjectLiteral(node) { |
| 31 | + return node && node.kind === ts.SyntaxKind.ObjectLiteralExpression; |
| 32 | +} |
| 33 | +function isPropertyAssignment(node) { |
| 34 | + return node && node.kind === ts.SyntaxKind.PropertyAssignment; |
| 35 | +} |
| 36 | +var NoUnexternalizedStringsRuleWalker = (function(_super) { |
| 37 | + __extends(NoUnexternalizedStringsRuleWalker, _super); |
| 38 | + function NoUnexternalizedStringsRuleWalker(file, opts) { |
| 39 | + var _this = this; |
| 40 | + _super.call(this, file, opts); |
| 41 | + this.signatures = Object.create(null); |
| 42 | + this.ignores = Object.create(null); |
| 43 | + this.messageIndex = undefined; |
| 44 | + this.keyIndex = undefined; |
| 45 | + this.usedKeys = Object.create(null); |
| 46 | + var options = this.getOptions(); |
| 47 | + var first = options && options.length > 0 ? options[0] : null; |
| 48 | + if (first) { |
| 49 | + if (Array.isArray(first.signatures)) { |
| 50 | + first.signatures.forEach(function(signature) { return _this.signatures[signature] = true; }); |
| 51 | + } |
| 52 | + if (Array.isArray(first.ignores)) { |
| 53 | + first.ignores.forEach(function(ignore) { return _this.ignores[ignore] = true; }); |
| 54 | + } |
| 55 | + if (typeof first.messageIndex !== 'undefined') { |
| 56 | + this.messageIndex = first.messageIndex; |
| 57 | + } |
| 58 | + if (typeof first.keyIndex !== 'undefined') { |
| 59 | + this.keyIndex = first.keyIndex; |
| 60 | + } |
| 61 | + } |
| 62 | + } |
| 63 | + NoUnexternalizedStringsRuleWalker.prototype.visitSourceFile = function(node) { |
| 64 | + var _this = this; |
| 65 | + _super.prototype.visitSourceFile.call(this, node); |
| 66 | + Object.keys(this.usedKeys).forEach(function(key) { |
| 67 | + var occurences = _this.usedKeys[key]; |
| 68 | + if (occurences.length > 1) { |
| 69 | + occurences.forEach(function(occurence) { |
| 70 | + _this.addFailure((_this.createFailure(occurence.key.getStart(), occurence.key.getWidth(), "Duplicate key " + occurence.key.getText() + " with different message value."))); |
| 71 | + }); |
| 72 | + } |
| 73 | + }); |
| 74 | + }; |
| 75 | + NoUnexternalizedStringsRuleWalker.prototype.visitStringLiteral = function(node) { |
| 76 | + this.checkStringLiteral(node); |
| 77 | + _super.prototype.visitStringLiteral.call(this, node); |
| 78 | + }; |
| 79 | + NoUnexternalizedStringsRuleWalker.prototype.checkStringLiteral = function(node) { |
| 80 | + var text = node.getText(); |
| 81 | + var doubleQuoted = text.length >= 2 && text[0] === NoUnexternalizedStringsRuleWalker.DOUBLE_QUOTE && text[text.length - 1] === NoUnexternalizedStringsRuleWalker.DOUBLE_QUOTE; |
| 82 | + var info = this.findDescribingParent(node); |
| 83 | + // Ignore strings in import and export nodes. |
| 84 | + if (info && info.ignoreUsage) { |
| 85 | + return; |
| 86 | + } |
| 87 | + var callInfo = info ? info.callInfo : null; |
| 88 | + var functionName = callInfo ? callInfo.callExpression.expression.getText() : null; |
| 89 | + if (functionName && this.ignores[functionName]) { |
| 90 | + return; |
| 91 | + } |
| 92 | + if (doubleQuoted && (!callInfo || callInfo.argIndex === -1 || !this.signatures[functionName])) { |
| 93 | + this.addFailure(this.createFailure(node.getStart(), node.getWidth(), "Unexternalized string found: " + node.getText())); |
| 94 | + return; |
| 95 | + } |
| 96 | + // We have a single quoted string outside a localize function name. |
| 97 | + if (!doubleQuoted && !this.signatures[functionName]) { |
| 98 | + return; |
| 99 | + } |
| 100 | + // We have a string that is a direct argument into the localize call. |
| 101 | + var keyArg = callInfo.argIndex === this.keyIndex |
| 102 | + ? callInfo.callExpression.arguments[this.keyIndex] |
| 103 | + : null; |
| 104 | + if (keyArg) { |
| 105 | + if (isStringLiteral(keyArg)) { |
| 106 | + this.recordKey(keyArg, this.messageIndex ? callInfo.callExpression.arguments[this.messageIndex] : undefined); |
| 107 | + } |
| 108 | + else if (isObjectLiteral(keyArg)) { |
| 109 | + for (var i = 0; i < keyArg.properties.length; i++) { |
| 110 | + var property = keyArg.properties[i]; |
| 111 | + if (isPropertyAssignment(property)) { |
| 112 | + var name_1 = property.name.getText(); |
| 113 | + if (name_1 === 'key') { |
| 114 | + var initializer = property.initializer; |
| 115 | + if (isStringLiteral(initializer)) { |
| 116 | + this.recordKey(initializer, this.messageIndex ? callInfo.callExpression.arguments[this.messageIndex] : undefined); |
| 117 | + } |
| 118 | + break; |
| 119 | + } |
| 120 | + } |
| 121 | + } |
| 122 | + } |
| 123 | + } |
| 124 | + var messageArg = callInfo.argIndex === this.messageIndex |
| 125 | + ? callInfo.callExpression.arguments[this.messageIndex] |
| 126 | + : null; |
| 127 | + if (messageArg && messageArg !== node) { |
| 128 | + this.addFailure(this.createFailure(messageArg.getStart(), messageArg.getWidth(), "Message argument to '" + callInfo.callExpression.expression.getText() + "' must be a string literal.")); |
| 129 | + return; |
| 130 | + } |
| 131 | + }; |
| 132 | + NoUnexternalizedStringsRuleWalker.prototype.recordKey = function(keyNode, messageNode) { |
| 133 | + var text = keyNode.getText(); |
| 134 | + var occurences = this.usedKeys[text]; |
| 135 | + if (!occurences) { |
| 136 | + occurences = []; |
| 137 | + this.usedKeys[text] = occurences; |
| 138 | + } |
| 139 | + if (messageNode) { |
| 140 | + if (occurences.some(function(pair) { return pair.message ? pair.message.getText() === messageNode.getText() : false; })) { |
| 141 | + return; |
| 142 | + } |
| 143 | + } |
| 144 | + occurences.push({ key: keyNode, message: messageNode }); |
| 145 | + }; |
| 146 | + NoUnexternalizedStringsRuleWalker.prototype.findDescribingParent = function(node) { |
| 147 | + var parent; |
| 148 | + while ((parent = node.parent)) { |
| 149 | + var kind = parent.kind; |
| 150 | + if (kind === ts.SyntaxKind.CallExpression) { |
| 151 | + var callExpression = parent; |
| 152 | + return { callInfo: { callExpression: callExpression, argIndex: callExpression.arguments.indexOf(node) } }; |
| 153 | + } |
| 154 | + else if (kind === ts.SyntaxKind.ImportEqualsDeclaration || kind === ts.SyntaxKind.ImportDeclaration || kind === ts.SyntaxKind.ExportDeclaration) { |
| 155 | + return { ignoreUsage: true }; |
| 156 | + } |
| 157 | + else if (kind === ts.SyntaxKind.VariableDeclaration || kind === ts.SyntaxKind.FunctionDeclaration || kind === ts.SyntaxKind.PropertyDeclaration |
| 158 | + || kind === ts.SyntaxKind.MethodDeclaration || kind === ts.SyntaxKind.VariableDeclarationList || kind === ts.SyntaxKind.InterfaceDeclaration |
| 159 | + || kind === ts.SyntaxKind.ClassDeclaration || kind === ts.SyntaxKind.EnumDeclaration || kind === ts.SyntaxKind.ModuleDeclaration |
| 160 | + || kind === ts.SyntaxKind.TypeAliasDeclaration || kind === ts.SyntaxKind.SourceFile) { |
| 161 | + return null; |
| 162 | + } |
| 163 | + node = parent; |
| 164 | + } |
| 165 | + }; |
| 166 | + NoUnexternalizedStringsRuleWalker.DOUBLE_QUOTE = '"'; |
| 167 | + return NoUnexternalizedStringsRuleWalker; |
| 168 | +} (Lint.RuleWalker)); |
0 commit comments