diff --git a/.commitlintrc.json b/.commitlintrc.json new file mode 100644 index 0000000..d3d7f0c --- /dev/null +++ b/.commitlintrc.json @@ -0,0 +1 @@ +{ "extends": ["@commitlint/config-conventional"] } diff --git a/.github/workflows/commitlint.yml b/.github/workflows/commitlint.yml deleted file mode 100644 index 9d85f4e..0000000 --- a/.github/workflows/commitlint.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: 'Lint the Commit Messages' - -on: - push: - branches: [master] - pull_request: - branches: [master] - -jobs: - commitlint: - runs-on: 'ubuntu-latest' - steps: - - uses: 'actions/checkout@v2.4.0' - with: - fetch-depth: 0 - - - uses: 'wagoid/commitlint-github-action@v4.1.9' diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index d177e47..557e3e0 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -7,7 +7,7 @@ on: branches: [master] jobs: - build: + tests: runs-on: 'ubuntu-latest' strategy: @@ -15,10 +15,7 @@ jobs: node-version: [12.x, 14.x, 16.x] steps: - - name: 'Checkout Project' - uses: 'actions/checkout@v2.4.0' - with: - fetch-depth: 0 + - uses: 'actions/checkout@v3.0.0' - name: Use Node.js ${{ matrix.node-version }} uses: 'actions/setup-node@v3.4.1' @@ -28,23 +25,7 @@ jobs: - name: 'Install Dependencies' run: 'npm install' - - name: Lint Files - run: 'npm run lint' - - - name: 'Run Tests and Coverage' - env: - CI: true - run: 'npm run coverage' - - - name: 'Coveralls Parallel' - uses: 'coverallsapp/github-action@master' - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - parallel: true - path-to-lcov: './coverage/lcov.info' - - - name: 'Coveralls Finished' - uses: 'coverallsapp/github-action@master' - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - parallel-finished: true + - run: 'npm run lint:commit -- --to "${{ github.sha }}"' + - run: 'npm run lint:standard' + - run: 'npm run lint:ts-standard' + - run: 'npm run test' diff --git a/.husky/commit-msg b/.husky/commit-msg deleted file mode 100644 index 7cd8dd9..0000000 --- a/.husky/commit-msg +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - -npx --no-install commitlint --edit diff --git a/.husky/pre-commit b/.husky/pre-commit deleted file mode 100644 index 419060e..0000000 --- a/.husky/pre-commit +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - -npm run lint -npm run build -git add ./dist diff --git a/.npmrc b/.npmrc index c1ca392..43c97e7 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1 @@ -package-lock = false +package-lock=false diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index d8dcbdc..0000000 --- a/.prettierignore +++ /dev/null @@ -1,4 +0,0 @@ -.vscode -node_modules -dist -coverage diff --git a/CHANGELOG.md b/CHANGELOG.md index 4dafedc..e34aade 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ # Changelog -## 12.0.0 - Pending +## 13.0.0 - Pending + +## 12.0.0 + +- **BREAKING:** Major rewrite of `ts-standard` to follow the structure of other `standard` engines (like `standard`, `semistandard`, `standardx`). +- **BREAKING**: Updated `eslint-config-standard-with-typescript` to version `^23.0.0`. Please visit their github page for any style/linter changes. +- **BREAKING**: Dropped support for many options (e.g: `--cwd`), these options should now be implemented in `standard-engine`. +- **BREAKING**: Dropped support for multiple tsconfig projects to be used (need to be rethought, please open an issue, if you really need this feature, explaining why you need this use case). ## 11.0.0 diff --git a/README.md b/README.md index 02b5d6c..fb053fa 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,8 @@ [![Tests](https://github.com/standard/ts-standard/workflows/tests/badge.svg?branch=master)](https://github.com/standard/ts-standard/actions?query=workflow%3A%22tests%22) -[![Coverage Status](https://coveralls.io/repos/github/standard/ts-standard/badge.svg?branch=master)](https://coveralls.io/github/standard/ts-standard?branch=master) [![npm](https://badgen.net/npm/v/ts-standard)](https://www.npmjs.com/package/ts-standard) [![npm](https://badgen.net/npm/dm/ts-standard)](https://www.npmjs.com/package/ts-standard) [![License](https://badgen.net/github/license/standard/ts-standard)](https://github.com/standard/ts-standard/blob/master/LICENSE) -[![TS-Standard - Typescript Standard Style Guide](https://badgen.net/badge/code%20style/ts-standard/blue?icon=typescript)](https://github.com/standard/ts-standard) +[![TS-Standard - TypeScript Standard Style Guide](https://badgen.net/badge/code%20style/ts-standard/blue?icon=typescript)](https://github.com/standard/ts-standard) [![Dependabot badge](https://badgen.net/github/dependabot/standard/ts-standard?icon=dependabot)](https://dependabot.com/) # ts-standard @@ -12,7 +11,7 @@ TypeScript Style Guide, with linter and automatic code fixer based on [StandardJ ## 💾 Install -`npm install ts-standard` +`npm install --save-dev ts-standard` ## ⌨️ Basic Usage @@ -33,28 +32,33 @@ section below for more details ## 📜 Help ```text -ts-standard - Standard for Typescript! (https://github.com/standard/ts-standard) +ts-standard - Standard for TypeScript! (https://github.com/standard/ts-standard) Usage: - ts-standard [FILES...] - If FILES is omitted, all JavaScript/Typescript source files (*.js, *.jsx, *.mjs, *.cjs, *.ts) - in the current working directory are checked, recursively. - Certain paths (node_modules/, coverage/, vendor/, *.min.js, bundle.js, and - files/folders that begin with '.' like .git/) are automatically ignored. - Paths in a project's root .gitignore file are also automatically ignored. + ts-standard [FILES...] + + If FILES is omitted, all JavaScript/TypeScript source files (*.js, *.jsx, *.mjs, *.cjs, *.ts, *.tsx) + in the current working directory are checked, recursively. + + Certain paths (node_modules/, coverage/, vendor/, *.min.js, and + files/folders that begin with '.' like .git/) are automatically ignored. + + Paths in a project's root .gitignore file are also automatically ignored. + Flags: - --fix Automatically fix problems - -p, --project Specify ts-config location (default: ./tsconfig.eslint.json or ./tsconfig.json) - --version Show current version - -h, --help Show usage information + --fix Automatically fix problems + -p, --project Specify ts-config location (default: ./tsconfig.eslint.json or ./tsconfig.json) + --version Show current version + -h, --help Show usage information + Flags (advanced): - --stdin Read file text from stdin - --globals Declare global variable - --plugins Use custom eslint plugin - --envs Use custom eslint environment - --parser Use custom ts/js parser (default: @typescript-eslint/parser) - --report Use a built-in eslint reporter or custom eslint reporter (default: standard) - --ext, --extensions List of files extensions to lint by default (default: js,jsx,ts,tsx,mjs,cjs) + --stdin Read file text from stdin + --ext Specify JavaScript/TypeScript file extensions + --global Declare global variable + --plugin Use custom eslint plugin + --env Use custom eslint environment + --parser Use custom ts/js parser (default: @typescript-eslint/parser) + ``` ## 🧬 TSConfig: Linting with Type Information @@ -75,14 +79,9 @@ the `--project` flag or adding a `ts-standard` configuration property to your `p } ``` -Its possible to specify multiple projects using an array as in the underlying -[parser](https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/parser#parseroptionsproject). - ## 🗑 Ignoring files and folders -You can ignore files and folders by either providing specific files/globs of the files you want linted -to `ts-standard` when running the command or you can add an `ignore` property to your `package.json` -`ts-standard` configuration settings. +You can add an `ignore` property to your `package.json` `ts-standard` configuration settings. ```json { @@ -95,28 +94,6 @@ to `ts-standard` when running the command or you can add an `ignore` property to } ``` -## 🎛 Package.json Options - -```jsonc -{ - "ts-standard": { - "ignore": [""], // files/folders/globs to ignore - "noDefaultIgnore": false, // disable ignoring default locations (e.g. node_modules, .git, etc...) - "globals": [""], // global variables to define (e.g. $, jquery, etc...) - "plugins": [""], // Extra eslint plugins to use - "envs": [""], // eslint environments to use (e.g. node, browser, etc...) - "parser": "", // a different eslint parser to use (e.g. babel, etc...) - "cwd": "", // the root working directory where the project file is located - "eslint": "", // path to a custom eslint linter - "files": [""], // files/folders/globs to include in the linting - "project": [""], // relative path to `tsconfig.json` file - "fix": false, // auto fix any lint errors found that are fixable - "report": "", // an eslint formatter to output the lint results as (e.g. standard, stylish, json, etc...) - "extensions": "", // a list of file extensions to lint by default (e.g. js,jsx,ts,tsx,mjs,cjs) - } -} -``` - ## 🚫 Please change X rule This project has no control over the rules implemented, as such this project cannot change any of the @@ -147,6 +124,5 @@ creating a typescript specific standard. I welcome all pull requests. Please make sure you add appropriate test cases for any features added. Before opening a PR please make sure to run the following scripts: -- `npm run lint` checks for code errors and format according to [ts-standard](https://github.com/standard/ts-standard) +- `npm run lint:standard` checks for code errors and format according to [standard](https://github.com/standard/standard) - `npm test` make sure all tests pass -- `npm run coverage` make sure the coverage has not decreased from current master diff --git a/bin/cmd.js b/bin/cmd.js deleted file mode 100755 index b2d0d73..0000000 --- a/bin/cmd.js +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env node - -require('../dist/cli').cli() diff --git a/cli.js b/cli.js new file mode 100755 index 0000000..a56d2fe --- /dev/null +++ b/cli.js @@ -0,0 +1,67 @@ +#!/usr/bin/env node + +import { cli } from 'standard-engine' +import minimist from 'minimist' + +import options from './options.js' +import { CURRENT_WORKING_DIRECTORY } from './constants.js' +import { _getTSConfigPath } from './resolve-tsconfig.js' + +const tsStandardCli = () => { + const cliOptions = { ...options } + + const argv = minimist(process.argv.slice(2), { + alias: { + project: 'p', + help: 'h' + }, + boolean: ['help'], + string: ['project'] + }) + + if (argv.help) { + console.log( + '%s - %s (%s)', + cliOptions.cmd, + cliOptions.tagline, + cliOptions.homepage + ) + console.log(` +Usage: + ${cliOptions.cmd} [FILES...] + + If FILES is omitted, all JavaScript/TypeScript source files (*.js, *.jsx, *.mjs, *.cjs, *.ts, *.tsx) + in the current working directory are checked, recursively. + + Certain paths (node_modules/, coverage/, vendor/, *.min.js, and + files/folders that begin with '.' like .git/) are automatically ignored. + + Paths in a project's root .gitignore file are also automatically ignored. + +Flags: + --fix Automatically fix problems + -p, --project Specify ts-config location (default: ./tsconfig.eslint.json or ./tsconfig.json) + --version Show current version + -h, --help Show usage information + +Flags (advanced): + --stdin Read file text from stdin + --ext Specify JavaScript/TypeScript file extensions + --global Declare global variable + --plugin Use custom eslint plugin + --env Use custom eslint environment + --parser Use custom ts/js parser (default: @typescript-eslint/parser) + `) + process.exitCode = 0 + return + } + + cliOptions.eslintConfig.overrideConfig.parserOptions.project = _getTSConfigPath( + CURRENT_WORKING_DIRECTORY, + argv.project + ) + + cli(cliOptions) +} + +tsStandardCli() diff --git a/constants.js b/constants.js new file mode 100644 index 0000000..7ea60de --- /dev/null +++ b/constants.js @@ -0,0 +1,8 @@ +export const DEFAULT_TSCONFIG_LOCATIONS = [ + 'tsconfig.eslint.json', + 'tsconfig.json' +] + +export const DEFAULT_EXTENSIONS = ['js', 'jsx', 'mjs', 'cjs', 'ts', 'tsx'] + +export const CURRENT_WORKING_DIRECTORY = process.cwd() diff --git a/dist/cli.d.ts b/dist/cli.d.ts deleted file mode 100644 index 45f29cb..0000000 --- a/dist/cli.d.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type * as eslint from 'eslint'; -import type { LintReport } from 'standard-engine'; -import { DefaultOptions } from './options'; -import { TSStandard } from './ts-standard'; -export declare const ESLINT_BUILTIN_REPORTERS: string[]; -export interface Options { - files?: string[]; - test?: string; - stdIn?: boolean; - project?: string | string[]; - fix?: boolean; - report?: string; - envs?: string[]; - ignore?: string[]; - noDefaultIgnore?: boolean; - globals?: string[]; - plugins?: string[]; - parser?: string; - eslint?: typeof eslint; - extensions?: string[]; -} -export declare function cli(): Promise; -export declare function lintStdIn(linter: TSStandard, options: Pick): Promise; -export declare function lintFiles(linter: TSStandard, options: Pick): Promise; -export declare function printReport(lintReport: LintReport, options: Pick): Promise; diff --git a/dist/cli.js b/dist/cli.js deleted file mode 100644 index 51a7bec..0000000 --- a/dist/cli.js +++ /dev/null @@ -1,158 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.printReport = exports.lintFiles = exports.lintStdIn = exports.cli = exports.ESLINT_BUILTIN_REPORTERS = void 0; -const getStdin = require("get-stdin"); -const options_1 = require("./options"); -const ts_standard_1 = require("./ts-standard"); -const constants_1 = require("./constants"); -exports.ESLINT_BUILTIN_REPORTERS = [ - 'stylish', - 'checkstyle', - 'codeframe', - 'compact', - 'html', - 'jslint-xml', - 'json', - 'junit', - 'table', - 'tap', - 'unix', - 'visualstudio' -]; -async function cli() { - // Get the default/cli options - const defaultOptions = (0, options_1.getDefaultOptions)(); - const packageOptions = (0, options_1.getPackageOptions)(); - const cliOptions = (0, options_1.getCLIOptions)(); - const options = (0, options_1.mergeOptions)(defaultOptions, packageOptions, cliOptions); - // Linting requires a project file - if (options.project == null || - (Array.isArray(options.project) && options.project.length === 0)) { - console.error('Unable to locate the project file. A project file (tsconfig.json or ' + - 'tsconfig.eslint.json) is required in order to use ts-standard.'); - return process.exit(1); - } - const tsStandard = new ts_standard_1.TSStandard({ - project: options.project, - fix: options.fix, - ignore: options.ignore, - noDefaultIgnore: options.noDefaultIgnore, - envs: options.envs, - globals: options.globals, - plugins: options.plugins, - parser: options.parser, - eslint: options.eslint, - cwd: options.cwd, - extensions: options.extensions - }); - // Perform the lint operation on the given files or text - let lintReport; - if (options.useStdIn) { - lintReport = await exports.lintStdIn(tsStandard, options); - } - else { - lintReport = await exports.lintFiles(tsStandard, options); - } - // If no errors or warnings return success - if (lintReport.errorCount === 0 && lintReport.warningCount === 0) { - return process.exit(0); - } - // Print the lint report results to console - await exports.printReport(lintReport, options); - // Only set exit code 1 if there were errors (warnings do not count) - process.exit(lintReport.errorCount > 0 ? 1 : 0); -} -exports.cli = cli; -async function lintStdIn(linter, options) { - // Get text from stdin - const text = await getStdin(); - // Lint the text - let lintReport; - try { - lintReport = await linter.lintText(text, { - filename: options.stdInFilename, - fix: options.fix - }); - } - catch (e) { - const err = e; - console.error(`${constants_1.CMD}: Unexpected linter output:\n`); - console.error(`${err.message}: ${err.stack}`); - console.error(`\nIf you think this is a bug in \`${constants_1.CMD}\`, open an issue: ` + - `${constants_1.BUGS_URL}`); - return process.exit(1); - } - // If we performed fixes then maybe return the fixed text - if (options.fix) { - if (lintReport.results[0].output != null) { - // Code contained fixable errors, so print the fixed code - process.stdout.write(lintReport.results[0].output); - } - else { - // Code did not contain fixable errors, so print original code - process.stdout.write(text); - } - } - return lintReport; -} -exports.lintStdIn = lintStdIn; -async function lintFiles(linter, options) { - // Lint the text - let lintReport; - try { - lintReport = await linter.lintFiles(options.files); - } - catch (e) { - const err = e; - console.error(`${constants_1.CMD}: Unexpected linter output:\n`); - console.error(`${err.message}: ${err.stack}`); - console.error(`\nIf you think this is a bug in \`${constants_1.CMD}\`, open an issue: ` + - `${constants_1.BUGS_URL}`); - return process.exit(1); - } - return lintReport; -} -exports.lintFiles = lintFiles; -async function printReport(lintReport, options) { - // Print tag line to stay consistent with standard output - console.error(`${constants_1.CMD}: ${constants_1.TAGLINE} (${constants_1.HOMEPAGE})`); - // Check for any fixable rules - const isFixable = lintReport.results.some((res) => { - return res.messages.some((msg) => { - return msg.fix != null; - }); - }); - // If there were fixable rules, then that means that `--fix` was not provided - if (isFixable && !options.fix) { - console.error(`${constants_1.CMD}: Run \`${constants_1.CMD} --fix\` to automatically fix some problems.`); - } - // Check to see if a custom reporter was given, if so use that. - let reporter; - if (options.report === 'standard') { - // Use standard reporter - const { standardReporter } = await Promise.resolve().then(() => require('./standard-reporter')); - reporter = standardReporter(options.useStdIn && options.fix); - } - else if (exports.ESLINT_BUILTIN_REPORTERS.includes(options.report)) { - // Use built-in eslint reporter - reporter = await Promise.resolve().then(() => require(`eslint/lib/cli-engine/formatters/${options.report}`)); - } - else { - // Use a custom reporter - reporter = await Promise.resolve().then(() => require(options.report)); - if (reporter == null) { - throw new Error('Error: Unable to import custom formatter.'); - } - } - // Print the lint results to console using the requested formatter - const outputReport = reporter(lintReport.results); - // When fixing code from stdin (`standard --stdin --fix`), the transformed - // code is printed to stdout, so print lint errors to stderr in this case. - if (options.useStdIn && options.fix) { - console.error(outputReport); - } - else { - console.log(outputReport); - } -} -exports.printReport = printReport; diff --git a/dist/constants.d.ts b/dist/constants.d.ts deleted file mode 100644 index 3611c2a..0000000 --- a/dist/constants.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -export declare const CMD = "ts-standard"; -export declare const TAGLINE = "Typescript Standard Style!"; -export declare const BUGS_URL: string; -export declare const HOMEPAGE: string; -export declare const VERSION: string; diff --git a/dist/constants.js b/dist/constants.js deleted file mode 100644 index b715c6e..0000000 --- a/dist/constants.js +++ /dev/null @@ -1,10 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.VERSION = exports.HOMEPAGE = exports.BUGS_URL = exports.TAGLINE = exports.CMD = void 0; -// eslint-disable-next-line @typescript-eslint/no-var-requires -const packageJson = require('../package.json'); -exports.CMD = 'ts-standard'; -exports.TAGLINE = 'Typescript Standard Style!'; -exports.BUGS_URL = packageJson.bugs.url; -exports.HOMEPAGE = packageJson.homepage; -exports.VERSION = packageJson.version; diff --git a/dist/index.d.ts b/dist/index.d.ts deleted file mode 100644 index 2d0754c..0000000 --- a/dist/index.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './ts-standard'; -export * from './standard-engine-api'; diff --git a/dist/index.js b/dist/index.js deleted file mode 100644 index a626e63..0000000 --- a/dist/index.js +++ /dev/null @@ -1,16 +0,0 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __exportStar = (this && this.__exportStar) || function(m, exports) { - for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -// Export the real API usage class -__exportStar(require("./ts-standard"), exports); -// Export standard-engine compatible api for so that editor extensions work as expected -__exportStar(require("./standard-engine-api"), exports); diff --git a/dist/options/cli-options.d.ts b/dist/options/cli-options.d.ts deleted file mode 100644 index 271354b..0000000 --- a/dist/options/cli-options.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -export interface CLIOptions { - fix: boolean; - useStdIn: boolean; - stdInFilename?: string; - files?: string[]; - project?: string[]; - globals?: string[]; - plugins?: string[]; - envs?: string[]; - parser?: string; - report?: string; - extensions?: string[]; -} -export declare function getCLIOptions(): CLIOptions; -export declare function _convertToArray(val?: string | string[]): string[] | undefined; diff --git a/dist/options/cli-options.js b/dist/options/cli-options.js deleted file mode 100644 index eaa10d2..0000000 --- a/dist/options/cli-options.js +++ /dev/null @@ -1,98 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports._convertToArray = exports.getCLIOptions = void 0; -const minimist = require("minimist"); -const constants_1 = require("../constants"); -function getCLIOptions() { - // Parse the command line arguments - const argv = minimist(process.argv.slice(2), { - alias: { - globals: 'global', - plugins: 'plugin', - envs: 'env', - help: 'h', - project: 'p', - extensions: 'ext' - }, - boolean: ['fix', 'help', 'stdin', 'version'], - string: [ - 'globals', - 'plugins', - 'parser', - 'envs', - 'project', - 'report', - 'stdin-filename', - 'extensions' - ] - }); - // Unix convention: Command line argument `-` is a shorthand for `--stdin` - if (argv._[0] === '-') { - argv.stdin = true; - argv._.shift(); - } - // Print the help section if so requested - if (argv.help) { - console.log('%s - %s (%s)', constants_1.CMD, constants_1.TAGLINE, constants_1.HOMEPAGE); - console.log(` -Usage: - ${constants_1.CMD} [FILES...] - If FILES is omitted, all JavaScript/Typescript source files (*.js, *.jsx, *.mjs, *.cjs, *.ts, *.tsx) - in the current working directory are checked, recursively. - Certain paths (node_modules/, coverage/, vendor/, *.min.js, bundle.js, and - files/folders that begin with '.' like .git/) are automatically ignored. - Paths in a project's root .gitignore file are also automatically ignored. -Flags: - --fix Automatically fix problems - -p, --project Specify ts-config location (default: ./tsconfig.eslint.json or ./tsconfig.json) - --version Show current version - -h, --help Show usage information -Flags (advanced): - --stdin Read file text from stdin (requires using --stdin-filename) - --stdin-filename The filename and path of the contents read by stdin - --globals Declare global variable - --plugins Use custom eslint plugin - --envs Use custom eslint environment - --parser Use custom ts/js parser (default: @typescript-eslint/parser) - --report Use a built-in eslint reporter or custom eslint reporter (default: standard) - --ext, --extensions List of files extensions to lint by default (default: js,jsx,ts,tsx,mjs,cjs) - `); - return process.exit(0); - } - // Print out the version number if requested - if (argv.version) { - console.log(constants_1.VERSION); - return process.exit(0); - } - // Get the files/globs to lint - const files = argv._.length !== 0 ? argv._ : undefined; - const options = { - fix: argv.fix, - useStdIn: argv.stdin, - stdInFilename: argv['stdin-filename'], - files, - project: exports._convertToArray(argv.project), - globals: exports._convertToArray(argv.globals), - plugins: exports._convertToArray(argv.plugins), - envs: exports._convertToArray(argv.envs), - parser: argv.parser, - report: argv.report, - extensions: exports._convertToArray(argv.extensions) - }; - if (options.useStdIn && options.stdInFilename == null) { - console.error('Must provide the `--stdin-filename` flag when using the `--stdin` flag.'); - process.exit(1); - } - return options; -} -exports.getCLIOptions = getCLIOptions; -function _convertToArray(val) { - if (val != null && !Array.isArray(val)) { - return [...val.split(',').map((v) => v.trim())]; - } - if (Array.isArray(val)) { - return val; - } - return undefined; -} -exports._convertToArray = _convertToArray; diff --git a/dist/options/default-options.d.ts b/dist/options/default-options.d.ts deleted file mode 100644 index 0c0634d..0000000 --- a/dist/options/default-options.d.ts +++ /dev/null @@ -1,22 +0,0 @@ -export declare const DEFAULT_TSCONFIG_LOCATIONS: string[]; -export declare const DEFAULT_EXTENSIONS: string[]; -export interface DefaultOptions { - files: string[]; - fix: boolean; - report: string; - useStdIn: boolean; - noDefaultIgnore: boolean; - eslint: undefined; - cwd: string; - stdInFilename?: string; - project?: string | string[]; - ignore?: string[]; - envs?: string[]; - globals?: string[]; - plugins?: string[]; - parser?: string; - extensions?: string[]; -} -export declare function getDefaultOptions(cwd?: string): DefaultOptions; -export declare function _getTSConfigFromDefaultLocations(cwd: string): string | undefined; -export declare function _isValidPath(pathToValidate: string): boolean; diff --git a/dist/options/default-options.js b/dist/options/default-options.js deleted file mode 100644 index d26a52c..0000000 --- a/dist/options/default-options.js +++ /dev/null @@ -1,50 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports._isValidPath = exports._getTSConfigFromDefaultLocations = exports.getDefaultOptions = exports.DEFAULT_EXTENSIONS = exports.DEFAULT_TSCONFIG_LOCATIONS = void 0; -const path_1 = require("path"); -const fs_1 = require("fs"); -exports.DEFAULT_TSCONFIG_LOCATIONS = [ - 'tsconfig.eslint.json', - 'tsconfig.json' -]; -exports.DEFAULT_EXTENSIONS = ['js', 'jsx', 'mjs', 'cjs', 'ts', 'tsx']; -function getDefaultOptions(cwd = process.cwd()) { - return { - files: [], - project: _getTSConfigFromDefaultLocations(cwd), - fix: false, - report: 'standard', - useStdIn: false, - noDefaultIgnore: false, - cwd, - stdInFilename: undefined, - eslint: undefined, - ignore: undefined, - envs: undefined, - globals: undefined, - plugins: undefined, - parser: undefined, - extensions: exports.DEFAULT_EXTENSIONS - }; -} -exports.getDefaultOptions = getDefaultOptions; -function _getTSConfigFromDefaultLocations(cwd) { - for (const tsFile of exports.DEFAULT_TSCONFIG_LOCATIONS) { - const absPath = (0, path_1.join)(cwd, tsFile); - if (exports._isValidPath(absPath)) { - return absPath; - } - } - return undefined; -} -exports._getTSConfigFromDefaultLocations = _getTSConfigFromDefaultLocations; -function _isValidPath(pathToValidate) { - try { - (0, fs_1.statSync)(pathToValidate); - } - catch (e) { - return false; - } - return true; -} -exports._isValidPath = _isValidPath; diff --git a/dist/options/index.d.ts b/dist/options/index.d.ts deleted file mode 100644 index 781f69b..0000000 --- a/dist/options/index.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './default-options'; -export * from './cli-options'; -export * from './package-options'; -export declare function mergeOptions(...objects: any[]): any; diff --git a/dist/options/index.js b/dist/options/index.js deleted file mode 100644 index 4ab615c..0000000 --- a/dist/options/index.js +++ /dev/null @@ -1,32 +0,0 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __exportStar = (this && this.__exportStar) || function(m, exports) { - for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.mergeOptions = void 0; -__exportStar(require("./default-options"), exports); -__exportStar(require("./cli-options"), exports); -__exportStar(require("./package-options"), exports); -// A simple utility function to merge objects together ignoring any undefined values -function mergeOptions(...objects) { - var _a; - const result = {}; - for (const obj of objects) { - if (obj == null) { - continue; - } - const keys = Object.keys(obj); - for (const key of keys) { - result[key] = (_a = obj[key]) !== null && _a !== void 0 ? _a : result[key]; - } - } - return result; -} -exports.mergeOptions = mergeOptions; diff --git a/dist/options/package-options.d.ts b/dist/options/package-options.d.ts deleted file mode 100644 index 8a93f05..0000000 --- a/dist/options/package-options.d.ts +++ /dev/null @@ -1,18 +0,0 @@ -interface PackageOptions { - files?: string[]; - project?: string | string[]; - fix?: boolean; - report?: string; - ignore?: string[]; - noDefaultIgnore?: boolean; - globals?: string[]; - plugins?: string[]; - envs?: string[]; - parser?: string; - eslint?: string; - cwd?: string; - extensions?: string[]; -} -export declare function getPackageOptions(cwd?: string): PackageOptions; -export declare function _resolveTSConfigPath(cwd: string, settingsProjectPath?: string | string[]): string | string[] | undefined; -export {}; diff --git a/dist/options/package-options.js b/dist/options/package-options.js deleted file mode 100644 index c4b2145..0000000 --- a/dist/options/package-options.js +++ /dev/null @@ -1,50 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports._resolveTSConfigPath = exports.getPackageOptions = void 0; -const path_1 = require("path"); -const pkg_conf_1 = require("pkg-conf"); -const default_options_1 = require("./default-options"); -function getPackageOptions(cwd) { - var _a; - const settings = (0, pkg_conf_1.sync)('ts-standard', { cwd }); - cwd = (_a = cwd !== null && cwd !== void 0 ? cwd : settings.cwd) !== null && _a !== void 0 ? _a : process.cwd(); - return { - files: settings.files, - project: exports._resolveTSConfigPath(cwd, settings.project), - fix: settings.fix, - report: settings.report, - ignore: settings.ignore, - noDefaultIgnore: settings.noDefaultIgnore, - globals: settings.globals, - plugins: settings.plugins, - envs: settings.envs, - parser: settings.parser, - eslint: settings.eslint, - cwd, - extensions: settings.extensions - }; -} -exports.getPackageOptions = getPackageOptions; -function _resolveTSConfigPath(cwd, settingsProjectPath) { - if (settingsProjectPath != null) { - if (Array.isArray(settingsProjectPath)) { - return settingsProjectPath - .map((p) => { - const settingsPath = (0, path_1.join)(cwd, p); - if ((0, default_options_1._isValidPath)(settingsPath)) { - return settingsPath; - } - return undefined; - }) - .filter((str) => str !== undefined); - } - else { - const settingsPath = (0, path_1.join)(cwd, settingsProjectPath); - if ((0, default_options_1._isValidPath)(settingsPath)) { - return settingsPath; - } - } - } - return undefined; -} -exports._resolveTSConfigPath = _resolveTSConfigPath; diff --git a/dist/standard-engine-api.d.ts b/dist/standard-engine-api.d.ts deleted file mode 100644 index 63a1834..0000000 --- a/dist/standard-engine-api.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { TSStandardLintOptions, LintCallBack } from './ts-standard'; -export interface ParseOptions { - cwd?: string; - filename?: string; -} -export declare function parseOpts(options: ParseOptions): ParseOptions; -export declare function lintText(text: string, options: LintCallBack): void; -export declare function lintText(text: string, options: TSStandardLintOptions & ParseOptions, callback: LintCallBack): void; -export declare function lintFiles(files: string[], options: LintCallBack): void; -export declare function lintFiles(files: string[], options: TSStandardLintOptions & ParseOptions, callback: LintCallBack): void; diff --git a/dist/standard-engine-api.js b/dist/standard-engine-api.js deleted file mode 100644 index 4311433..0000000 --- a/dist/standard-engine-api.js +++ /dev/null @@ -1,57 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.lintFiles = exports.lintText = exports.parseOpts = void 0; -const ts_standard_1 = require("./ts-standard"); -// All exports are to satisfy the `standard-engine` export interface used by -// the vscode standard extension and other editor extensions -// Yes this is a singleton and I don't really like them either -let singletonInstance; -let cachedCWD; -// Typically called before any lint calls, allows early reading and manipulation of options provided -// to the later lint call -function parseOpts(options) { - // If the singleton does not exist or a new working directory is given then create a new instance - if (singletonInstance == null || cachedCWD !== options.cwd) { - cachedCWD = options.cwd; - singletonInstance = new ts_standard_1.TSStandard({ - cwd: options.cwd - }); - } - return Object.assign({}, options); -} -exports.parseOpts = parseOpts; -function lintText(text, options, callback) { - let cb = callback; - if (typeof options === 'function') { - cb = options; - options = {}; - } - if (singletonInstance == null) { - exports.parseOpts(options); - } - let filename; - if (options.filename != null) { - // the vscode-standardjs extention provided the filename as a uri, so remove the uri component - filename = options.filename.replace('file://', ''); - } - singletonInstance - .lintText(text, Object.assign(Object.assign({}, options), { filename })) - .then((res) => cb(undefined, res)) - .catch(cb); -} -exports.lintText = lintText; -function lintFiles(files, options, callback) { - let cb = callback; - if (typeof options === 'function') { - cb = options; - options = {}; - } - if (singletonInstance == null) { - exports.parseOpts(options); - } - singletonInstance - .lintFiles(files, options) - .then((res) => cb(undefined, res)) - .catch(cb); -} -exports.lintFiles = lintFiles; diff --git a/dist/standard-reporter.d.ts b/dist/standard-reporter.d.ts deleted file mode 100644 index be26854..0000000 --- a/dist/standard-reporter.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -import type { LintResult } from 'standard-engine'; -export declare function standardReporter(isUsingStdInAndFix: boolean): (results: LintResult[]) => string; diff --git a/dist/standard-reporter.js b/dist/standard-reporter.js deleted file mode 100644 index 7ba1857..0000000 --- a/dist/standard-reporter.js +++ /dev/null @@ -1,21 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.standardReporter = void 0; -function standardReporter(isUsingStdInAndFix) { - return (lintResults) => { - const prefix = isUsingStdInAndFix ? 'ts-standard:' : ' '; - let logResults = ''; - lintResults.forEach((res) => { - res.messages.forEach((msg) => { - var _a, _b, _c, _d, _e; - logResults += - prefix + - ` ${res.filePath}:${(_b = (_a = msg.line) === null || _a === void 0 ? void 0 : _a.toString(10)) !== null && _b !== void 0 ? _b : '0'}:` + - `${(_d = (_c = msg.column) === null || _c === void 0 ? void 0 : _c.toString(10)) !== null && _d !== void 0 ? _d : '0'}: ${msg.message}` + - ` (${(_e = msg.ruleId) !== null && _e !== void 0 ? _e : ''})\n`; - }); - }); - return logResults.slice(0, logResults.length - 1); - }; -} -exports.standardReporter = standardReporter; diff --git a/dist/ts-standard.d.ts b/dist/ts-standard.d.ts deleted file mode 100644 index 314047d..0000000 --- a/dist/ts-standard.d.ts +++ /dev/null @@ -1,35 +0,0 @@ -import * as eslint from 'eslint'; -import { LintReport } from 'standard-engine'; -export interface Options { - project?: string | string[]; - fix?: boolean; - envs?: string[]; - ignore?: string[]; - noDefaultIgnore?: boolean; - globals?: string[]; - plugins?: string[]; - cwd?: string; - parser?: string; - eslint?: string | typeof eslint; - extensions?: string[]; -} -export interface TSStandardLintOptions { - fix?: boolean; - globals?: string[]; - plugins?: string[]; - envs?: string[]; - parser?: string; - cwd?: string; - filename?: string; - extensions?: string[]; -} -export declare type LintCallBack = (error?: Error, results?: LintReport) => void; -export declare class TSStandard { - private readonly standardEngine; - private readonly defaultPerLintOptions; - constructor(options?: Options); - lintText(text: string, options?: TSStandardLintOptions & { - filename?: string; - }): Promise; - lintFiles(files: string[], options?: TSStandardLintOptions): Promise; -} diff --git a/dist/ts-standard.js b/dist/ts-standard.js deleted file mode 100644 index f13d98c..0000000 --- a/dist/ts-standard.js +++ /dev/null @@ -1,84 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.TSStandard = void 0; -const eslint = require("eslint"); -const path_1 = require("path"); -const standard_engine_1 = require("standard-engine"); -const options_1 = require("./options"); -const constants_1 = require("./constants"); -class TSStandard { - constructor(options = {}) { - // Get the default/cli options - const defaultOptions = (0, options_1.getDefaultOptions)(options.cwd); - const packageOptions = (0, options_1.getPackageOptions)(options.cwd); - options = (0, options_1.mergeOptions)(defaultOptions, packageOptions, options); - // Linting requires a project file - if (options.project == null || - (Array.isArray(options.project) && options.project.length === 0)) { - throw new Error('Unable to locate the project file. A project file (tsconfig.json or ' + - 'tsconfig.eslint.json) is required in order to use ts-standard.'); - } - // Handle the case where eslint lib could be a string, library, or undefined (use default lib) - let eslintLib; - if (typeof options.eslint === 'string') { - eslintLib = require(options.eslint); - } - else if (options.eslint != null) { - eslintLib = options.eslint; - } - else { - eslintLib = eslint; - } - // Compile all the various options needed to construct standard-engine linter - const standardEngineOptions = { - cmd: constants_1.CMD, - version: constants_1.VERSION, - homepage: constants_1.HOMEPAGE, - bugs: constants_1.BUGS_URL, - tagline: constants_1.TAGLINE, - eslint: eslintLib, - eslintConfig: { - configFile: (0, path_1.resolve)((0, path_1.join)(__dirname, '../eslintrc.json')), - cwd: options.cwd, - parserOptions: { - project: options.project, - tsconfigRootDir: options.cwd - } - } - }; - this.standardEngine = new standard_engine_1.linter(standardEngineOptions); - // Set the standard-engine linter options - this.defaultPerLintOptions = { - fix: options.fix, - globals: options.globals, - plugins: options.plugins, - envs: options.envs, - parser: options.parser, - extensions: options.extensions - }; - } - async lintText(text, options) { - options = options !== null && options !== void 0 ? options : {}; - options = Object.assign(Object.assign({}, this.defaultPerLintOptions), options); - return await new Promise((resolve, reject) => { - this.standardEngine.lintText(text, options, (err, val) => { - if (err != null) { - return reject(err); - } - return resolve(val); - }); - }); - } - async lintFiles(files, options = {}) { - options = Object.assign(Object.assign({}, this.defaultPerLintOptions), options); - return await new Promise((resolve, reject) => { - this.standardEngine.lintFiles(files, options, (err, val) => { - if (err != null) { - return reject(err); - } - return resolve(val); - }); - }); - } -} -exports.TSStandard = TSStandard; diff --git a/index.js b/index.js new file mode 100644 index 0000000..6264ba9 --- /dev/null +++ b/index.js @@ -0,0 +1,13 @@ +import { StandardEngine } from 'standard-engine' + +import options from './options.js' +import { CURRENT_WORKING_DIRECTORY } from './constants.js' +import { _getTSConfigPath } from './resolve-tsconfig.js' + +const engineOptions = { ...options } + +engineOptions.eslintConfig.overrideConfig.parserOptions.project = _getTSConfigPath( + CURRENT_WORKING_DIRECTORY +) + +export default new StandardEngine(engineOptions) diff --git a/options.js b/options.js new file mode 100644 index 0000000..e510a64 --- /dev/null +++ b/options.js @@ -0,0 +1,30 @@ +import { readFileSync } from 'node:fs' +import { fileURLToPath } from 'node:url' + +import eslint from 'eslint' + +import { DEFAULT_EXTENSIONS } from './constants.js' + +// eslintConfig.overrideConfigFile have problem reading URLs and file:/// +const overrideConfigFile = fileURLToPath(new URL('./eslintrc.json', import.meta.url)) +const pkgURL = new URL('./package.json', import.meta.url) +const pkgJSON = readFileSync(pkgURL, { encoding: 'utf-8' }) +const pkg = JSON.parse(pkgJSON) + +export default { + bugs: pkg.bugs.url, + cmd: 'ts-standard', + eslint, + eslintConfig: { + overrideConfigFile, + extensions: DEFAULT_EXTENSIONS, + overrideConfig: { + parserOptions: { + project: undefined + } + } + }, + homepage: pkg.homepage, + tagline: 'Standard for TypeScript!', + version: pkg.version +} diff --git a/package.json b/package.json index 35af28b..e2bdfab 100644 --- a/package.json +++ b/package.json @@ -1,23 +1,23 @@ { "name": "ts-standard", - "description": "Typescript Standard Style based on StandardJS", - "version": "11.0.0", - "main": "dist/index.js", - "types": "dist/index.d.ts", + "description": "TypeScript Standard Style based on StandardJS", + "version": "12.0.0", + "main": "./index.js", + "type": "module", "bin": { - "ts-standard": "./bin/cmd.js" + "ts-standard": "./cli.js" }, "bugs": { "url": "https://github.com/standard/ts-standard/issues" }, "engines": { - "node": ">=12.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "author": "Todd Bluhm", "license": "MIT", "homepage": "https://github.com/standard/ts-standard", "keywords": [ - "Typescript Standard Style", + "TypeScript Standard Style", "check", "checker", "code", @@ -42,88 +42,37 @@ "tslint" ], "ts-standard": { - "project": "./tsconfig.eslint.json", - "ignore": [ - "dist" - ], - "env": [ - "node", - "jest" - ] - }, - "commitlint": { - "extends": [ - "@commitlint/config-conventional" - ] - }, - "jest": { - "preset": "ts-jest", - "testEnvironment": "node", - "clearMocks": true, - "bail": true, - "testPathIgnorePatterns": [ - "/dist" - ], - "globals": { - "ts-jest": { - "diagnostics": false - } - }, - "collectCoverageFrom": [ - "**/*.ts", - "!**/*.d.ts", - "!**/dist/**", - "!**/node_modules/**" - ] - }, - "prettier": { - "singleQuote": true, - "semi": false, - "trailingComma": "none" + "ignore": ["test/fixtures/*.ts"] }, "scripts": { - "test": "jest", - "coverage": "npm test -- --coverage", - "coveralls": "coveralls < coverage/lcov.info", - "lint": "prettier --write '**/*.{ts*,js*,yml}' && bin/cmd.js --fix && tsc -p './tsconfig.eslint.json' --noEmit", - "build": "tsc", - "watch": "tsc -w", - "postinstall": "husky install", - "prepublishOnly": "pinst --disable", - "postpublish": "pinst --enable" + "lint:standard": "standard", + "lint:ts-standard": "node cli.js", + "lint:commit": "commitlint", + "test": "tape test/*.js" }, "peerDependencies": { - "typescript": ">=3.8" + "typescript": "*" }, "dependencies": { - "@typescript-eslint/eslint-plugin": "^4.26.1", - "eslint": "^7.28.0", - "eslint-config-standard": "^16.0.3", - "eslint-config-standard-jsx": "^10.0.0", - "eslint-config-standard-with-typescript": "^21.0.1", - "eslint-plugin-import": "^2.23.4", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^5.1.0", - "eslint-plugin-react": "^7.24.0", - "get-stdin": "^8.0.0", - "minimist": "^1.2.5", - "pkg-conf": "^3.1.0", - "standard-engine": "^14.0.1" + "@typescript-eslint/eslint-plugin": "^5.0.0", + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^8.0.1", + "eslint-config-standard-jsx": "^11.0.0", + "eslint-config-standard-with-typescript": "^23.0.0", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-n": "^15.0.0", + "eslint-plugin-promise": "^6.0.0", + "eslint-plugin-react": "^7.28.0", + "minimist": "^1.2.6", + "pkg-conf": "^4.0.0", + "standard-engine": "^15.0.0" }, "devDependencies": { - "@commitlint/cli": "^14.1.0", - "@commitlint/config-conventional": "^14.1.0", - "@types/eslint": "^7.2.13", - "@types/jest": "^27.0.1", - "@types/minimist": "^1.2.1", - "@types/node": "^16.0.0", - "coveralls": "^3.1.0", - "husky": "^7.0.0", - "jest": "^26.6.3", - "pinst": "^2.1.6", - "prettier": "^2.3.1", - "ts-jest": "^26.5.1", - "ts-node": "^10.0.0", - "typescript": "^4.3.2" + "@commitlint/cli": "^17.0.3", + "@commitlint/config-conventional": "^17.0.3", + "@types/minimist": "^1.2.2", + "@types/node": "^18.0.6", + "standard": "^17.0.0", + "tape": "^5.5.3" } } diff --git a/resolve-tsconfig.js b/resolve-tsconfig.js new file mode 100644 index 0000000..12a9d2e --- /dev/null +++ b/resolve-tsconfig.js @@ -0,0 +1,79 @@ +import { join } from 'node:path' +import { statSync } from 'node:fs' + +import { packageConfigSync } from 'pkg-conf' + +import { DEFAULT_TSCONFIG_LOCATIONS } from './constants.js' + +/** + * + * @param {string} pathToValidate + * @returns {boolean} + */ +const _isValidPath = (pathToValidate) => { + try { + statSync(pathToValidate) + } catch { + return false + } + return true +} + +/** + * + * @param {string} cwd + * @param {string} settingsProjectPath + * @returns {string | undefined} + */ +export const _resolveTSConfigPath = (cwd, settingsProjectPath) => { + const settingsPath = join(cwd, settingsProjectPath) + if (_isValidPath(settingsPath)) { + return settingsPath + } + return undefined +} + +/** + * + * @param {string} cwd + * @returns {string | undefined} + */ +export const _getTSConfigFromDefaultLocations = (cwd) => { + for (const tsFile of DEFAULT_TSCONFIG_LOCATIONS) { + const absolutePath = _resolveTSConfigPath(cwd, tsFile) + if (absolutePath !== undefined) { + return absolutePath + } + } + return undefined +} + +/** + * + * @param {string} cwd + * @param {string | undefined} cliProjectOption + * @returns {string} + */ +export const _getTSConfigPath = (cwd, cliProjectOption) => { + /** @type {string | undefined} */ + let tsConfigPath = _getTSConfigFromDefaultLocations(cwd) + + if (cliProjectOption !== undefined) { + tsConfigPath = _resolveTSConfigPath(cwd, cliProjectOption) + } else { + const settings = packageConfigSync('ts-standard', { cwd }) + if (settings.project != null) { + tsConfigPath = _resolveTSConfigPath(cwd, settings.project) + } + } + + if (tsConfigPath === undefined) { + console.error( + 'Unable to locate the project file. A project file (tsconfig.json or ' + + 'tsconfig.eslint.json) is required in order to use ts-standard.' + ) + return process.exit(1) + } + + return tsConfigPath +} diff --git a/src/cli.test.ts b/src/cli.test.ts deleted file mode 100644 index 1ebc3fa..0000000 --- a/src/cli.test.ts +++ /dev/null @@ -1,642 +0,0 @@ -import * as cliLib from './cli' -import { cli, lintFiles, lintStdIn, printReport } from './cli' -import * as standardReporterLib from './standard-reporter' -import * as stylish from 'eslint/lib/cli-engine/formatters/stylish' -import * as getStdin from 'get-stdin' -import * as defaultOptionsLib from './options/default-options' -import * as packageOptionsLib from './options/package-options' -import * as cliOptionsLib from './options/cli-options' - -jest.mock('eslint/lib/cli-engine/formatters/stylish') -jest.mock('get-stdin') -const mockCustomReporter = jest.fn() -jest.mock('custom-reporter', () => mockCustomReporter, { virtual: true }) -jest.mock('custom-bad-reporter', () => undefined, { virtual: true }) - -const mockStylish = stylish as jest.MockedFunction -const mockGetStdin = getStdin as jest.MockedFunction -const mockProcess = process - -describe('cli', () => { - describe('printReport', () => { - it('should use `standard` reporter to print the lint report to console', async (): Promise => { - const consoleLogSpy = jest - .spyOn(console, 'log') - .mockImplementation((() => undefined) as any) - const consoleErrorSpy = jest - .spyOn(console, 'error') - .mockImplementation((() => undefined) as any) - const reporterSpy = jest.fn() - const standardReporterSpy = jest - .spyOn(standardReporterLib, 'standardReporter') - .mockReturnValue(reporterSpy) - await printReport( - { - errorCount: 1, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - results: [ - { - filePath: '/some/path', - messages: [ - { - column: 1, - line: 1, - endColumn: 2, - endLine: 2, - ruleId: 'custom-rule', - message: 'lint error', - fatal: true, - severity: 0, - nodeType: 'test' - } - ], - errorCount: 1, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0 - } - ] - }, - { - report: 'standard', - useStdIn: false, - fix: false - } - ) - - expect(consoleErrorSpy).toHaveBeenCalledTimes(1) - expect(consoleLogSpy).toHaveBeenCalledTimes(1) - expect(standardReporterSpy).toHaveBeenCalledTimes(1) - expect(standardReporterSpy.mock.calls[0][0]).toEqual(false) - expect(reporterSpy).toHaveBeenCalledTimes(1) - expect(reporterSpy.mock.calls[0][0]).toHaveLength(1) - }) - - it('should print fixable message if `fix` option not provided and errors are fixable', async (): Promise => { - const consoleLogSpy = jest - .spyOn(console, 'log') - .mockImplementation((() => undefined) as any) - const consoleErrorSpy = jest - .spyOn(console, 'error') - .mockImplementation((() => undefined) as any) - await printReport( - { - errorCount: 1, - warningCount: 0, - fixableErrorCount: 1, - fixableWarningCount: 0, - results: [ - { - filePath: '/some/path', - messages: [ - { - column: 1, - line: 1, - endColumn: 2, - endLine: 2, - ruleId: 'custom-rule', - message: 'lint error', - fatal: true, - severity: 0, - fix: false, - nodeType: 'test' - } - ], - errorCount: 1, - warningCount: 0, - fixableErrorCount: 1, - fixableWarningCount: 0 - } - ] - }, - { - report: 'standard', - useStdIn: false, - fix: false - } - ) - expect(consoleErrorSpy).toHaveBeenCalledTimes(2) - expect(consoleLogSpy).toHaveBeenCalledTimes(1) - }) - - it('should not print fixable message if no errors are fixable', async (): Promise => { - const consoleLogSpy = jest - .spyOn(console, 'log') - .mockImplementation((() => undefined) as any) - const consoleErrorSpy = jest - .spyOn(console, 'error') - .mockImplementation((() => undefined) as any) - await printReport( - { - errorCount: 1, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - results: [ - { - filePath: '/some/path', - messages: [ - { - column: 1, - line: 1, - endColumn: 2, - endLine: 2, - ruleId: 'custom-rule', - message: 'lint error', - fatal: true, - severity: 0, - nodeType: 'test' - } - ], - errorCount: 1, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0 - } - ] - }, - { - report: 'standard', - useStdIn: false, - fix: false - } - ) - expect(consoleErrorSpy).toHaveBeenCalledTimes(1) - expect(consoleLogSpy).toHaveBeenCalledTimes(1) - }) - - it('should log lint errors to stderror if stdin was used and fix is enabled', async (): Promise => { - const consoleLogSpy = jest - .spyOn(console, 'log') - .mockImplementation((() => undefined) as any) - const consoleErrorSpy = jest - .spyOn(console, 'error') - .mockImplementation((() => undefined) as any) - await printReport( - { - errorCount: 1, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - results: [ - { - filePath: '/some/path', - messages: [ - { - column: 1, - line: 1, - endColumn: 2, - endLine: 2, - ruleId: 'custom-rule', - message: 'lint error', - fatal: true, - severity: 0, - nodeType: 'test' - } - ], - errorCount: 1, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0 - } - ] - }, - { - report: 'standard', - useStdIn: true, - fix: true - } - ) - - expect(consoleErrorSpy).toHaveBeenCalledTimes(2) - expect(consoleLogSpy).toHaveBeenCalledTimes(0) - }) - - it('should use eslint built-in `stylish` reporter', async (): Promise => { - jest.spyOn(console, 'log').mockImplementation((() => undefined) as any) - jest.spyOn(console, 'error').mockImplementation((() => undefined) as any) - mockStylish.mockReturnValue('') - await printReport( - { - errorCount: 1, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - results: [ - { - filePath: '/some/path', - messages: [ - { - column: 1, - line: 1, - endColumn: 2, - endLine: 2, - ruleId: 'custom-rule', - message: 'lint error', - fatal: true, - severity: 0, - nodeType: 'test' - } - ], - errorCount: 1, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0 - } - ] - }, - { - report: 'stylish', - useStdIn: false, - fix: false - } - ) - expect(mockStylish).toHaveBeenCalledTimes(1) - }) - - it('should use custom lint reporter', async (): Promise => { - jest.spyOn(console, 'log').mockImplementation((() => undefined) as any) - jest.spyOn(console, 'error').mockImplementation((() => undefined) as any) - await printReport( - { - errorCount: 1, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - results: [ - { - filePath: '/some/path', - messages: [ - { - column: 1, - line: 1, - endColumn: 2, - endLine: 2, - ruleId: 'custom-rule', - message: 'lint error', - fatal: true, - severity: 0, - nodeType: 'test' - } - ], - errorCount: 1, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0 - } - ] - }, - { - report: 'custom-reporter', - useStdIn: false, - fix: false - } - ) - expect(mockCustomReporter).toHaveBeenCalledTimes(1) - }) - - it('should throw error if custom lint reporter not found', async (): Promise => { - jest.spyOn(console, 'log').mockImplementation((() => undefined) as any) - jest.spyOn(console, 'error').mockImplementation((() => undefined) as any) - expect.assertions(1) - try { - await printReport( - { - errorCount: 1, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - results: [ - { - filePath: '/some/path', - messages: [ - { - column: 1, - line: 1, - endColumn: 2, - endLine: 2, - ruleId: 'custom-rule', - message: 'lint error', - fatal: true, - severity: 0, - nodeType: 'test' - } - ], - errorCount: 1, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0 - } - ] - }, - { - report: 'bad-custom-reporter', - useStdIn: false, - fix: false - } - ) - } catch (e: any) { - expect(e.message).toMatch(/cannot find/gi) - } - }) - - it('should throw error if custom lint reporter import returns undefined', async (): Promise => { - jest.spyOn(console, 'log').mockImplementation((() => undefined) as any) - jest.spyOn(console, 'error').mockImplementation((() => undefined) as any) - expect.assertions(1) - try { - await printReport( - { - errorCount: 1, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - results: [ - { - filePath: '/some/path', - messages: [ - { - column: 1, - line: 1, - endColumn: 2, - endLine: 2, - ruleId: 'custom-rule', - message: 'lint error', - fatal: true, - severity: 0, - nodeType: 'test' - } - ], - errorCount: 1, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0 - } - ] - }, - { - report: 'custom-bad-reporter', - useStdIn: false, - fix: false - } - ) - } catch (e: any) { - expect(e.message).toMatch(/import custom/gi) - } - }) - }) - - describe('lintFiles', () => { - it('should execute `lintFiles` with the given files on the provided linter', async (): Promise => { - const mockLinter = { lintFiles: jest.fn() } - mockLinter.lintFiles.mockResolvedValue('linted!') - const options = { files: ['some/file.ts'] } - const res = await lintFiles(mockLinter as any, options) - - expect(mockLinter.lintFiles).toHaveBeenCalledTimes(1) - expect(mockLinter.lintFiles).toHaveBeenCalledWith(options.files) - expect(res).toEqual('linted!') - }) - - it('should log error and exit with code 1 if `lintFiles` fails', async (): Promise => { - const mockLinter = { lintFiles: jest.fn() } - mockLinter.lintFiles.mockRejectedValue(new Error('Bad lint!')) - const options = { files: ['some/file.ts'] } - const consoleErrorSpy = jest.spyOn(console, 'error').mockReturnValue() - const exitSpy = jest - .spyOn(mockProcess, 'exit') - .mockImplementation((() => undefined) as any) - await lintFiles(mockLinter as any, options) - expect(consoleErrorSpy).toHaveBeenCalledTimes(3) - expect(exitSpy).toHaveBeenCalledTimes(1) - expect(exitSpy).toHaveBeenCalledWith(1) - expect(mockLinter.lintFiles).toHaveBeenCalledTimes(1) - expect(mockLinter.lintFiles).toHaveBeenCalledWith(options.files) - }) - }) - - describe('lintStdIn', () => { - it('should execute `lintText` with the given stdin text on the provided linter', async (): Promise => { - const stdinText = 'I am stdin text!' - mockGetStdin.mockResolvedValueOnce(stdinText) - - const mockLinter = { lintText: jest.fn() } - mockLinter.lintText.mockResolvedValue('linted!') - - const options = { fix: false, stdInFilename: './test-file.ts' } - const res = await lintStdIn(mockLinter as any, options) - - expect(mockLinter.lintText).toHaveBeenCalledTimes(1) - expect(mockLinter.lintText.mock.calls[0][0]).toEqual(stdinText) - - expect(res).toEqual('linted!') - }) - - it('should write the fixed output to stdout', async (): Promise => { - const stdinText = 'I am stdin text!' - mockGetStdin.mockResolvedValueOnce(stdinText) - - const mockLinter = { lintText: jest.fn() } - const lintReport = { - results: [{ output: 'linted!' }] - } - mockLinter.lintText.mockResolvedValue(lintReport) - - const stdoutSpy = jest - .spyOn(process.stdout, 'write') - .mockReturnValue(true) - - const options = { fix: true, stdInFilename: './test-file.ts' } - const res = await lintStdIn(mockLinter as any, options) - - expect(mockLinter.lintText).toHaveBeenCalledTimes(1) - expect(mockLinter.lintText.mock.calls[0][0]).toEqual(stdinText) - - expect(res).toEqual(lintReport) - - expect(stdoutSpy).toHaveBeenCalledTimes(1) - expect(stdoutSpy).toHaveBeenCalledWith('linted!') - }) - - it('should write the original input to stdout if nothing fixed', async (): Promise => { - const stdinText = 'I am stdin text!' - mockGetStdin.mockResolvedValueOnce(stdinText) - - const mockLinter = { lintText: jest.fn() } - const lintResults = { - results: [{ output: undefined }] - } - mockLinter.lintText.mockResolvedValue(lintResults) - - const stdoutSpy = jest - .spyOn(process.stdout, 'write') - .mockReturnValue(true) - - const options = { fix: true, stdInFilename: './test-file.ts' } - const res = await lintStdIn(mockLinter as any, options) - - expect(mockLinter.lintText).toHaveBeenCalledTimes(1) - expect(mockLinter.lintText.mock.calls[0][0]).toEqual(stdinText) - - expect(res).toEqual(lintResults) - - expect(stdoutSpy).toHaveBeenCalledTimes(1) - expect(stdoutSpy).toHaveBeenCalledWith(stdinText) - }) - - it('should log error and exit with code 1 if `lintText` fails', async (): Promise => { - const stdinText = 'I am stdin text!' - mockGetStdin.mockResolvedValueOnce(stdinText) - - const mockLinter = { lintText: jest.fn() } - mockLinter.lintText.mockRejectedValue(new Error('Bad lint!')) - - const consoleErrorSpy = jest.spyOn(console, 'error').mockReturnValue() - const exitSpy = jest - .spyOn(mockProcess, 'exit') - .mockImplementation((() => undefined) as any) - - const options = { fix: true, stdInFilename: './test-file.ts' } - await lintStdIn(mockLinter as any, options) - - expect(mockLinter.lintText).toHaveBeenCalledTimes(1) - expect(mockLinter.lintText.mock.calls[0][0]).toEqual(stdinText) - - expect(consoleErrorSpy).toHaveBeenCalledTimes(3) - expect(exitSpy).toHaveBeenCalledTimes(1) - expect(exitSpy).toHaveBeenCalledWith(1) - }) - }) - - describe('cli', () => { - it('should log error if no project provided', async (): Promise => { - jest - .spyOn(defaultOptionsLib, 'getDefaultOptions') - .mockReturnValue({} as any) - jest - .spyOn(packageOptionsLib, 'getPackageOptions') - .mockReturnValue({} as any) - jest.spyOn(cliOptionsLib, 'getCLIOptions').mockReturnValue({} as any) - - const consoleErrorSpy = jest.spyOn(console, 'error').mockReturnValue() - const exitSpy = jest - .spyOn(mockProcess, 'exit') - .mockImplementation((() => undefined) as any) - - await cli() - - expect(consoleErrorSpy).toHaveBeenCalledTimes(1) - expect(exitSpy).toHaveBeenCalledTimes(1) - expect(exitSpy).toHaveBeenCalledWith(1) - }) - - it('should log error if empty project array is provided', async (): Promise => { - jest - .spyOn(defaultOptionsLib, 'getDefaultOptions') - .mockReturnValue({} as any) - jest - .spyOn(packageOptionsLib, 'getPackageOptions') - .mockReturnValue({} as any) - jest - .spyOn(cliOptionsLib, 'getCLIOptions') - .mockReturnValue({ project: [] } as any) - - const consoleErrorSpy = jest.spyOn(console, 'error').mockReturnValue() - const exitSpy = jest - .spyOn(mockProcess, 'exit') - .mockImplementation((() => undefined) as any) - - await cli() - - expect(consoleErrorSpy).toHaveBeenCalledTimes(1) - expect(exitSpy).toHaveBeenCalledTimes(1) - expect(exitSpy).toHaveBeenCalledWith(1) - }) - - it('should call `lintStdIn` function if `useStdIn` option provided and exit with code 0 if no errors/warnings', async (): Promise => { - jest - .spyOn(defaultOptionsLib, 'getDefaultOptions') - .mockReturnValue({ project: '/project/location.json' } as any) - jest - .spyOn(packageOptionsLib, 'getPackageOptions') - .mockReturnValue({} as any) - jest.spyOn(cliOptionsLib, 'getCLIOptions').mockReturnValue({ - useStdIn: true, - stdInFilename: './test-file.ts' - } as any) - - const lintStdInSpy = jest.spyOn(cliLib, 'lintStdIn').mockResolvedValue({ - errorCount: 0, - warningCount: 0 - } as any) - - const exitSpy = jest - .spyOn(mockProcess, 'exit') - .mockImplementation((() => undefined) as any) - - await cli() - - expect(lintStdInSpy).toHaveBeenCalledTimes(1) - expect(exitSpy).toHaveBeenCalledTimes(1) - expect(exitSpy).toHaveBeenCalledWith(0) - }) - - it('should call `lintFiles` function and `printReport` and exit with code 1 if errors found', async (): Promise => { - jest - .spyOn(defaultOptionsLib, 'getDefaultOptions') - .mockReturnValue({ project: '/project/location.json' } as any) - jest - .spyOn(packageOptionsLib, 'getPackageOptions') - .mockReturnValue({} as any) - jest.spyOn(cliOptionsLib, 'getCLIOptions').mockReturnValue({} as any) - - const lintFilesSpy = jest.spyOn(cliLib, 'lintFiles').mockResolvedValue({ - errorCount: 1, - warningCount: 1 - } as any) - const printReportSpy = jest - .spyOn(cliLib, 'printReport') - .mockResolvedValueOnce() - const exitSpy = jest - .spyOn(mockProcess, 'exit') - .mockImplementation((() => undefined) as any) - - await cli() - - expect(lintFilesSpy).toHaveBeenCalledTimes(1) - expect(printReportSpy).toHaveBeenCalledTimes(1) - expect(exitSpy).toHaveBeenCalledTimes(1) - expect(exitSpy).toHaveBeenCalledWith(1) - }) - - it('should call `lintFiles` function and `printReport` and exit with code 0 if only warnings found', async (): Promise => { - jest - .spyOn(defaultOptionsLib, 'getDefaultOptions') - .mockReturnValue({ project: '/project/location.json' } as any) - jest - .spyOn(packageOptionsLib, 'getPackageOptions') - .mockReturnValue({} as any) - jest.spyOn(cliOptionsLib, 'getCLIOptions').mockReturnValue({} as any) - - const lintFilesSpy = jest.spyOn(cliLib, 'lintFiles').mockResolvedValue({ - errorCount: 0, - warningCount: 1 - } as any) - const printReportSpy = jest - .spyOn(cliLib, 'printReport') - .mockResolvedValueOnce() - const exitSpy = jest - .spyOn(mockProcess, 'exit') - .mockImplementation((() => undefined) as any) - - await cli() - - expect(lintFilesSpy).toHaveBeenCalledTimes(1) - expect(printReportSpy).toHaveBeenCalledTimes(1) - expect(exitSpy).toHaveBeenCalledTimes(1) - expect(exitSpy).toHaveBeenCalledWith(0) - }) - }) -}) diff --git a/src/cli.ts b/src/cli.ts deleted file mode 100644 index 2863f06..0000000 --- a/src/cli.ts +++ /dev/null @@ -1,217 +0,0 @@ -import type * as eslint from 'eslint' -import type { LintReport, LintResult, LintMessage } from 'standard-engine' -import * as getStdin from 'get-stdin' -import { - getCLIOptions, - getPackageOptions, - getDefaultOptions, - mergeOptions, - DefaultOptions -} from './options' -import { TSStandard } from './ts-standard' -import { CMD, TAGLINE, BUGS_URL, HOMEPAGE } from './constants' - -export const ESLINT_BUILTIN_REPORTERS = [ - 'stylish', - 'checkstyle', - 'codeframe', - 'compact', - 'html', - 'jslint-xml', - 'json', - 'junit', - 'table', - 'tap', - 'unix', - 'visualstudio' -] - -export interface Options { - // Input methods - files?: string[] - test?: string - stdIn?: boolean - // Additional options - project?: string | string[] - fix?: boolean - report?: string - envs?: string[] - ignore?: string[] - noDefaultIgnore?: boolean - globals?: string[] - plugins?: string[] - // Really advanced options - parser?: string - eslint?: typeof eslint - extensions?: string[] -} - -export async function cli (): Promise { - // Get the default/cli options - const defaultOptions = getDefaultOptions() - const packageOptions = getPackageOptions() - const cliOptions = getCLIOptions() - - const options: DefaultOptions = mergeOptions( - defaultOptions, - packageOptions, - cliOptions - ) - - // Linting requires a project file - if ( - options.project == null || - (Array.isArray(options.project) && options.project.length === 0) - ) { - console.error( - 'Unable to locate the project file. A project file (tsconfig.json or ' + - 'tsconfig.eslint.json) is required in order to use ts-standard.' - ) - return process.exit(1) - } - - const tsStandard = new TSStandard({ - project: options.project, - fix: options.fix, - ignore: options.ignore, - noDefaultIgnore: options.noDefaultIgnore, - envs: options.envs, - globals: options.globals, - plugins: options.plugins, - parser: options.parser, - eslint: options.eslint, - cwd: options.cwd, - extensions: options.extensions - }) - - // Perform the lint operation on the given files or text - let lintReport: LintReport - if (options.useStdIn) { - lintReport = await exports.lintStdIn(tsStandard, options) - } else { - lintReport = await exports.lintFiles(tsStandard, options) - } - - // If no errors or warnings return success - if (lintReport.errorCount === 0 && lintReport.warningCount === 0) { - return process.exit(0) - } - - // Print the lint report results to console - await exports.printReport(lintReport, options) - - // Only set exit code 1 if there were errors (warnings do not count) - process.exit(lintReport.errorCount > 0 ? 1 : 0) -} - -export async function lintStdIn ( - linter: TSStandard, - options: Pick -): Promise { - // Get text from stdin - const text = await getStdin() - - // Lint the text - let lintReport: LintReport - try { - lintReport = await linter.lintText(text, { - filename: options.stdInFilename, - fix: options.fix - }) - } catch (e) { - const err = e as Error - console.error(`${CMD}: Unexpected linter output:\n`) - console.error(`${err.message}: ${err.stack as string}`) - console.error( - `\nIf you think this is a bug in \`${CMD}\`, open an issue: ` + - `${BUGS_URL}` - ) - return process.exit(1) - } - - // If we performed fixes then maybe return the fixed text - if (options.fix) { - if (lintReport.results[0].output != null) { - // Code contained fixable errors, so print the fixed code - process.stdout.write(lintReport.results[0].output) - } else { - // Code did not contain fixable errors, so print original code - process.stdout.write(text) - } - } - - return lintReport -} - -export async function lintFiles ( - linter: TSStandard, - options: Pick -): Promise { - // Lint the text - let lintReport: LintReport - try { - lintReport = await linter.lintFiles(options.files) - } catch (e) { - const err = e as Error - console.error(`${CMD}: Unexpected linter output:\n`) - console.error(`${err.message}: ${err.stack as string}`) - console.error( - `\nIf you think this is a bug in \`${CMD}\`, open an issue: ` + - `${BUGS_URL}` - ) - return process.exit(1) - } - - return lintReport -} - -export async function printReport ( - lintReport: LintReport, - options: Pick -): Promise { - // Print tag line to stay consistent with standard output - console.error(`${CMD}: ${TAGLINE} (${HOMEPAGE})`) - - // Check for any fixable rules - const isFixable: boolean = lintReport.results.some((res: LintResult) => { - return res.messages.some((msg: LintMessage) => { - return msg.fix != null - }) - }) - - // If there were fixable rules, then that means that `--fix` was not provided - if (isFixable && !options.fix) { - console.error( - `${CMD}: Run \`${CMD} --fix\` to automatically fix some problems.` - ) - } - - // Check to see if a custom reporter was given, if so use that. - let reporter: (reportResults: LintResult[]) => string - if (options.report === 'standard') { - // Use standard reporter - const { standardReporter } = await import('./standard-reporter') - reporter = standardReporter(options.useStdIn && options.fix) - } else if (ESLINT_BUILTIN_REPORTERS.includes(options.report)) { - // Use built-in eslint reporter - reporter = await import( - `eslint/lib/cli-engine/formatters/${options.report}` - ) - } else { - // Use a custom reporter - reporter = await import(options.report) - if (reporter == null) { - throw new Error('Error: Unable to import custom formatter.') - } - } - - // Print the lint results to console using the requested formatter - const outputReport = reporter(lintReport.results) - // When fixing code from stdin (`standard --stdin --fix`), the transformed - // code is printed to stdout, so print lint errors to stderr in this case. - if (options.useStdIn && options.fix) { - console.error(outputReport) - } else { - console.log(outputReport) - } -} diff --git a/src/constants.ts b/src/constants.ts deleted file mode 100644 index f09140a..0000000 --- a/src/constants.ts +++ /dev/null @@ -1,8 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/no-var-requires -const packageJson = require('../package.json') - -export const CMD = 'ts-standard' -export const TAGLINE = 'Typescript Standard Style!' -export const BUGS_URL: string = packageJson.bugs.url -export const HOMEPAGE: string = packageJson.homepage -export const VERSION: string = packageJson.version diff --git a/src/index.test.ts b/src/index.test.ts deleted file mode 100644 index f28a47e..0000000 --- a/src/index.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -import * as lib from './index' - -describe('index', () => { - it('should export the TSStandard class', () => { - expect(lib).toHaveProperty('TSStandard') - expect(lib.TSStandard).toBeDefined() - }) - - it('should export the necessary standard-engine compliant api', () => { - expect(lib).toHaveProperty('parseOpts') - expect(lib.parseOpts).toBeDefined() - expect(lib).toHaveProperty('lintText') - expect(lib.lintText).toBeDefined() - expect(lib).toHaveProperty('lintFiles') - expect(lib.lintFiles).toBeDefined() - }) -}) diff --git a/src/index.ts b/src/index.ts deleted file mode 100644 index 58f69fb..0000000 --- a/src/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -// Export the real API usage class -export * from './ts-standard' - -// Export standard-engine compatible api for so that editor extensions work as expected -export * from './standard-engine-api' diff --git a/src/options/cli-options.test.ts b/src/options/cli-options.test.ts deleted file mode 100644 index 61602db..0000000 --- a/src/options/cli-options.test.ts +++ /dev/null @@ -1,169 +0,0 @@ -import * as assert from 'assert' -import { getCLIOptions, _convertToArray } from './cli-options' - -const mockProcess = process as any - -describe('tsconfig', () => { - describe('_convertToArray', () => { - it('should convert a single value to an array', () => { - const res = _convertToArray('luke') - assert(res != null) - expect(res).toHaveLength(1) - expect(res[0]).toEqual('luke') - }) - - it('should convert a comma separate list of values to an array', () => { - const res = _convertToArray('luke,leia,obi-wan') - assert(res != null) - expect(res).toHaveLength(3) - expect(res[0]).toEqual('luke') - expect(res[1]).toEqual('leia') - expect(res[2]).toEqual('obi-wan') - }) - - it('should ignore a provided array', () => { - const data = ['luke', 'leia'] - const res = _convertToArray(data) - assert(res != null) - expect(res).toEqual(data) - }) - - it('should return undefined if provided', () => { - const res = _convertToArray(undefined) - expect(res).toBeUndefined() - }) - }) - - describe('getCLIOptions', () => { - it('should enable stdIn if `-` used as first unparsed argument', () => { - const oldArgs = mockProcess.argv - mockProcess.argv = [ - 'path', - 'node', - '-', - '--stdin-filename', - './test-file.ts' - ] - const res = getCLIOptions() - expect(res.useStdIn).toEqual(true) - mockProcess.argv = oldArgs - }) - - it('should throw error if `--stdin` used without `--stdin-filename`', () => { - // Mock argv - const oldArgs = mockProcess.argv - mockProcess.argv = ['path', 'node', '--stdin'] - - // Mock process.exit - const mockExit = jest.fn() - const oldExit = mockProcess.exit - mockProcess.exit = mockExit - - // mock console.error - const mockError = jest.fn() - const oldError = console.error - console.error = mockError - - // Run test - getCLIOptions() - - // Restore the mocked methods - mockProcess.exit = oldExit - console.error = oldError - mockProcess.argv = oldArgs - - expect(mockExit).toHaveBeenCalledTimes(1) - expect(mockExit.mock.calls[0][0]).toEqual(1) - expect(mockError).toHaveBeenCalledTimes(1) - expect(mockError.mock.calls[0][0]).toMatch('--stdin-filename') - }) - - it('should print help and exit if `--help` passed', () => { - const oldArgs = mockProcess.argv - mockProcess.argv = ['path', 'node', '--help'] - const exitSpy = jest - .spyOn(mockProcess, 'exit') - .mockImplementation((() => undefined) as any) - const consoleLogSpy = jest - .spyOn(console, 'log') - .mockImplementation((() => undefined) as any) - const res = getCLIOptions() - expect(res).toBeUndefined() - expect(exitSpy.mock.calls[0][0]).toEqual(0) - expect(consoleLogSpy.mock.calls).toHaveLength(2) - mockProcess.argv = oldArgs - }) - - it('should print version and exit if `--version` passed', () => { - const oldArgs = mockProcess.argv - mockProcess.argv = ['path', 'node', '--version'] - const exitSpy = jest - .spyOn(mockProcess, 'exit') - .mockImplementation((() => undefined) as any) - const consoleLogSpy = jest - .spyOn(console, 'log') - .mockImplementation((() => undefined) as any) - const res = getCLIOptions() - expect(res).toBeUndefined() - expect(exitSpy.mock.calls[0][0]).toEqual(0) - expect(consoleLogSpy.mock.calls).toHaveLength(1) - mockProcess.argv = oldArgs - }) - - it('should return options with the provided files', () => { - const oldArgs = mockProcess.argv - mockProcess.argv = ['path', 'node', './src/**/*.ts', './*.ts'] - const res = getCLIOptions() - expect(res.files).toEqual(mockProcess.argv.slice(2)) - mockProcess.argv = oldArgs - }) - - it('should return options with undefined files if no file provided', () => { - const oldArgs = mockProcess.argv - mockProcess.argv = ['path', 'node'] - const res = getCLIOptions() - expect(res.files).toBeUndefined() - mockProcess.argv = oldArgs - }) - - it('should return all cli options provided', () => { - const oldArgs = mockProcess.argv - mockProcess.argv = [ - 'path', - 'node', - '--fix', - '--env', - 'env1', - '--plugins', - 'plugin1', - '--parser', - 'death-star', - '-p', - './project-file.json', - '--envs', - 'env2', - '--globals', - '$', - '--report', - 'stylish', - './**/*.ts', - '--stdin-filename', - './test-file.ts' - ] - const res = getCLIOptions() - expect(res).toEqual({ - fix: true, - useStdIn: false, - files: ['./**/*.ts'], - project: ['./project-file.json'], - globals: ['$'], - plugins: ['plugin1'], - envs: ['env1', 'env2'], - parser: 'death-star', - report: 'stylish', - stdInFilename: './test-file.ts' - }) - mockProcess.argv = oldArgs - }) - }) -}) diff --git a/src/options/cli-options.ts b/src/options/cli-options.ts deleted file mode 100644 index e296d43..0000000 --- a/src/options/cli-options.ts +++ /dev/null @@ -1,133 +0,0 @@ -import * as minimist from 'minimist' -import { CMD, TAGLINE, HOMEPAGE, VERSION } from '../constants' - -interface ParsedArgs extends minimist.ParsedArgs { - fix: boolean - help: boolean - stdin: boolean - version: boolean - ['stdin-filename']?: string - globals?: string | string[] - plugins?: string | string[] - envs?: string | string[] - parser?: string - project?: string - report?: string - extensions?: string[] -} - -export interface CLIOptions { - fix: boolean - useStdIn: boolean - stdInFilename?: string - files?: string[] - project?: string[] - globals?: string[] - plugins?: string[] - envs?: string[] - parser?: string - report?: string - extensions?: string[] -} - -export function getCLIOptions (): CLIOptions { - // Parse the command line arguments - const argv = minimist(process.argv.slice(2), { - alias: { - globals: 'global', - plugins: 'plugin', - envs: 'env', - help: 'h', - project: 'p', - extensions: 'ext' - }, - boolean: ['fix', 'help', 'stdin', 'version'], - string: [ - 'globals', - 'plugins', - 'parser', - 'envs', - 'project', - 'report', - 'stdin-filename', - 'extensions' - ] - }) as ParsedArgs - - // Unix convention: Command line argument `-` is a shorthand for `--stdin` - if (argv._[0] === '-') { - argv.stdin = true - argv._.shift() - } - - // Print the help section if so requested - if (argv.help) { - console.log('%s - %s (%s)', CMD, TAGLINE, HOMEPAGE) - console.log(` -Usage: - ${CMD} [FILES...] - If FILES is omitted, all JavaScript/Typescript source files (*.js, *.jsx, *.mjs, *.cjs, *.ts, *.tsx) - in the current working directory are checked, recursively. - Certain paths (node_modules/, coverage/, vendor/, *.min.js, bundle.js, and - files/folders that begin with '.' like .git/) are automatically ignored. - Paths in a project's root .gitignore file are also automatically ignored. -Flags: - --fix Automatically fix problems - -p, --project Specify ts-config location (default: ./tsconfig.eslint.json or ./tsconfig.json) - --version Show current version - -h, --help Show usage information -Flags (advanced): - --stdin Read file text from stdin (requires using --stdin-filename) - --stdin-filename The filename and path of the contents read by stdin - --globals Declare global variable - --plugins Use custom eslint plugin - --envs Use custom eslint environment - --parser Use custom ts/js parser (default: @typescript-eslint/parser) - --report Use a built-in eslint reporter or custom eslint reporter (default: standard) - --ext, --extensions List of files extensions to lint by default (default: js,jsx,ts,tsx,mjs,cjs) - `) - return process.exit(0) - } - - // Print out the version number if requested - if (argv.version) { - console.log(VERSION) - return process.exit(0) - } - - // Get the files/globs to lint - const files = argv._.length !== 0 ? argv._ : undefined - - const options = { - fix: argv.fix, - useStdIn: argv.stdin, - stdInFilename: argv['stdin-filename'], - files, - project: exports._convertToArray(argv.project), - globals: exports._convertToArray(argv.globals), - plugins: exports._convertToArray(argv.plugins), - envs: exports._convertToArray(argv.envs), - parser: argv.parser, - report: argv.report, - extensions: exports._convertToArray(argv.extensions) - } - - if (options.useStdIn && options.stdInFilename == null) { - console.error( - 'Must provide the `--stdin-filename` flag when using the `--stdin` flag.' - ) - process.exit(1) - } - - return options -} - -export function _convertToArray (val?: string | string[]): string[] | undefined { - if (val != null && !Array.isArray(val)) { - return [...val.split(',').map((v) => v.trim())] - } - if (Array.isArray(val)) { - return val - } - return undefined -} diff --git a/src/options/default-options.test.ts b/src/options/default-options.test.ts deleted file mode 100644 index 3dfba4d..0000000 --- a/src/options/default-options.test.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { join, resolve } from 'path' -import { statSync } from 'fs' -import { - _isValidPath, - _getTSConfigFromDefaultLocations, - getDefaultOptions -} from './default-options' - -jest.mock('pkg-conf') -jest.mock('fs') - -const mockStatSync = statSync as jest.MockedFunction - -describe('default-options', () => { - describe('_isValidPath', () => { - beforeEach(() => { - mockStatSync.mockImplementation((...args) => - jest.requireActual('fs').statSync(...args) - ) - }) - - it('should return true for a valid path', () => { - const validPath = resolve(join(__dirname, '/default-options.test.ts')) - const isValid = _isValidPath(validPath) - expect(isValid).toEqual(true) - }) - - it('should return false for an invalid path', () => { - const invalidPath = join(resolve(__dirname, '/non-existent-file.ts')) - const isValid = _isValidPath(invalidPath) - expect(isValid).toEqual(false) - }) - }) - - describe('_getTSConfigFromDefaultLocations', (): void => { - it('should return a valid path if one is found', (): void => { - const validPath = _getTSConfigFromDefaultLocations(process.cwd()) - expect(validPath).toMatch(/tsconfig\.eslint\.json/gi) - }) - - it('should use custom working directory provided', (): void => { - mockStatSync.mockReturnValueOnce(true as any) - const validPath = _getTSConfigFromDefaultLocations('/custom/path') - expect(validPath).toMatch(/tsconfig\.eslint\.json/gi) - expect(validPath).toMatch(/\/custom\/path/gi) - expect(mockStatSync.mock.calls[0][0]).toMatch(/\/custom\/path/gi) - }) - - it('should return undefined if no valid path found', (): void => { - mockStatSync.mockImplementation(() => { - throw new Error('Error') - }) - const validPath = _getTSConfigFromDefaultLocations(process.cwd()) - expect(validPath).toBeUndefined() - }) - }) - - describe('getDefaultOptions', (): void => { - it('should return all default options', async (): Promise => { - mockStatSync.mockImplementation((...args) => - jest.requireActual('fs').statSync(...args) - ) - const options = await getDefaultOptions() - expect(options.files).toHaveLength(0) - expect(options.project).toMatch(/tsconfig.eslint.json/gi) - expect(options.fix).toEqual(false) - expect(options.report).toEqual('standard') - expect(options.useStdIn).toEqual(false) - expect(options.noDefaultIgnore).toEqual(false) - expect(options.eslint).toBeUndefined() - expect(options.cwd).toEqual(process.cwd()) - expect(options.ignore).toBeUndefined() - expect(options.envs).toBeUndefined() - expect(options.globals).toBeUndefined() - expect(options.plugins).toBeUndefined() - expect(options.parser).toBeUndefined() - expect(options.stdInFilename).toBeUndefined() - expect(options.extensions).toHaveLength(6) - }) - - it('should use provided cwd', async (): Promise => { - mockStatSync.mockReturnValueOnce(true as any) - const options = await getDefaultOptions('/custom/path') - expect(options.files).toHaveLength(0) - expect(options.project).toMatch(/\/custom\/path/gi) - expect(options.fix).toEqual(false) - expect(options.report).toEqual('standard') - expect(options.useStdIn).toEqual(false) - expect(options.noDefaultIgnore).toEqual(false) - expect(options.eslint).toBeUndefined() - expect(options.cwd).toEqual('/custom/path') - expect(options.ignore).toBeUndefined() - expect(options.envs).toBeUndefined() - expect(options.globals).toBeUndefined() - expect(options.plugins).toBeUndefined() - expect(options.parser).toBeUndefined() - expect(options.stdInFilename).toBeUndefined() - expect(options.extensions).toHaveLength(6) - }) - - it('should return undefined if project options not provided', async (): Promise => { - mockStatSync.mockImplementation(() => { - throw new Error('Error') - }) - const options = await getDefaultOptions() - expect(options.project).toBeUndefined() - }) - }) -}) diff --git a/src/options/default-options.ts b/src/options/default-options.ts deleted file mode 100644 index 8680de3..0000000 --- a/src/options/default-options.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { join } from 'path' -import { statSync } from 'fs' - -export const DEFAULT_TSCONFIG_LOCATIONS = [ - 'tsconfig.eslint.json', - 'tsconfig.json' -] - -export const DEFAULT_EXTENSIONS = ['js', 'jsx', 'mjs', 'cjs', 'ts', 'tsx'] - -export interface DefaultOptions { - files: string[] - fix: boolean - report: string - useStdIn: boolean - noDefaultIgnore: boolean - eslint: undefined - cwd: string - stdInFilename?: string - project?: string | string[] - ignore?: string[] - envs?: string[] - globals?: string[] - plugins?: string[] - parser?: string - extensions?: string[] -} - -export function getDefaultOptions (cwd: string = process.cwd()): DefaultOptions { - return { - files: [], - project: _getTSConfigFromDefaultLocations(cwd), - fix: false, - report: 'standard', - useStdIn: false, - noDefaultIgnore: false, - cwd, - stdInFilename: undefined, - eslint: undefined, - ignore: undefined, - envs: undefined, - globals: undefined, - plugins: undefined, - parser: undefined, - extensions: DEFAULT_EXTENSIONS - } -} - -export function _getTSConfigFromDefaultLocations ( - cwd: string -): string | undefined { - for (const tsFile of DEFAULT_TSCONFIG_LOCATIONS) { - const absPath = join(cwd, tsFile) - if (exports._isValidPath(absPath) as boolean) { - return absPath - } - } - return undefined -} - -export function _isValidPath (pathToValidate: string): boolean { - try { - statSync(pathToValidate) - } catch (e) { - return false - } - return true -} diff --git a/src/options/index.test.ts b/src/options/index.test.ts deleted file mode 100644 index 2597642..0000000 --- a/src/options/index.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { mergeOptions } from './index' - -describe('options/index.ts', () => { - describe('mergeOptions', (): void => { - it('should return the options together ignoring undefined or null keys', (): void => { - const res = mergeOptions( - { luke: 'skywalker', leia: undefined }, - { luke: undefined, leia: 'morgana' }, - { vader: 'is your father', leia: undefined } - ) - expect(res).toEqual({ - luke: 'skywalker', - leia: 'morgana', - vader: 'is your father' - }) - }) - - it('should ignore undefined values passed in', (): void => { - const res = mergeOptions( - { luke: 'skywalker', leia: undefined }, - undefined, - { vader: 'is your father', leia: undefined } - ) - expect(res).toEqual({ - luke: 'skywalker', - leia: undefined, - vader: 'is your father' - }) - }) - }) -}) diff --git a/src/options/index.ts b/src/options/index.ts deleted file mode 100644 index 38fca6f..0000000 --- a/src/options/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -export * from './default-options' -export * from './cli-options' -export * from './package-options' - -// A simple utility function to merge objects together ignoring any undefined values -export function mergeOptions (...objects: any[]): any { - const result: any = {} - for (const obj of objects) { - if (obj == null) { - continue - } - const keys = Object.keys(obj) - for (const key of keys) { - result[key] = obj[key] ?? result[key] - } - } - return result -} diff --git a/src/options/package-options.test.ts b/src/options/package-options.test.ts deleted file mode 100644 index 536793c..0000000 --- a/src/options/package-options.test.ts +++ /dev/null @@ -1,128 +0,0 @@ -import * as pkgConf from 'pkg-conf' -import { _resolveTSConfigPath, getPackageOptions } from './package-options' -import * as defaultLib from './default-options' - -describe('package-options', () => { - describe('_resolveTSConfigPath', (): void => { - const packageJsonPath = '/path/from/package.json' - - it('should return a valid path from package.json first', (): void => { - jest.spyOn(defaultLib, '_isValidPath').mockReturnValueOnce(true) - const validPath = _resolveTSConfigPath(process.cwd(), packageJsonPath) - expect(validPath).toMatch(packageJsonPath) - }) - - it('should return a valid path[] from package.json first', (): void => { - jest.spyOn(defaultLib, '_isValidPath').mockReturnValue(true) - const validPath = _resolveTSConfigPath(process.cwd(), [ - packageJsonPath, - packageJsonPath - ]) - expect(validPath).toHaveLength(2) - }) - - it('should use provided cwd when resolving the path', (): void => { - jest.spyOn(defaultLib, '_isValidPath').mockReturnValueOnce(true) - const validPath = _resolveTSConfigPath('/custom/cwd', packageJsonPath) - expect(validPath).toMatch(/\/custom\/cwd/gi) - expect(validPath).toMatch(packageJsonPath) - }) - - it('should return undefined if no valid paths found', (): void => { - jest.spyOn(defaultLib, '_isValidPath').mockReturnValueOnce(false) - const validPath = _resolveTSConfigPath(process.cwd(), packageJsonPath) - expect(validPath).toBeUndefined() - }) - - it('should return [] if no valid paths found', (): void => { - jest.spyOn(defaultLib, '_isValidPath').mockReturnValueOnce(false) - const validPath = _resolveTSConfigPath(process.cwd(), [packageJsonPath]) - expect(validPath).toStrictEqual([]) - }) - - it('should return undefined if no valid project path given', (): void => { - jest.spyOn(defaultLib, '_isValidPath').mockReturnValueOnce(true) - const validPath = _resolveTSConfigPath(process.cwd()) - expect(validPath).toBeUndefined() - }) - }) - - describe('getPackageOptions', (): void => { - it('should return a valid project path if one is found found', (): void => { - const projectRelativeLocation = './tsconfig.eslint.json' - jest.spyOn(pkgConf, 'sync').mockReturnValueOnce({ - project: projectRelativeLocation - }) - jest.spyOn(defaultLib, '_isValidPath').mockReturnValueOnce(true) - const options = getPackageOptions() - expect(options.project).toMatch(/tsconfig.eslint.json/gi) - }) - - it('should return all settings provided by package.json', (): void => { - const packageData = { - ignore: ['ignore'], - noDefaultIgnore: true, - globals: ['$', 'jquery'], - plugins: 'babel', - envs: ['lightside', 'darkside'], - parser: 'obi-wan', - cwd: '/the/path/to/the/darkside', - eslint: 'custom-linter!', - files: ['./src/**/*.ts', './*.ts'], - project: './tsconfig.json', - fix: true, - report: 'stylish' - } - jest.spyOn(pkgConf, 'sync').mockReturnValueOnce(packageData) - jest.spyOn(defaultLib, '_isValidPath').mockReturnValueOnce(true) - const options = getPackageOptions() - expect(options.files).toEqual(packageData.files) - expect(options.project).toMatch(packageData.project.slice(1)) - expect(options.project).toMatch(packageData.cwd) - expect(options.fix).toEqual(packageData.fix) - expect(options.report).toEqual(packageData.report) - expect(options.noDefaultIgnore).toEqual(packageData.noDefaultIgnore) - expect(options.globals).toEqual(packageData.globals) - expect(options.plugins).toEqual(packageData.plugins) - expect(options.envs).toEqual(packageData.envs) - expect(options.parser).toEqual(packageData.parser) - expect(options.eslint).toEqual(packageData.eslint) - }) - - it('should use cwd function arg over package.json setting', (): void => { - const packageData = { - files: ['./src/**/*.ts', './*.ts'], - project: './tsconfig.json', - cwd: '/the/path/to/the/darkside' - } - jest.spyOn(pkgConf, 'sync').mockReturnValueOnce(packageData) - jest.spyOn(defaultLib, '_isValidPath').mockReturnValueOnce(true) - const options = getPackageOptions('/custom/cwd') - expect(options.project).toMatch(packageData.project.slice(1)) - expect(options.project).toMatch(/\/custom\/cwd/) - }) - - it('should return undefined for any settings not provided by package.json', (): void => { - const packageData = { - files: ['./src/**/*.ts', './*.ts'], - project: './tsconfig.json', - fix: true, - report: 'stylish' - } - jest.spyOn(pkgConf, 'sync').mockReturnValueOnce(packageData) - jest.spyOn(defaultLib, '_isValidPath').mockReturnValueOnce(true) - const options = getPackageOptions() - expect(options.files).toEqual(packageData.files) - expect(options.project).toMatch(packageData.project.slice(1)) - expect(options.project).toMatch(process.cwd()) - expect(options.fix).toEqual(packageData.fix) - expect(options.report).toEqual(packageData.report) - expect(options.noDefaultIgnore).toBeUndefined() - expect(options.globals).toBeUndefined() - expect(options.plugins).toBeUndefined() - expect(options.envs).toBeUndefined() - expect(options.parser).toBeUndefined() - expect(options.eslint).toBeUndefined() - }) - }) -}) diff --git a/src/options/package-options.ts b/src/options/package-options.ts deleted file mode 100644 index c09f4f3..0000000 --- a/src/options/package-options.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { join } from 'path' -import { Config, sync } from 'pkg-conf' -import { _isValidPath } from './default-options' - -// Note most of these options are passed to `standard-engine` automagically because -// `standard-engine` also uses `pkg-conf` under the hood as well -interface PackageConfigOptions extends Config { - ignore?: string[] - noDefaultIgnore?: boolean - globals?: string[] - plugins?: string[] - envs?: string[] - parser?: string - cwd?: string - eslint?: string - files?: string[] - project?: string | string[] - fix?: boolean - report?: string - extensions?: string[] -} - -interface PackageOptions { - files?: string[] - project?: string | string[] - fix?: boolean - report?: string - ignore?: string[] - noDefaultIgnore?: boolean - globals?: string[] - plugins?: string[] - envs?: string[] - parser?: string - eslint?: string - cwd?: string - extensions?: string[] -} - -export function getPackageOptions (cwd?: string): PackageOptions { - const settings: PackageConfigOptions = sync('ts-standard', { cwd }) - cwd = cwd ?? settings.cwd ?? process.cwd() - return { - files: settings.files, - project: exports._resolveTSConfigPath(cwd, settings.project), - fix: settings.fix, - report: settings.report, - ignore: settings.ignore, - noDefaultIgnore: settings.noDefaultIgnore, - globals: settings.globals, - plugins: settings.plugins, - envs: settings.envs, - parser: settings.parser, - eslint: settings.eslint, - cwd, - extensions: settings.extensions - } -} - -export function _resolveTSConfigPath ( - cwd: string, - settingsProjectPath?: string | string[] -): string | string[] | undefined { - if (settingsProjectPath != null) { - if (Array.isArray(settingsProjectPath)) { - return settingsProjectPath - .map((p) => { - const settingsPath = join(cwd, p) - if (_isValidPath(settingsPath)) { - return settingsPath - } - return undefined - }) - .filter((str): str is string => str !== undefined) - } else { - const settingsPath = join(cwd, settingsProjectPath) - if (_isValidPath(settingsPath)) { - return settingsPath - } - } - } - return undefined -} diff --git a/src/standard-engine-api.test.ts b/src/standard-engine-api.test.ts deleted file mode 100644 index dea6301..0000000 --- a/src/standard-engine-api.test.ts +++ /dev/null @@ -1,300 +0,0 @@ -import type { - lintFiles as lintFilesType, - lintText as lintTextType, - parseOpts as parseOptsType -} from './standard-engine-api' -import * as tsStandardLib from './ts-standard' - -let parseOpts: typeof parseOptsType, - lintFiles: typeof lintFilesType, - lintText: typeof lintTextType -jest.mock('standard-engine') - -const customEslint = jest.fn().mockReturnThis() -jest.mock('custom-eslint', () => customEslint, { virtual: true }) - -describe('standard-engine-api', () => { - beforeEach(() => { - jest.isolateModules(() => { - ;({ parseOpts, lintFiles, lintText } = require('./standard-engine-api')) - }) - }) - - describe('parseOpts', () => { - it('should initialize a new ts-standard linter with the given settings', () => { - const tsStandardSpy = jest - .spyOn(tsStandardLib, 'TSStandard') - .mockReturnThis() - const options = { - cwd: '/some/cwd', - filename: 'file:///jedi/yoda.ts' - } - const res = parseOpts(options) - expect(res).toEqual(options) - expect(tsStandardSpy).toHaveBeenCalledTimes(1) - expect(tsStandardSpy).toHaveBeenCalledWith({ - cwd: options.cwd - }) - }) - - it('should use cached ts-standard instance on subsequent calls', () => { - const tsStandardSpy = jest - .spyOn(tsStandardLib, 'TSStandard') - .mockReturnThis() - const options = { - cwd: '/some/cwd', - filename: 'file:///jedi/yoda.ts' - } - const res = parseOpts(options) - const res2 = parseOpts(options) - expect(res).toEqual(options) - expect(res2).toEqual(options) - expect(tsStandardSpy).toHaveBeenCalledTimes(1) - expect(tsStandardSpy).toHaveBeenCalledWith({ - cwd: options.cwd - }) - }) - - it('should use re-initialize ts-standard instance if cwd changes', () => { - const tsStandardSpy = jest - .spyOn(tsStandardLib, 'TSStandard') - .mockReturnThis() - const options = { - cwd: '/some/cwd', - filename: 'file:///jedi/yoda.ts' - } - const res = parseOpts(options) - const options2 = { - cwd: '/some/other/cwd', - filename: 'file:///jedi/obi-wan.ts' - } - const res2 = parseOpts(options2) - - expect(tsStandardSpy).toHaveBeenCalledTimes(2) - expect(res).toEqual(options) - expect(tsStandardSpy).toHaveBeenNthCalledWith(1, { - cwd: options.cwd - }) - expect(res2).toEqual(options2) - expect(tsStandardSpy).toHaveBeenNthCalledWith(2, { - cwd: options2.cwd - }) - }) - }) - - describe('lintText', () => { - const options = { - fix: true, - globals: ['$'], - plugins: ['webpack'], - envs: ['node', 'browser'], - parser: 'babel' - } - let tsStandardSpy: jest.SpyInstance - const lintTextSpy = jest.fn() - beforeEach(() => { - tsStandardSpy = jest - .spyOn(tsStandardLib, 'TSStandard') - .mockReturnValueOnce({ - lintText: lintTextSpy - } as any) - }) - - it('should lint the given text without providing options', (cb): void => { - const text = 'The darkside is strong in this one.' - lintTextSpy.mockResolvedValueOnce('success!') - - expect.assertions(6) - lintText(text, (err, res) => { - try { - expect(err).toBeUndefined() - expect(res).toEqual('success!') - expect(tsStandardSpy).toHaveBeenCalledTimes(1) - expect(lintTextSpy).toHaveBeenCalledTimes(1) - expect(lintTextSpy.mock.calls[0][0]).toEqual(text) - expect(lintTextSpy.mock.calls[0][1]).toBeDefined() - cb() - } catch (e) { - cb(e) - } - }) - }) - - it('should lint the given text with the given options', (cb): void => { - const text = 'The darkside is strong in this one.' - lintTextSpy.mockResolvedValueOnce('success!') - - expect.assertions(6) - lintText(text, options, (err, res) => { - try { - expect(err).toBeUndefined() - expect(res).toEqual('success!') - expect(tsStandardSpy).toHaveBeenCalledTimes(1) - expect(lintTextSpy).toHaveBeenCalledTimes(1) - expect(lintTextSpy.mock.calls[0][0]).toEqual(text) - expect(lintTextSpy.mock.calls[0][1]).toEqual(options) - cb() - } catch (e) { - cb(e) - } - }) - }) - - it('should used cached ts-standard instance if it exists', (cb): void => { - const text = 'The darkside is strong in this one.' - lintTextSpy.mockResolvedValueOnce('success!') - - expect.assertions(3) - parseOpts({}) - expect(tsStandardSpy).toHaveBeenCalledTimes(1) - lintText(text, options, (err) => { - try { - expect(err).toBeUndefined() - expect(tsStandardSpy).toHaveBeenCalledTimes(1) - cb() - } catch (e) { - cb(e) - } - }) - }) - - it('should return an error if linting failed', (cb): void => { - const text = 'The darkside is strong in this one.' - lintTextSpy.mockRejectedValueOnce(new Error('the darkside')) - - expect.assertions(3) - lintText(text, options, (err, res) => { - try { - expect(err?.message).toMatch(/the darkside/gi) - expect(tsStandardSpy).toHaveBeenCalledTimes(1) - expect(res).toBeUndefined() - cb() - } catch (e) { - cb(e) - } - }) - }) - - it('should replace any uri tokens in filename if provided', (cb): void => { - const text = 'The darkside is strong in this one.' - lintTextSpy.mockResolvedValueOnce('success!') - - expect.assertions(6) - lintText( - text, - { - filename: 'file:///some/path/to/the/darkside' - }, - (err, res) => { - try { - expect(err).toBeUndefined() - expect(res).toEqual('success!') - expect(tsStandardSpy).toHaveBeenCalledTimes(1) - expect(lintTextSpy).toHaveBeenCalledTimes(1) - expect(lintTextSpy.mock.calls[0][0]).toEqual(text) - expect(lintTextSpy.mock.calls[0][1].filename).toEqual( - '/some/path/to/the/darkside' - ) - cb() - } catch (e) { - cb(e) - } - } - ) - }) - }) - - describe('lintFiles', () => { - const options = { - fix: true, - globals: ['$'], - plugins: ['webpack'], - envs: ['node', 'browser'], - parser: 'babel' - } - let tsStandardSpy: jest.SpyInstance - const lintFilesSpy = jest.fn() - beforeEach(() => { - tsStandardSpy = jest - .spyOn(tsStandardLib, 'TSStandard') - .mockReturnValueOnce({ - lintFiles: lintFilesSpy - } as any) - }) - - it('should lint the given files without providing options', (cb): void => { - const files = ['The darkside is strong in this one.'] - lintFilesSpy.mockResolvedValueOnce('success!') - - expect.assertions(6) - lintFiles(files, (err, res) => { - try { - expect(err).toBeUndefined() - expect(res).toEqual('success!') - expect(tsStandardSpy).toHaveBeenCalledTimes(1) - expect(lintFilesSpy).toHaveBeenCalledTimes(1) - expect(lintFilesSpy.mock.calls[0][0]).toEqual(files) - expect(lintFilesSpy.mock.calls[0][1]).toBeDefined() - cb() - } catch (e) { - cb(e) - } - }) - }) - - it('should lint the given files with the given options', (cb): void => { - const files = ['The darkside is strong in this one.'] - lintFilesSpy.mockResolvedValueOnce('success!') - - expect.assertions(6) - lintFiles(files, options, (err, res) => { - try { - expect(err).toBeUndefined() - expect(res).toEqual('success!') - expect(tsStandardSpy).toHaveBeenCalledTimes(1) - expect(lintFilesSpy).toHaveBeenCalledTimes(1) - expect(lintFilesSpy.mock.calls[0][0]).toEqual(files) - expect(lintFilesSpy.mock.calls[0][1]).toEqual(options) - cb() - } catch (e) { - cb(e) - } - }) - }) - - it('should used cached ts-standard instance if it exists', (cb): void => { - const files = ['The darkside is strong in this one.'] - lintFilesSpy.mockResolvedValueOnce('success!') - - expect.assertions(3) - parseOpts({}) - expect(tsStandardSpy).toHaveBeenCalledTimes(1) - lintFiles(files, options, (err) => { - try { - expect(err).toBeUndefined() - expect(tsStandardSpy).toHaveBeenCalledTimes(1) - cb() - } catch (e) { - cb(e) - } - }) - }) - - it('should return an error if linting failed', (cb): void => { - const files = ['The darkside is strong in this one.'] - lintFilesSpy.mockRejectedValueOnce(new Error('the darkside')) - - expect.assertions(3) - lintFiles(files, options, (err, res) => { - try { - expect(err?.message).toMatch(/the darkside/gi) - expect(tsStandardSpy).toHaveBeenCalledTimes(1) - expect(res).toBeUndefined() - cb() - } catch (e) { - cb(e) - } - }) - }) - }) -}) diff --git a/src/standard-engine-api.ts b/src/standard-engine-api.ts deleted file mode 100644 index fbc855a..0000000 --- a/src/standard-engine-api.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { TSStandard, TSStandardLintOptions, LintCallBack } from './ts-standard' - -// All exports are to satisfy the `standard-engine` export interface used by -// the vscode standard extension and other editor extensions - -// Yes this is a singleton and I don't really like them either -let singletonInstance: TSStandard -let cachedCWD: string | undefined - -export interface ParseOptions { - cwd?: string - filename?: string -} - -// Typically called before any lint calls, allows early reading and manipulation of options provided -// to the later lint call -export function parseOpts (options: ParseOptions): ParseOptions { - // If the singleton does not exist or a new working directory is given then create a new instance - if (singletonInstance == null || cachedCWD !== options.cwd) { - cachedCWD = options.cwd - singletonInstance = new TSStandard({ - cwd: options.cwd - }) - } - return { ...options } -} - -// Export some function overload signatures to help with intellisense -export function lintText (text: string, options: LintCallBack): void -export function lintText ( - text: string, - options: TSStandardLintOptions & ParseOptions, - callback: LintCallBack -): void -export function lintText ( - text: string, - options: (TSStandardLintOptions & ParseOptions) | LintCallBack, - callback?: LintCallBack -): void { - let cb = callback as LintCallBack - if (typeof options === 'function') { - cb = options - options = {} - } - if (singletonInstance == null) { - exports.parseOpts(options) - } - let filename: string | undefined - if (options.filename != null) { - // the vscode-standardjs extention provided the filename as a uri, so remove the uri component - filename = options.filename.replace('file://', '') - } - singletonInstance - .lintText(text, { - ...options, - filename - }) - .then((res) => cb(undefined, res)) - .catch(cb) -} - -// Export some function overload signatures to help with intellisense -export function lintFiles (files: string[], options: LintCallBack): void -export function lintFiles ( - files: string[], - options: TSStandardLintOptions & ParseOptions, - callback: LintCallBack -): void -export function lintFiles ( - files: string[], - options: (TSStandardLintOptions & ParseOptions) | LintCallBack, - callback?: LintCallBack -): void { - let cb = callback as LintCallBack - if (typeof options === 'function') { - cb = options - options = {} - } - if (singletonInstance == null) { - exports.parseOpts(options) - } - singletonInstance - .lintFiles(files, options) - .then((res) => cb(undefined, res)) - .catch(cb) -} diff --git a/src/standard-reporter.test.ts b/src/standard-reporter.test.ts deleted file mode 100644 index dd53674..0000000 --- a/src/standard-reporter.test.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { standardReporter } from './standard-reporter' - -describe('standard-reporter', () => { - describe('standardReporter', () => { - it('return a formatted string containing the lint results in standardjs output format', () => { - const res = standardReporter(false)([ - { - filePath: '/some/path', - messages: [ - { - column: 1, - line: 1, - endColumn: 2, - endLine: 2, - ruleId: 'custom-rule', - message: 'lint error', - fatal: true, - severity: 0, - nodeType: 'test' - } - ], - errorCount: 1, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0 - } - ]) - expect(res).toEqual(' /some/path:1:1: lint error (custom-rule)') - }) - - it('return a formatted string containing multiple lint results in standardjs output format', () => { - const res = standardReporter(false)([ - { - filePath: '/some/path', - messages: [ - { - column: 1, - line: 1, - endColumn: 2, - endLine: 2, - ruleId: 'custom-rule', - message: 'lint error', - fatal: true, - severity: 0, - nodeType: 'test' - }, - { - column: 21, - line: 35, - endColumn: 10, - endLine: 36, - ruleId: 'other-rule', - message: 'another lint error', - fatal: undefined, - severity: 2, - nodeType: 'test' - } - ], - errorCount: 1, - warningCount: 1, - fixableErrorCount: 1, - fixableWarningCount: 0 - }, - { - filePath: '/some-other/path', - messages: [ - { - column: 4, - line: 16, - endColumn: 2, - endLine: 2, - ruleId: 'other-custom-rule', - message: 'other lint error', - fatal: undefined, - severity: 1, - nodeType: 'test' - } - ], - errorCount: 0, - warningCount: 1, - fixableErrorCount: 0, - fixableWarningCount: 1 - } - ]) - expect(res).toEqual( - ` /some/path:1:1: lint error (custom-rule) - /some/path:35:21: another lint error (other-rule) - /some-other/path:16:4: other lint error (other-custom-rule)` - ) - }) - - it('when using stdin and fix, should prefix each lint message', () => { - const res = standardReporter(true)([ - { - filePath: '/some/path', - messages: [ - { - column: 1, - line: 1, - endColumn: 2, - endLine: 2, - ruleId: 'custom-rule', - message: 'lint error', - fatal: true, - severity: 0, - nodeType: 'test' - }, - { - column: 21, - line: 35, - endColumn: 10, - endLine: 36, - ruleId: 'other-rule', - message: 'another lint error', - fatal: undefined, - severity: 2, - nodeType: 'test' - } - ], - errorCount: 1, - warningCount: 1, - fixableErrorCount: 1, - fixableWarningCount: 0 - }, - { - filePath: '/some-other/path', - messages: [ - { - message: 'other lint error', - fatal: undefined, - severity: 1, - nodeType: 'test2' - } - ], - errorCount: 0, - warningCount: 1, - fixableErrorCount: 0, - fixableWarningCount: 1 - } - ]) - expect(res).toEqual( - `ts-standard: /some/path:1:1: lint error (custom-rule) -ts-standard: /some/path:35:21: another lint error (other-rule) -ts-standard: /some-other/path:0:0: other lint error ()` - ) - }) - }) -}) diff --git a/src/standard-reporter.ts b/src/standard-reporter.ts deleted file mode 100644 index ae7dfe2..0000000 --- a/src/standard-reporter.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { LintResult } from 'standard-engine' - -export function standardReporter ( - isUsingStdInAndFix: boolean -): (results: LintResult[]) => string { - return (lintResults: LintResult[]): string => { - const prefix = isUsingStdInAndFix ? 'ts-standard:' : ' ' - let logResults = '' - lintResults.forEach((res) => { - res.messages.forEach((msg) => { - logResults += - prefix + - ` ${res.filePath}:${msg.line?.toString(10) ?? '0'}:` + - `${msg.column?.toString(10) ?? '0'}: ${msg.message}` + - ` (${msg.ruleId ?? ''})\n` - }) - }) - return logResults.slice(0, logResults.length - 1) - } -} diff --git a/src/ts-standard.test.ts b/src/ts-standard.test.ts deleted file mode 100644 index 4547f41..0000000 --- a/src/ts-standard.test.ts +++ /dev/null @@ -1,244 +0,0 @@ -import * as lintLib from 'standard-engine' -import { resolve } from 'path' -import * as eslint from 'eslint' -import * as defaultOptions from './options/default-options' -import * as packageOptions from './options/package-options' -import { TSStandard, Options } from './ts-standard' -import { CMD, TAGLINE, BUGS_URL, HOMEPAGE, VERSION } from './constants' - -jest.mock('standard-engine') - -const customEslint = jest.fn().mockReturnThis() -jest.mock('custom-eslint', () => customEslint, { virtual: true }) - -describe('ts-standard', () => { - describe('TSStandard', () => { - describe('constructor', () => { - it('should initialize a new standard-engine linter with the given settings', () => { - const linterSpy = jest.spyOn(lintLib, 'linter').mockReturnThis() - - const options = { - cwd: '/some/path', - envs: ['node', 'browser'], - plugins: ['babel'], - eslint: {} as any, - fix: true, - globals: ['$', 'jquery'], - ignore: ['/some/ignore/path'], - noDefaultIgnore: true, - parser: 'typescript', - project: '/some/project.json' - } - const linter = new TSStandard(options) - expect(linter).toBeDefined() - expect(linterSpy).toHaveBeenCalledTimes(1) - expect(linterSpy).toHaveBeenCalledWith({ - cmd: CMD, - version: VERSION, - homepage: HOMEPAGE, - bugs: BUGS_URL, - tagline: TAGLINE, - eslint: options.eslint, - eslintConfig: { - cwd: options.cwd, - configFile: resolve(__dirname, '..', 'eslintrc.json'), - parserOptions: { - project: options.project, - tsconfigRootDir: options.cwd - } - } - }) - }) - - it('should import a custom eslinter if `string` provided', () => { - const linterSpy = jest.spyOn(lintLib, 'linter').mockReturnThis() - - const options = { - eslint: 'custom-eslint' - } - const linter = new TSStandard(options) - expect(linter).toBeDefined() - expect(linterSpy).toHaveBeenCalledTimes(1) - expect((linterSpy.mock.calls[0][0] as Options).eslint).toEqual( - customEslint - ) - }) - - it('should use the provide eslint if given', () => { - const linterSpy = jest.spyOn(lintLib, 'linter').mockReturnThis() - - const options = { - eslint: {} as any - } - const linter = new TSStandard(options) - expect(linter).toBeDefined() - expect(linterSpy).toHaveBeenCalledTimes(1) - expect((linterSpy.mock.calls[0][0] as Options).eslint).toEqual( - options.eslint - ) - }) - - it('should use the default eslint if eslint option not provided', () => { - const linterSpy = jest.spyOn(lintLib, 'linter').mockReturnThis() - const linter = new TSStandard() - expect(linter).toBeDefined() - expect(linterSpy).toHaveBeenCalledTimes(1) - expect((linterSpy.mock.calls[0][0] as Options).eslint).toEqual(eslint) - }) - - it('should throw error if no project file can be found', () => { - jest - .spyOn(defaultOptions, 'getDefaultOptions') - .mockReturnValue(undefined as any) - jest - .spyOn(packageOptions, 'getPackageOptions') - .mockReturnValue(undefined as any) - expect.assertions(1) - - try { - new TSStandard() // eslint-disable-line no-new - } catch (e: any) { - expect(e.message).toMatch(/unable to locate the project file/gi) - } - }) - }) - - describe('lintText', () => { - let tsStandard: TSStandard - const defaultOptions = { - fix: true, - globals: ['$'], - plugins: ['webpack'], - envs: ['node', 'browser'], - parser: 'babel' - } - const lintTextSpy = jest.fn() - beforeEach(() => { - jest.spyOn(lintLib, 'linter').mockReturnValue({ - lintText: lintTextSpy - } as any) - tsStandard = new TSStandard({ - ...defaultOptions, - project: '../tsconfig.eslint.json' - }) - }) - - it('should lint the given text with default options', async (): Promise => { - const text = 'The darkside is strong in this one.' - lintTextSpy.mockImplementationOnce((_text, _options, cb) => - cb(undefined, 'success!') - ) - const res = await tsStandard.lintText(text) - - expect(res).toEqual('success!') - expect(lintTextSpy).toHaveBeenCalledTimes(1) - expect(lintTextSpy.mock.calls[0][0]).toEqual(text) - expect(lintTextSpy.mock.calls[0][1]).toEqual(defaultOptions) - }) - - it('should overide default options with method options', async (): Promise => { - const text = 'The darkside is strong in this one.' - lintTextSpy.mockImplementationOnce((_text, _options, cb) => - cb(undefined, 'success!') - ) - const newOptions = { - fix: false, - globals: ['jquery'], - plugins: undefined, - envs: ['browser'], - parser: 'typescript' - } - const res = await tsStandard.lintText(text, newOptions) - - expect(res).toEqual('success!') - expect(lintTextSpy).toHaveBeenCalledTimes(1) - expect(lintTextSpy.mock.calls[0][0]).toEqual(text) - expect(lintTextSpy.mock.calls[0][1]).toEqual(newOptions) - }) - - it('should return error if linting failed', async (): Promise => { - const text = 'The darkside is strong in this one.' - lintTextSpy.mockImplementationOnce((_text, _options, cb) => - cb(new Error('the darkside')) - ) - expect.assertions(2) - - try { - await tsStandard.lintText(text) - } catch (e: any) { - expect(lintTextSpy).toHaveBeenCalledTimes(1) - expect(e.message).toMatch(/the darkside/gi) - } - }) - }) - - describe('lintFiles', () => { - let tsStandard: TSStandard - const defaultOptions = { - fix: true, - globals: ['$'], - plugins: ['webpack'], - envs: ['node', 'browser'], - parser: 'babel' - } - const lintFilesSpy = jest.fn() - beforeEach(() => { - jest.spyOn(lintLib, 'linter').mockReturnValue({ - lintFiles: lintFilesSpy - } as any) - tsStandard = new TSStandard({ - ...defaultOptions, - project: '../tsconfig.eslint.json' - }) - }) - - it('should lint the given files with default options', async (): Promise => { - const files = ['The darkside is strong in this one.'] - lintFilesSpy.mockImplementationOnce((_files, _options, cb) => - cb(undefined, 'success!') - ) - const res = await tsStandard.lintFiles(files) - - expect(res).toEqual('success!') - expect(lintFilesSpy).toHaveBeenCalledTimes(1) - expect(lintFilesSpy.mock.calls[0][0]).toEqual(files) - expect(lintFilesSpy.mock.calls[0][1]).toEqual(defaultOptions) - }) - - it('should overide default options with method options', async (): Promise => { - const files = ['The darkside is strong in this one.'] - lintFilesSpy.mockImplementationOnce((_files, _options, cb) => - cb(undefined, 'success!') - ) - const newOptions = { - fix: false, - globals: ['jquery'], - plugins: undefined, - envs: ['browser'], - parser: 'typescript' - } - const res = await tsStandard.lintFiles(files, newOptions) - - expect(res).toEqual('success!') - expect(lintFilesSpy).toHaveBeenCalledTimes(1) - expect(lintFilesSpy.mock.calls[0][0]).toEqual(files) - expect(lintFilesSpy.mock.calls[0][1]).toEqual(newOptions) - }) - - it('should return error if linting failed', async (): Promise => { - const files = ['The darkside is strong in this one.'] - lintFilesSpy.mockImplementationOnce((_files, _options, cb) => - cb(new Error('the darkside')) - ) - expect.assertions(2) - - try { - await tsStandard.lintFiles(files) - } catch (e: any) { - expect(lintFilesSpy).toHaveBeenCalledTimes(1) - expect(e.message).toMatch(/the darkside/gi) - } - }) - }) - }) -}) diff --git a/src/ts-standard.ts b/src/ts-standard.ts deleted file mode 100644 index 6e13e22..0000000 --- a/src/ts-standard.ts +++ /dev/null @@ -1,132 +0,0 @@ -import * as eslint from 'eslint' -import { join, resolve } from 'path' -import { linter as Linter, LintReport } from 'standard-engine' -import { getDefaultOptions, getPackageOptions, mergeOptions } from './options' -import { CMD, TAGLINE, BUGS_URL, HOMEPAGE, VERSION } from './constants' - -export interface Options { - project?: string | string[] - fix?: boolean - envs?: string[] - ignore?: string[] - noDefaultIgnore?: boolean - globals?: string[] - plugins?: string[] - cwd?: string - // Really advanced options - parser?: string - eslint?: string | typeof eslint - extensions?: string[] -} - -export interface TSStandardLintOptions { - fix?: boolean - globals?: string[] - plugins?: string[] - envs?: string[] - parser?: string - cwd?: string - filename?: string - extensions?: string[] -} - -export type LintCallBack = (error?: Error, results?: LintReport) => void - -export class TSStandard { - private readonly standardEngine: Linter - private readonly defaultPerLintOptions: TSStandardLintOptions - - constructor (options: Options = {}) { - // Get the default/cli options - const defaultOptions = getDefaultOptions(options.cwd) - const packageOptions = getPackageOptions(options.cwd) - options = mergeOptions(defaultOptions, packageOptions, options) - - // Linting requires a project file - if ( - options.project == null || - (Array.isArray(options.project) && options.project.length === 0) - ) { - throw new Error( - 'Unable to locate the project file. A project file (tsconfig.json or ' + - 'tsconfig.eslint.json) is required in order to use ts-standard.' - ) - } - - // Handle the case where eslint lib could be a string, library, or undefined (use default lib) - let eslintLib: typeof eslint - if (typeof options.eslint === 'string') { - eslintLib = require(options.eslint) - } else if (options.eslint != null) { - eslintLib = options.eslint - } else { - eslintLib = eslint - } - - // Compile all the various options needed to construct standard-engine linter - const standardEngineOptions = { - cmd: CMD, - version: VERSION, - homepage: HOMEPAGE, - bugs: BUGS_URL, - tagline: TAGLINE, - eslint: eslintLib, - eslintConfig: { - configFile: resolve(join(__dirname, '../eslintrc.json')), - cwd: options.cwd, - parserOptions: { - project: options.project, - tsconfigRootDir: options.cwd - } - } - } - this.standardEngine = new Linter(standardEngineOptions) - - // Set the standard-engine linter options - this.defaultPerLintOptions = { - fix: options.fix, - globals: options.globals, - plugins: options.plugins, - envs: options.envs, - parser: options.parser, - extensions: options.extensions - } - } - - async lintText ( - text: string, - options?: TSStandardLintOptions & { filename?: string } - ): Promise { - options = options ?? {} - options = { - ...this.defaultPerLintOptions, - ...options - } - return await new Promise((resolve, reject) => { - this.standardEngine.lintText(text, options, (err, val) => { - if (err != null) { - return reject(err) - } - return resolve(val as LintReport) - }) - }) - } - - async lintFiles ( - files: string[], - options: TSStandardLintOptions = {} - ): Promise { - options = { - ...this.defaultPerLintOptions, - ...options - } - return await new Promise((resolve, reject) => { - this.standardEngine.lintFiles(files, options, (err, val) => { - if (err != null) { - return reject(err) - } - return resolve(val as LintReport) - }) - }) - } -} diff --git a/src/types/standard-engine.d.ts b/src/types/standard-engine.d.ts deleted file mode 100644 index ec92331..0000000 --- a/src/types/standard-engine.d.ts +++ /dev/null @@ -1,57 +0,0 @@ -declare module 'standard-engine' { - // Typing ported over from @types/eslint - export type Severity = 0 | 1 | 2 - export interface LintMessage { - column?: number - line?: number - endColumn?: number - endLine?: number - ruleId?: string - message: string - nodeType: string - fatal?: true - severity: Severity - fix?: Object - source?: string - } - - export interface FixReport { - fixed: boolean - output: string - messages: LintMessage[] - } - - export interface LintResult { - filePath: string - messages: LintMessage[] - errorCount: number - warningCount: number - fixableErrorCount: number - fixableWarningCount: number - output?: string - source?: string - } - - export interface LintReport { - results: LintResult[] - errorCount: number - warningCount: number - fixableErrorCount: number - fixableWarningCount: number - } - // End eslint types porting - - export class linter { - constructor (options: any) - lintText ( - text: string, - options: any, - callback: (err?: Error, results?: LintReport) => void - ): void - lintFiles ( - files: string[], - options: any, - callback: (err?: Error, results?: LintReport) => void - ): void - } -} diff --git a/src/types/stylish.d.ts b/src/types/stylish.d.ts deleted file mode 100644 index 15beb2e..0000000 --- a/src/types/stylish.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -declare module 'eslint/lib/cli-engine/formatters/stylish' { - import type { LintResult } from 'standard-engine' - - function stylish (results: LintResult[]): string - namespace stylish {} - export = stylish -} diff --git a/test/api.js b/test/api.js new file mode 100644 index 0000000..cfe60f9 --- /dev/null +++ b/test/api.js @@ -0,0 +1,23 @@ +import test from 'tape' + +import tsStandard from '../index.js' + +test('api: lintFiles', async (t) => { + t.plan(2) + const [result] = await tsStandard.lintFiles([ + 'cli.js', + 'constants.js', + 'index.js', + 'options.js', + 'resolve-tsconfig.js' + ]) + t.equal(typeof result, 'object', 'result is an object') + t.equal(result.errorCount, 0) +}) + +test('api: lintText', async (t) => { + t.plan(2) + const [result] = await tsStandard.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') +}) diff --git a/test/cli.js b/test/cli.js new file mode 100644 index 0000000..043a53c --- /dev/null +++ b/test/cli.js @@ -0,0 +1,18 @@ +import { fileURLToPath } from 'node:url' + +import test from 'tape' +import crossSpawn from 'cross-spawn' + +const CLI_PATH = fileURLToPath(new URL('../cli.js', import.meta.url)) + +test('command line usage: --help', (t) => { + t.plan(1) + + const child = crossSpawn(CLI_PATH, ['--help']) + child.on('error', (err) => { + t.fail(err) + }) + child.on('close', (code) => { + t.equal(code, 0, 'zero exit code') + }) +}) diff --git a/test/fixtures/typescript-no-errors.ts b/test/fixtures/typescript-no-errors.ts new file mode 100644 index 0000000..102eb78 --- /dev/null +++ b/test/fixtures/typescript-no-errors.ts @@ -0,0 +1,5 @@ +const foo = 1 +const bar = (argument: number): number => { + return argument + foo +} +bar(foo) diff --git a/test/fixtures/typescript-with-errors.ts b/test/fixtures/typescript-with-errors.ts new file mode 100644 index 0000000..866984e --- /dev/null +++ b/test/fixtures/typescript-with-errors.ts @@ -0,0 +1,5 @@ +const foo = 1 +const bar = (argument: number) => { + return argument + foo +} +bar(foo) diff --git a/test/validate-config.js b/test/validate-config.js new file mode 100644 index 0000000..68987a9 --- /dev/null +++ b/test/validate-config.js @@ -0,0 +1,38 @@ +import test from 'tape' + +import tsStandard from '../index.js' + +test('load config in eslint to validate all rule syntax is correct', async (t) => { + t.plan(1) + const code = 'const foo = 1\nconst bar = function () {}\nbar(foo)\n' + const [result] = await tsStandard.lintText(code) + t.equal(result.errorCount, 0) +}) + +test('ensure we allow top level await', async (t) => { + t.plan(1) + const code = + 'const foo = await 1\nconst bar = function () {}\nawait bar(foo)\n' + const [result] = await tsStandard.lintText(code) + t.equal(result.errorCount, 0) +}) + +test('lint correctly fixtures/typescript-with-errors.ts', async (t) => { + const [result] = await tsStandard.lintFiles( + ['test/fixtures/typescript-with-errors.ts'], + { usePackageJson: false } + ) + t.equal(typeof result, 'object', 'result is an object') + t.equal(result.errorCount, 1) + t.equal(result.messages.length, 1) + t.equal(result.messages[0].ruleId, '@typescript-eslint/explicit-function-return-type') +}) + +test('lint correctly fixtures/typescript-no-errors.ts', async (t) => { + const [result] = await tsStandard.lintFiles( + ['test/fixtures/typescript-no-errors.ts'], + { usePackageJson: false } + ) + t.equal(typeof result, 'object', 'result is an object') + t.equal(result.errorCount, 0) +}) diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json deleted file mode 100644 index 54a50dd..0000000 --- a/tsconfig.eslint.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "compilerOptions": { - "outDir": "./dist", - "target": "es2017", - "module": "commonjs", - "strict": true, - "declaration": true, - "resolveJsonModule": true, - "lib": ["es2018", "es2019", "es2020"], - "allowJs": true, - "checkJs": true, - "forceConsistentCasingInFileNames": true, - "isolatedModules": true, - "newLine": "lf", - "noImplicitReturns": true, - "noUnusedLocals": true, - "noUnusedParameters": true - }, - "include": ["src", "bin", "*.json"] -} diff --git a/tsconfig.json b/tsconfig.json index 9574c22..79303f1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,21 +1,14 @@ { "compilerOptions": { - "outDir": "./dist", - "target": "es2017", - "module": "commonjs", + "target": "ESNext", + "module": "ESNext", + "lib": ["ESNext"], + "moduleResolution": "node", + "rootDir": "./", "strict": true, - "declaration": true, - "resolveJsonModule": true, - "lib": ["es2018", "es2019", "es2020"], + "skipLibCheck": true, + "esModuleInterop": true, "allowJs": true, - "checkJs": true, - "forceConsistentCasingInFileNames": true, - "isolatedModules": true, - "newLine": "lf", - "noImplicitReturns": true, - "noUnusedLocals": true, - "noUnusedParameters": true - }, - "include": ["src/**/*.ts"], - "exclude": ["src/**/*.test.ts"] + "noEmit": true + } }