From 0e4a8206be46dc52e0c1a6e6b3ada68e50d00777 Mon Sep 17 00:00:00 2001 From: Armano Date: Sat, 4 Jan 2020 04:05:30 +0100 Subject: [PATCH 1/4] test: migrate validation tools to jest test cases --- azure-pipelines.yml | 8 - packages/eslint-plugin/package.json | 4 +- packages/eslint-plugin/tests/configs.test.ts | 71 +++++++++ .../eslint-plugin/tests/configs/all.test.ts | 42 ----- packages/eslint-plugin/tests/docs.test.ts | 141 +++++++++++++++++ .../tools/validate-configs/checkConfigAll.ts | 35 ----- .../checkConfigRecommended.ts | 39 ----- ...kConfigRecommendedRequiringTypeChecking.ts | 45 ------ .../tools/validate-configs/index.ts | 28 ---- .../validate-docs/check-for-rule-docs.ts | 67 -------- .../tools/validate-docs/index.ts | 34 ---- .../tools/validate-docs/parse-readme.ts | 28 ---- .../validate-docs/validate-table-rules.ts | 147 ------------------ .../validate-docs/validate-table-structure.ts | 55 ------- 14 files changed, 214 insertions(+), 530 deletions(-) create mode 100644 packages/eslint-plugin/tests/configs.test.ts delete mode 100644 packages/eslint-plugin/tests/configs/all.test.ts create mode 100644 packages/eslint-plugin/tests/docs.test.ts delete mode 100644 packages/eslint-plugin/tools/validate-configs/checkConfigAll.ts delete mode 100644 packages/eslint-plugin/tools/validate-configs/checkConfigRecommended.ts delete mode 100644 packages/eslint-plugin/tools/validate-configs/checkConfigRecommendedRequiringTypeChecking.ts delete mode 100644 packages/eslint-plugin/tools/validate-configs/index.ts delete mode 100644 packages/eslint-plugin/tools/validate-docs/check-for-rule-docs.ts delete mode 100644 packages/eslint-plugin/tools/validate-docs/index.ts delete mode 100644 packages/eslint-plugin/tools/validate-docs/parse-readme.ts delete mode 100644 packages/eslint-plugin/tools/validate-docs/validate-table-rules.ts delete mode 100644 packages/eslint-plugin/tools/validate-docs/validate-table-structure.ts diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 4b20b6a825be..8eca71b7c172 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -31,18 +31,10 @@ jobs: yarn lint displayName: 'Run linting' - - script: | - yarn check:docs - displayName: 'Validate documentation' - - script: | yarn check:spelling displayName: 'Validate documentation spelling' - - script: | - yarn check:configs - displayName: 'Validate plugin configs' - - script: | yarn test displayName: 'Run unit tests' diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 1d26d0143e7b..d190ebaf68b8 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -30,8 +30,8 @@ "main": "dist/index.js", "scripts": { "build": "tsc -b tsconfig.build.json", - "check:docs": "../../node_modules/.bin/ts-node --files --transpile-only ./tools/validate-docs/index.ts", - "check:configs": "../../node_modules/.bin/ts-node --files --transpile-only ./tools/validate-configs/index.ts", + "check:docs": "jest tests/docs.test.ts --runTestsByPath --silent --runInBand", + "check:configs": "jest ests/configs.test.ts --runTestsByPath --silent --runInBand", "clean": "tsc -b tsconfig.build.json --clean", "format": "prettier --write \"./**/*.{ts,js,json,md}\" --ignore-path ../../.prettierignore", "generate:configs": "../../node_modules/.bin/ts-node --files --transpile-only tools/generate-configs.ts", diff --git a/packages/eslint-plugin/tests/configs.test.ts b/packages/eslint-plugin/tests/configs.test.ts new file mode 100644 index 000000000000..2fd27ac4831a --- /dev/null +++ b/packages/eslint-plugin/tests/configs.test.ts @@ -0,0 +1,71 @@ +import rules from '../src/rules'; +import plugin from '../src/index'; + +function entriesToObject(value: [string, T][]): Record { + return value.reduce>((accum, [k, v]) => { + accum[k] = v; + return accum; + }, {}); +} + +const notDeprecatedRules = Object.entries(rules).filter( + ([, rule]) => !rule.meta.deprecated, +); + +function filterRules(values: Record): [string, string][] { + return Object.entries(values).filter(([name]) => + name.startsWith(RULE_NAME_PREFIX), + ); +} + +const RULE_NAME_PREFIX = '@typescript-eslint/'; + +describe('all.json config', () => { + const configRules = filterRules(plugin.configs.all.rules); + const ruleConfigs = notDeprecatedRules.map<[string, string]>(([name]) => [ + `${RULE_NAME_PREFIX}${name}`, + 'error', + ]); + + it('contains all @typescript-eslint/eslint-plugin rule modules, except the deprecated ones', () => { + expect(entriesToObject(ruleConfigs)).toEqual(entriesToObject(configRules)); + }); +}); + +describe('recommended.json config', () => { + const configRules = filterRules(plugin.configs.recommended.rules); + const ruleConfigs = notDeprecatedRules + .filter( + ([, rule]) => + rule.meta.docs.recommended !== false && + rule.meta.docs.requiresTypeChecking !== true, + ) + .map<[string, string]>(([name, rule]) => [ + `${RULE_NAME_PREFIX}${name}`, + rule.meta.docs.recommended || 'off', + ]); + + it('contains all @typescript-eslint/eslint-plugin rule modules, except the deprecated ones', () => { + expect(entriesToObject(ruleConfigs)).toEqual(entriesToObject(configRules)); + }); +}); + +describe('recommended-requiring-type-checking.json config', () => { + const configRules = filterRules( + plugin.configs['recommended-requiring-type-checking'].rules, + ); + const ruleConfigs = notDeprecatedRules + .filter( + ([, rule]) => + rule.meta.docs.recommended !== false && + rule.meta.docs.requiresTypeChecking === true, + ) + .map<[string, string]>(([name, rule]) => [ + `${RULE_NAME_PREFIX}${name}`, + rule.meta.docs.recommended || 'off', + ]); + + it('contains all @typescript-eslint/eslint-plugin rule modules, except the deprecated ones', () => { + expect(entriesToObject(ruleConfigs)).toEqual(entriesToObject(configRules)); + }); +}); diff --git a/packages/eslint-plugin/tests/configs/all.test.ts b/packages/eslint-plugin/tests/configs/all.test.ts deleted file mode 100644 index 871576f2543a..000000000000 --- a/packages/eslint-plugin/tests/configs/all.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -import rules from '../../src/rules'; -import allConfig from '../../src/configs/all.json'; - -interface JsonRules { - [name: string]: string; -} - -describe('all.json config', () => { - const RULE_NAME_PREFIX = '@typescript-eslint/'; - - const rulesNames = Object.keys(rules) as (keyof typeof rules)[]; - const notDeprecatedRuleNames = rulesNames.reduce( - (collection, name) => { - if (!rules[name].meta.deprecated) { - collection.push(`${RULE_NAME_PREFIX}${name}`); - } - return collection; - }, - [], - ); - - // with end of Node.js 6 support, we can use Object.entries(allConfig.rules) here - const configRules: JsonRules = allConfig.rules; - const typescriptEslintConfigRules = Object.keys(configRules).filter(name => - name.startsWith(RULE_NAME_PREFIX), - ); - const typescriptEslintConfigRuleValues = typescriptEslintConfigRules.map( - name => configRules[name], - ); - - it('contains all @typescript-eslint/eslint-plugin rule modules, except the deprecated ones', () => { - expect(notDeprecatedRuleNames).toEqual( - expect.arrayContaining(typescriptEslintConfigRules), - ); - }); - - it('has all containing @typescript-eslint/eslint-plugin rules enabled with "error"', () => { - expect(['error']).toEqual( - expect.arrayContaining(typescriptEslintConfigRuleValues), - ); - }); -}); diff --git a/packages/eslint-plugin/tests/docs.test.ts b/packages/eslint-plugin/tests/docs.test.ts new file mode 100644 index 000000000000..eff0f61cc6e1 --- /dev/null +++ b/packages/eslint-plugin/tests/docs.test.ts @@ -0,0 +1,141 @@ +import fs from 'fs'; +import path from 'path'; + +import marked from 'marked'; +import rules from '../src/rules'; + +const docsRoot = path.resolve(__dirname, '../docs/rules'); +const rulesData = Object.entries(rules); + +function createRuleLink(ruleName: string): string { + return `[\`@typescript-eslint/${ruleName}\`](./docs/rules/${ruleName}.md)`; +} + +function parseReadme(): marked.Tokens.Table { + const readmeRaw = fs.readFileSync( + path.resolve(__dirname, '../README.md'), + 'utf8', + ); + const readme = marked.lexer(readmeRaw, { + gfm: true, + silent: false, + }); + + // find the table + const rulesTable = readme.find( + token => token.type === 'table', + ) as marked.Tokens.Table; + if (!rulesTable) { + throw Error('Could not find the rules table in README.md'); + } + + return rulesTable; +} + +describe('Validating rule docs', () => { + it(`check if all files are present`, () => { + const files = fs.readdirSync(docsRoot); + const ruleFiles = Object.keys(rules) + .map(rule => `${rule}.md`) + .sort(); + + expect(files.sort()).toEqual(ruleFiles); + }); + + for (const [ruleName, rule] of rulesData) { + const filePath = path.join(docsRoot, `${ruleName}.md`); + it(`Description of ${ruleName}.md must match`, () => { + // validate if description of rule is same as in docs + const file = fs.readFileSync(filePath, 'utf-8'); + const tokens = marked.lexer(file, { + gfm: true, + silent: false, + }); + + // Rule title not found. + // Rule title does not match the rule metadata. + expect(tokens[0]).toEqual({ + type: 'heading', + depth: 1, + text: `${rule.meta.docs.description} (\`${ruleName}\`)`, + }); + }); + } +}); + +describe('Validating rule metadata', () => { + for (const [ruleName, rule] of rulesData) { + describe(`${ruleName}`, () => { + it(`name field in rule must be correct`, () => { + // validate if rule name is same as url + // there is no way to access this field but its used only in generation of docs url + expect( + rule.meta.docs.url.endsWith(`rules/${ruleName}.md`), + ).toBeTruthy(); + }); + + it('Expects type checking to be set for rule', () => { + // quick-and-dirty check to see if it uses parserServices + // not perfect but should be good enough + const ruleFileContents = fs.readFileSync( + path.resolve(__dirname, `../src/rules/${ruleName}.ts`), + ); + + expect(ruleFileContents.includes('getParserServices')).toEqual( + rule.meta.docs.requiresTypeChecking ?? false, + ); + }); + }); + } +}); + +describe('Validating README.md', () => { + const rulesTable = parseReadme().cells; + const notDeprecated = rulesData.filter( + ([, rule]) => rule.meta.deprecated !== true, + ); + + it('Checking table structure...', () => { + const ruleNames = notDeprecated + .map(([ruleName]) => ruleName) + .sort() + .map(createRuleLink); + + expect(rulesTable.map(row => row[0])).toStrictEqual(ruleNames); + }); + + for (const [ruleName, rule] of notDeprecated) { + describe(`Checking rule ${ruleName}`, () => { + const ruleRow = + rulesTable.find(row => row[0].includes(`/${ruleName}.md`)) ?? []; + + it(`Link column should be set correctly`, () => { + expect(ruleRow[0]).toEqual(createRuleLink(ruleName)); + }); + + it(`Description column should be set correctly`, () => { + expect(ruleRow[1]).toEqual(rule.meta.docs.description); + }); + + it(`Recommended column should be set correctly`, () => { + expect(ruleRow[2]).toEqual( + rule.meta.docs.recommended ? ':heavy_check_mark:' : '', + ); + }); + + it(`Fixable column should be set correctly`, () => { + expect(ruleRow[3]).toEqual( + rule.meta.fixable !== undefined ? ':wrench:' : '', + ); + }); + + it(`Requiring type information column should be set correctly`, () => { + expect(ruleRow[4]).toEqual( + rule.meta.docs.requiresTypeChecking === true + ? ':thought_balloon:' + : '', + ); + }); + }); + } +}); diff --git a/packages/eslint-plugin/tools/validate-configs/checkConfigAll.ts b/packages/eslint-plugin/tools/validate-configs/checkConfigAll.ts deleted file mode 100644 index fab57fe20cf6..000000000000 --- a/packages/eslint-plugin/tools/validate-configs/checkConfigAll.ts +++ /dev/null @@ -1,35 +0,0 @@ -import plugin from '../../src/index'; -import { logRule } from '../log'; - -const prefix = '@typescript-eslint/'; - -function checkConfigAll(): boolean { - const { rules } = plugin; - - const all = plugin.configs.all.rules; - const allNames = new Set(Object.keys(all)); - - return Object.entries(rules).reduce((acc, [ruleName, rule]) => { - if (!rule.meta.deprecated) { - const prefixed = `${prefix}${ruleName}` as keyof typeof all; - if (allNames.has(prefixed)) { - if (all[prefixed] !== 'error') { - logRule( - false, - ruleName, - 'incorrect setting compared to the rule meta.', - ); - return true; - } - } else { - logRule(false, ruleName, 'missing in the config.'); - return true; - } - } - - logRule(true, ruleName); - return acc; - }, false); -} - -export { checkConfigAll }; diff --git a/packages/eslint-plugin/tools/validate-configs/checkConfigRecommended.ts b/packages/eslint-plugin/tools/validate-configs/checkConfigRecommended.ts deleted file mode 100644 index 28f0c7b21a50..000000000000 --- a/packages/eslint-plugin/tools/validate-configs/checkConfigRecommended.ts +++ /dev/null @@ -1,39 +0,0 @@ -import plugin from '../../src/index'; -import { logRule } from '../log'; - -const prefix = '@typescript-eslint/'; - -function checkConfigRecommended(): boolean { - const { rules } = plugin; - - const recommended = plugin.configs.recommended.rules; - const recommendedNames = new Set(Object.keys(recommended)); - - return Object.entries(rules).reduce((acc, [ruleName, rule]) => { - if ( - !rule.meta.deprecated && - rule.meta.docs.recommended !== false && - rule.meta.docs.requiresTypeChecking !== true - ) { - const prefixed = `${prefix}${ruleName}` as keyof typeof recommended; - if (recommendedNames.has(prefixed)) { - if (recommended[prefixed] !== rule.meta.docs.recommended) { - logRule( - false, - ruleName, - 'incorrect setting compared to the rule meta.', - ); - return true; - } - } else { - logRule(false, ruleName, 'missing in the config.'); - return true; - } - } - - logRule(true, ruleName); - return acc; - }, false); -} - -export { checkConfigRecommended }; diff --git a/packages/eslint-plugin/tools/validate-configs/checkConfigRecommendedRequiringTypeChecking.ts b/packages/eslint-plugin/tools/validate-configs/checkConfigRecommendedRequiringTypeChecking.ts deleted file mode 100644 index a63f5e42c236..000000000000 --- a/packages/eslint-plugin/tools/validate-configs/checkConfigRecommendedRequiringTypeChecking.ts +++ /dev/null @@ -1,45 +0,0 @@ -import plugin from '../../src/index'; -import { logRule } from '../log'; - -const prefix = '@typescript-eslint/'; - -function checkConfigRecommendedRequiringTypeChecking(): boolean { - const { rules } = plugin; - - const recommendedRequiringTypeChecking = - plugin.configs['recommended-requiring-type-checking'].rules; - const recommendedNames = new Set( - Object.keys(recommendedRequiringTypeChecking), - ); - - return Object.entries(rules).reduce((acc, [ruleName, rule]) => { - if ( - !rule.meta.deprecated && - rule.meta.docs.recommended !== false && - rule.meta.docs.requiresTypeChecking === true - ) { - const prefixed = `${prefix}${ruleName}` as keyof typeof recommendedRequiringTypeChecking; - if (recommendedNames.has(prefixed)) { - if ( - recommendedRequiringTypeChecking[prefixed] !== - rule.meta.docs.recommended - ) { - logRule( - false, - ruleName, - 'incorrect setting compared to the rule meta.', - ); - return true; - } - } else { - logRule(false, ruleName, 'missing in the config.'); - return true; - } - } - - logRule(true, ruleName); - return acc; - }, false); -} - -export { checkConfigRecommendedRequiringTypeChecking }; diff --git a/packages/eslint-plugin/tools/validate-configs/index.ts b/packages/eslint-plugin/tools/validate-configs/index.ts deleted file mode 100644 index 1c05196a677e..000000000000 --- a/packages/eslint-plugin/tools/validate-configs/index.ts +++ /dev/null @@ -1,28 +0,0 @@ -import chalk from 'chalk'; -import { checkConfigRecommended } from './checkConfigRecommended'; -import { checkConfigRecommendedRequiringTypeChecking } from './checkConfigRecommendedRequiringTypeChecking'; -import { checkConfigAll } from './checkConfigAll'; - -let hasErrors = false; -console.log(chalk.underline('Checking config "recommended"')); -hasErrors = checkConfigRecommended() || hasErrors; - -console.log( - chalk.underline('Checking config "recommended-requiring-type-checking"'), -); -hasErrors = checkConfigRecommendedRequiringTypeChecking() || hasErrors; - -console.log(); -console.log(chalk.underline('Checking config "all"')); -hasErrors = checkConfigAll() || hasErrors; - -if (hasErrors) { - console.log('\n\n'); - console.error( - chalk.bold.bgRed.white('There were errors found in the configs'), - ); - console.error(`Please run ${chalk.inverse('yarn generate:configs')}`); - console.log('\n\n'); - // eslint-disable-next-line no-process-exit - process.exit(1); -} diff --git a/packages/eslint-plugin/tools/validate-docs/check-for-rule-docs.ts b/packages/eslint-plugin/tools/validate-docs/check-for-rule-docs.ts deleted file mode 100644 index f52c1bccbb0d..000000000000 --- a/packages/eslint-plugin/tools/validate-docs/check-for-rule-docs.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { TSESLint } from '@typescript-eslint/experimental-utils'; -import fs from 'fs'; -import path from 'path'; -import { logRule } from '../log'; -import chalk from 'chalk'; -import marked from 'marked'; - -function isHeading( - token: marked.Token, - depth = 1, -): token is marked.Tokens.Heading { - return token && token.type === 'heading' && token.depth === depth; -} - -export function checkForRuleDocs( - rules: Record>>, -): boolean { - const docsRoot = path.resolve(__dirname, '../../docs/rules'); - const ruleDocs = new Set(fs.readdirSync(docsRoot)); - - let hasErrors = false; - Object.keys(rules).forEach(ruleName => { - const rule = rules[ruleName].meta; - const errors: string[] = []; - const ruleHasDoc = ruleDocs.has(`${ruleName}.md`); - if (!ruleHasDoc) { - errors.push(`Couldn't find file docs/rules/${ruleName}.md`); - } else { - const file = fs.readFileSync( - path.join(docsRoot, `${ruleName}.md`), - 'utf-8', - ); - - const tokens = marked.lexer(file, { - gfm: true, - silent: false, - }); - - if (isHeading(tokens[0])) { - const expectedDescription = `${rule.docs.description} (\`${ruleName}\`)`; - if (tokens[0].text !== expectedDescription) { - errors.push( - 'Rule title does not match the rule metadata.', - ` Expected: ${chalk.underline(expectedDescription)}`, - ` Received: ${chalk.underline(tokens[0].text)}`, - ); - } - } else { - errors.push('Rule title not found.'); - } - } - - const ruleConfigName = /([A-Za-z-]+)\.md/.exec(rule.docs.url) ?? []; - if (ruleConfigName[1] !== ruleName) { - errors.push( - 'Name field does not match with rule name.', - ` Expected: ${chalk.underline(ruleName)}`, - ` Received: ${chalk.underline(ruleConfigName[1])}`, - ); - } - - logRule(errors.length === 0, ruleName, ...errors); - hasErrors = hasErrors || errors.length !== 0; - }); - - return hasErrors; -} diff --git a/packages/eslint-plugin/tools/validate-docs/index.ts b/packages/eslint-plugin/tools/validate-docs/index.ts deleted file mode 100644 index 5f2cf1fa9f8b..000000000000 --- a/packages/eslint-plugin/tools/validate-docs/index.ts +++ /dev/null @@ -1,34 +0,0 @@ -import chalk from 'chalk'; -import plugin from '../../src/index'; -import { checkForRuleDocs } from './check-for-rule-docs'; -import { parseReadme } from './parse-readme'; -import { validateTableStructure } from './validate-table-structure'; -import { validateTableRules } from './validate-table-rules'; - -const { rules } = plugin; - -let hasErrors = false; -console.log(chalk.underline('Checking for rule docs')); -hasErrors = hasErrors || checkForRuleDocs(rules); - -console.log(); -console.log(chalk.underline('Validating README.md')); -const rulesTable = parseReadme(); - -console.log(); -console.log(chalk.italic('Checking table structure...')); -hasErrors = hasErrors || validateTableStructure(rules, rulesTable); - -console.log(); -console.log(chalk.italic('Checking rules...')); -hasErrors = hasErrors || validateTableRules(rules, rulesTable); - -if (hasErrors) { - console.log('\n\n'); - console.error( - chalk.bold.bgRed.white('There were errors found in the documentation.'), - ); - console.log('\n\n'); - // eslint-disable-next-line no-process-exit - process.exit(1); -} diff --git a/packages/eslint-plugin/tools/validate-docs/parse-readme.ts b/packages/eslint-plugin/tools/validate-docs/parse-readme.ts deleted file mode 100644 index aace3736fcbe..000000000000 --- a/packages/eslint-plugin/tools/validate-docs/parse-readme.ts +++ /dev/null @@ -1,28 +0,0 @@ -import fs from 'fs'; -import marked from 'marked'; -import path from 'path'; - -function parseReadme(): marked.Tokens.Table { - const readmeRaw = fs.readFileSync( - path.resolve(__dirname, '../../README.md'), - 'utf8', - ); - const readme = marked.lexer(readmeRaw, { - gfm: true, - silent: false, - }); - - // find the table - const rulesTable = readme.find( - token => token.type === 'table', - ) as marked.Tokens.Table; - if (!rulesTable) { - console.error('Could not find the rules table in README.md'); - // eslint-disable-next-line no-process-exit - process.exit(1); - } - - return rulesTable; -} - -export { parseReadme }; diff --git a/packages/eslint-plugin/tools/validate-docs/validate-table-rules.ts b/packages/eslint-plugin/tools/validate-docs/validate-table-rules.ts deleted file mode 100644 index b85172d0fdc4..000000000000 --- a/packages/eslint-plugin/tools/validate-docs/validate-table-rules.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { TSESLint } from '@typescript-eslint/experimental-utils'; -import chalk from 'chalk'; -import fs from 'fs'; -import marked from 'marked'; -import path from 'path'; -import { logRule } from '../log'; - -function validateTableRules( - rules: Record>>, - rulesTable: marked.Tokens.Table, -): boolean { - let hasErrors = false; - - Object.entries(rules).forEach(([ruleName, rule]) => { - const row = rulesTable.cells.find(row => - row[0].includes(`/${ruleName}.md`), - ); - - if (!row) { - if (!rule.meta.deprecated) { - hasErrors = true; - logRule(false, ruleName, 'Missing entry in table'); - return; - } - - // all is well, the rule shouldn't have a row as it's deprecated - return; - } - if (row && rule.meta.deprecated) { - hasErrors = true; - logRule( - false, - ruleName, - 'Rule is marked as deprecated, should not have an entry in the table', - ); - return; - } - - const errors: string[] = []; - const [ - rowLink, - rowDescription, - rowIsRecommended, - rowIsFixable, - rowNeedsTypeInfo, - ] = row; - - function validateTableBoolean( - value: boolean, - cell: string, - trueString: string, - columnLabel: string, - ): void { - if (value && cell !== trueString) { - errors.push( - `Rule ${chalk.red( - 'not', - )} marked as ${columnLabel} when it ${chalk.bold('should')} be`, - ); - } - - if (!value && cell !== '') { - errors.push( - `Rule ${chalk.red( - 'was', - )} marked as ${columnLabel} when it ${chalk.bold('should not')} be`, - ); - } - } - - const expectedLink = `[\`@typescript-eslint/${ruleName}\`](./docs/rules/${ruleName}.md)`; - if (rowLink !== expectedLink) { - errors.push( - `Link is invalid.`, - ` Expected: ${chalk.underline(expectedLink)}`, - ` Received: ${chalk.underline(rowLink)}`, - ); - } - - const expectedDescription = rule.meta.docs.description; - if (rowDescription !== expectedDescription) { - errors.push( - 'Description does not match the rule metadata.', - ` Expected: ${chalk.underline(expectedDescription)}`, - ` Received: ${chalk.underline(rowDescription)}`, - ); - } - - validateTableBoolean( - !!rule.meta.docs.recommended, - rowIsRecommended, - ':heavy_check_mark:', - 'recommended', - ); - - validateTableBoolean( - rule.meta.fixable !== undefined, - rowIsFixable, - ':wrench:', - 'fixable', - ); - - // quick-and-dirty check to see if it uses parserServices - // not perfect but should be good enough - const ruleFileContents = fs.readFileSync( - path.resolve(__dirname, `../../src/rules/${ruleName}.ts`), - ); - - const usesTypeInformation = ruleFileContents.includes('getParserServices'); - const tableRowHasThoughtBalloon = !!rowNeedsTypeInfo; - if (rule.meta.docs.requiresTypeChecking === true) { - if (!usesTypeInformation) { - errors.push( - 'Rule has `requiresTypeChecking` set in its meta, but it does not actually use type information - fix by removing `meta.docs.requiresTypeChecking`', - ); - } else if (!tableRowHasThoughtBalloon) { - errors.push( - 'Rule was documented as not using type information, when it actually does - fix by updating the plugin README.md', - ); - } - } else { - if (usesTypeInformation) { - errors.push( - 'Rule does not have `requiresTypeChecking` set in its meta, despite using type information - fix by setting `meta.docs.requiresTypeChecking: true` in the rule', - ); - } else if (tableRowHasThoughtBalloon) { - errors.push( - `Rule was documented as using type information, when it actually doesn't - fix by updating the plugin README.md`, - ); - } - } - - validateTableBoolean( - usesTypeInformation, - rowNeedsTypeInfo, - ':thought_balloon:', - 'requiring type information', - ); - - hasErrors = hasErrors || errors.length > 0; - logRule(errors.length === 0, ruleName, ...errors); - }); - - return hasErrors; -} - -export { validateTableRules }; diff --git a/packages/eslint-plugin/tools/validate-docs/validate-table-structure.ts b/packages/eslint-plugin/tools/validate-docs/validate-table-structure.ts deleted file mode 100644 index cd5828a8e36f..000000000000 --- a/packages/eslint-plugin/tools/validate-docs/validate-table-structure.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { TSESLint } from '@typescript-eslint/experimental-utils'; -import chalk from 'chalk'; -import marked from 'marked'; -import { logError } from '../log'; - -const RULE_LINK_REGEX = /\[`@typescript-eslint\/(.+)`\]/; -function validateTableStructure( - rules: Record>>, - rulesTable: marked.Tokens.Table, -): boolean { - const ruleNames = Object.keys(rules) - .filter(ruleName => rules[ruleName].meta.deprecated !== true) - .sort(); - let hasErrors = false; - - rulesTable.cells.forEach((row, rowIndex) => { - const match = RULE_LINK_REGEX.exec(row[0]); - if (!match) { - logError(chalk.bold(`Unable to parse link in row ${rowIndex}:`), row[0]); - hasErrors = true; - return; - } - - const rowRuleName = match[1]; - const ruleIndex = ruleNames.findIndex(ruleName => rowRuleName === ruleName); - if (ruleIndex === -1) { - logError( - chalk.bold( - `Found rule ${rowRuleName} in table, but it doesn't exist in the plugin.`, - ), - ); - hasErrors = true; - return; - } - - if (ruleIndex !== rowIndex) { - console.error( - chalk.bold.red('✗'), - chalk.bold('Sorting:'), - 'Incorrect row index for', - chalk.bold(rowRuleName), - 'expected', - ruleIndex, - 'got', - rowIndex, - ); - hasErrors = true; - return; - } - }); - - return hasErrors; -} - -export { validateTableStructure }; From 2d29d46de9a9501c5b91db25044c141b463918e7 Mon Sep 17 00:00:00 2001 From: Armano Date: Sun, 5 Jan 2020 09:02:13 +0100 Subject: [PATCH 2/4] fix: package.json --- packages/eslint-plugin/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index d190ebaf68b8..7def1f18d012 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -31,7 +31,7 @@ "scripts": { "build": "tsc -b tsconfig.build.json", "check:docs": "jest tests/docs.test.ts --runTestsByPath --silent --runInBand", - "check:configs": "jest ests/configs.test.ts --runTestsByPath --silent --runInBand", + "check:configs": "jest tests/configs.test.ts --runTestsByPath --silent --runInBand", "clean": "tsc -b tsconfig.build.json --clean", "format": "prettier --write \"./**/*.{ts,js,json,md}\" --ignore-path ../../.prettierignore", "generate:configs": "../../node_modules/.bin/ts-node --files --transpile-only tools/generate-configs.ts", From ca3f8e26d8a9c091e52ceee5361763dec03001d1 Mon Sep 17 00:00:00 2001 From: Armano Date: Tue, 7 Jan 2020 20:16:21 +0100 Subject: [PATCH 3/4] chore: update test messages --- packages/eslint-plugin/tests/configs.test.ts | 6 +++--- packages/eslint-plugin/tests/docs.test.ts | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/eslint-plugin/tests/configs.test.ts b/packages/eslint-plugin/tests/configs.test.ts index 2fd27ac4831a..3ec22f349e8c 100644 --- a/packages/eslint-plugin/tests/configs.test.ts +++ b/packages/eslint-plugin/tests/configs.test.ts @@ -27,7 +27,7 @@ describe('all.json config', () => { 'error', ]); - it('contains all @typescript-eslint/eslint-plugin rule modules, except the deprecated ones', () => { + it('contains all of the rules, excluding the deprecated ones', () => { expect(entriesToObject(ruleConfigs)).toEqual(entriesToObject(configRules)); }); }); @@ -45,7 +45,7 @@ describe('recommended.json config', () => { rule.meta.docs.recommended || 'off', ]); - it('contains all @typescript-eslint/eslint-plugin rule modules, except the deprecated ones', () => { + it("contains all recommended rules that don't require typechecking, excluding the deprecated ones", () => { expect(entriesToObject(ruleConfigs)).toEqual(entriesToObject(configRules)); }); }); @@ -65,7 +65,7 @@ describe('recommended-requiring-type-checking.json config', () => { rule.meta.docs.recommended || 'off', ]); - it('contains all @typescript-eslint/eslint-plugin rule modules, except the deprecated ones', () => { + it('contains all recommended rules that require type checking, excluding the deprecated ones', () => { expect(entriesToObject(ruleConfigs)).toEqual(entriesToObject(configRules)); }); }); diff --git a/packages/eslint-plugin/tests/docs.test.ts b/packages/eslint-plugin/tests/docs.test.ts index eff0f61cc6e1..0cdf3328ae35 100644 --- a/packages/eslint-plugin/tests/docs.test.ts +++ b/packages/eslint-plugin/tests/docs.test.ts @@ -33,7 +33,7 @@ function parseReadme(): marked.Tokens.Table { } describe('Validating rule docs', () => { - it(`check if all files are present`, () => { + it('All rules must have a corresponding rule doc', () => { const files = fs.readdirSync(docsRoot); const ruleFiles = Object.keys(rules) .map(rule => `${rule}.md`) @@ -66,7 +66,7 @@ describe('Validating rule docs', () => { describe('Validating rule metadata', () => { for (const [ruleName, rule] of rulesData) { describe(`${ruleName}`, () => { - it(`name field in rule must be correct`, () => { + it('`name` field in rule must match the filename', () => { // validate if rule name is same as url // there is no way to access this field but its used only in generation of docs url expect( @@ -74,7 +74,7 @@ describe('Validating rule metadata', () => { ).toBeTruthy(); }); - it('Expects type checking to be set for rule', () => { + it('`requiresTypeChecking` should be set if the rule uses type information', () => { // quick-and-dirty check to see if it uses parserServices // not perfect but should be good enough const ruleFileContents = fs.readFileSync( @@ -95,7 +95,7 @@ describe('Validating README.md', () => { ([, rule]) => rule.meta.deprecated !== true, ); - it('Checking table structure...', () => { + it('All non-deprecated rules should have a row in the table, and the table should be ordered alphabetically', () => { const ruleNames = notDeprecated .map(([ruleName]) => ruleName) .sort() @@ -109,27 +109,27 @@ describe('Validating README.md', () => { const ruleRow = rulesTable.find(row => row[0].includes(`/${ruleName}.md`)) ?? []; - it(`Link column should be set correctly`, () => { + it('Link column should be correct', () => { expect(ruleRow[0]).toEqual(createRuleLink(ruleName)); }); - it(`Description column should be set correctly`, () => { + it('Description column should be correct', () => { expect(ruleRow[1]).toEqual(rule.meta.docs.description); }); - it(`Recommended column should be set correctly`, () => { + it('Recommended column should be correct', () => { expect(ruleRow[2]).toEqual( rule.meta.docs.recommended ? ':heavy_check_mark:' : '', ); }); - it(`Fixable column should be set correctly`, () => { + it('Fixable column should be correct', () => { expect(ruleRow[3]).toEqual( rule.meta.fixable !== undefined ? ':wrench:' : '', ); }); - it(`Requiring type information column should be set correctly`, () => { + it('Requiring type information column should be correct', () => { expect(ruleRow[4]).toEqual( rule.meta.docs.requiresTypeChecking === true ? ':thought_balloon:' From c584f72eca28a8da5dd4d07b871a033d9393a265 Mon Sep 17 00:00:00 2001 From: Armano Date: Tue, 7 Jan 2020 20:17:06 +0100 Subject: [PATCH 4/4] fix: use correct typing for readme.find --- packages/eslint-plugin/tests/docs.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/tests/docs.test.ts b/packages/eslint-plugin/tests/docs.test.ts index 0cdf3328ae35..d85dc8fb204e 100644 --- a/packages/eslint-plugin/tests/docs.test.ts +++ b/packages/eslint-plugin/tests/docs.test.ts @@ -23,8 +23,8 @@ function parseReadme(): marked.Tokens.Table { // find the table const rulesTable = readme.find( - token => token.type === 'table', - ) as marked.Tokens.Table; + (token): token is marked.Tokens.Table => token.type === 'table', + ); if (!rulesTable) { throw Error('Could not find the rules table in README.md'); }