aboutsummaryrefslogtreecommitdiffstats
path: root/packages/tslint-config
diff options
context:
space:
mode:
authorXianny <8582774+xianny@users.noreply.github.com>2019-01-12 02:04:30 +0800
committerGitHub <noreply@github.com>2019-01-12 02:04:30 +0800
commit943c378309a84ed142852c6584b53958df7817c8 (patch)
tree43536b7812b11f3a83db4208bfa5640643cfd6e8 /packages/tslint-config
parentcf3787edbb9e8acf7160ab93b903b54c63bdffda (diff)
downloaddexon-sol-tools-943c378309a84ed142852c6584b53958df7817c8.tar.gz
dexon-sol-tools-943c378309a84ed142852c6584b53958df7817c8.tar.zst
dexon-sol-tools-943c378309a84ed142852c6584b53958df7817c8.zip
Implement tslint enum-naming to enforce PascalCase on enum members (#1474)
Diffstat (limited to 'packages/tslint-config')
-rw-r--r--packages/tslint-config/package.json1
-rw-r--r--packages/tslint-config/rules/enumNamingRule.ts60
-rw-r--r--packages/tslint-config/test/enumNamingSpec.spec.ts88
-rw-r--r--packages/tslint-config/test/lintrunner.ts23
-rw-r--r--packages/tslint-config/tsconfig.json4
-rw-r--r--packages/tslint-config/tslint.json3
6 files changed, 176 insertions, 3 deletions
diff --git a/packages/tslint-config/package.json b/packages/tslint-config/package.json
index 77c28f32c..cf39bde3e 100644
--- a/packages/tslint-config/package.json
+++ b/packages/tslint-config/package.json
@@ -9,6 +9,7 @@
"scripts": {
"build": "tsc -b",
"build:ci": "yarn build",
+ "test": "mocha ./lib/test/*.spec.js",
"clean": "shx rm -rf lib",
"lint": "tslint --format stylish --project ."
},
diff --git a/packages/tslint-config/rules/enumNamingRule.ts b/packages/tslint-config/rules/enumNamingRule.ts
new file mode 100644
index 000000000..56499618f
--- /dev/null
+++ b/packages/tslint-config/rules/enumNamingRule.ts
@@ -0,0 +1,60 @@
+import * as Lint from 'tslint';
+import * as ts from 'typescript';
+
+export class Rule extends Lint.Rules.AbstractRule {
+ public static FAILURE_STRING = `Enum member names should be PascalCase`;
+
+ public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
+ return this.applyWithFunction(sourceFile, walk);
+ }
+}
+
+function walk(ctx: Lint.WalkContext<void>): void {
+ // Recursively walk the AST starting with root node, `ctx.sourceFile`.
+ // Call the function `cb` (defined below) for each child.
+ return ts.forEachChild(ctx.sourceFile, cb);
+
+ function cb(node: ts.Node): void {
+ if (node.kind === ts.SyntaxKind.EnumMember) {
+ const keyNode = node.getFirstToken(ctx.sourceFile);
+ if (keyNode !== undefined) {
+ const keyText = keyNode.getText(ctx.sourceFile);
+ if (!isPascalCase(keyText)) {
+ return ctx.addFailureAtNode(node, Rule.FAILURE_STRING, getFix(keyText, node));
+ }
+ }
+ }
+ // Continue recursion into the AST by calling function `cb` for every child of the current node.
+ return ts.forEachChild(node, cb);
+ }
+
+ function getFix(text: string, node: ts.Node): Lint.Replacement {
+ let fix = toPascalCase(text);
+ // check for `member = value`
+ if (node.getChildCount(ctx.sourceFile) === 3) {
+ const value = node.getLastToken(ctx.sourceFile);
+ if (value !== undefined) {
+ fix += ` = ${value.getText(ctx.sourceFile)}`;
+ }
+ }
+ return new Lint.Replacement(node.getStart(ctx.sourceFile), node.getWidth(ctx.sourceFile), fix);
+ }
+}
+
+// Modified from: https://github.com/jonschlinkert/pascalcase/
+function toPascalCase(str: string): string {
+ let result = str.replace(/([a-z0-9\W])([A-Z])/g, '$1 $2');
+ if (result.length === 1) {
+ return result.toUpperCase();
+ }
+ result = result.replace(/^[\W_\.]+|[\W_\.]+$/g, '').toLowerCase();
+ result = result.charAt(0).toUpperCase() + result.slice(1);
+ return result.replace(/[\W_\.]+(\w|$)/g, (_, ch) => {
+ return ch.toUpperCase();
+ });
+}
+function isPascalCase(s: string): boolean {
+ const regex = /^([A-Z0-9]+[a-z0-9]+)+$/g;
+ const key = s.split('=')[0].trim();
+ return regex.test(key);
+}
diff --git a/packages/tslint-config/test/enumNamingSpec.spec.ts b/packages/tslint-config/test/enumNamingSpec.spec.ts
new file mode 100644
index 000000000..d5b864eba
--- /dev/null
+++ b/packages/tslint-config/test/enumNamingSpec.spec.ts
@@ -0,0 +1,88 @@
+import * as assert from 'assert';
+
+import { Rule } from '../rules/enumNamingRule';
+
+import { getFixedResult, helper } from './lintrunner';
+const rule = 'enum-naming';
+
+describe('enumNamingRule', () => {
+ it(`should not fail PascalCase`, () => {
+ const src = `enum test { MemberOne, MemberTwo }`;
+ const result = helper(src, rule);
+ assert.equal(result.errorCount, 0);
+ });
+ it(`should not fail PascalCase keys with uncased values`, () => {
+ const src = `enum test { MemberOne = 'member_one', MemberTwo = 'member two' }`;
+ const result = helper(src, rule);
+ assert.equal(result.errorCount, 0);
+ });
+ it(`should not fail PascalCase keys with numbers`, () => {
+ const src = `enum test { Member1 = 'member_one', MemberTwo = 'member two' }`;
+ const result = helper(src, rule);
+ assert.equal(result.errorCount, 0);
+ });
+ it(`should fail with camelCase`, () => {
+ const src = `enum test { memberOne, memberTwo }`;
+ const result = helper(src, rule);
+ assert.equal(result.errorCount, 2);
+ });
+ it(`should fail with snake case`, () => {
+ const src = `enum test { member_one, member_two }`;
+ const result = helper(src, rule);
+ assert.equal(result.errorCount, 2);
+ });
+ it(`should fail with all caps`, () => {
+ const src = `enum test { MEMBERONE, MEMBER_TWO }`;
+ const result = helper(src, rule);
+ assert.equal(result.errorCount, 2);
+ });
+ it(`should fail with mixed case`, () => {
+ const src = `enum test { member_one, MemberTwo }`;
+ const result = helper(src, rule);
+ assert.equal(result.errorCount, 1);
+ });
+
+ it(`should fail with the right position`, () => {
+ const src = `enum test { MemberOne, member_two }`;
+ const startPosition = src.indexOf('member_two');
+ const endPosition = startPosition + 'member_two'.length;
+ const failure = helper(src, rule).failures[0];
+
+ assert.equal(failure.getStartPosition().getPosition(), startPosition);
+ assert.equal(failure.getEndPosition().getPosition(), endPosition);
+ assert.equal(failure.getFailure(), Rule.FAILURE_STRING);
+ });
+
+ it(`should fail with the right message`, () => {
+ const src = `enum test { memberOne, memberTwo }`;
+ const failure = helper(src, rule).failures[0];
+
+ assert.equal(failure.getFailure(), Rule.FAILURE_STRING);
+ });
+});
+describe('enumNaming fixer', () => {
+ it('should fix keys', () => {
+ const src = `enum test { MemberOne, memberTwo, member_three, MEMBER_FOUR, MEMBERFIVE }`;
+ const expected = `enum test { MemberOne, MemberTwo, MemberThree, MemberFour, Memberfive }`;
+ const actual = getFixedResult(src, rule);
+ const result = helper(src, rule);
+ assert.equal(result.errorCount, 4); // tslint:disable-line:custom-no-magic-numbers
+ assert.equal(actual, expected);
+ });
+ it('should not fix values', () => {
+ const src = `enum test { MemberOne = 'MemberOne', memberTwo = 'memberTwo', member_three = 'member_three', MEMBER_FOUR = 'MEMBER_FOUR' }`;
+ const expected = `enum test { MemberOne = 'MemberOne', MemberTwo = 'memberTwo', MemberThree = 'member_three', MemberFour = 'MEMBER_FOUR' }`;
+ const actual = getFixedResult(src, rule);
+ const result = helper(src, rule);
+ assert.equal(result.errorCount, 3); // tslint:disable-line:custom-no-magic-numbers
+ assert.equal(actual, expected);
+ });
+ it('should preserve values with equals sign', () => {
+ const src = `enum Operators { assign = '=', EQUALS = '==', Triple_Equals = '===' }`;
+ const expected = `enum Operators { Assign = '=', Equals = '==', TripleEquals = '===' }`;
+ const actual = getFixedResult(src, rule);
+ const result = helper(src, rule);
+ assert.equal(result.errorCount, 3); // tslint:disable-line:custom-no-magic-numbers
+ assert.equal(actual, expected);
+ });
+});
diff --git a/packages/tslint-config/test/lintrunner.ts b/packages/tslint-config/test/lintrunner.ts
new file mode 100644
index 000000000..fcd1b6844
--- /dev/null
+++ b/packages/tslint-config/test/lintrunner.ts
@@ -0,0 +1,23 @@
+import * as path from 'path';
+import { Configuration, Linter, Replacement } from 'tslint';
+
+export const helper = (src: string, rule: string) => {
+ const linter = new Linter({ fix: false });
+ linter.lint(
+ '',
+ src,
+ Configuration.parseConfigFile({
+ rules: {
+ [rule]: true,
+ },
+ rulesDirectory: path.join(__dirname, '../rules'),
+ }),
+ );
+ return linter.getResult();
+};
+
+export const getFixedResult = (src: string, rule: string) => {
+ const result = helper(src, rule);
+ const fixes = [].concat.apply(result.failures.map(x => x.getFix()));
+ return Replacement.applyFixes(src, fixes);
+};
diff --git a/packages/tslint-config/tsconfig.json b/packages/tslint-config/tsconfig.json
index 44845cf1f..b9a4dd03e 100644
--- a/packages/tslint-config/tsconfig.json
+++ b/packages/tslint-config/tsconfig.json
@@ -2,7 +2,7 @@
"extends": "../../tsconfig",
"compilerOptions": {
"outDir": "lib",
- "rootDir": "rules"
+ "rootDir": "."
},
- "include": ["./rules/**/*"]
+ "include": ["./rules/**/*", "test/**/*"]
}
diff --git a/packages/tslint-config/tslint.json b/packages/tslint-config/tslint.json
index e8de6221e..a5fa6962c 100644
--- a/packages/tslint-config/tslint.json
+++ b/packages/tslint-config/tslint.json
@@ -25,6 +25,7 @@
"curly": true,
"custom-no-magic-numbers": [true, 0, 1, 2, 3, -1],
"encoding": true,
+ "enum-naming": true,
"eofline": true,
"import-spacing": true,
"indent": [true, "spaces", 4],
@@ -125,5 +126,5 @@
"check-preblock"
]
},
- "rulesDirectory": "lib"
+ "rulesDirectory": "lib/rules"
}