Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(eslint6): Add new plugin for eslint6
- Loading branch information
Showing
6 changed files
with
427 additions
and
26 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,42 @@ | ||
# spire-plugin-eslint6 | ||
|
||
[ESLint](https://eslint.org/) version 6 plugin for | ||
[Spire](https://github.com/researchgate/spire). | ||
|
||
<!-- START doctoc generated TOC please keep comment here to allow auto update --> | ||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> | ||
|
||
- [Hooks](#hooks) | ||
- [Options](#options) | ||
|
||
<!-- END doctoc generated TOC please keep comment here to allow auto update --> | ||
|
||
## Hooks | ||
|
||
- `setup` Adds `lint` and prepares eslint arguments. | ||
- `precommit` Adds eslint linter. | ||
- `run` Runs eslint. | ||
|
||
## Options | ||
|
||
- Plugin `['spire-plugin-eslint', options]` | ||
|
||
- `command` \<string\> Command name to run eslint on. Defaults to `lint`. | ||
- `eslintConfig` \<string\> Default [eslint] configuration. Defaults to | ||
[`./config.js`](./config.js). | ||
- `autosetEslintConfig` \<boolean\> Decides if the plugin should automatically | ||
create an `.eslintrc.js ` file. It will only create the file if there isn't | ||
already a config present. Defaults to `true`. | ||
- `allowCustomConfig` \<boolean\> Whether to allow user-provided config. If | ||
this option is `false` and there's custom eslint config found it will throw | ||
an error. Defaults to `true`. | ||
- `eslintIgnore` \<string\> Path to default `.eslintignore`. Defaults to | ||
`.gitignore`. | ||
- `allowCustomIgnore` \<boolean\> Whether to allow user-provided | ||
`.eslintignore`. If this option is `false` and there's custom ignore file | ||
found it will throw an error. Defaults to `true`. | ||
- `fileExtensions` \<string[]\> Extension of files eslint should scan. | ||
Defaults to `['.js', '.jsx', '.mjs', '.ts', '.tsx']`. | ||
|
||
- CLI `npx spire lint [args]` | ||
- Passes all arguments as-is to eslint. |
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,112 @@ | ||
const { createFixture } = require('spire-test-utils'); | ||
const { stat, readFile } = require('fs-extra'); | ||
const { join } = require('path'); | ||
|
||
const configWithEslintPlugin = (options = {}) => | ||
JSON.stringify({ | ||
name: 'spire-plugin-eslint6-test', | ||
spire: { | ||
plugins: [[require.resolve('spire-plugin-eslint6'), options]], | ||
}, | ||
}); | ||
|
||
describe('spire-plugin-eslint6', () => { | ||
test('adds lint command', async () => { | ||
const fixture = await createFixture({ | ||
'package.json': configWithEslintPlugin(), | ||
}); | ||
await expect(fixture.run('spire', ['--help'])).resolves.toMatchObject({ | ||
stdout: /Commands:\s+spire lint/, | ||
}); | ||
await fixture.clean(); | ||
}); | ||
|
||
test('adds eslint linter', async () => { | ||
const fixture = await createFixture({ | ||
'package.json': configWithEslintPlugin(), | ||
}); | ||
const { stdout } = await fixture.run('spire', [ | ||
'hook', | ||
'precommit', | ||
'--debug', | ||
]); | ||
expect(stdout).toMatch(/Using linters:/); | ||
expect(stdout).toMatch(/\*\.\(js\|jsx\|mjs\|ts\|tsx\)/); | ||
await fixture.clean(); | ||
}); | ||
|
||
test('passes custom arguments to eslint', async () => { | ||
const fixture = await createFixture({ | ||
'package.json': configWithEslintPlugin(), | ||
}); | ||
await expect( | ||
fixture.run('spire', ['lint', '--version']) | ||
).resolves.toMatchObject({ | ||
stdout: /v\d\.\d\.\d/, | ||
}); | ||
await fixture.clean(); | ||
}); | ||
|
||
test('creates default eslint config for editors', async () => { | ||
const fixture = await createFixture({ | ||
'package.json': configWithEslintPlugin(), | ||
}); | ||
await fixture.run('spire', ['hook', 'postinstall']); | ||
const eslintConfig = join(fixture.cwd, '.eslintrc.js'); | ||
expect(await stat(eslintConfig)).toBeTruthy(); | ||
expect(await readFile(eslintConfig, 'UTF-8')).toMatch( | ||
/spire-plugin-eslint6\/config/ | ||
); | ||
await fixture.clean(); | ||
}); | ||
|
||
test('prints deprecation warning for glob option', async () => { | ||
const fixture = await createFixture({ | ||
'package.json': configWithEslintPlugin({ glob: 'abc' }), | ||
}); | ||
await fixture.run('spire', ['hook', 'precommit']); | ||
|
||
await expect( | ||
fixture.run('spire', ['hook', 'precommit']) | ||
).resolves.toMatchObject({ | ||
stdout: /The glob option is deprecated\. Use the option `fileExtensions` instead\./, | ||
}); | ||
await fixture.clean(); | ||
}); | ||
|
||
test('creates custom eslint config for editors', async () => { | ||
const fixture = await createFixture({ | ||
'node_modules/eslint6-config-cool-test/package.json': JSON.stringify({ | ||
name: 'eslint6-config-cool-test', | ||
version: '1.0.0', | ||
main: 'index.js', | ||
}), | ||
'node_modules/eslint6-config-cool-test/index.js': 'module.exports = {};', | ||
'package.json': configWithEslintPlugin({ | ||
config: 'eslint6-config-cool-test', | ||
}), | ||
}); | ||
await fixture.run('spire', ['hook', 'postinstall']); | ||
const eslintConfig = join(fixture.cwd, '.eslintrc.js'); | ||
expect(await stat(eslintConfig)).toBeTruthy(); | ||
expect(await readFile(eslintConfig, 'UTF-8')).toMatch( | ||
/eslint6-config-cool-test/ | ||
); | ||
await fixture.clean(); | ||
}); | ||
|
||
test('warns about custom eslint config not extending default config', async () => { | ||
const fixture = await createFixture({ | ||
'package.json': configWithEslintPlugin(), | ||
'.eslintrc.json': '', | ||
}); | ||
await expect( | ||
fixture.run('spire', ['hook', 'postinstall']) | ||
).resolves.toMatchObject({ | ||
stdout: expect.stringMatching( | ||
'Attempted to set ESLint config but it already exists. Please ensure existing config re-exports' | ||
), | ||
}); | ||
await fixture.clean(); | ||
}); | ||
}); |
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,19 @@ | ||
module.exports = { | ||
parserOptions: { | ||
ecmaVersion: '2020', | ||
sourceType: 'script', | ||
}, | ||
extends: ['eslint:recommended', 'plugin:prettier/recommended'], | ||
env: { | ||
es6: true, | ||
node: true, | ||
}, | ||
overrides: [ | ||
{ | ||
files: ['**.spec.js', '**.test.js', '**/__tests__/**.js'], | ||
env: { | ||
jest: true, | ||
}, | ||
}, | ||
], | ||
}; |
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 @@ | ||
const execa = require('execa'); | ||
const SpireError = require('spire/error'); | ||
|
||
const SUPPORTED_CONFIG_FILES = [ | ||
'.eslintrc', | ||
'.eslintrc.js', | ||
'.eslintrc.json', | ||
'.eslintrc.yaml', | ||
'.eslintrc.yml', | ||
]; | ||
|
||
function eslint( | ||
{ setState, getState, hasFile, readFile, writeFile, hasPackageProp }, | ||
{ | ||
command = 'lint', | ||
config: defaultEslintConfig = 'spire-plugin-eslint6/config', | ||
autosetEslintConfig = true, | ||
allowCustomConfig = true, | ||
eslintIgnore: defaultEslintIgnore = '.gitignore', | ||
allowCustomIgnore = true, | ||
fileExtensions = ['.js', '.jsx', '.mjs', '.ts', '.tsx'], | ||
} | ||
) { | ||
async function hasCustomEslintConfig() { | ||
for (const file of SUPPORTED_CONFIG_FILES) { | ||
if (await hasFile(file)) { | ||
return file; | ||
} | ||
} | ||
|
||
return hasPackageProp('eslintConfig'); | ||
} | ||
return { | ||
name: 'spire-plugin-eslint6', | ||
command, | ||
description: 'lint files with ESLint', | ||
async postinstall({ logger }) { | ||
if (autosetEslintConfig) { | ||
const hasCustomConfig = await hasCustomEslintConfig(); | ||
if (hasCustomConfig && typeof hasCustomConfig === 'string') { | ||
const currentContent = await readFile(hasCustomConfig, 'UTF-8'); | ||
if (!currentContent.includes(defaultEslintConfig)) { | ||
return logger.warn( | ||
'Attempted to set ESLint config but it already exists. ' + | ||
'Please ensure existing config re-exports `%s`.', | ||
defaultEslintConfig | ||
); | ||
} | ||
} | ||
if (!hasCustomConfig) { | ||
await writeFile( | ||
'.eslintrc.js', | ||
"'use strict';\n" + | ||
'// This file was created by spire-plugin-eslint for editor support\n' + | ||
`module.exports = require('${defaultEslintConfig}');\n` | ||
); | ||
} | ||
} | ||
}, | ||
async setup({ argv, resolve }) { | ||
const hasCustomConfig = | ||
argv.includes('--config') || (await hasCustomEslintConfig()); | ||
const eslintConfig = | ||
allowCustomConfig && hasCustomConfig | ||
? [] | ||
: ['--config', resolve(defaultEslintConfig)]; | ||
const hasCustomIgnore = | ||
argv.includes('--ignore-path') || | ||
(await hasFile('.eslintignore')) || | ||
(await hasPackageProp('eslintIgnore')); | ||
const eslintIgnore = | ||
allowCustomIgnore && hasCustomIgnore | ||
? [] | ||
: (await hasFile(defaultEslintIgnore)) | ||
? ['--ignore-path', defaultEslintIgnore] | ||
: []; | ||
const eslintExtensions = ['--ext', fileExtensions.join(',')]; | ||
setState({ | ||
eslintArgs: [...eslintConfig, ...eslintIgnore, ...eslintExtensions], | ||
}); | ||
// Inform user about disallowed overrides | ||
if (hasCustomConfig && !allowCustomConfig) { | ||
throw new SpireError( | ||
`Custom eslint config is not allowed, using ${defaultEslintConfig} instead` | ||
); | ||
} | ||
if (hasCustomIgnore && !allowCustomIgnore) { | ||
throw new SpireError( | ||
`Custom eslint ignore is not allowed, using ${defaultEslintIgnore} instead` | ||
); | ||
} | ||
}, | ||
async precommit() { | ||
setState((prev) => ({ | ||
linters: [ | ||
...prev.linters, | ||
{ | ||
[`*.(${fileExtensions.map((ext) => ext.substr(1)).join('|')})`]: [ | ||
'eslint', | ||
...prev.eslintArgs, | ||
'--fix', | ||
], | ||
}, | ||
], | ||
})); | ||
}, | ||
async run({ options, logger, cwd }) { | ||
const { eslintArgs } = getState(); | ||
const [, ...userProvidedArgs] = options._; | ||
const finalEslintArgs = [ | ||
...eslintArgs, | ||
...(userProvidedArgs.length ? userProvidedArgs : ['.']), | ||
]; | ||
logger.debug('Using eslint arguments: %s', finalEslintArgs.join(' ')); | ||
await execa('eslint', finalEslintArgs, { | ||
cwd, | ||
stdio: 'inherit', | ||
preferLocal: true, | ||
}); | ||
}, | ||
}; | ||
} | ||
|
||
module.exports = eslint; |
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,24 @@ | ||
{ | ||
"name": "spire-plugin-eslint6", | ||
"version": "3.1.0", | ||
"description": "ESLint v6 plugin for Spire", | ||
"main": "index.js", | ||
"repository": "researchgate/spire", | ||
"author": "Daniel Tschinder <daniel@tschinder.de>", | ||
"license": "MIT", | ||
"engines": { | ||
"node": ">=10.18.0" | ||
}, | ||
"dependencies": { | ||
"eslint": "^6.0.0", | ||
"eslint-config-prettier": "^6.0.0", | ||
"eslint-plugin-prettier": "^3.1.0", | ||
"execa": "^4.0.0" | ||
}, | ||
"devDependencies": { | ||
"spire-test-utils": "^3.0.0" | ||
}, | ||
"peerDependencies": { | ||
"spire": "^3.0.0" | ||
} | ||
} |
Oops, something went wrong.