-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
21 changed files
with
10,231 additions
and
3,369 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# unconventional js | ||
/vendor/ | ||
|
||
# compiled output | ||
/dist/ | ||
/tmp/ | ||
|
||
# dependencies | ||
/bower_components/ | ||
/node_modules/ | ||
|
||
# misc | ||
/coverage/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
* text=auto eol=lf |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node | ||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions | ||
|
||
name: CI | ||
|
||
on: | ||
push: | ||
branches: [ main ] | ||
pull_request: | ||
branches: [ main ] | ||
|
||
jobs: | ||
build: | ||
|
||
runs-on: ${{ matrix.os }}-latest | ||
|
||
strategy: | ||
matrix: | ||
os: [ ubuntu, windows ] | ||
node-version: [14.x, 16.x, 18.x] | ||
|
||
steps: | ||
- uses: actions/checkout@v3 | ||
|
||
- name: Use Node.js version ${{ matrix.node-version }} | ||
uses: actions/setup-node@v3 | ||
with: | ||
node-version: ${{ matrix.node-version }} | ||
cache: 'npm' | ||
|
||
- name: Install dependencies | ||
run: npm ci | ||
|
||
- name: Run linters | ||
run: npm run lint | ||
|
||
- name: Run tests | ||
run: npm test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,102 @@ | ||
# eslint-doc-generator | ||
|
||
[![npm version][npm-image]][npm-url] | ||
|
||
Generates the following documentation: | ||
|
||
- README rules table | ||
- Rule doc titles and notices | ||
|
||
Also performs some basic section consistency checks on rule docs (will eventually be configurable): | ||
|
||
- Contains an `## Options` section and mentions each named option (for rules with options) | ||
|
||
## Setup | ||
|
||
Install it: | ||
|
||
```sh | ||
npm run --save-dev eslint-doc-generator | ||
``` | ||
|
||
Add it as as script in `package.json` (included as a lint script to demonstrate how we can ensure it passes on CI along with other linting): | ||
|
||
```json | ||
{ | ||
"scripts": { | ||
"lint": "npm-run-all \"lint:*\"", | ||
"lint:docs": "markdownlint \"**/*.md\"", | ||
"lint:eslint-docs": "npm-run-all update:docs && git diff --exit-code", | ||
"lint:js": "eslint .", | ||
"update:eslint-docs": "eslint-doc-generator" | ||
} | ||
} | ||
``` | ||
|
||
Add the rule list markers in your `README.md` rules section: | ||
|
||
```md | ||
<!-- begin rules list --> | ||
<!-- end rules list --> | ||
``` | ||
|
||
The new title and notices will be added to the top of each rule doc, but you may need to manually remove the old ones. | ||
|
||
## Usage | ||
|
||
```sh | ||
npm run update:eslint-docs | ||
``` | ||
|
||
[npm-image]: https://badge.fury.io/js/eslint-doc-generator.svg | ||
[npm-url]: https://www.npmjs.com/package/eslint-doc-generator | ||
|
||
## Example | ||
|
||
Generated content in a rule doc: | ||
|
||
```md | ||
# Disallow use of `foo` (`no-foo`) | ||
|
||
💼 This rule is enabled in the following configs: `all`, `recommended`. | ||
|
||
🔧 This rule is automatically fixable using the `--fix` [option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) on the command line. | ||
|
||
💡 This rule provides [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions) that can be applied manually. | ||
|
||
❌ This rule is deprecated. It was replaced by [some-new-rule](some-new-rule.md). | ||
|
||
<!-- end rule header --> | ||
|
||
... | ||
``` | ||
|
||
Generated rules table in `README.md`: | ||
|
||
```md | ||
# eslint-plugin-test | ||
|
||
## Rules | ||
|
||
✅: Enabled in the `recommended` configuration.\ | ||
🔧: Fixable with [`eslint --fix`](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems).\ | ||
💡: Provides editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).\ | ||
💭: Requires type information.\ | ||
❌: This rule is deprecated. | ||
|
||
<!-- begin rules list --> | ||
|
||
| Rule | Description | 💼 | 🔧 | 💡 | 💭 | | ||
| -------------------------------------------------------------- | ------------------------------------------------- | ------------- | --- | --- | --- | | ||
| [max-nested-describe](docs/rules/max-nested-describe.md) | Enforces a maximum depth to nested describe calls | | | | | | ||
| [no-alias-methods](docs/rules/no-alias-methods.md) | Disallow alias methods | ✅ ![style][] | 🔧 | | | | ||
| [no-commented-out-tests](docs/rules/no-commented-out-tests.md) | Disallow commented out tests | ✅ | | | | | ||
|
||
<!-- end rules list --> | ||
|
||
... | ||
|
||
<!-- define the badge for any custom configs (besides `recommended`, `all`) here --> | ||
|
||
[style]: https://img.shields.io/badge/-style-blue.svg | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { run } from '../lib/cli.js'; | ||
|
||
try { | ||
run(); | ||
} catch (error) { | ||
if (error instanceof Error) { | ||
console.error(error.message); | ||
} | ||
process.exitCode = 1; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// https://kulshekhar.github.io/ts-jest/docs/guides/esm-support/ | ||
|
||
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ | ||
module.exports = { | ||
preset: 'ts-jest/presets/default-esm', | ||
testEnvironment: 'node', | ||
testMatch: ['<rootDir>/test/**/*-test.ts'], | ||
globals: { | ||
'ts-jest': { | ||
useESM: true, | ||
}, | ||
}, | ||
moduleNameMapper: { | ||
'^(\\.{1,2}/.*)\\.js$': '$1', | ||
'#(.*)': '<rootDir>/node_modules/$1', | ||
}, | ||
coveragePathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/test/'], | ||
coverageThreshold: { | ||
global: { | ||
branches: 100, | ||
functions: 96.77, // TODO: Should be 100% but unclear what function is missing coverage. | ||
lines: 100, | ||
statements: 100, | ||
}, | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import { Command, Argument } from 'commander'; | ||
import { join, dirname } from 'node:path'; | ||
import { fileURLToPath } from 'node:url'; | ||
import { readFileSync } from 'node:fs'; | ||
import { generate } from './generator.js'; | ||
import type { PackageJson } from 'type-fest'; | ||
|
||
const __dirname = dirname(fileURLToPath(import.meta.url)); | ||
|
||
function getCurrentPackageVersion(): string { | ||
const packageJson: PackageJson = JSON.parse( | ||
readFileSync(join(__dirname, '..', '..', 'package.json'), 'utf8') // Relative to compiled version of this file in the dist folder. | ||
); | ||
if (!packageJson.version) { | ||
throw new Error('Could not find package.json `version`.'); | ||
} | ||
return packageJson.version; | ||
} | ||
|
||
export function run() { | ||
const program = new Command(); | ||
|
||
program | ||
.version(getCurrentPackageVersion()) | ||
.addArgument( | ||
new Argument('[path]', 'path to ESLint plugin root').default('.') | ||
) | ||
.action(async function (path) { | ||
await generate(path); | ||
}) | ||
.parse(process.argv); | ||
|
||
return program; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
import { readFileSync, writeFileSync } from 'node:fs'; | ||
import { join, resolve } from 'node:path'; | ||
import prettier from 'prettier'; // eslint-disable-line node/no-extraneous-import -- prettier is included by eslint-plugin-square | ||
import { getAllNamedOptions, hasOptions } from './rule-options.js'; | ||
import { | ||
loadPlugin, | ||
getPluginPrefix, | ||
getPluginPrettierConfig, | ||
} from './package-json.js'; | ||
import { updateRulesList } from './rule-list.js'; | ||
import { generateRuleHeaderLines } from './rule-notices.js'; | ||
import { END_RULE_HEADER_MARKER } from './markers.js'; | ||
import type { RuleModule, RuleDetails } from './types.js'; | ||
|
||
function format(str: string, pluginPath: string): string { | ||
return prettier.format(str, { | ||
...getPluginPrettierConfig(pluginPath), | ||
parser: 'markdown', | ||
}); | ||
} | ||
|
||
/** | ||
* Replace the header of a doc up to and including the specified marker. | ||
* Insert at beginning if header doesn't exist. | ||
* @param lines - lines of doc | ||
* @param newHeaderLines - lines of new header including marker | ||
* @param marker - marker to indicate end of header | ||
*/ | ||
function replaceOrCreateHeader( | ||
lines: string[], | ||
newHeaderLines: string[], | ||
marker: string | ||
) { | ||
const markerLineIndex = lines.indexOf(marker); | ||
|
||
// Replace header section (or create at top if missing). | ||
lines.splice(0, markerLineIndex + 1, ...newHeaderLines); | ||
} | ||
|
||
/** | ||
* Ensure a rule doc contains (or doesn't contain) some particular content. | ||
* Upon failure, output the failure and set a failure exit code. | ||
* @param ruleName - which rule we are checking | ||
* @param contents - the rule doc's contents | ||
* @param content - the content we are checking for | ||
* @param expected - whether the content should be present or not present | ||
*/ | ||
function expectContent( | ||
ruleName: string, | ||
contents: string, | ||
content: string, | ||
expected: boolean | ||
) { | ||
if (contents.includes(content) !== expected) { | ||
console.error( | ||
`\`${ruleName}\` rule doc should ${ | ||
expected ? '' : 'not ' | ||
}have included: ${content}` | ||
); | ||
process.exitCode = 1; | ||
} | ||
} | ||
|
||
export async function generate(path: string) { | ||
const plugin = await loadPlugin(path); | ||
const pluginPrefix = getPluginPrefix(path); | ||
|
||
const pathTo = { | ||
readme: resolve(path, 'README.md'), | ||
rules: resolve(path, 'src', 'rules'), | ||
docs: resolve(path, 'docs'), | ||
}; | ||
|
||
// Gather details about rules. | ||
const details: RuleDetails[] = Object.entries(plugin.rules) | ||
.filter((nameAndRule): nameAndRule is [string, Required<RuleModule>] => | ||
Boolean(nameAndRule[1].meta) | ||
) | ||
.map( | ||
([name, rule]): RuleDetails => ({ | ||
name, | ||
description: rule.meta.docs.description, | ||
fixable: rule.meta.fixable | ||
? ['code', 'whitespace'].includes(rule.meta.fixable) | ||
: false, | ||
hasSuggestions: rule.meta.hasSuggestions ?? false, | ||
requiresTypeChecking: rule.meta.docs.requiresTypeChecking ?? false, | ||
deprecated: rule.meta.deprecated ?? false, | ||
schema: rule.meta.schema, | ||
}) | ||
); | ||
|
||
// Update rule doc for each rule. | ||
for (const { name, description, schema } of details) { | ||
const pathToDoc = join(pathTo.docs, 'rules', `${name}.md`); | ||
const contents = readFileSync(pathToDoc).toString(); | ||
const lines = contents.split('\n'); | ||
|
||
// Regenerate the header (title/notices) of each rule doc. | ||
const newHeaderLines = generateRuleHeaderLines( | ||
description, | ||
name, | ||
plugin, | ||
pluginPrefix | ||
); | ||
|
||
replaceOrCreateHeader(lines, newHeaderLines, END_RULE_HEADER_MARKER); | ||
|
||
writeFileSync(pathToDoc, format(lines.join('\n'), path)); | ||
|
||
// Check for potential issues with the rule doc. | ||
|
||
// "Options" section. | ||
expectContent(name, contents, '## Options', hasOptions(schema)); | ||
for (const namedOption of getAllNamedOptions(schema)) { | ||
expectContent(name, contents, namedOption, true); // Each rule option is mentioned. | ||
} | ||
} | ||
|
||
// Update the rules list in the README. | ||
let readme = readFileSync(pathTo.readme, 'utf8'); | ||
readme = updateRulesList(details, readme, plugin, pluginPrefix); | ||
writeFileSync(pathTo.readme, format(readme, path), 'utf8'); | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
// Markers so that the README rules list can be automatically updated. | ||
export const BEGIN_RULE_LIST_MARKER = '<!-- begin rules list -->'; | ||
export const END_RULE_LIST_MARKER = '<!-- end rules list -->'; | ||
|
||
// Marker so that rule doc header (title/notices) can be automatically updated. | ||
export const END_RULE_HEADER_MARKER = '<!-- end rule header -->'; |
Oops, something went wrong.