From 6de494fd685a107f3a9a371e663a1f8d68d6d31f Mon Sep 17 00:00:00 2001 From: Andres Suarez Date: Tue, 16 May 2017 07:16:52 -0400 Subject: [PATCH] Merge with eslint-plugin-prettify (#21) * Chore: Use eslint-plugin-prettier without symlink * Chore: Lint .eslintrc.js via CLI automatically * Chore: Update remaining repo links * New: Replace implementation with line-by-line reporting (fixes #17) This code is originally from https://github.com/zertosh/eslint-plugin-prettify/commit/2fb5dfbb52a2577180bf8fcccb83b3fa7aa4d69c. It's has been adjusted to conform to the existing ESLint style. * Fix: Use non-global line ending regex * Fix: Use unicode literals instead of escapes in showInvisibles * Fix: Use documented range form when calling insertTextAfterRange * Fix: Un-shebang test file * Fix: ESLint 4.x compat (handle shebangs and no deprecated APIs) --- .eslintignore | 1 + .eslintrc.js | 14 +- CHANGELOG.md | 2 - CONTRIBUTING.md | 2 +- LICENSE.md | 2 +- README.md | 130 +++++++++------ eslint-plugin-prettier.js | 313 ++++++++++++++++++++++++++++++++++++ lib/index.js | 18 --- lib/rules/prettier.js | 75 --------- package.json | 21 ++- test/invalid/01.txt | 16 ++ test/invalid/02.txt | 17 ++ test/invalid/03.txt | 17 ++ test/invalid/04.txt | 16 ++ test/invalid/05.txt | 16 ++ test/invalid/06.txt | 16 ++ test/invalid/07.txt | 17 ++ test/invalid/08.txt | 16 ++ test/invalid/09.txt | 16 ++ test/invalid/10.txt | 20 +++ test/invalid/11-a.txt | 20 +++ test/invalid/11-b.txt | 20 +++ test/invalid/11-c.txt | 22 +++ test/invalid/12.txt | 18 +++ test/invalid/13.txt | 18 +++ test/invalid/14.txt | 23 +++ test/invalid/15.txt | 16 ++ test/invalid/16.txt | 15 ++ test/invalid/17.txt | 15 ++ test/invalid/18.txt | 17 ++ test/invalid/19.txt | 20 +++ test/prettier.js | 92 +++++++++++ tests/lib/rules/prettier.js | 61 ------- 33 files changed, 881 insertions(+), 221 deletions(-) create mode 100644 .eslintignore create mode 100644 eslint-plugin-prettier.js delete mode 100644 lib/index.js delete mode 100644 lib/rules/prettier.js create mode 100644 test/invalid/01.txt create mode 100644 test/invalid/02.txt create mode 100644 test/invalid/03.txt create mode 100644 test/invalid/04.txt create mode 100644 test/invalid/05.txt create mode 100644 test/invalid/06.txt create mode 100644 test/invalid/07.txt create mode 100644 test/invalid/08.txt create mode 100644 test/invalid/09.txt create mode 100644 test/invalid/10.txt create mode 100644 test/invalid/11-a.txt create mode 100644 test/invalid/11-b.txt create mode 100644 test/invalid/11-c.txt create mode 100644 test/invalid/12.txt create mode 100644 test/invalid/13.txt create mode 100644 test/invalid/14.txt create mode 100644 test/invalid/15.txt create mode 100644 test/invalid/16.txt create mode 100644 test/invalid/17.txt create mode 100644 test/invalid/18.txt create mode 100644 test/invalid/19.txt create mode 100644 test/prettier.js delete mode 100644 tests/lib/rules/prettier.js diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..09a8422e --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +!.eslintrc.js diff --git a/.eslintrc.js b/.eslintrc.js index d2c34d70..4ff74862 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,17 +1,11 @@ 'use strict'; -const fs = require('fs'); -const path = require('path'); -const PACKAGE_NAME = require('./package').name; -const SYMLINK_LOCATION = path.join(__dirname, 'node_modules', PACKAGE_NAME); - -// Symlink node_modules/{package name} to this directory so that ESLint resolves this plugin name correctly. -if (!fs.existsSync(SYMLINK_LOCATION)) { - fs.symlinkSync(__dirname, SYMLINK_LOCATION); -} +// Register ourselves as a plugin to avoid `node_modules` trickery. +const Plugins = require('eslint/lib/config/plugins'); +Plugins.define('prettier', require('.')); module.exports = { - plugins: ['node', 'eslint-plugin', PACKAGE_NAME], + plugins: ['node', 'eslint-plugin', 'prettier'], extends: [ 'not-an-aardvark/node', 'plugin:node/recommended', diff --git a/CHANGELOG.md b/CHANGELOG.md index 87ca1fd0..c278617b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,5 +16,3 @@ * Breaking: Make prettier a peerDependency ([#1](https://github.com/not-an-aardvark/eslint-plugin-prettier/issues/1)) ([d8a8992](https://github.com/not-an-aardvark/eslint-plugin-prettier/commit/d8a89922ddc6b747c474b62a0948deba6ea2657d)) * Docs: add repo url to package.json ([2474bc9](https://github.com/not-an-aardvark/eslint-plugin-prettier/commit/2474bc9dd3f05dbd0b1fec38e27bc91a9cb0f1c7)) * Docs: suggest prettier-eslint if eslint rules disagree with prettier ([3414437](https://github.com/not-an-aardvark/eslint-plugin-prettier/commit/341443754ae231a17d82f037f8b35663257d282a)) - - diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ea96efe7..5747e3ae 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,4 +18,4 @@ npm test This is an [ESLint](http://eslint.org) plugin. Documentation for the APIs that it uses can be found on ESLint's [Working with Plugins](http://eslint.org/docs/developer-guide/working-with-plugins) page. -This plugin is used to lint itself. The style is checked when `npm test` is run, and the build will fail if there are any linting errors. You can use `npm run lint -- --fix` to fix some linting errors. To run the tests without running the linter, you can use `node_modules/.bin/mocha tests --recursive`. +This plugin is used to lint itself. The style is checked when `npm test` is run, and the build will fail if there are any linting errors. You can use `npm run lint -- --fix` to fix some linting errors. To run the tests without running the linter, you can use `node_modules/.bin/mocha`. diff --git a/LICENSE.md b/LICENSE.md index efa2b866..cc7c8dda 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,7 +1,7 @@ The MIT License (MIT) ===================== -Copyright © 2017 Teddy Katz +Copyright © 2017 Andres Suarez and Teddy Katz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/README.md b/README.md index 604f9b88..d958599d 100644 --- a/README.md +++ b/README.md @@ -1,78 +1,118 @@ # eslint-plugin-prettier [![Build Status](https://travis-ci.org/prettier/eslint-plugin-prettier.svg?branch=master)](https://travis-ci.org/prettier/eslint-plugin-prettier) -Runs [prettier](https://github.com/jlongster/prettier) as an eslint rule +Runs [Prettier](https://github.com/prettier/prettier) as an [ESLint](http://eslint.org) rule and reports differences as individual ESLint issues. -## Installation +## Sample -You'll first need to install [ESLint](http://eslint.org): +```js +error: Insert `,` (prettier/prettier) at pkg/commons-atom/ActiveEditorRegistry.js:22:25: + 20 | import { + 21 | observeActiveEditorsDebounced, +> 22 | editorChangesDebounced + | ^ + 23 | } from './debounced';; + 24 | + 25 | import {observableFromSubscribeFunction} from '../commons-node/event'; -``` -$ npm install eslint --save-dev -``` -Next, install `prettier`: +error: Delete `;` (prettier/prettier) at pkg/commons-atom/ActiveEditorRegistry.js:23:21: + 21 | observeActiveEditorsDebounced, + 22 | editorChangesDebounced +> 23 | } from './debounced';; + | ^ + 24 | + 25 | import {observableFromSubscribeFunction} from '../commons-node/event'; + 26 | import {cacheWhileSubscribed} from '../commons-node/observable'; -``` -$ npm install prettier --save-dev + +2 errors found. ``` -Finally, install `eslint-plugin-prettier`: +> `./node_modules/.bin/eslint --format codeframe pkg/commons-atom/ActiveEditorRegistry.js` (code from [nuclide](https://github.com/facebook/nuclide)). -``` -$ npm install eslint-plugin-prettier --save-dev -``` +## Installation -**Note:** If you installed ESLint globally (using the `-g` flag) then you must also install `eslint-plugin-prettier` globally. +```sh +npm install --save-dev prettier eslint-plugin-prettier +``` -## Usage +**_`eslint-plugin-prettier` does not install Prettier or ESLint for you._** _You must install these yourself._ -Add `prettier` to the plugins section of your `.eslintrc` configuration file. You can omit the `eslint-plugin-` prefix: +Then, in your `.eslintrc`: ```json { - "plugins": [ - "prettier" - ] + "plugins": [ + "prettier" + ], + "rules": { + "prettier/prettier": "error" + } } ``` +## Options -Then configure the `prettier` rule under the `rules` section: +* The first option: -```json -{ - "rules": { - "prettier/prettier": "error" - } -} -``` + - Objects are passed directly to Prettier as [options](https://github.com/prettier/prettier#api). Example: + + ```json + "prettier/prettier": ["error", {"singleQuote": true, "parser": "flow"}] + ``` -You can also pass [`prettier` configuration](https://github.com/prettier/prettier#api) as an option: + - Or the string `"fb"` may be used to set "Facebook style" defaults: -```json -{ - "rules": { - "prettier/prettier": ["error", {"trailingComma": "es5", "singleQuote": true}] - } -} -``` + ```json + "prettier/prettier": ["error", "fb"] + ``` -The rule will report an error if your code does not match `prettier` style. The rule is autofixable -- if you run `eslint` with the `--fix` flag, your code will be formatted according to `prettier` style. + Equivalent to: ---- + ```json + "prettier/prettier": ["error", { + "singleQuote": true, + "trailingComma": "all", + "bracketSpacing": false, + "jsxBracketSameLine": true, + "parser": "flow" + }] + ``` -This plugin works best if you disable all other ESLint rules relating to code formatting, and only enable rules that detect patterns in the AST. (If another active ESLint rule disagrees with `prettier` about how code should be formatted, it will be impossible to avoid lint errors.) You can use [eslint-config-prettier](https://github.com/lydell/eslint-config-prettier) to disable all formatting-related ESLint rules. If your desired formatting does not match the `prettier` output, you should use a different tool such as [prettier-eslint](https://github.com/kentcdodds/prettier-eslint) instead. +* The second option: -## Migrating to 2.0.0 + - A string with a pragma that triggers this rule. By default, this rule applies to all files. However, if you set a pragma (this option), only files with that pragma in the heading docblock will be checked. All pragmas must start with `@`. Example: -Starting in 2.0.0, `prettier` is a peerDependency of this module, rather than a dependency. This means that you can use any version of `prettier` with this plugin, but it also means that you need to install it separately and include it as a devDependency in your project. + ```json + "prettier/prettier": ["error", null, "@prettier"] + ``` -To install `prettier`, use: + Only files with `@prettier` in the heading docblock will be checked: -```bash -npm install prettier --save-dev -``` + ```js + /** @prettier */ + + console.log(1 + 2 + 3); + ``` + + Or: + + ```js + /** + * @prettier + */ + + console.log(4 + 5 + 6); + ``` + + _This option is useful if you're migrating a large codebase and already use pragmas like `@flow`._ + +* The rule is autofixable -- if you run `eslint` with the `--fix` flag, your code will be formatted according to `prettier` style. + +--- + +This plugin works best if you disable all other ESLint rules relating to code formatting, and only enable rules that detect patterns in the AST. (If another active ESLint rule disagrees with `prettier` about how code should be formatted, it will be impossible to avoid lint errors.) You can use [eslint-config-prettier](https://github.com/prettier/eslint-config-prettier) to disable all formatting-related ESLint rules. If your desired formatting does not match the `prettier` output, you should use a different tool such as [prettier-eslint](https://github.com/prettier/prettier-eslint) instead. ## Contributing -See [CONTRIBUTING.md](https://github.com/not-an-aardvark/eslint-plugin-prettier/blob/master/CONTRIBUTING.md) +See [CONTRIBUTING.md](https://github.com/prettier/eslint-plugin-prettier/blob/master/CONTRIBUTING.md) diff --git a/eslint-plugin-prettier.js b/eslint-plugin-prettier.js new file mode 100644 index 00000000..909f04d8 --- /dev/null +++ b/eslint-plugin-prettier.js @@ -0,0 +1,313 @@ +/** + * @fileoverview Runs `prettier` as an ESLint rule. + * @author Andres Suarez + */ + +'use strict'; + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const diff = require('fast-diff'); +const docblock = require('jest-docblock'); + +// ------------------------------------------------------------------------------ +// Constants +// ------------------------------------------------------------------------------ + +// Preferred Facebook style. +const FB_PRETTIER_OPTIONS = { + singleQuote: true, + trailingComma: 'all', + bracketSpacing: false, + jsxBracketSameLine: true, + parser: 'flow' +}; + +const LINE_ENDING_RE = /\r\n|[\r\n\u2028\u2029]/; + +// ------------------------------------------------------------------------------ +// Privates +// ------------------------------------------------------------------------------ + +// Lazily-loaded Prettier. +let prettier; + +// ------------------------------------------------------------------------------ +// Helpers +// ------------------------------------------------------------------------------ + +/** + * Gets the location of a given index in the source code for a given context. + * @param {RuleContext} context - The ESLint rule context. + * @param {number} index - An index in the source code. + * @returns {Object} An object containing numeric `line` and `column` keys. + */ +function getLocFromIndex(context, index) { + // If `sourceCode.getLocFromIndex` is available from ESLint, use it. + // Otherwise, use the private version from eslint/lib/ast-utils. + // `sourceCode.getLocFromIndex` was added in ESLint 3.16.0. + const sourceCode = context.getSourceCode(); + if (typeof sourceCode.getLocFromIndex === 'function') { + return sourceCode.getLocFromIndex(index); + } + const astUtils = require('eslint/lib/ast-utils'); + return astUtils.getLocationFromRangeIndex(sourceCode, index); +} + +/** + * Converts invisible characters to a commonly recognizable visible form. + * @param {string} str - The string with invisibles to convert. + * @returns {string} The converted string. + */ +function showInvisibles(str) { + let ret = ''; + for (let i = 0; i < str.length; i++) { + switch (str[i]) { + case ' ': + ret += '·'; // Middle Dot, \u00B7 + break; + case '\n': + ret += '⏎'; // Return Symbol, \u23ce + break; + case '\t': + ret += '↹'; // Left Arrow To Bar Over Right Arrow To Bar, \u21b9 + break; + default: + ret += str[i]; + break; + } + } + return ret; +} + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +/** + * Reports issues where the context's source code differs from the Prettier + formatted version. + * @param {RuleContext} context - The ESLint rule context. + * @param {string} prettierSource - The Prettier formatted source. + * @returns {void} + */ +function reportDifferences(context, prettierSource) { + // fast-diff returns the differences between two texts as a series of + // INSERT, DELETE or EQUAL operations. The results occur only in these + // sequences: + // /-> INSERT -> EQUAL + // EQUAL | /-> EQUAL + // \-> DELETE | + // \-> INSERT -> EQUAL + // Instead of reporting issues at each INSERT or DELETE, certain sequences + // are batched together and are reported as a friendlier "replace" operation: + // - A DELETE immediately followed by an INSERT. + // - Any number of INSERTs and DELETEs where the joining EQUAL of one's end + // and another's beginning does not have line endings (i.e. issues that occur + // on contiguous lines). + + const source = context.getSourceCode().text; + const results = diff(source, prettierSource); + + const batch = []; + let offset = 0; // NOTE: INSERT never advances the offset. + while (results.length) { + const result = results.shift(); + const op = result[0]; + const text = result[1]; + switch (op) { + case diff.INSERT: + case diff.DELETE: + batch.push(result); + break; + case diff.EQUAL: + if (results.length) { + if (batch.length) { + if (LINE_ENDING_RE.test(text)) { + flush(); + offset += text.length; + } else { + batch.push(result); + } + } else { + offset += text.length; + } + } + break; + default: + throw new Error(`Unexpected fast-diff operation "${op}"`); + } + if (batch.length && !results.length) { + flush(); + } + } + + function flush() { + let aheadDeleteText = ''; + let aheadInsertText = ''; + while (batch.length) { + const next = batch.shift(); + const op = next[0]; + const text = next[1]; + switch (op) { + case diff.INSERT: + aheadInsertText += text; + break; + case diff.DELETE: + aheadDeleteText += text; + break; + case diff.EQUAL: + aheadDeleteText += text; + aheadInsertText += text; + break; + } + } + if (aheadDeleteText && aheadInsertText) { + reportReplace(context, offset, aheadDeleteText, aheadInsertText); + } else if (!aheadDeleteText && aheadInsertText) { + reportInsert(context, offset, aheadInsertText); + } else if (aheadDeleteText && !aheadInsertText) { + reportDelete(context, offset, aheadDeleteText); + } + offset += aheadDeleteText.length; + } +} + +/** + * Reports an "Insert ..." issue where text must be inserted. + * @param {RuleContext} context - The ESLint rule context. + * @param {number} offset - The source offset where to insert text. + * @param {string} text - The text to be inserted. + * @returns {void} + */ +function reportInsert(context, offset, text) { + const pos = getLocFromIndex(context, offset); + const range = [offset, offset]; + context.report({ + message: 'Insert `{{ code }}`', + data: { code: showInvisibles(text) }, + loc: { start: pos, end: pos }, + fix(fixer) { + return fixer.insertTextAfterRange(range, text); + } + }); +} + +/** + * Reports a "Delete ..." issue where text must be deleted. + * @param {RuleContext} context - The ESLint rule context. + * @param {number} offset - The source offset where to delete text. + * @param {string} text - The text to be deleted. + * @returns {void} + */ +function reportDelete(context, offset, text) { + const start = getLocFromIndex(context, offset); + const end = getLocFromIndex(context, offset + text.length); + const range = [offset, offset + text.length]; + context.report({ + message: 'Delete `{{ code }}`', + data: { code: showInvisibles(text) }, + loc: { start, end }, + fix(fixer) { + return fixer.removeRange(range); + } + }); +} + +/** + * Reports a "Replace ... with ..." issue where text must be replaced. + * @param {RuleContext} context - The ESLint rule context. + * @param {number} offset - The source offset where to replace deleted text + with inserted text. + * @param {string} deleteText - The text to be deleted. + * @param {string} insertText - The text to be inserted. + * @returns {void} + */ +function reportReplace(context, offset, deleteText, insertText) { + const start = getLocFromIndex(context, offset); + const end = getLocFromIndex(context, offset + deleteText.length); + const range = [offset, offset + deleteText.length]; + context.report({ + message: 'Replace `{{ deleteCode }}` with `{{ insertCode }}`', + data: { + deleteCode: showInvisibles(deleteText), + insertCode: showInvisibles(insertText) + }, + loc: { start, end }, + fix(fixer) { + return fixer.replaceTextRange(range, insertText); + } + }); +} + +// ------------------------------------------------------------------------------ +// Module Definition +// ------------------------------------------------------------------------------ + +module.exports.rules = { + prettier: { + meta: { + fixable: 'code', + schema: [ + // Prettier options: + { + anyOf: [ + { enum: [null, 'fb'] }, + { type: 'object', properties: {}, additionalProperties: true } + ] + }, + // Pragma: + { type: 'string', pattern: '^@\\w+$' } + ] + }, + create(context) { + const prettierOptions = context.options[0] === 'fb' + ? FB_PRETTIER_OPTIONS + : context.options[0]; + + const pragma = context.options[1] + ? context.options[1].slice(1) // Remove leading @ + : null; + + const sourceCode = context.getSourceCode(); + const source = sourceCode.text; + + // The pragma is only valid if it is found in a block comment at the very + // start of the file. + if (pragma) { + // ESLint 3.x reports the shebang as a "Line" node, while ESLint 4.x + // reports it as a "Shebang" node. This works for both versions: + const hasShebang = source.startsWith('#!'); + const allComments = sourceCode.getAllComments(); + const firstComment = hasShebang ? allComments[1] : allComments[0]; + if ( + !(firstComment && + firstComment.type === 'Block' && + firstComment.loc.start.line === (hasShebang ? 2 : 1) && + firstComment.loc.start.column === 0) + ) { + return {}; + } + const parsed = docblock.parse(firstComment.value); + if (parsed[pragma] !== '') { + return {}; + } + } + + return { + Program() { + if (!prettier) { + // Prettier is expensive to load, so only load it if needed. + prettier = require('prettier'); + } + const prettierSource = prettier.format(source, prettierOptions); + if (source !== prettierSource) { + reportDifferences(context, prettierSource); + } + } + }; + } + } +}; diff --git a/lib/index.js b/lib/index.js deleted file mode 100644 index 48051120..00000000 --- a/lib/index.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * @fileoverview Runs prettier as an eslint rule - * @author Teddy Katz - */ - -'use strict'; - -// ------------------------------------------------------------------------------ -// Requirements -// ------------------------------------------------------------------------------ - -const requireIndex = require('requireindex'); - -// ------------------------------------------------------------------------------ -// Plugin Definition -// ------------------------------------------------------------------------------ -// import all rules in lib/rules -module.exports.rules = requireIndex(__dirname + '/rules'); diff --git a/lib/rules/prettier.js b/lib/rules/prettier.js deleted file mode 100644 index 1ae1d058..00000000 --- a/lib/rules/prettier.js +++ /dev/null @@ -1,75 +0,0 @@ -/** - * @fileoverview runs `prettier` as an eslint rule - * @author Teddy Katz - */ - -'use strict'; - -const util = require('util'); -const prettier = require('prettier'); - -// ------------------------------------------------------------------------------ -// Rule Definition -// ------------------------------------------------------------------------------ -module.exports = { - meta: { fixable: 'code', schema: [{ type: 'object' }] }, - create(context) { - const sourceCode = context.getSourceCode(); - - /** - * Gets the location of a given index in the source code - * @param {number} index An index in the source code - * @returns {object} An object containing numberic `line` and `column` keys - */ - function getLocation(index) { - // If sourceCode.getLocFromIndex is available from eslint, use it. - // Otherwise, use the private version from eslint/lib/ast-utils. - - return sourceCode.getLocFromIndex - ? sourceCode.getLocFromIndex(index) - : require('eslint/lib/ast-utils').getLocationFromRangeIndex( - sourceCode, - index - ); - } - - return { - Program() { - // This isn't really very performant (prettier needs to reparse the text). - // However, I don't think it's possible to run `prettier` on an ESTree AST. - const desiredText = prettier.format( - sourceCode.text, - context.options[0] - ); - - if (sourceCode.text !== desiredText) { - // Find the first character that differs - const firstBadIndex = Array.from(desiredText).findIndex( - (char, index) => char !== sourceCode.text[index] - ); - const expectedChar = firstBadIndex === -1 - ? 'EOF' - : desiredText[firstBadIndex]; - const foundChar = firstBadIndex >= sourceCode.text.length - ? 'EOF' - : firstBadIndex === -1 - ? sourceCode.text[desiredText.length] - : sourceCode.text[firstBadIndex]; - - context.report({ - loc: getLocation( - firstBadIndex === -1 ? desiredText.length : firstBadIndex - ), - message: 'Follow `prettier` formatting (expected {{expectedChar}} but found {{foundChar}}).', - data: { - expectedChar: util.inspect(expectedChar), - foundChar: util.inspect(foundChar) - }, - fix: fixer => - fixer.replaceTextRange([0, sourceCode.text.length], desiredText) - }); - } - } - }; - } -}; diff --git a/package.json b/package.json index d2343af1..02e8ac1e 100644 --- a/package.json +++ b/package.json @@ -5,24 +5,29 @@ "keywords": [ "eslint", "eslintplugin", - "eslint-plugin" + "eslint-plugin", + "prettier" ], "author": "Teddy Katz", - "main": "lib/index.js", + "files": [ + "eslint-plugin-prettier.js" + ], + "main": "eslint-plugin-prettier.js", "scripts": { - "lint": "eslint .eslintrc.js lib/ tests/ --ignore-pattern !.eslintrc.js", - "test": "npm run lint && mocha tests --recursive" + "lint": "eslint .", + "test": "npm run lint && mocha" }, "repository": { "type": "git", - "url": "git+https://github.com/not-an-aardvark/eslint-plugin-prettier.git" + "url": "git+https://github.com/prettier/eslint-plugin-prettier.git" }, "bugs": { - "url": "https://github.com/not-an-aardvark/eslint-plugin-prettier/issues" + "url": "https://github.com/prettier/eslint-plugin-prettier/issues" }, - "homepage": "https://github.com/not-an-aardvark/eslint-plugin-prettier#readme", + "homepage": "https://github.com/prettier/eslint-plugin-prettier#readme", "dependencies": { - "requireindex": "~1.1.0" + "fast-diff": "^1.1.1", + "jest-docblock": "^20.0.1" }, "peerDependencies": { "eslint": "^3.14.1", diff --git a/test/invalid/01.txt b/test/invalid/01.txt new file mode 100644 index 00000000..efc556c0 --- /dev/null +++ b/test/invalid/01.txt @@ -0,0 +1,16 @@ +CODE: +a();;;;;; + +OUTPUT: +a(); + +OPTIONS: +[] + +ERRORS: +[ + { + message: 'Delete `;;;;;`', + line: 1, column: 5, endLine: 1, endColumn: 10, + }, +] diff --git a/test/invalid/02.txt b/test/invalid/02.txt new file mode 100644 index 00000000..8368378f --- /dev/null +++ b/test/invalid/02.txt @@ -0,0 +1,17 @@ +CODE: +a();;; +;;; + +OUTPUT: +a(); + +OPTIONS: +[] + +ERRORS: +[ + { + message: 'Delete `;;⏎;;;`', + line: 1, column: 5, endLine: 2, endColumn: 4, + }, +] diff --git a/test/invalid/03.txt b/test/invalid/03.txt new file mode 100644 index 00000000..4376ed4a --- /dev/null +++ b/test/invalid/03.txt @@ -0,0 +1,17 @@ +CODE: + a();;; +;;; + +OUTPUT: +a(); + +OPTIONS: +[] + +ERRORS: +[ + { + message: 'Replace `··a();;;⏎;;` with `a()`', + line: 1, column: 1, endLine: 2, endColumn: 3, + }, +] diff --git a/test/invalid/04.txt b/test/invalid/04.txt new file mode 100644 index 00000000..4ea8c5eb --- /dev/null +++ b/test/invalid/04.txt @@ -0,0 +1,16 @@ +CODE: +var foo= ""; + +OUTPUT: +var foo = ""; + +OPTIONS: +[] + +ERRORS: +[ + { + message: 'Insert `·`', + line: 1, column: 8, endLine: 1, endColumn: 8, + }, +] diff --git a/test/invalid/05.txt b/test/invalid/05.txt new file mode 100644 index 00000000..8044e88b --- /dev/null +++ b/test/invalid/05.txt @@ -0,0 +1,16 @@ +CODE: +var foo=""; + +OUTPUT: +var foo = ""; + +OPTIONS: +[] + +ERRORS: +[ + { + message: 'Replace `=` with `·=·`', + line: 1, column: 8, endLine: 1, endColumn: 9, + }, +] diff --git a/test/invalid/06.txt b/test/invalid/06.txt new file mode 100644 index 00000000..cdfc47a6 --- /dev/null +++ b/test/invalid/06.txt @@ -0,0 +1,16 @@ +CODE: +var foo='';;; + +OUTPUT: +var foo = ""; + +OPTIONS: +[] + +ERRORS: +[ + { + message: 'Replace `=\'\';;` with `·=·""`', + line: 1, column: 8, endLine: 1, endColumn: 13, + }, +] diff --git a/test/invalid/07.txt b/test/invalid/07.txt new file mode 100644 index 00000000..ee81827e --- /dev/null +++ b/test/invalid/07.txt @@ -0,0 +1,17 @@ +CODE: +var foo='';; +;; + +OUTPUT: +var foo = ""; + +OPTIONS: +[] + +ERRORS: +[ + { + message: 'Replace `=\'\';;⏎;` with `·=·""`', + line: 1, column: 8, endLine: 2, endColumn: 2, + }, +] diff --git a/test/invalid/08.txt b/test/invalid/08.txt new file mode 100644 index 00000000..dc72ac94 --- /dev/null +++ b/test/invalid/08.txt @@ -0,0 +1,16 @@ +CODE: +var foo =''; + +OUTPUT: +var foo = ""; + +OPTIONS: +[] + +ERRORS: +[ + { + message: 'Replace `\'\'` with `·""`', + line: 1, column: 10, endLine: 1, endColumn: 12, + }, +] diff --git a/test/invalid/09.txt b/test/invalid/09.txt new file mode 100644 index 00000000..bb557390 --- /dev/null +++ b/test/invalid/09.txt @@ -0,0 +1,16 @@ +CODE: +var foo =""; + +OUTPUT: +var foo = ''; + +OPTIONS: +[{singleQuote: true}] + +ERRORS: +[ + { + message: 'Replace `""` with `·\'\'`', + line: 1, column: 10, endLine: 1, endColumn: 12, + }, +] diff --git a/test/invalid/10.txt b/test/invalid/10.txt new file mode 100644 index 00000000..bb04d6d4 --- /dev/null +++ b/test/invalid/10.txt @@ -0,0 +1,20 @@ +CODE: +var a = { +b: 1 +}; + +OUTPUT: +var a = { + b: 1 +}; + +OPTIONS: +[{useTabs: true}] + +ERRORS: +[ + { + message: 'Insert `↹`', + line: 2, column: 1, endLine: 2, endColumn: 1, + }, +] diff --git a/test/invalid/11-a.txt b/test/invalid/11-a.txt new file mode 100644 index 00000000..39ced840 --- /dev/null +++ b/test/invalid/11-a.txt @@ -0,0 +1,20 @@ +CODE: +var a = { + b: '', +}; + +OUTPUT: +var a = { + b: "" +}; + +OPTIONS: +[] + +ERRORS: +[ + { + message: 'Replace `\'\',` with `""`', + line: 2, column: 6, endLine: 2, endColumn: 9, + }, +] diff --git a/test/invalid/11-b.txt b/test/invalid/11-b.txt new file mode 100644 index 00000000..0417b073 --- /dev/null +++ b/test/invalid/11-b.txt @@ -0,0 +1,20 @@ +CODE: +var a = { + b: '', +}; + +OUTPUT: +var a = { + b: "" +}; + +OPTIONS: +[null] + +ERRORS: +[ + { + message: 'Replace `\'\',` with `""`', + line: 2, column: 6, endLine: 2, endColumn: 9, + }, +] diff --git a/test/invalid/11-c.txt b/test/invalid/11-c.txt new file mode 100644 index 00000000..5f4db24a --- /dev/null +++ b/test/invalid/11-c.txt @@ -0,0 +1,22 @@ +CODE: +/** @format */ +var a = { + b: "" +}; + +OUTPUT: +/** @format */ +var a = { + b: '', +}; + +OPTIONS: +['fb'] + +ERRORS: +[ + { + message: 'Replace `""` with `\'\',`', + line: 3, column: 6, endLine: 3, endColumn: 8, + }, +] diff --git a/test/invalid/12.txt b/test/invalid/12.txt new file mode 100644 index 00000000..1373e442 --- /dev/null +++ b/test/invalid/12.txt @@ -0,0 +1,18 @@ +CODE: +/** @format */ +var foo = ''; + +OUTPUT: +/** @format */ +var foo = ""; + +OPTIONS: +[null, "@format"] + +ERRORS: +[ + { + message: 'Replace `\'\'` with `""`', + line: 2, column: 11, endLine: 2, endColumn: 13, + }, +] diff --git a/test/invalid/13.txt b/test/invalid/13.txt new file mode 100644 index 00000000..6b07ceb0 --- /dev/null +++ b/test/invalid/13.txt @@ -0,0 +1,18 @@ +CODE: +var foo ='';var bar ='';var baz =''; + +OUTPUT: +var foo = ""; +var bar = ""; +var baz = ""; + +OPTIONS: +[] + +ERRORS: +[ + { + message: 'Replace `\'\';var·bar·=\'\';var·baz·=\'\'` with `·"";⏎var·bar·=·"";⏎var·baz·=·""`', + line: 1, column: 10, endLine: 1, endColumn: 36, + }, +] diff --git a/test/invalid/14.txt b/test/invalid/14.txt new file mode 100644 index 00000000..5e38ab98 --- /dev/null +++ b/test/invalid/14.txt @@ -0,0 +1,23 @@ +CODE: +var foo ='';var bar =''; +var baz =''; + +OUTPUT: +var foo = ""; +var bar = ""; +var baz = ""; + +OPTIONS: +[] + +ERRORS: +[ + { + message: 'Replace `\'\';var·bar·=\'\'` with `·"";⏎var·bar·=·""`', + line: 1, column: 10, endLine: 1, endColumn: 24, + }, + { + message: 'Replace `\'\'` with `·""`', + line: 2, column: 10, endLine: 2, endColumn: 12, + }, +] diff --git a/test/invalid/15.txt b/test/invalid/15.txt new file mode 100644 index 00000000..48ce7d30 --- /dev/null +++ b/test/invalid/15.txt @@ -0,0 +1,16 @@ +CODE: +var foo = { "bar": "", "baz": "" }; + +OUTPUT: +var foo = { bar: "", baz: "" }; + +OPTIONS: +[] + +ERRORS: +[ + { + message: 'Replace `"bar":·"",·"baz"` with `bar:·"",·baz`', + line: 1, column: 13, endLine: 1, endColumn: 29, + }, +] diff --git a/test/invalid/16.txt b/test/invalid/16.txt new file mode 100644 index 00000000..e4b1d416 --- /dev/null +++ b/test/invalid/16.txt @@ -0,0 +1,15 @@ +CODE: +var foo = '' +OUTPUT: +var foo = ""; + +OPTIONS: +[] + +ERRORS: +[ + { + message: 'Replace `\'\'` with `"";⏎`', + line: 1, column: 11, endLine: 1, endColumn: 13, + }, +] diff --git a/test/invalid/17.txt b/test/invalid/17.txt new file mode 100644 index 00000000..889f570f --- /dev/null +++ b/test/invalid/17.txt @@ -0,0 +1,15 @@ +CODE: +var foo = ""; +OUTPUT: +var foo = ""; + +OPTIONS: +[] + +ERRORS: +[ + { + message: 'Insert `⏎`', + line: 1, column: 14, endLine: 1, endColumn: 14, + }, +] diff --git a/test/invalid/18.txt b/test/invalid/18.txt new file mode 100644 index 00000000..1595da21 --- /dev/null +++ b/test/invalid/18.txt @@ -0,0 +1,17 @@ +CODE: + +var foo = ""; + +OUTPUT: +var foo = ""; + +OPTIONS: +[] + +ERRORS: +[ + { + message: 'Delete `⏎`', + line: 1, column: 1, endLine: 2, endColumn: 1, + }, +] diff --git a/test/invalid/19.txt b/test/invalid/19.txt new file mode 100644 index 00000000..be5cf6b8 --- /dev/null +++ b/test/invalid/19.txt @@ -0,0 +1,20 @@ +CODE: +#!/usr/bin/env node +/** @format */ +var foo = ''; + +OUTPUT: +#!/usr/bin/env node +/** @format */ +var foo = ""; + +OPTIONS: +[null, '@format'] + +ERRORS: +[ + { + message: 'Replace `\'\'` with `""`', + line: 3, column: 11, endLine: 3, endColumn: 13, + }, +] diff --git a/test/prettier.js b/test/prettier.js new file mode 100644 index 00000000..83edfe65 --- /dev/null +++ b/test/prettier.js @@ -0,0 +1,92 @@ +/** + * @fileoverview Runs `prettier` as an ESLint rule. + * @author Andres Suarez + */ + +'use strict'; + +// This test is optimized for debuggability. +// Please do not attempt to DRY this file or dynamically load the fixtures. + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const fs = require('fs'); +const path = require('path'); + +const rule = require('..').rules.prettier; +const RuleTester = require('eslint').RuleTester; + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +const ruleTester = new RuleTester(); + +ruleTester.run('prettier', rule, { + valid: [ + // Correct style. + { code: '"";\n' }, + // No pragma = No prettier check. + { code: '""\n', options: [null, '@format'] }, + // Facebook style uses single quotes. + { code: `'';\n`, options: ['fb'] }, + // Facebook style but missing pragma. + { code: `"";\n`, options: ['fb', '@format'] }, + // Facebook style with pragma. + { code: `/** @format */\n'';\n`, options: ['fb', '@format'] }, + // Shebang with pragma. + { code: `#!/bin/node\n/** @format */\n"";\n`, options: [null, '@format'] } + ], + invalid: [ + '01', + '02', + '03', + '04', + '05', + '06', + '07', + '08', + '09', + '10', + '11-a', + '11-b', + '11-c', + '12', + '13', + '14', + '15', + '16', + '17', + '18', + '19' + ].map(loadInvalidFixture) +}); + +// ------------------------------------------------------------------------------ +// Helpers +// ------------------------------------------------------------------------------ + +/** + * Reads a fixture file and returns an "invalid" case for use by RuleTester. + * The fixture format aims to reduce the pain of debugging offsets by keeping + * the lines and columns of the test code as close to what the rule will report + * as possible. + * @param {string} name - Fixture basename. + * @returns {Object} A {code, output, options, errors} test object. + */ +function loadInvalidFixture(name) { + const filename = path.join(__dirname, 'invalid', name + '.txt'); + const src = fs.readFileSync(filename, 'utf8'); + const sections = src + .split(/^[A-Z]+:\n/m) + .map(x => x.replace(/(?=\n)\n$/, '')); + const item = { + code: sections[1], + output: sections[2], + options: eval(sections[3]), // eslint-disable-line no-eval + errors: eval(sections[4]) // eslint-disable-line no-eval + }; + return item; +} diff --git a/tests/lib/rules/prettier.js b/tests/lib/rules/prettier.js deleted file mode 100644 index e4057a52..00000000 --- a/tests/lib/rules/prettier.js +++ /dev/null @@ -1,61 +0,0 @@ -/** - * @fileoverview runs `prettier` as an eslint rule - * @author Teddy Katz - */ - -'use strict'; - -// ------------------------------------------------------------------------------ -// Requirements -// ------------------------------------------------------------------------------ - -const rule = require('../../../lib/rules/prettier'); -const RuleTester = require('eslint').RuleTester; - -// ------------------------------------------------------------------------------ -// Tests -// ------------------------------------------------------------------------------ -const ruleTester = new RuleTester(); - -ruleTester.run('prettier', rule, { - valid: [ - 'foo(bar);\n', - 'foo("bar");\n', - { code: "foo('bar');\n", options: [{ singleQuote: true }] } - ], - invalid: [ - { - code: 'foo(bar )', - output: 'foo(bar);\n', - errors: [ - { - line: 1, - column: 8, - message: "Follow `prettier` formatting (expected ')' but found ' ')." - } - ] - }, - { - code: 'foo(bar);', - output: 'foo(bar);\n', - errors: [ - { - line: 1, - column: 10, - message: "Follow `prettier` formatting (expected '\\n' but found 'EOF')." - } - ] - }, - { - code: 'foo(bar);\n\n', - output: 'foo(bar);\n', - errors: [ - { - line: 2, - column: 1, - message: "Follow `prettier` formatting (expected 'EOF' but found '\\n')." - } - ] - } - ] -});