From 318f4826052ac8188e6bc04e94232a923cac5228 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Nevyho=C5=A1t=C4=9Bn=C3=BD?= Date: Sun, 28 Oct 2018 14:36:46 +0100 Subject: [PATCH] chore: test docs check script (#368) * chore: Add checking script for tests and docs * chore: Fix issues discovered by tests-docs-check * chore: Add tests-docs-checker to pre-commit hook * docs: generate docs * docs: Mention tests-docs-check in contributing guide --- .README/rules/no-flow-fix-me-comments.md | 2 +- .README/rules/require-types-at-top.md | 2 +- .README/rules/valid-syntax.md | 2 + CONTRIBUTING.md | 1 + README.md | 109 +++++++++++++- bin/testsAndDocsCheck.js | 174 +++++++++++++++++++++++ package.json | 3 +- tests/rules/index.js | 1 + 8 files changed, 289 insertions(+), 5 deletions(-) create mode 100644 bin/testsAndDocsCheck.js diff --git a/.README/rules/no-flow-fix-me-comments.md b/.README/rules/no-flow-fix-me-comments.md index afeef1db..534b2762 100644 --- a/.README/rules/no-flow-fix-me-comments.md +++ b/.README/rules/no-flow-fix-me-comments.md @@ -19,4 +19,4 @@ This rule takes an optional RegExp that comments a text RegExp that makes the su } ``` - + diff --git a/.README/rules/require-types-at-top.md b/.README/rules/require-types-at-top.md index 19a48141..c680d64c 100644 --- a/.README/rules/require-types-at-top.md +++ b/.README/rules/require-types-at-top.md @@ -11,4 +11,4 @@ The rule has a string option: The default value is `"always"`. - + diff --git a/.README/rules/valid-syntax.md b/.README/rules/valid-syntax.md index a0126e23..5701d81e 100644 --- a/.README/rules/valid-syntax.md +++ b/.README/rules/valid-syntax.md @@ -3,3 +3,5 @@ **Deprecated** Babylon (the Babel parser) v6.10.0 fixes parsing of the invalid syntax this plugin warned against. Checks for simple Flow syntax errors. + + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9c72ff4d..b22dc054 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,6 +6,7 @@ When making a commit, the following Pre-Commit hooks run: +* tests and docs checks * tests * lint * commit message validation (see "Commit Messages" below) diff --git a/README.md b/README.md index 882ad6a1..2f152e53 100644 --- a/README.md +++ b/README.md @@ -1308,7 +1308,44 @@ This rule takes an optional RegExp that comments a text RegExp that makes the su } ``` - +The following patterns are considered problems: + +```js +// $FlowFixMe I am doing something evil here +const text = 'HELLO'; +// Message: $FlowFixMe is treated as `any` and should be fixed. + +// Options: ["TODO [0-9]+"] +// $FlowFixMe I am doing something evil here +const text = 'HELLO'; +// Message: $FlowFixMe is treated as `any` and should be fixed. Fix it or match `/TODO [0-9]+/`. + +// Options: ["TODO [0-9]+"] +// $FlowFixMe TODO abc 47 I am doing something evil here +const text = 'HELLO'; +// Message: $FlowFixMe is treated as `any` and should be fixed. Fix it or match `/TODO [0-9]+/`. + +// $$FlowFixMeProps I am doing something evil here +const text = 'HELLO'; +// Message: $FlowFixMe is treated as `any` and should be fixed. + +// Options: ["TODO [0-9]+"] +// $FlowFixMeProps I am doing something evil here +const text = 'HELLO'; +// Message: $FlowFixMe is treated as `any` and should be fixed. Fix it or match `/TODO [0-9]+/`. +``` + +The following patterns are not considered problems: + +```js +const text = 'HELLO'; + +// Options: ["TODO [0-9]+"] +// $FlowFixMe TODO 48 +const text = 'HELLO'; +``` + + ### no-mutable-array @@ -2494,7 +2531,65 @@ The rule has a string option: The default value is `"always"`. - +The following patterns are considered problems: + +```js +const foo = 3; +type Foo = number; +// Message: All type declaration should be at the top of the file, after any import declarations. + +const foo = 3; +opaque type Foo = number; +// Message: All type declaration should be at the top of the file, after any import declarations. + +const foo = 3; +export type Foo = number; +// Message: All type declaration should be at the top of the file, after any import declarations. + +const foo = 3; +export opaque type Foo = number; +// Message: All type declaration should be at the top of the file, after any import declarations. + +const foo = 3; +type Foo = number | string; +// Message: All type declaration should be at the top of the file, after any import declarations. + +import bar from "./bar"; +const foo = 3; +type Foo = number; +// Message: All type declaration should be at the top of the file, after any import declarations. +``` + +The following patterns are not considered problems: + +```js +type Foo = number; +const foo = 3; + +opaque type Foo = number; +const foo = 3; + +export type Foo = number; +const foo = 3; + +export opaque type Foo = number; +const foo = 3; + +type Foo = number; +const foo = 3; + +import bar from "./bar"; +type Foo = number; + +type Foo = number; +import bar from "./bar"; + +// Options: ["never"] +const foo = 3; +type Foo = number; +``` + + ### require-valid-file-annotation @@ -4658,3 +4753,13 @@ declare var A: Y Checks for simple Flow syntax errors. +The following patterns are not considered problems: + +```js +function x(foo: string = "1") {} + +function x(foo: Type = bar()) {} +``` + + + diff --git a/bin/testsAndDocsCheck.js b/bin/testsAndDocsCheck.js new file mode 100644 index 00000000..582149d9 --- /dev/null +++ b/bin/testsAndDocsCheck.js @@ -0,0 +1,174 @@ +/** + * This script checks if there is test suite and documentation for every rule and if they are correctly included in corresponding "index" files. + * + * Performed checks: + * + * - tests + * - file `/tests/rules/assertions/.js` exists + * - rule is included in `reportingRules` variable in `/tests/rules/index.js` + * + * - docs + * - file `/.README/rules/.md` exists + * - file `/.README/rules/.md` contains correct assertions placeholder (``) + * - rule is included in gitdown directive in `/.README/README.md` + * - rules in `/.README/README.md` are alphabetically sorted + */ + +import path from 'path'; +import fs from 'fs'; +import glob from 'glob'; +import _ from 'lodash'; + +// returns Array<[camelCase, kebab-case]> +const getRules = () => { + const rulesFiles = glob.sync(path.resolve(__dirname, '../src/rules/*.js')); + + const rulesNames = rulesFiles.map((file) => { + return path.basename(file, '.js'); + }).map((name) => { + return [name, _.kebabCase(name)]; + }); + + return rulesNames; +}; + +const isFile = (filepath) => { + try { + return fs.statSync(filepath).isFile(); + } catch (error) { + return false; + } +}; + +const windows = (array, size) => { + const output = []; + + for (let ii = 0; ii < array.length - size + 1; ii++) { + output.push(array.slice(ii, ii + size)); + } + + return output; +}; + +const getTestIndexRules = () => { + const content = fs.readFileSync(path.resolve(__dirname, '../tests/rules/index.js'), 'utf-8'); + + const result = content.split('\n').reduce((acc, line) => { + if (acc.inRulesArray) { + if (line === '];') { + acc.inRulesArray = false; + } else { + acc.rules.push(line.replace(/^\s*'([^']+)',?$/, '$1')); + } + } else if (line === 'const reportingRules = [') { + acc.inRulesArray = true; + } + + return acc; + }, { + inRulesArray: false, + rules: [] + }); + + const rules = result.rules; + + if (rules.length === 0) { + throw new Error('Tests checker is broken - it could not extract rules from test index file.'); + } + + return rules; +}; + +const getDocIndexRules = () => { + const content = fs.readFileSync(path.resolve(__dirname, '../.README/README.md'), 'utf-8'); + + const rules = content.split('\n').map((line) => { + const match = /^{"gitdown": "include", "file": "([^"]+)"}$/.exec(line); + + if (match === null) { + return null; + } else { + return match[1].replace('./rules/', '').replace('.md', ''); + } + }).filter((rule) => { + return rule !== null; + }); + + if (rules.length === 0) { + throw new Error('Docs checker is broken - it could not extract rules from docs index file.'); + } + + return rules; +}; + +const hasCorrectAssertions = (docPath, name) => { + const content = fs.readFileSync(docPath, 'utf-8'); + + const match = //.exec(content); + + if (match === null) { + return false; + } else { + return match[1] === name; + } +}; + +const checkTests = (rulesNames) => { + const testIndexRules = getTestIndexRules(); + + const invalid = rulesNames.filter((names) => { + const testExists = isFile(path.resolve(__dirname, '../tests/rules/assertions', names[0] + '.js')); + const inIndex = testIndexRules.indexOf(names[1]) !== -1; + + return !(testExists && inIndex); + }); + + if (invalid.length > 0) { + const invalidList = invalid.map((names) => { + return names[0]; + }).join(', '); + + throw new Error( + 'Tests checker encountered an error in: ' + invalidList + '. ' + + 'Make sure that for every rule you created test suite and included the rule name in `tests/rules/index.js` file.' + ); + } +}; + +const checkDocs = (rulesNames) => { + const docIndexRules = getDocIndexRules(); + + const sorted = windows(docIndexRules, 2).every((chunk) => { + return chunk[0] < chunk[1]; + }); + + if (!sorted) { + throw new Error('Rules are not alphabetically sorted in `.README/README.md` file.'); + } + + const invalid = rulesNames.filter((names) => { + const docPath = path.resolve(__dirname, '../.README/rules', names[1] + '.md'); + const docExists = isFile(docPath); + const inIndex = docIndexRules.indexOf(names[1]) !== -1; + const hasAssertions = docExists ? hasCorrectAssertions(docPath, names[0]) : false; + + return !(docExists && inIndex && hasAssertions); + }); + + if (invalid.length > 0) { + const invalidList = invalid.map((names) => { + return names[0]; + }).join(', '); + + throw new Error( + 'Docs checker encountered an error in: ' + invalidList + '. ' + + 'Make sure that for every rule you created documentation file with assertions placeholder in camelCase ' + + 'and included the file path in `.README/README.md` file.' + ); + } +}; + +const rulesList = getRules(); + +checkTests(rulesList); +checkDocs(rulesList); diff --git a/package.json b/package.json index 26cf7e31..ee3c84c6 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "husky": { "hooks": { "post-commit": "npm run create-readme && git add README.md && git commit -m 'docs: generate docs' --no-verify", - "pre-commit": "npm run lint && npm run test && npm run build && npm run format-json" + "pre-commit": "npm run tests-docs-check && npm run lint && npm run test && npm run build && npm run format-json" } }, "repository": { @@ -55,6 +55,7 @@ "create-readme": "gitdown ./.README/README.md --output-file ./README.md && npm run documentation-add-assertions", "documentation-add-assertions": "babel-node ./bin/readmeAssertions", "format-json": "jsonlint --sort-keys --in-place --indent ' ' ./src/configs/recommended.json && echo '' >> ./src/configs/recommended.json", + "tests-docs-check": "babel-node ./bin/testsAndDocsCheck", "lint": "eslint ./src ./tests", "test": "mocha --compilers js:babel-register ./tests/rules/index.js" }, diff --git a/tests/rules/index.js b/tests/rules/index.js index 27271e06..b0b3a799 100644 --- a/tests/rules/index.js +++ b/tests/rules/index.js @@ -19,6 +19,7 @@ const reportingRules = [ 'generic-spacing', 'newline-after-flow-annotation', 'no-dupe-keys', + 'no-existential-type', 'no-flow-fix-me-comments', 'no-mutable-array', 'no-primitive-constructor-types',