diff --git a/.github/dependabot.yml b/.github/dependabot.yml index eba656e..846a9b2 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,13 +1,21 @@ -# For more information see: https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates - version: 2 updates: - - package-ecosystem: 'github-actions' - directory: '/' + - package-ecosystem: "npm" + directory: "/" schedule: - interval: 'daily' + interval: "daily" + labels: + - "dependency" + # Always increase the version requirement to match the new version. + versioning-strategy: increase - - package-ecosystem: 'npm' - directory: '/' + - package-ecosystem: "github-actions" + directory: "/" + ignore: + - dependency-name: "actions/*" + update-types: + ["version-update:semver-minor", "version-update:semver-patch"] schedule: - interval: 'daily' + interval: "daily" + labels: + - "github-actions" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..bf63283 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,41 @@ +name: 'linting' + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + lint: + runs-on: 'ubuntu-latest' + + strategy: + matrix: + node-version: ['lts/*'] + fail-fast: false + + steps: + - name: 'Checkout Project' + uses: 'actions/checkout@v2' + with: + fetch-depth: 0 + + - name: Use Node.js ${{ matrix.node-version }} + uses: 'actions/setup-node@v2' + with: + node-version: ${{ matrix.node-version }} + + - name: 'Cache Node dependencies' + uses: 'actions/cache@v2' + with: + path: '~/.npm' + key: ${{ runner.os }}-node-${{ hashFiles('**/package.json') }} + restore-keys: | + ${{ runner.os }}-node- + + - name: 'Install Dependencies' + run: 'npm install' + + - name: 'Run Tests' + run: 'npm run check' diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 6c1df73..da86d41 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -17,17 +17,17 @@ jobs: steps: - name: 'Checkout Project' - uses: 'actions/checkout@v2.4.0' + uses: 'actions/checkout@v2' with: fetch-depth: 0 - name: Use Node.js ${{ matrix.node-version }} - uses: 'actions/setup-node@v2.4.1' + uses: 'actions/setup-node@v2' with: node-version: ${{ matrix.node-version }} - name: 'Cache Node dependencies' - uses: 'actions/cache@v2.1.7' + uses: 'actions/cache@v2' with: path: '~/.npm' key: ${{ runner.os }}-node-${{ hashFiles('**/package.json') }} @@ -38,4 +38,4 @@ jobs: run: 'npm install' - name: 'Run Tests' - run: 'npm test' + run: 'npm run test-ci' diff --git a/.gitignore b/.gitignore index 92b2c45..10b7101 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules/ tmp/ +coverage/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 103c84e..fbb6ae3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,67 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## 15.0.0 YYYY-MM-DD + +- **BREAKING CHANGE:** To avoid confusion with ESLint exports and types, our `Linter` class has been renamed to `StandardEngine` and `cli()` now takes a `standardEngine` key instead of a `linter` key if a custom engine wants to be provided. #275 +- **BREAKING CHANGE:** Removed use of ESLint's deprecated `CLIEngine` API. This affects the `eslintConfig` option to our `StandardEngine` (formerly called `Linter`) constructor. #275 +- **BREAKING CHANGE:** Print additional label on warnings (to separate them from errors) b7c1e17 +- **BREAKING CHANGE:** Drop support for Node 10.x. Now require ESM-compatible Node.js versions: `^12.20.0 || ^14.13.1 || >=16.0.0` #252 +- **BREAKING CHANGE:** the `parseOpts` option to the `StandardEngine` (formerly called `Linter`) constructor has been replaced with a new `resolveEslintConfig` one +- Change: make `--verbose` the default #232 + + +## 14.0.1 2020-08-31 + +- _Missing release notes_ + +## 14.0.0 2020-08-29 + +- _Missing release notes_ + +## 13.0.0 2020-08-27 + +- _Missing release notes_ + +## 12.1.1 2020-05-21 + +- Enhancement: Allow passing in a custom linter to `cli` + +## 12.0.1 2020-04-30 + +- Enhancements: Add ts-standard to README linters list +- Fixes: Bump deglob & minimist dependencies + +## 12.0.0 2019-08-19 + +- **BREAKING CHANGE:** Remove `bundle.js` from the list of default ignored files +- **BREAKING CHANGE:** Ignore patterns from `.git/info/exclude` in addition to `.gitignore` +- Enhancement: Update deglob to 4.x + +## 11.0.1 2019-07-12 + +- _Missing release notes_ + +## 11.0.0 2019-07-11 + +- _Missing release notes_ + +## 10.0.0 2018-08-30 + +- _Missing release notes_ + +## 9.0.0 2018-05-15 + +- _Missing release notes_ + +## 8.0.1 2018-03-02 + +- _Missing release notes_ + +## 8.0.0 2018-02-18 + +- _Missing release notes_ + ## 7.2.0 2017-11-07 - New Feature: `noDefaultIgnore` option to can now be used to turn off default ignores. diff --git a/README.md b/README.md index a6eb1fc..32427b9 100644 --- a/README.md +++ b/README.md @@ -42,9 +42,9 @@ Create the files below and fill in your own values for `options.js`. ```js // programmatic usage -const { Linter } = require('standard-engine') +const { StandardEngine } = require('standard-engine') const opts = require('./options.js') -module.exports = new Linter(opts) +module.exports = new StandardEngine(opts) ``` ### `cli.js` @@ -64,6 +64,7 @@ const eslint = require('eslint') const path = require('path') const pkg = require('./package.json') +/** @type {import('standard-engine').StandardEngineOptions} **/ module.exports = { // homepage, version and bugs pulled from package.json version: pkg.version, @@ -73,7 +74,7 @@ module.exports = { cmd: 'pocketlint', // should match the "bin" key in your package.json tagline: 'Live by your own standards!', // displayed in output --help eslintConfig: { - configFile: path.join(__dirname, 'eslintrc.json') + overrideConfigFile: path.join(__dirname, 'eslintrc.json') }, cwd: '' // current working directory, passed to eslint } @@ -293,13 +294,13 @@ module.exports = { } ``` -This function is called with the current ESLint config (the options passed to [ESLint's `CLIEngine`](http://eslint.org/docs/developer-guide/nodejs-api#cliengine)), the options object (`opts`), any options extracted from the project's `package.json` (`packageOpts`), and the directory that contained that `package.json` file (`rootDir`, equivalent to `opts.cwd` if no file was found). +This function is called with the current ESLint config (the options passed to the [`ESLint`](https://eslint.org/docs/developer-guide/nodejs-api#-new-eslintoptions) constructor), the options object (`opts`), any options extracted from the project's `package.json` (`packageOpts`), and the directory that contained that `package.json` file (`rootDir`, equivalent to `opts.cwd` if no file was found). Modify and return `eslintConfig`, or return a new object with the eslint config to be used. ## API Usage -### `engine.lintText(text, [opts])` +### `async engine.lintText(text, [opts])` Lint the provided source `text` to enforce your defined style. An `opts` object may be provided: @@ -348,12 +349,7 @@ const results = { } ``` -### `results = engine.lintTextSync(text, [opts])` - -Synchronous version of `engine.lintText()`. If an error occurs, an exception is -thrown. Otherwise, a `results` object is returned. - -### `engine.lintFiles(files, [opts], callback)` +### `async engine.lintFiles(files, [opts])` Lint the provided `files` globs. An `opts` object may be provided: diff --git a/bin/cmd.js b/bin/cmd.js index dc6b843..a175cb9 100755 --- a/bin/cmd.js +++ b/bin/cmd.js @@ -5,7 +5,7 @@ const getStdin = require('get-stdin') /** * @typedef StandardCliOptions - * @property {import('../').Linter} [linter] + * @property {import('../').StandardEngine} [standardEngine] * @property {string} [cmd] * @property {string} [tagline] * @property {string} [homepage] @@ -13,7 +13,7 @@ const getStdin = require('get-stdin') */ /** - * @param {Omit & StandardCliOptions} rawOpts + * @param {Omit & StandardCliOptions} rawOpts * @returns {void} */ function cli (rawOpts) { @@ -24,7 +24,7 @@ function cli (rawOpts) { ...rawOpts } - const standard = rawOpts.linter || new (require('../').Linter)(opts) + const standard = rawOpts.standardEngine || new (require('../').StandardEngine)(opts) const argv = minimist(process.argv.slice(2), { alias: { @@ -118,11 +118,11 @@ Flags (advanced): } Promise.resolve(argv.stdin ? getStdin() : '').then(async stdinText => { - /** @type {import('eslint').CLIEngine.LintReport} */ - let result + /** @type {import('eslint').ESLint.LintResult[]} */ + let results try { - result = argv.stdin + results = argv.stdin ? await standard.lintText(stdinText, lintOpts) : await standard.lintFiles(argv._, lintOpts) } catch (err) { @@ -141,29 +141,29 @@ Flags (advanced): return } - if (!result) throw new Error('expected a result') + if (!results) throw new Error('expected a results') if (outputFixed) { - if (result.results[0] && result.results[0].output) { + if (results[0] && results[0].output) { // Code contained fixable errors, so print the fixed code - process.stdout.write(result.results[0].output) + process.stdout.write(results[0].output) } else { // Code did not contain fixable errors, so print original code process.stdout.write(stdinText) } } - if (!result.errorCount && !result.warningCount) { + const hasErrors = results.some(item => item.errorCount !== 0) + const hasWarnings = results.some(item => item.warningCount !== 0) + + if (!hasErrors && !hasWarnings) { process.exitCode = 0 return } console.error('%s: %s (%s)', opts.cmd, opts.tagline, opts.homepage) - // Are any warnings present? - const isSomeWarnings = result.results.some(item => item.messages.some(message => message.severity === 1)) - - if (isSomeWarnings) { + if (hasWarnings) { const homepage = opts.homepage != null ? ` (${opts.homepage})` : '' console.error( '%s: %s', @@ -173,9 +173,9 @@ Flags (advanced): } // Are any fixable rules present? - const isSomeFixable = result.results.some(item => item.messages.some(message => !!message.fix)) + const hasFixable = results.some(item => item.messages.some(message => !!message.fix)) - if (isSomeFixable) { + if (hasFixable) { console.error( '%s: %s', opts.cmd, @@ -183,7 +183,7 @@ Flags (advanced): ) } - for (const item of result.results) { + for (const item of results) { for (const message of item.messages) { log( ' %s:%d:%d: %s%s%s', @@ -197,7 +197,7 @@ Flags (advanced): } } - process.exitCode = result.errorCount ? 1 : 0 + process.exitCode = hasErrors ? 1 : 0 }) .catch(err => process.nextTick(() => { throw err })) } diff --git a/index.js b/index.js index 9df358f..35abbaa 100644 --- a/index.js +++ b/index.js @@ -7,22 +7,22 @@ const CACHE_HOME = require('xdg-basedir').cache || os.tmpdir() const { resolveEslintConfig } = require('./lib/resolve-eslint-config') -/** @typedef {ConstructorParameters[0]} CLIEngineOptions */ +/** @typedef {import('eslint').ESLint.Options} EslintOptions */ /** @typedef {Omit} BaseLintOptions */ /** - * @typedef LinterOptions + * @typedef StandardEngineOptions * @property {string} cmd * @property {import('eslint')} eslint * @property {string} [cwd] - * @property {CLIEngineOptions} [eslintConfig] + * @property {EslintOptions} [eslintConfig] * @property {import('./lib/resolve-eslint-config').CustomEslintConfigResolver} [resolveEslintConfig] * @property {string} [version] */ -class Linter { +class StandardEngine { /** - * @param {LinterOptions} opts + * @param {StandardEngineOptions} opts */ constructor (opts) { if (!opts || !opts.cmd) throw new Error('opts.cmd option is required') @@ -42,23 +42,19 @@ class Linter { // Example cache location: ~/.cache/standard/v12/ const cacheLocation = path.join(CACHE_HOME, this.cmd, `v${majorVersion}/`) - /** @type {CLIEngineOptions} */ + /** @type {EslintOptions} */ this.eslintConfig = { cache: true, cacheLocation, - envs: [], fix: false, - globals: [], - plugins: [], - ignorePattern: [], extensions: [], useEslintrc: false, ...(opts.eslintConfig || {}) } - if (this.eslintConfig.configFile != null) { + if (this.eslintConfig.overrideConfigFile != null) { this.eslintConfig.resolvePluginsRelativeTo = path.dirname( - this.eslintConfig.configFile + this.eslintConfig.overrideConfigFile ) } } @@ -68,23 +64,12 @@ class Linter { * * @param {string} text file text to lint * @param {Omit & { filename?: string }} [opts] base options + path of file containing the text being linted - * @returns {import('eslint').CLIEngine.LintReport} + * @returns {Promise} */ - lintTextSync (text, opts) { + async lintText (text, { filename: filePath, ...opts } = {}) { const eslintConfig = this.resolveEslintConfig(opts) - const engine = new this.eslint.CLIEngine(eslintConfig) - return engine.executeOnText(text, (opts || {}).filename) - } - - /** - * Lint text to enforce JavaScript Style. - * - * @param {string} text file text to lint - * @param {Omit & { filename?: string }} [opts] base options + path of file containing the text being linted - * @returns {Promise} - */ - async lintText (text, opts) { - return this.lintTextSync(text, opts) + const engine = new this.eslint.ESLint(eslintConfig) + return engine.lintText(text, { filePath }) } /** @@ -92,7 +77,7 @@ class Linter { * * @param {Array} files file globs to lint * @param {BaseLintOptions & { cwd?: string }} [opts] base options + file globs to ignore (has sane defaults) + current working directory (default: process.cwd()) - * @returns {Promise} + * @returns {Promise} */ async lintFiles (files, opts) { const eslintConfig = this.resolveEslintConfig(opts) @@ -100,10 +85,11 @@ class Linter { if (typeof files === 'string') files = [files] if (files.length === 0) files = ['.'] - const result = new this.eslint.CLIEngine(eslintConfig).executeOnFiles(files) + const eslintInstance = new this.eslint.ESLint(eslintConfig) + const result = await eslintInstance.lintFiles(files) if (eslintConfig.fix) { - this.eslint.CLIEngine.outputFixes(result) + this.eslint.ESLint.outputFixes(result) } return result @@ -111,7 +97,7 @@ class Linter { /** * @param {BaseLintOptions & { cwd?: string }} [opts] - * @returns {CLIEngineOptions} + * @returns {EslintOptions} */ resolveEslintConfig (opts) { const eslintConfig = resolveEslintConfig( @@ -129,4 +115,4 @@ class Linter { } module.exports.cli = require('./bin/cmd') -module.exports.Linter = Linter +module.exports.StandardEngine = StandardEngine diff --git a/lib/resolve-eslint-config.js b/lib/resolve-eslint-config.js index e165de5..15cad99 100644 --- a/lib/resolve-eslint-config.js +++ b/lib/resolve-eslint-config.js @@ -5,7 +5,13 @@ const path = require('path') const pkgConf = require('pkg-conf') -/** @typedef {import('../').CLIEngineOptions} CLIEngineOptions */ +const { + ensureArray, + ensureStringArrayValue, + stringArrayToObj +} = require('./utils') + +/** @typedef {import('../').EslintOptions} EslintOptions */ const DEFAULT_EXTENSIONS = [ '.js', @@ -22,24 +28,7 @@ const DEFAULT_IGNORE = [ ] /** - * @param {unknown} value - * @returns {string[]} - */ -const ensureStringArrayValue = (value) => { - if (!Array.isArray(value)) return [] - - /** @type {string[]} */ - const result = [] - - for (const item of value) { - if (typeof item === 'string') result.push(item) - } - - return result -} - -/** - * @param {CLIEngineOptions} eslintConfig + * @param {EslintOptions} eslintConfig * @param {string|string[]} [extensions] */ const addExtensions = (eslintConfig, extensions) => { @@ -48,63 +37,83 @@ const addExtensions = (eslintConfig, extensions) => { } /** - * @param {CLIEngineOptions} eslintConfig + * @param {EslintOptions} eslintConfig * @param {string|string[]} [ignore] */ const addIgnore = (eslintConfig, ignore) => { if (!ignore) return - if (typeof eslintConfig.ignorePattern === 'string') { - eslintConfig.ignorePattern = [eslintConfig.ignorePattern] - } - eslintConfig.ignorePattern = (eslintConfig.ignorePattern || []).concat(ignore) + + if (!eslintConfig.baseConfig) eslintConfig.baseConfig = {} + + eslintConfig.baseConfig.ignorePatterns = [ + ...ensureArray(eslintConfig.baseConfig.ignorePatterns || []), + ...ensureArray(ignore) + ] } /** - * @param {CLIEngineOptions} eslintConfig - * @param {CLIEngineOptions} baseEslintConfig + * @param {EslintOptions} eslintConfig * @param {string|string[]} [globals] */ -const addGlobals = (eslintConfig, baseEslintConfig, globals) => { +const addGlobals = (eslintConfig, globals) => { if (!globals) return - eslintConfig.globals = (baseEslintConfig.globals || []).concat(globals) + + if (!eslintConfig.baseConfig) eslintConfig.baseConfig = {} + + eslintConfig.baseConfig.globals = stringArrayToObj(globals, eslintConfig.baseConfig.globals) } /** - * @param {CLIEngineOptions} eslintConfig - * @param {CLIEngineOptions} baseEslintConfig + * @param {EslintOptions} eslintConfig * @param {string|string[]} [plugins] */ -const addPlugins = (eslintConfig, baseEslintConfig, plugins) => { +const addPlugins = (eslintConfig, plugins) => { if (!plugins) return - eslintConfig.plugins = (baseEslintConfig.plugins || []).concat(plugins) + + if (!eslintConfig.baseConfig) eslintConfig.baseConfig = {} + + eslintConfig.baseConfig.plugins = [ + ...ensureArray(eslintConfig.baseConfig.plugins || []), + ...ensureArray(plugins) + ] } /** - * @param {CLIEngineOptions} eslintConfig - * @param {CLIEngineOptions} baseEslintConfig + * @param {EslintOptions} eslintConfig * @param {string|string[]|{[key: string]: string}} [envs] */ -const addEnvs = (eslintConfig, baseEslintConfig, envs) => { +const addEnvs = (eslintConfig, envs) => { if (!envs) return + + /** @type {string[]} */ + let values + if (!Array.isArray(envs) && typeof envs !== 'string') { + values = [] // envs can be an object in `package.json` - const envsObj = envs - envs = [] - for (const key in envsObj) { - const value = envsObj[key] - if (value) envs.push(value) + for (const key in envs) { + const value = envs[key] + if (value) values.push(value) } + } else { + values = ensureArray(envs) } - eslintConfig.envs = (baseEslintConfig.envs || []).concat(envs) + + if (!eslintConfig.baseConfig) eslintConfig.baseConfig = {} + + eslintConfig.baseConfig.env = stringArrayToObj(values, eslintConfig.baseConfig.env) } /** - * @param {CLIEngineOptions} eslintConfig + * @param {EslintOptions} eslintConfig * @param {string} [parser] */ const setParser = (eslintConfig, parser) => { if (!parser) return - eslintConfig.parser = parser + + if (!eslintConfig.baseConfig) eslintConfig.baseConfig = {} + + eslintConfig.baseConfig.parser = parser } /** @@ -129,18 +138,18 @@ const setParser = (eslintConfig, parser) => { /** * @callback CustomEslintConfigResolver - * @param {Readonly} eslintConfig + * @param {Readonly} eslintConfig * @param {Readonly} opts * @param {import('pkg-conf').Config} packageOpts * @param {string} rootDir - * @returns {CLIEngineOptions} + * @returns {EslintOptions} */ /** * @param {Readonly} rawOpts - * @param {Readonly} baseEslintConfig + * @param {Readonly} baseEslintConfig * @param {CustomEslintConfigResolver} [customEslintConfigResolver] - * @returns {CLIEngineOptions} + * @returns {EslintOptions} */ const resolveEslintConfig = function (rawOpts, baseEslintConfig, customEslintConfigResolver) { const opts = { @@ -197,14 +206,14 @@ const resolveEslintConfig = function (rawOpts, baseEslintConfig, customEslintCon }) } - addGlobals(eslintConfig, baseEslintConfig, ensureStringArrayValue(packageOpts.globals || packageOpts.global)) - addGlobals(eslintConfig, baseEslintConfig, opts.globals || opts.global) + addGlobals(eslintConfig, ensureStringArrayValue(packageOpts.globals || packageOpts.global)) + addGlobals(eslintConfig, opts.globals || opts.global) - addPlugins(eslintConfig, baseEslintConfig, ensureStringArrayValue(packageOpts.plugins || packageOpts.plugin)) - addPlugins(eslintConfig, baseEslintConfig, opts.plugins || opts.plugin) + addPlugins(eslintConfig, ensureStringArrayValue(packageOpts.plugins || packageOpts.plugin)) + addPlugins(eslintConfig, opts.plugins || opts.plugin) - addEnvs(eslintConfig, baseEslintConfig, ensureStringArrayValue(packageOpts.envs || packageOpts.env)) - addEnvs(eslintConfig, baseEslintConfig, opts.envs || opts.env) + addEnvs(eslintConfig, ensureStringArrayValue(packageOpts.envs || packageOpts.env)) + addEnvs(eslintConfig, opts.envs || opts.env) setParser(eslintConfig, typeof packageOpts.parser === 'string' ? packageOpts.parser : opts.parser) diff --git a/lib/utils.js b/lib/utils.js new file mode 100644 index 0000000..9fc1464 --- /dev/null +++ b/lib/utils.js @@ -0,0 +1,48 @@ +/*! standard-engine. MIT License. Feross Aboukhadijeh */ + +/** + * @template T + * @param {T[]|T} value + * @returns {T[]} + */ +const ensureArray = (value) => Array.isArray(value) ? [...value] : [value] + +/** + * @param {unknown} value + * @returns {string[]} + */ +const ensureStringArrayValue = (value) => { + if (!Array.isArray(value)) return [] + + /** @type {string[]} */ + const result = [] + + for (const item of value) { + if (typeof item === 'string') result.push(item) + } + + return result +} + +/** + * @template T + * @param {string|string[]} values + * @param {{ [key: string]: T }} base + * @returns {{ [key: string]: T|true }} + */ +const stringArrayToObj = (values, base = {}) => { + /** @type {{ [key: string]: T|true }} */ + const result = { ...base } + + for (const value of ensureArray(values)) { + result[value] = true + } + + return result +} + +module.exports = { + ensureArray, + ensureStringArrayValue, + stringArrayToObj +} diff --git a/package.json b/package.json index 9faafa7..3724a23 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,14 @@ "url": "https://feross.org" }, "scripts": { - "test": "standard && tape test/clone.js test/api.js && tsc" + "check:dependency-check": "dependency-check *.js 'bin/**/*.js' 'lib/**/*.js' --no-dev", + "check:installed-check": "installed-check", + "check:standard": "standard", + "check:tsc": "tsc", + "check": "run-p check:*", + "test-ci": "run-s test:*", + "test:tape": "c8 --reporter=lcov --reporter=text tape test/clone.js test/*.js", + "test": "run-s check test:*" }, "dependencies": { "get-stdin": "^8.0.0", @@ -45,10 +52,14 @@ }, "devDependencies": { "@tsconfig/node12": "^1.0.9", + "@types/cross-spawn": "^6.0.2", "@types/eslint": "^7.28.0", "@types/minimist": "^1.2.2", "@types/node": "~12.20.0", + "@types/tape": "^4.13.2", + "c8": "^7.10.0", "cross-spawn": "^7.0.3", + "dependency-check": "^5.0.0-4", "eslint": "^7.12.1", "eslint-config-standard": "^16.0.0", "eslint-config-standard-jsx": "^10.0.0", @@ -56,6 +67,8 @@ "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^5.1.0", "eslint-plugin-react": "^7.21.5", + "installed-check": "^5.0.0", + "npm-run-all": "^4.1.5", "standard": "*", "tape": "^5.0.1", "typescript": "~4.5.2" diff --git a/test/api.js b/test/api.js index 20e97a5..a26bf30 100644 --- a/test/api.js +++ b/test/api.js @@ -2,14 +2,16 @@ const eslint = require('eslint') const path = require('path') const test = require('tape') -const { Linter } = require('../') +const { StandardEngine } = require('../') async function getStandard () { - return new Linter({ + /** @type {string} */ + const configFile = (await import('../tmp/standard/options.js')).default.eslintConfig.configFile + return new StandardEngine({ cmd: 'pocketlint', version: '0.0.0', eslint, - eslintConfig: (await import('../tmp/standard/options.js')).default.eslintConfig + eslintConfig: { overrideConfigFile: configFile } }) } @@ -17,38 +19,30 @@ test('api: lintFiles', async function (t) { t.plan(2) const standard = await getStandard() const result = await standard.lintFiles([], { cwd: path.join(__dirname, '../bin') }) - t.equal(typeof result, 'object', 'result is an object') - t.equal(result.errorCount, 0) + t.ok(Array.isArray(result), 'result is an array') + t.equal((result[0] || {}).errorCount, 0) }) test('api: lintText', async function (t) { t.plan(2) const standard = await getStandard() const result = await standard.lintText('console.log("hi there")\n') - t.equal(typeof result, 'object', 'result is an object') - t.equal(result.errorCount, 1, 'should have used single quotes') -}) - -test('api: lintTextSync', async function (t) { - t.plan(2) - const standard = await getStandard() - const result = standard.lintTextSync('console.log("hi there")\n') - t.equal(typeof result, 'object', 'result is an object') - t.equal(result.errorCount, 1, 'should have used single quotes') + t.ok(Array.isArray(result), 'result is an array') + t.equal((result[0] || {}).errorCount, 1, 'should have used single quotes') }) test('api: resolveEslintConfig -- avoid this.eslintConfig parser mutation', async function (t) { t.plan(2) const standard = await getStandard() const opts = await standard.resolveEslintConfig({ parser: 'blah' }) - t.equal(opts.parser, 'blah') - t.equal(standard.eslintConfig.parser, undefined) + t.equal((opts.baseConfig || {}).parser, 'blah') + t.equal((standard.eslintConfig.baseConfig || {}).parser, undefined) }) test('api: resolveEslintConfig -- avoid this.eslintConfig global mutation', async function (t) { t.plan(2) const standard = await getStandard() const opts = await standard.resolveEslintConfig({ globals: ['what'] }) - t.deepEqual(opts.globals, ['what']) - t.deepEqual(standard.eslintConfig.globals, []) + t.deepEqual((opts.baseConfig || {}).globals, { what: true }) + t.strictEqual((standard.eslintConfig.baseConfig || {}).globals, undefined) }) diff --git a/test/clone.js b/test/clone.js index 3eb43e2..f10e235 100644 --- a/test/clone.js +++ b/test/clone.js @@ -4,6 +4,7 @@ const crossSpawn = require('cross-spawn') const fs = require('fs') const path = require('path') const test = require('tape') +const { R_OK, W_OK } = fs.constants const GIT = 'git' const STANDARD = path.join(__dirname, 'lib', 'standard-cmd.js') @@ -22,37 +23,40 @@ test('test `standard` repo', function (t) { const name = pkg.name const url = pkg.repo + '.git' const folder = path.join(TMP, name) - fs.access(path.join(TMP, name), fs.R_OK | fs.W_OK, function (err) { + fs.access(path.join(TMP, name), R_OK | W_OK, function (err) { downloadPackage(function (err) { if (err) throw err runStandard() }) + /** @param {(err?: Error|null) => void} cb */ function downloadPackage (cb) { if (err) gitClone(cb) else gitPull(cb) } + /** @param {(err?: Error|null) => void} cb */ function gitClone (cb) { - const args = ['clone', '--depth', 1, url, path.join(TMP, name)] - spawn(GIT, args, { stdio: 'ignore' }, function (err) { + const args = ['clone', '--depth', '1', url, path.join(TMP, name)] + spawn(GIT, args, { stdio: 'ignore' }, err => { if (err) err.message += ' (git clone) (' + name + ')' cb(err) }) } + /** @param {(err?: Error|null) => void} cb */ function gitPull (cb) { const args = ['pull'] - spawn(GIT, args, { cwd: folder, stdio: 'ignore' }, function (err) { + spawn(GIT, args, { cwd: folder, stdio: 'ignore' }, err => { if (err) err.message += ' (git pull) (' + name + ')' cb(err) }) } function runStandard () { + /** @type {string[]} */ const args = [] - if (pkg.args) args.push.apply(args, pkg.args) - spawn(STANDARD, args, { cwd: folder }, function (err) { + spawn(STANDARD, args, { cwd: folder }, err => { const str = name + ' (' + pkg.repo + ')' if (err) { t.fail(str) } else { t.pass(str) } }) @@ -60,6 +64,13 @@ test('test `standard` repo', function (t) { }) }) +/** + * @param {string} command + * @param {string[]} args + * @param {import('child_process').SpawnOptions} opts + * @param {(err?: Error|null) => void} cb + * @returns + */ function spawn (command, args, opts, cb) { if (!opts.stdio) opts.stdio = 'inherit' diff --git a/test/lib/standard-cmd.js b/test/lib/standard-cmd.js index 2f898f9..a87569b 100755 --- a/test/lib/standard-cmd.js +++ b/test/lib/standard-cmd.js @@ -7,8 +7,7 @@ const opts = { version: '0.0.0', eslint, eslintConfig: { - configFile: path.join(__dirname, 'standard.json'), - useEslintrc: false + overrideConfigFile: path.join(__dirname, 'standard.json') } } diff --git a/test/utils-ensure-array.js b/test/utils-ensure-array.js new file mode 100644 index 0000000..fd6dea0 --- /dev/null +++ b/test/utils-ensure-array.js @@ -0,0 +1,25 @@ +const test = require('tape') + +const { ensureArray } = require('../lib/utils') + +test('utils: ensureArray', t => { + t.test('ensureArray converts string to array', t => { + t.plan(4) + + t.deepEqual(ensureArray('foo'), ['foo'], 'string becomes array containing that string') + t.deepEqual(ensureArray(), [undefined], 'undefined becomes array containing that undefined') + t.deepEqual(ensureArray(null), [null], 'null becomes array containing that null') + t.deepEqual(ensureArray(true), [true], 'true becomes array containing that true') + }) + + t.test('ensureArray clones array', t => { + t.plan(3) + + const input = ['foo'] + const result = ensureArray(input) + + t.ok(Array.isArray(result), 'array stays an array') + t.deepEqual(result, ['foo'], 'array keeps it content') + t.notStrictEqual(result, input, 'array is cloned') + }) +}) diff --git a/test/utils-ensure-string-array-value.js b/test/utils-ensure-string-array-value.js new file mode 100644 index 0000000..ac57fac --- /dev/null +++ b/test/utils-ensure-string-array-value.js @@ -0,0 +1,35 @@ +const test = require('tape') + +const { ensureStringArrayValue } = require('../lib/utils') + +test('utils: ensureStringArrayValue', t => { + t.test('ensureStringArrayValue returns empty array on non-array input', t => { + t.plan(5) + + t.deepEqual(ensureStringArrayValue(), [], 'should return empty array on non-array input') + t.deepEqual(ensureStringArrayValue(null), [], 'should return empty array on non-array input') + t.deepEqual(ensureStringArrayValue(true), [], 'should return empty array on non-array input') + t.deepEqual(ensureStringArrayValue('foo'), [], 'should return empty array on non-array input') + t.deepEqual(ensureStringArrayValue(123), [], 'should return empty array on non-array input') + }) + + t.test('ensureStringArrayValue clones array', t => { + t.plan(2) + + const input = ['foo'] + const result = ensureStringArrayValue(input) + + t.deepEqual(result, ['foo'], 'array keeps it content') + t.notStrictEqual(result, input, 'array is cloned') + }) + + t.test('ensureStringArrayValue removes non-string values', t => { + t.plan(2) + + const input = ['foo', true, 123, 'bar'] + const result = ensureStringArrayValue(input) + + t.deepEqual(result, ['foo', 'bar'], 'non-string values has been removed') + t.deepEqual(input, ['foo', true, 123, 'bar'], 'input value remains untouched') + }) +}) diff --git a/test/utils-string-array-to-obj.js b/test/utils-string-array-to-obj.js new file mode 100644 index 0000000..3d56d56 --- /dev/null +++ b/test/utils-string-array-to-obj.js @@ -0,0 +1,32 @@ +const test = require('tape') + +const { stringArrayToObj } = require('../lib/utils') + +test('utils: stringArrayToObj', t => { + t.test('stringArrayToObj accepts string input', t => { + t.plan(1) + + const result = stringArrayToObj('foo') + + t.deepEqual(result, { foo: true }, 'string becomes key with true value in result') + }) + + t.test('stringArrayToObj accepts array input', t => { + t.plan(1) + + const result = stringArrayToObj(['foo', 'bar']) + + t.deepEqual(result, { foo: true, bar: true }, 'string array becomes keys with true values in result') + }) + + t.test('stringArrayToObj clones input base', t => { + t.plan(3) + + const base = { car: 123 } + const result = stringArrayToObj('foo', base) + + t.deepEqual(result, { foo: true, car: 123 }, 'object gets value added') + t.notStrictEqual(result, base, 'object is cloned') + t.deepEqual(base, { car: 123 }, 'base remains untouched') + }) +}) diff --git a/tsconfig.json b/tsconfig.json index ef97a1a..c66244c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,10 @@ ], "include": [ "bin/**/*.js", - "lib/**/*.js", + "lib/**/*.js" + ], + "exclude": [ + "tmp/**/*.js", ], "compilerOptions": { /* Configures it for targeting javascript */