tslint-immutable
Advanced tools
| /** | ||
| * This file has code that is shared for all the readonly rules. | ||
| * It supports the options for ignore-local and ignore-prefix which all readonly rules have. | ||
| * The rules ony need to provide a checker function. | ||
| */ | ||
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| var ts = require("typescript"); | ||
| var OPTION_IGNORE_LOCAL = "ignore-local"; | ||
| var OPTION_IGNORE_PREFIX = "ignore-prefix"; | ||
| function createInvalidNode(node, replacement) { | ||
| return { node: node, replacement: replacement }; | ||
| } | ||
| exports.createInvalidNode = createInvalidNode; | ||
| function parseOptions(options) { | ||
| var ignoreLocal = options.indexOf(OPTION_IGNORE_LOCAL) !== -1; | ||
| var ignorePrefix; | ||
| for (var _i = 0, options_1 = options; _i < options_1.length; _i++) { | ||
| var o = options_1[_i]; | ||
| if (typeof o === "object" && o[OPTION_IGNORE_PREFIX] !== null) { | ||
| ignorePrefix = o[OPTION_IGNORE_PREFIX]; | ||
| break; | ||
| } | ||
| } | ||
| return { ignoreLocal: ignoreLocal, ignorePrefix: ignorePrefix }; | ||
| } | ||
| exports.parseOptions = parseOptions; | ||
| function walk(ctx, checkNode, failureString) { | ||
| return ts.forEachChild(ctx.sourceFile, cb); | ||
| function cb(node) { | ||
| // Skip checking in functions if ignore-local is set | ||
| if (ctx.options.ignoreLocal && (node.kind === ts.SyntaxKind.FunctionDeclaration | ||
| || node.kind === ts.SyntaxKind.ArrowFunction || node.kind === ts.SyntaxKind.FunctionExpression)) { | ||
| // We still need to check the parameters and return type | ||
| var functionNode = node; //tslint:disable-line | ||
| var invalidNodes = checkIgnoreLocalFunctionNode(functionNode, ctx, checkNode); | ||
| // invalidNodes.forEach((n) => reportInvalidNodes(n, ctx, failureString)); | ||
| reportInvalidNodes(invalidNodes, ctx, failureString); | ||
| // Now skip this whole branch | ||
| return; | ||
| } | ||
| // Check the node | ||
| reportInvalidNodes(checkNode(node, ctx), ctx, failureString); | ||
| // Use return becuase performance hints docs say it optimizes the function using tail-call recursion | ||
| return ts.forEachChild(node, cb); | ||
| } | ||
| } | ||
| exports.walk = walk; | ||
| function reportInvalidNodes(invalidNodes, ctx, failureString) { | ||
| invalidNodes.forEach(function (invalidNode) { return ctx.addFailureAtNode(invalidNode.node, failureString, invalidNode.replacement); }); | ||
| } | ||
| exports.reportInvalidNodes = reportInvalidNodes; | ||
| function checkIgnoreLocalFunctionNode(functionNode, ctx, checkNode) { | ||
| var invalidNodes = []; | ||
| // Check either the parameter's explicit type if it has one, or itself for implict type | ||
| for (var _i = 0, _a = functionNode.parameters.map(function (p) { return p.type ? p.type : p; }); _i < _a.length; _i++) { | ||
| var n = _a[_i]; | ||
| var invalidCheckNodes = checkNode(n, ctx); | ||
| if (invalidCheckNodes) { | ||
| invalidNodes = invalidNodes.concat.apply(invalidNodes, invalidCheckNodes); | ||
| } | ||
| } | ||
| // Check the return type | ||
| if (functionNode.type) { | ||
| var invalidCheckNodes = checkNode(functionNode.type, ctx); | ||
| if (invalidCheckNodes) { | ||
| invalidNodes = invalidNodes.concat.apply(invalidNodes, invalidCheckNodes); | ||
| } | ||
| } | ||
| return invalidNodes; | ||
| } | ||
| exports.checkIgnoreLocalFunctionNode = checkIgnoreLocalFunctionNode; | ||
| function shouldIgnorePrefix(node, options, sourceFile) { | ||
| // Check ignore-prefix for VariableDeclaration, PropertySignature, TypeAliasDeclaration, Parameter | ||
| if (options.ignorePrefix) { | ||
| if (node && (node.kind === ts.SyntaxKind.VariableDeclaration | ||
| || node.kind === ts.SyntaxKind.Parameter | ||
| || node.kind === ts.SyntaxKind.PropertySignature | ||
| || node.kind === ts.SyntaxKind.TypeAliasDeclaration)) { | ||
| var variableDeclarationNode = node; | ||
| if (variableDeclarationNode.name.getText(sourceFile).substr(0, options.ignorePrefix.length) === options.ignorePrefix) { | ||
| return true; | ||
| } | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
| exports.shouldIgnorePrefix = shouldIgnorePrefix; |
+6
-1
@@ -9,2 +9,6 @@ # Change Log | ||
| ## [v3.3.2] - 2017-05-13 | ||
| ### Fixed | ||
| - Functions in interfaces cannot have readonly specified but are still checked [#28](https://github.com/jonaskello/tslint-immutable/issues/28) | ||
| ## [v3.3.1] - 2017-05-09 | ||
@@ -87,3 +91,4 @@ ### Fixed | ||
| [Unreleased]: https://github.com/jonaskello/tslint-immutable/compare/v3.3.1...master | ||
| [Unreleased]: https://github.com/jonaskello/tslint-immutable/compare/v3.3.2...master | ||
| [v3.3.2]: https://github.com/jonaskello/tslint-immutable/compare/v3.3.1...v3.3.2 | ||
| [v3.3.1]: https://github.com/jonaskello/tslint-immutable/compare/v3.3.0...v3.3.1 | ||
@@ -90,0 +95,0 @@ [v3.3.0]: https://github.com/jonaskello/tslint-immutable/compare/v3.2.0...v3.3.0 |
+1
-1
| { | ||
| "name": "tslint-immutable", | ||
| "version": "3.3.1", | ||
| "version": "3.3.2", | ||
| "description": "TSLint rules to disable mutation in TypeScript.", | ||
@@ -5,0 +5,0 @@ "main": "tslint-immutable.json", |
@@ -15,2 +15,3 @@ "use strict"; | ||
| var Lint = require("tslint"); | ||
| var Shared = require("./readonly-shared"); | ||
| var Rule = (function (_super) { | ||
@@ -22,71 +23,12 @@ __extends(Rule, _super); | ||
| Rule.prototype.apply = function (sourceFile) { | ||
| return this.applyWithFunction(sourceFile, walk, parseOptions(this.ruleArguments)); | ||
| return this.applyWithFunction(sourceFile, function (ctx) { return Shared.walk(ctx, checkNode, "Only ReadonlyArray allowed."); }, Shared.parseOptions(this.ruleArguments)); | ||
| }; | ||
| return Rule; | ||
| }(Lint.Rules.AbstractRule)); | ||
| Rule.FAILURE_STRING = "Only ReadonlyArray allowed."; | ||
| exports.Rule = Rule; | ||
| var OPTION_IGNORE_LOCAL = "ignore-local"; | ||
| var OPTION_IGNORE_PREFIX = "ignore-prefix"; | ||
| function createInvalidNode(node, replacement) { | ||
| return { node: node, replacement: replacement }; | ||
| } | ||
| function parseOptions(options) { | ||
| var ignoreLocal = options.indexOf(OPTION_IGNORE_LOCAL) !== -1; | ||
| var ignorePrefix; | ||
| for (var _i = 0, options_1 = options; _i < options_1.length; _i++) { | ||
| var o = options_1[_i]; | ||
| if (typeof o === "object" && o[OPTION_IGNORE_PREFIX] !== null) { | ||
| ignorePrefix = o[OPTION_IGNORE_PREFIX]; | ||
| break; | ||
| } | ||
| } | ||
| return { ignoreLocal: ignoreLocal, ignorePrefix: ignorePrefix }; | ||
| } | ||
| function walk(ctx) { | ||
| return ts.forEachChild(ctx.sourceFile, cb); | ||
| function cb(node) { | ||
| // Skip checking in functions if ignore-local is set | ||
| if (ctx.options.ignoreLocal && (node.kind === ts.SyntaxKind.FunctionDeclaration | ||
| || node.kind === ts.SyntaxKind.ArrowFunction || node.kind === ts.SyntaxKind.FunctionExpression)) { | ||
| // We still need to check the parameters and return type | ||
| var functionNode = node; //tslint:disable-line | ||
| var invalidNodes = checkIgnoreLocalFunctionNode(functionNode, ctx); | ||
| invalidNodes.forEach(function (n) { return reportInvalidNode(n, ctx); }); | ||
| // Now skip this whole branch | ||
| return; | ||
| } | ||
| // Check the node | ||
| reportInvalidNode(checkNode(node, ctx), ctx); | ||
| // Use return becuase performance hints docs say it optimizes the function using tail-call recursion | ||
| return ts.forEachChild(node, cb); | ||
| } | ||
| } | ||
| function checkNode(node, ctx) { | ||
| return checkArrayTypeOrReference(node, ctx) || checkVariableOrParameterImplicitType(node, ctx); | ||
| var explicitTypeFailures = checkArrayTypeOrReference(node, ctx); | ||
| var implicitTypeFailures = checkVariableOrParameterImplicitType(node, ctx); | ||
| return explicitTypeFailures.concat(implicitTypeFailures); | ||
| } | ||
| function reportInvalidNode(invalidNode, ctx) { | ||
| if (invalidNode) { | ||
| ctx.addFailureAtNode(invalidNode.node, Rule.FAILURE_STRING, invalidNode.replacement); | ||
| } | ||
| } | ||
| function checkIgnoreLocalFunctionNode(functionNode, ctx) { | ||
| var invalidNodes = []; | ||
| // Check either the parameter's explicit type if it has one, or itself for implict type | ||
| for (var _i = 0, _a = functionNode.parameters.map(function (p) { return p.type ? p.type : p; }); _i < _a.length; _i++) { | ||
| var n = _a[_i]; | ||
| var invalidNode = checkNode(n, ctx); | ||
| if (invalidNode) { | ||
| invalidNodes.push(invalidNode); | ||
| } | ||
| } | ||
| // Check the return type | ||
| if (functionNode.type) { | ||
| var invalidNode = checkNode(functionNode.type, ctx); | ||
| if (invalidNode) { | ||
| invalidNodes.push(invalidNode); | ||
| } | ||
| } | ||
| return invalidNodes; | ||
| } | ||
| function checkArrayTypeOrReference(node, ctx) { | ||
@@ -96,4 +38,4 @@ // We need to check both shorthand syntax "number[]" and type reference "Array<number>" | ||
| || (node.kind === ts.SyntaxKind.TypeReference && node.typeName.getText(ctx.sourceFile) === "Array")) { | ||
| if (node.parent && shouldIgnorePrefix(node.parent, ctx.options, ctx.sourceFile)) { | ||
| return undefined; | ||
| if (node.parent && Shared.shouldIgnorePrefix(node.parent, ctx.options, ctx.sourceFile)) { | ||
| return []; | ||
| } | ||
@@ -112,5 +54,5 @@ var typeArgument = "T"; | ||
| var length_1 = node.getWidth(ctx.sourceFile); | ||
| return createInvalidNode(node, new Lint.Replacement(node.end - length_1, length_1, "ReadonlyArray<" + typeArgument + ">")); | ||
| return [Shared.createInvalidNode(node, new Lint.Replacement(node.end - length_1, length_1, "ReadonlyArray<" + typeArgument + ">"))]; | ||
| } | ||
| return undefined; | ||
| return []; | ||
| } | ||
@@ -121,4 +63,4 @@ function checkVariableOrParameterImplicitType(node, ctx) { | ||
| var varOrParamNode = node; | ||
| if (shouldIgnorePrefix(node, ctx.options, ctx.sourceFile)) { | ||
| return undefined; | ||
| if (Shared.shouldIgnorePrefix(node, ctx.options, ctx.sourceFile)) { | ||
| return []; | ||
| } | ||
@@ -142,22 +84,7 @@ if (!varOrParamNode.type) { | ||
| // } | ||
| return createInvalidNode(varOrParamNode.name, new Lint.Replacement(varOrParamNode.name.end - length_2, length_2, nameText + ": ReadonlyArray<" + typeArgument + ">")); | ||
| return [Shared.createInvalidNode(varOrParamNode.name, new Lint.Replacement(varOrParamNode.name.end - length_2, length_2, nameText + ": ReadonlyArray<" + typeArgument + ">"))]; | ||
| } | ||
| } | ||
| } | ||
| return undefined; | ||
| return []; | ||
| } | ||
| function shouldIgnorePrefix(node, options, sourceFile) { | ||
| // Check ignore-prefix for VariableDeclaration, PropertySignature, TypeAliasDeclaration, Parameter | ||
| if (options.ignorePrefix) { | ||
| if (node && (node.kind === ts.SyntaxKind.VariableDeclaration | ||
| || node.kind === ts.SyntaxKind.Parameter | ||
| || node.kind === ts.SyntaxKind.PropertySignature | ||
| || node.kind === ts.SyntaxKind.TypeAliasDeclaration)) { | ||
| var variableDeclarationNode = node; | ||
| if (variableDeclarationNode.name.getText(sourceFile).substr(0, options.ignorePrefix.length) === options.ignorePrefix) { | ||
| return true; | ||
| } | ||
| } | ||
| } | ||
| return false; | ||
| } |
@@ -15,2 +15,3 @@ "use strict"; | ||
| var Lint = require("tslint"); | ||
| var Shared = require("./readonly-shared"); | ||
| var Rule = (function (_super) { | ||
@@ -22,38 +23,28 @@ __extends(Rule, _super); | ||
| Rule.prototype.apply = function (sourceFile) { | ||
| var walker = new ReadonlyInterfaceWalker(sourceFile, this.getOptions()); | ||
| return this.applyWithWalker(walker); | ||
| return this.applyWithFunction(sourceFile, function (ctx) { return Shared.walk(ctx, checkNode, "Interface members must have readonly modifier."); }, Shared.parseOptions(this.ruleArguments)); | ||
| }; | ||
| return Rule; | ||
| }(Lint.Rules.AbstractRule)); | ||
| Rule.FAILURE_STRING = "Interface members must have readonly modifier."; | ||
| Rule.FAILURE_STRING_ARRAY = "Interface members of array type must be ReadonlyArray."; | ||
| exports.Rule = Rule; | ||
| var ReadonlyInterfaceWalker = (function (_super) { | ||
| __extends(ReadonlyInterfaceWalker, _super); | ||
| function ReadonlyInterfaceWalker() { | ||
| return _super !== null && _super.apply(this, arguments) || this; | ||
| } | ||
| ReadonlyInterfaceWalker.prototype.visitInterfaceDeclaration = function (node) { | ||
| for (var _i = 0, _a = node.members; _i < _a.length; _i++) { | ||
| function checkNode(node, ctx) { | ||
| return checkInterfaceDeclaration(node, ctx); | ||
| } | ||
| function checkInterfaceDeclaration(node, ctx) { | ||
| if (node.kind === ts.SyntaxKind.InterfaceDeclaration) { | ||
| var interfaceDeclarationNode = node; | ||
| var invalidNodes = []; | ||
| for (var _i = 0, _a = interfaceDeclarationNode.members; _i < _a.length; _i++) { | ||
| var member = _a[_i]; | ||
| if (!(member.modifiers && member.modifiers.filter(function (m) { return m.kind === ts.SyntaxKind.ReadonlyKeyword; }).length > 0)) { | ||
| this.addFailure(this.createFailure(member.getStart(), member.getWidth(), Rule.FAILURE_STRING)); | ||
| // readonly modifier is only allowed for PropertySignature and IndexSignature | ||
| if (member.kind === ts.SyntaxKind.PropertySignature || member.kind === ts.SyntaxKind.IndexSignature) { | ||
| if (!(member.modifiers && member.modifiers.filter(function (m) { return m.kind === ts.SyntaxKind.ReadonlyKeyword; }).length > 0)) { | ||
| var length_1 = member.getWidth(ctx.sourceFile); | ||
| var memberName = member.name; | ||
| invalidNodes.push(Shared.createInvalidNode(member, new Lint.Replacement(node.end - length_1, length_1, "readonly " + memberName))); | ||
| } | ||
| } | ||
| // interface Foo { | ||
| // readonly bar: number, | ||
| // readonly zoo: () => string | ||
| // readonly loo: Array<string>, | ||
| // readonly <T1>(): Array<T1> | ||
| // } | ||
| // let f: Foo; | ||
| // f.zoo = () => ""; | ||
| // console.log("member.type", (member as any).type); | ||
| // | ||
| // if (member.kind === ts.SyntaxKind.ArrayType) { | ||
| // this.addFailure(this.createFailure(member.getStart(), member.getWidth(), Rule.FAILURE_STRING_ARRAY)); | ||
| // } | ||
| } | ||
| _super.prototype.visitInterfaceDeclaration.call(this, node); | ||
| }; | ||
| return ReadonlyInterfaceWalker; | ||
| }(Lint.RuleWalker)); | ||
| return invalidNodes; | ||
| } | ||
| return []; | ||
| } |
68622
1.5%24
4.35%1003
0.6%