Skip to content
Permalink
Browse files

test(eslint-plugin): migrate validation tools to jest test cases (#1403)

  • Loading branch information
armano2 authored and bradzacher committed Jan 7, 2020
1 parent f7ad716 commit 40d91270449742ced130cbeaeaba32c68fd39178
@@ -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'
@@ -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 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",
@@ -0,0 +1,71 @@
import rules from '../src/rules';
import plugin from '../src/index';

function entriesToObject<T = unknown>(value: [string, T][]): Record<string, T> {
return value.reduce<Record<string, T>>((accum, [k, v]) => {
accum[k] = v;
return accum;
}, {});
}

const notDeprecatedRules = Object.entries(rules).filter(
([, rule]) => !rule.meta.deprecated,
);

function filterRules(values: Record<string, string>): [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 of the rules, excluding 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 recommended rules that don't require typechecking, excluding 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 recommended rules that require type checking, excluding the deprecated ones', () => {
expect(entriesToObject(ruleConfigs)).toEqual(entriesToObject(configRules));
});
});

This file was deleted.

@@ -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 is marked.Tokens.Table => token.type === 'table',
);
if (!rulesTable) {
throw Error('Could not find the rules table in README.md');
}

return rulesTable;
}

describe('Validating rule docs', () => {
it('All rules must have a corresponding rule doc', () => {
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 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(
rule.meta.docs.url.endsWith(`rules/${ruleName}.md`),
).toBeTruthy();
});

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(
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('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()
.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 correct', () => {
expect(ruleRow[0]).toEqual(createRuleLink(ruleName));
});

it('Description column should be correct', () => {
expect(ruleRow[1]).toEqual(rule.meta.docs.description);
});

it('Recommended column should be correct', () => {
expect(ruleRow[2]).toEqual(
rule.meta.docs.recommended ? ':heavy_check_mark:' : '',
);
});

it('Fixable column should be correct', () => {
expect(ruleRow[3]).toEqual(
rule.meta.fixable !== undefined ? ':wrench:' : '',
);
});

it('Requiring type information column should be correct', () => {
expect(ruleRow[4]).toEqual(
rule.meta.docs.requiresTypeChecking === true
? ':thought_balloon:'
: '',
);
});
});
}
});

This file was deleted.

This file was deleted.

0 comments on commit 40d9127

Please sign in to comment.
You can’t perform that action at this time.