From 721746695cc4422a3c5d7d90bf356f133f40d722 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Wed, 24 Jul 2019 13:18:48 +1000 Subject: [PATCH 1/5] chore(no-identical-title): convert to typescript --- ...tle.test.js => no-identical-title.test.ts} | 96 ++++++------------- src/rules/no-identical-title.js | 85 ---------------- src/rules/no-identical-title.ts | 77 +++++++++++++++ 3 files changed, 106 insertions(+), 152 deletions(-) rename src/rules/__tests__/{no-identical-title.test.js => no-identical-title.test.ts} (74%) delete mode 100644 src/rules/no-identical-title.js create mode 100644 src/rules/no-identical-title.ts diff --git a/src/rules/__tests__/no-identical-title.test.js b/src/rules/__tests__/no-identical-title.test.ts similarity index 74% rename from src/rules/__tests__/no-identical-title.test.js rename to src/rules/__tests__/no-identical-title.test.ts index c71216346..97da12e30 100644 --- a/src/rules/__tests__/no-identical-title.test.js +++ b/src/rules/__tests__/no-identical-title.test.ts @@ -1,7 +1,11 @@ -import { RuleTester } from 'eslint'; +import { TSESLint } from '@typescript-eslint/experimental-utils'; import rule from '../no-identical-title'; -const ruleTester = new RuleTester(); +const ruleTester = new TSESLint.RuleTester({ + parserOptions: { + ecmaVersion: 6, + }, +}); ruleTester.run('no-identical-title', rule, { valid: [ @@ -58,21 +62,8 @@ ruleTester.run('no-identical-title', rule, { 'describe("describe2", function() {});', ].join('\n'), ['it("it" + n, function() {});', 'it("it" + n, function() {});'].join('\n'), - { - code: [ - 'it(`it${n}`, function() {});', - 'it(`it${n}`, function() {});', - ].join('\n'), - env: { - es6: true, - }, - }, - { - code: 'it(`${n}`, function() {});', - env: { - es6: true, - }, - }, + ['it(`it${n}`, function() {});', 'it(`it${n}`, function() {});'].join('\n'), + 'it(`${n}`, function() {});', [ 'describe("title " + foo, function() {', ' describe("describe1", function() {});', @@ -87,54 +78,28 @@ ruleTester.run('no-identical-title', rule, { ' });', '});', ].join('\n'), - { - code: [ - 'describe("describe", () => {', - ' it(`testing ${someVar} with the same title`, () => {});', - ' it(`testing ${someVar} with the same title`, () => {});', - '});', - ].join('\n'), - env: { - es6: true, - }, - }, - { - code: ['it(`it1`, () => {});', 'it(`it2`, () => {});'].join('\n'), - env: { - es6: true, - }, - }, - { - code: [ - 'describe(`describe1`, () => {});', - 'describe(`describe2`, () => {});', - ].join('\n'), - env: { - es6: true, - }, - }, - { - code: [ - 'const test = { content: () => "foo" }', - 'test.content(`testing backticks with the same title`);', - 'test.content(`testing backticks with the same title`);', - ].join('\n'), - env: { - es6: true, - }, - }, - { - code: [ - 'const describe = { content: () => "foo" }', - 'describe.content(`testing backticks with the same title`);', - 'describe.content(`testing backticks with the same title`);', - ].join('\n'), - env: { - es6: true, - }, - }, + [ + 'describe("describe", () => {', + ' it(`testing ${someVar} with the same title`, () => {});', + ' it(`testing ${someVar} with the same title`, () => {});', + '});', + ].join('\n'), + ['it(`it1`, () => {});', 'it(`it2`, () => {});'].join('\n'), + [ + 'describe(`describe1`, () => {});', + 'describe(`describe2`, () => {});', + ].join('\n'), + [ + 'const test = { content: () => "foo" }', + 'test.content(`testing backticks with the same title`);', + 'test.content(`testing backticks with the same title`);', + ].join('\n'), + [ + 'const describe = { content: () => "foo" }', + 'describe.content(`testing backticks with the same title`);', + 'describe.content(`testing backticks with the same title`);', + ].join('\n'), ], - invalid: [ { code: [ @@ -208,9 +173,6 @@ ruleTester.run('no-identical-title', rule, { ' it(`testing backticks with the same title`, () => {});', '});', ].join('\n'), - env: { - es6: true, - }, errors: [{ messageId: 'multipleTestTitle', column: 5, line: 3 }], }, ], diff --git a/src/rules/no-identical-title.js b/src/rules/no-identical-title.js deleted file mode 100644 index 832b9ed36..000000000 --- a/src/rules/no-identical-title.js +++ /dev/null @@ -1,85 +0,0 @@ -import { - getDocsUrl, - getStringValue, - hasExpressions, - isDescribe, - isString, - isTestCase, -} from './util'; - -const newDescribeContext = () => ({ - describeTitles: [], - testTitles: [], -}); - -const handleTestCaseTitles = (context, titles, node, title) => { - if (isTestCase(node)) { - if (titles.indexOf(title) !== -1) { - context.report({ messageId: 'multipleTestTitle', node }); - } - titles.push(title); - } -}; - -const handleDescribeBlockTitles = (context, titles, node, title) => { - if (!isDescribe(node)) { - return; - } - if (titles.indexOf(title) !== -1) { - context.report({ messageId: 'multipleDescribeTitle', node }); - } - titles.push(title); -}; - -const isFirstArgValid = arg => { - if (!arg || !isString(arg)) { - return false; - } - if (arg.type === 'TemplateLiteral' && hasExpressions(arg)) { - return false; - } - return true; -}; - -export default { - meta: { - docs: { - url: getDocsUrl(__filename), - }, - messages: { - multipleTestTitle: - 'Test title is used multiple times in the same describe block.', - multipleDescribeTitle: - 'Describe block title is used multiple times in the same describe block.', - }, - schema: [], - }, - create(context) { - const contexts = [newDescribeContext()]; - return { - CallExpression(node) { - const currentLayer = contexts[contexts.length - 1]; - if (isDescribe(node)) { - contexts.push(newDescribeContext()); - } - const [firstArgument] = node.arguments; - if (!isFirstArgValid(firstArgument)) { - return; - } - const title = getStringValue(firstArgument); - handleTestCaseTitles(context, currentLayer.testTitles, node, title); - handleDescribeBlockTitles( - context, - currentLayer.describeTitles, - node, - title, - ); - }, - 'CallExpression:exit'(node) { - if (isDescribe(node)) { - contexts.pop(); - } - }, - }; - }, -}; diff --git a/src/rules/no-identical-title.ts b/src/rules/no-identical-title.ts new file mode 100644 index 000000000..9a9219ffb --- /dev/null +++ b/src/rules/no-identical-title.ts @@ -0,0 +1,77 @@ +import { + createRule, + getStringValue, + hasExpressions, + isDescribe, + isStringNode, + isTemplateLiteral, + isTestCase, +} from './tsUtils'; + +interface DescribeContext { + describeTitles: string[]; + testTitles: String[]; +} + +const newDescribeContext = (): DescribeContext => ({ + describeTitles: [], + testTitles: [], +}); + +export default createRule({ + name: __filename, + meta: { + docs: { + category: 'Best Practices', + description: '', + recommended: false, + }, + messages: { + multipleTestTitle: + 'Test title is used multiple times in the same describe block.', + multipleDescribeTitle: + 'Describe block title is used multiple times in the same describe block.', + }, + schema: [], + type: 'suggestion', + }, + defaultOptions: [], + create(context) { + const contexts = [newDescribeContext()]; + return { + CallExpression(node) { + const currentLayer = contexts[contexts.length - 1]; + if (isDescribe(node)) { + contexts.push(newDescribeContext()); + } + const [firstArgument] = node.arguments; + if ( + !isStringNode(firstArgument) || + (isTemplateLiteral(firstArgument) && hasExpressions(firstArgument)) + ) { + return; + } + const title = getStringValue(firstArgument); + if (isTestCase(node)) { + if (currentLayer.testTitles.indexOf(title) !== -1) { + context.report({ messageId: 'multipleTestTitle', node }); + } + currentLayer.testTitles.push(title); + } + + if (!isDescribe(node)) { + return; + } + if (currentLayer.describeTitles.indexOf(title) !== -1) { + context.report({ messageId: 'multipleDescribeTitle', node }); + } + currentLayer.describeTitles.push(title); + }, + 'CallExpression:exit'(node) { + if (isDescribe(node)) { + contexts.pop(); + } + }, + }; + }, +}); From cd9f583540f5aab05a4ab407755bc7d1a2a12e5d Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Wed, 24 Jul 2019 13:24:09 +1000 Subject: [PATCH 2/5] chore(no-identical-title): update meta --- src/rules/no-identical-title.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rules/no-identical-title.ts b/src/rules/no-identical-title.ts index 9a9219ffb..ec6d634fd 100644 --- a/src/rules/no-identical-title.ts +++ b/src/rules/no-identical-title.ts @@ -23,8 +23,8 @@ export default createRule({ meta: { docs: { category: 'Best Practices', - description: '', - recommended: false, + description: 'Disallow identical titles', + recommended: 'error', }, messages: { multipleTestTitle: From 6ab8d30ae163809a545aa4f91a12962c2ad73de0 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Wed, 24 Jul 2019 13:36:49 +1000 Subject: [PATCH 3/5] chore(util): remove unused functions --- src/rules/util.js | 37 ------------------------------------- 1 file changed, 37 deletions(-) diff --git a/src/rules/util.js b/src/rules/util.js index af1540c92..7e395266a 100644 --- a/src/rules/util.js +++ b/src/rules/util.js @@ -97,51 +97,14 @@ export const argument = node => export const argument2 = node => node.parent.parent.parent.arguments && node.parent.parent.parent.arguments[0]; -const describeAliases = new Set(['describe', 'fdescribe', 'xdescribe']); - -const testCaseNames = new Set(['fit', 'it', 'test', 'xit', 'xtest']); - -const describeProperties = new Set(['each', 'only', 'skip']); - -const testCaseProperties = new Set(['each', 'only', 'skip', 'todo']); - -export const isTestCase = node => - node && - node.type === 'CallExpression' && - ((node.callee.type === 'Identifier' && testCaseNames.has(node.callee.name)) || - (node.callee.type === 'MemberExpression' && - node.callee.object.type === 'Identifier' && - testCaseNames.has(node.callee.object.name) && - node.callee.property.type === 'Identifier' && - testCaseProperties.has(node.callee.property.name))); - -export const isDescribe = node => - node && - node.type === 'CallExpression' && - ((node.callee.type === 'Identifier' && - describeAliases.has(node.callee.name)) || - (node.callee.type === 'MemberExpression' && - node.callee.object.type === 'Identifier' && - describeAliases.has(node.callee.object.name) && - node.callee.property.type === 'Identifier' && - describeProperties.has(node.callee.property.name))); - export const isFunction = node => node && (node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression'); -export const isString = node => - node && - ((node.type === 'Literal' && typeof node.value === 'string') || - isTemplateLiteral(node)); - export const isTemplateLiteral = node => node && node.type === 'TemplateLiteral'; -export const hasExpressions = node => - node && node.expressions && node.expressions.length > 0; - export const getStringValue = arg => isTemplateLiteral(arg) ? arg.quasis[0].value.raw : arg.value; From 3934c7fe456212542d248cea004be0c740550023 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Wed, 24 Jul 2019 13:54:44 +1000 Subject: [PATCH 4/5] chore(util): remove unused code --- src/rules/util.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/rules/util.js b/src/rules/util.js index 7e395266a..e1edfd291 100644 --- a/src/rules/util.js +++ b/src/rules/util.js @@ -102,11 +102,7 @@ export const isFunction = node => (node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression'); -export const isTemplateLiteral = node => - node && node.type === 'TemplateLiteral'; - -export const getStringValue = arg => - isTemplateLiteral(arg) ? arg.quasis[0].value.raw : arg.value; +export const getStringValue = arg => arg.quasis[0].value.raw; /** * Generates the URL to documentation for the given rule name. It uses the From ceb11dd99c3bd1825abd2a23f2ad15c8cc9e6d7c Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Thu, 25 Jul 2019 07:49:12 +1200 Subject: [PATCH 5/5] chore(no-identical-title): use `includes` instead of `indexof` --- src/rules/no-identical-title.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rules/no-identical-title.ts b/src/rules/no-identical-title.ts index ec6d634fd..4b13649fc 100644 --- a/src/rules/no-identical-title.ts +++ b/src/rules/no-identical-title.ts @@ -53,7 +53,7 @@ export default createRule({ } const title = getStringValue(firstArgument); if (isTestCase(node)) { - if (currentLayer.testTitles.indexOf(title) !== -1) { + if (currentLayer.testTitles.includes(title)) { context.report({ messageId: 'multipleTestTitle', node }); } currentLayer.testTitles.push(title); @@ -62,7 +62,7 @@ export default createRule({ if (!isDescribe(node)) { return; } - if (currentLayer.describeTitles.indexOf(title) !== -1) { + if (currentLayer.describeTitles.includes(title)) { context.report({ messageId: 'multipleDescribeTitle', node }); } currentLayer.describeTitles.push(title);