├── .editorconfig ├── .eslintrc.js ├── .github ├── CODEOWNERS ├── dependabot.yml └── workflows │ └── actions.yml ├── .gitignore ├── .npmignore ├── .npmrc ├── LICENSE.txt ├── README.md ├── docs └── rules │ ├── charset.md │ ├── eol-last.md │ ├── indent.md │ ├── linebreak-style.md │ └── no-trailing-spaces.md ├── examples └── flat-config │ ├── .editorconfig │ ├── README.md │ ├── eslint.config.js │ ├── package.json │ └── src │ ├── invalid.ts │ └── valid.js ├── lib ├── base.js ├── clone.js └── rules │ ├── charset.js │ ├── eol-last.js │ ├── indent.js │ ├── linebreak-style.js │ └── no-trailing-spaces.js ├── main.js ├── package.json ├── test-packages ├── failure │ └── missing-ts-eslint │ │ ├── .eslintrc.js │ │ ├── main.ts │ │ └── package.json ├── success │ ├── base-rules-conflict │ │ ├── .eslintrc.js │ │ ├── main.js │ │ └── package.json │ ├── js │ │ ├── .eslintrc.js │ │ ├── main.js │ │ └── package.json │ └── ts │ │ ├── .eslintrc.js │ │ ├── main.ts │ │ └── package.json └── test.sh └── tests ├── configs └── default │ └── .editorconfig └── lib └── rules ├── editorconfig-ts.js └── editorconfig.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | root: true, 5 | extends: "phanective/node", 6 | 7 | env: { 8 | node: true, 9 | es6: true, 10 | }, 11 | parserOptions: { 12 | ecmaVersion: "latest", 13 | }, 14 | ignorePatterns: [ "test-packages/**" ], 15 | rules: { 16 | "node/no-unpublished-require": "off", 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @phanect 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | assignees: 8 | - "phanect" 9 | groups: 10 | security: 11 | applies-to: "security-updates" 12 | update-types: 13 | - "minor" 14 | - "patch" 15 | security-breaking: 16 | applies-to: "security-updates" 17 | update-types: 18 | - "major" 19 | compatible: 20 | applies-to: "version-updates" 21 | update-types: 22 | - "minor" 23 | - "patch" 24 | less-prioritized: 25 | applies-to: "version-updates" 26 | patterns: 27 | - "eslint" 28 | - "eslint-plugin-*" 29 | - "eslint-config-*" 30 | 31 | - package-ecosystem: "github-actions" 32 | directory: "/" 33 | schedule: 34 | interval: "weekly" 35 | assignees: 36 | - "phanect" 37 | -------------------------------------------------------------------------------- /.github/workflows/actions.yml: -------------------------------------------------------------------------------- 1 | name: GitHub Actions 2 | 3 | on: 4 | schedule: 5 | - cron: "8 11 * * 5" # At 11:08 on Friday 6 | pull_request: 7 | branches: 8 | - main 9 | push: 10 | branches: 11 | - main 12 | release: 13 | types: 14 | - published 15 | 16 | jobs: 17 | test: 18 | runs-on: ubuntu-latest 19 | 20 | strategy: 21 | matrix: 22 | node-version: 23 | - 18 24 | - 20 25 | - 22 26 | eslint-version: 27 | - 8 28 | env: 29 | ESLINT_VERSION: ${{ matrix.eslint-version }} 30 | 31 | steps: 32 | - uses: actions/checkout@v3 33 | - uses: actions/setup-node@v3 34 | with: 35 | node-version: ${{ matrix.node-version }} 36 | 37 | - run: npm install # not `npm ci` since there is no package-lock.json in this project 38 | - run: npm test 39 | 40 | - name: Install this version of eslint-plugin-editorconfig 41 | run: npm pack && npm install "eslint-plugin-editorconfig-$(jq --raw-output .version package.json).tgz" 42 | if: 16 <= matrix.node-version 43 | - run: npm run lint 44 | if: 16 <= matrix.node-version 45 | 46 | release: 47 | needs: test 48 | if: github.event_name == 'release' 49 | runs-on: ubuntu-latest 50 | 51 | steps: 52 | - uses: actions/checkout@v3 53 | - uses: actions/setup-node@v3 54 | with: 55 | node-version: 20 56 | 57 | - run: npm install # not `npm ci` since there is no package-lock.json in this project 58 | 59 | - name: set npm auth token 60 | run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_AUTH_TOKEN }}" > ~/.npmrc 61 | - run: npm run release 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # 2 | # System 3 | # 4 | 5 | # Logs 6 | logs/ 7 | *.log 8 | 9 | # PIDs 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | pids 14 | 15 | # File manager configs 16 | .directory 17 | Thumbs.db 18 | *.DS_Store 19 | 20 | # 21 | # JavaScript 22 | # 23 | # NPM 24 | .npm/ 25 | node_modules/ 26 | npm-debug.log* 27 | 28 | # Node.js 29 | .node_repl_history 30 | 31 | # Yarn 32 | .yarn-integrity 33 | yarn-debug.log* 34 | yarn-error.log* 35 | 36 | # lockfiles are ignored in npm modules 37 | package-lock.json 38 | yarn.lock 39 | 40 | # tarball generated by npm pack 41 | *.tgz 42 | 43 | # Other tools 44 | .eslintcache 45 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .github/ 2 | .editorconfig 3 | .eslintrc.js 4 | node_modules/ 5 | tests/ 6 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-present Jumpei Ogawa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eslint-plugin-editorconfig 2 | 3 | An ESLint plugin to enforce EditorConfig rules 4 | 5 | [![GitHub Actions Status](https://github.com/phanect/eslint-plugin-editorconfig/actions/workflows/actions.yml/badge.svg)](https://github.com/phanect/eslint-plugin-editorconfig/actions/workflows/actions.yml) [![NPM Version](https://img.shields.io/npm/v/eslint-plugin-editorconfig.svg)](https://npmjs.org/package/eslint-plugin-editorconfig) 6 | 7 | ## Requirements 8 | 9 | - ESLint v8 10 | - ESLint v9 and flat config support is not ready yet. Even if you use legacy .eslintrc.\*, it does not work on v9. Sorry! :pray: 11 | - Node.js v{18, 20, 22} 12 | 13 | ## Install 14 | 15 | ```bash 16 | $ npm install --save-dev eslint@8 eslint-plugin-editorconfig 17 | ``` 18 | 19 | or 20 | 21 | ```bash 22 | $ yarn add --dev eslint@8 eslint-plugin-editorconfig 23 | ``` 24 | 25 | If you use [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint), you need to install `@typescript-eslint/eslint-plugin` too. 26 | 27 | ```bash 28 | $ npm install --save-dev @typescript-eslint/eslint-plugin 29 | ``` 30 | 31 | ## Usage 32 | 33 | ### eslint.config.* (Flat Config) 34 | 35 | Unfortunately, eslint-plugin-editorconfig does not work properly with Flat Config for now, even if you use `@eslint/eslintrc` for compatibility. 36 | Please wait for the next version. 37 | 38 | ### .eslintrc.* 39 | 40 | Like other ESLint plugins, 41 | 42 | - add rules in the `rules` 43 | - add `"editorconfig"` in the `plugins` 44 | 45 | in your .eslintrc* 46 | 47 | ```json 48 | { 49 | // ... 50 | "rules": { 51 | "editorconfig/charset": "error", 52 | "editorconfig/eol-last": "error", 53 | "editorconfig/indent": "error", 54 | "editorconfig/linebreak-style": "error", 55 | "editorconfig/no-trailing-spaces": "error" 56 | }, 57 | "plugins": [ "editorconfig" ] 58 | } 59 | ``` 60 | 61 | Or you can extend `plugin:editorconfig/all` instead of adding rules. 62 | 63 | ```json 64 | { 65 | // ... 66 | "extends": [ "plugin:editorconfig/all" ], 67 | "plugins": [ "editorconfig" ] 68 | } 69 | ``` 70 | 71 | ## Conflicting ESLint rules 72 | 73 | Following rules may conflict `editorconfig/*` rule. 74 | It is recommended to disable them. 75 | 76 | - `eol-last` 77 | - `indent` 78 | - `linebreak-style` 79 | - `no-trailing-spaces` 80 | - `unicode-bom` 81 | - `@typescript-eslint/eol-last` 82 | - `@typescript-eslint/indent` 83 | - `@typescript-eslint/linebreak-style` 84 | - `@typescript-eslint/no-trailing-spaces` 85 | - `@typescript-eslint/unicode-bom` 86 | 87 | If above rules are specified in your .eslintrc, just remove them. 88 | If they are specified in the extended config, consider adding `plugin:editorconfig/noconflict` to your `extends`. 89 | 90 | ```json 91 | { 92 | "extends": [ 93 | "@phanect/phanective", 94 | "plugin:editorconfig/noconflict" 95 | ], 96 | // ... 97 | } 98 | ``` 99 | 100 | If you extend `plugin:editorconfig/all`, the rules above are turned off too, so you don't have to add `plugin:editorconfig/noconflict` in addition to `plugin:editorconfig/all`. 101 | 102 | ### Rules 103 | 104 | Internally, eslint-plugin-editorconfig uses the existing ESLint and typescript-eslint rules to verify/fix the code. 105 | Some rules allow passing options. 106 | 107 | All the citation in the docs is from the backend ESLint rule document otherwise noted. 108 | 109 | | Rule | Description | Fixable | 110 | | ------------------------------------------------------------------- | --------------------------------------------------------------- | -- | 111 | | [editorconfig/charset](docs/rules/charset.md) | Enforce EditorConfig rules for charset | ✅ | 112 | | [editorconfig/eol-last](docs/rules/eol-last.md) | Enforce EditorConfig rules for the newlines at the end of files | ✅ | 113 | | [editorconfig/indent](docs/rules/indent.md) | Enforce EditorConfig rules for indentation | ✅ | 114 | | [editorconfig/linebreak-style](docs/rules/linebreak-style.md) | Enforce EditorConfig rules for linebreak style | ✅ | 115 | | [editorconfig/no-trailing-spaces](docs/rules/no-trailing-spaces.md) | Enforce EditorConfig rules for trailing spaces | ✅ | 116 | 117 | ## License & Credit 118 | 119 | [MIT](./LICENSE) 120 | 121 | This plugin includes code derived from [klona](https://github.com/lukeed/klona). 122 | -------------------------------------------------------------------------------- /docs/rules/charset.md: -------------------------------------------------------------------------------- 1 | # Enforce EditorConfig rules for charset (`editorconfig/charset`) 2 | 3 | The corresponding EditorCongig property is `charset`. 4 | The backend ESLint rule is [`unicode-bom`](https://eslint.org/docs/rules/unicode-bom) 5 | 6 | Unlike other rules, this plugin works only when `utf-8` or `utf-8-bom` is specified. If other value is specified in .editorconfig, ESLint does not verify charset. ESLint only verifies if BOM is specified or not. 7 | 8 | If you set `charset = utf-8` in .editorconfig, the files **cannot** include a BOM. 9 | If you set `charset = utf-8-bom` in .editorconfig, the files **must** include a BOM. 10 | If you don't set `charset` in .editorconfig or set `charset = unset`, this rule is disabled. 11 | 12 | ## Options 13 | 14 | None 15 | 16 | ## When Not To Use It 17 | 18 | > If you use some UTF-16 or UTF-32 files and you want to allow a file to optionally begin with a Unicode BOM, you should turn this rule off. 19 | -------------------------------------------------------------------------------- /docs/rules/eol-last.md: -------------------------------------------------------------------------------- 1 | # Enforce EditorConfig rules for the newlines at the end of files (`editorconfig/eol-last`) 2 | 3 | The corresponding EditorCongig property is `insert_final_newline`. 4 | The backend ESLint rule is [`eol-last`](https://eslint.org/docs/rules/eol-last) 5 | 6 | If you set `insert_final_newline = true` in .editorconfig, the files **must** ends with a new line. 7 | If you set `insert_final_newline = false` in .editorconfig, the files **must not** ends with a new line. 8 | If you don't set `insert_final_newline` in .editorconfig or set `insert_final_newline = unset`, this rule is disabled. 9 | 10 | ## Options 11 | 12 | None 13 | -------------------------------------------------------------------------------- /docs/rules/indent.md: -------------------------------------------------------------------------------- 1 | # Enforce EditorConfig rules for indentation (`editorconfig/indent`) 2 | 3 | The corresponding EditorCongig property is `indent_style` and `indent_size`. 4 | The backend ESLint rule is [`indent`](https://eslint.org/docs/rules/indent) and [`@typescript-eslint/indent`](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/indent.md). 5 | 6 | If you set `indent_style = space` in .editorconfig, the indent size **must** be the value of `indent_size`. 7 | If you set `indent_style = tab` in .editorconfig, the indent **must** be the tabs. 8 | If you don't set `indent_style` in .editorconfig or set `indent_style = unset`, this rule is disabled. 9 | 10 | As documented, `@typescript-eslint/indent` is unstable currently. Please read typescript-eslint/typescript-eslint#1824 before using this rule for TypeScript. 11 | 12 | ## Options 13 | 14 | > * `"SwitchCase"` (default: 0) enforces indentation level for `case` clauses in `switch` statements 15 | > * `"VariableDeclarator"` (default: 1) enforces indentation level for `var` declarators; can also take an object to define separate rules for `var`, `let` and `const` declarations. It can also be `"first"`, indicating all the declarators should be aligned with the first declarator. 16 | > * `"outerIIFEBody"` (default: 1) enforces indentation level for file-level IIFEs. This can also be set to `"off"` to disable checking for file-level IIFEs. 17 | > * `"MemberExpression"` (default: 1) enforces indentation level for multi-line property chains. This can also be set to `"off"` to disable checking for MemberExpression indentation. 18 | > * `"FunctionDeclaration"` takes an object to define rules for function declarations. 19 | > * `parameters` (default: 1) enforces indentation level for parameters in a function declaration. This can either be a number indicating indentation level, or the string `"first"` indicating that all parameters of the declaration must be aligned with the first parameter. This can also be set to `"off"` to disable checking for FunctionDeclaration parameters. 20 | > * `body` (default: 1) enforces indentation level for the body of a function declaration. 21 | > * `"FunctionExpression"` takes an object to define rules for function expressions. 22 | > * `parameters` (default: 1) enforces indentation level for parameters in a function expression. This can either be a number indicating indentation level, or the string `"first"` indicating that all parameters of the expression must be aligned with the first parameter. This can also be set to `"off"` to disable checking for FunctionExpression parameters. 23 | > * `body` (default: 1) enforces indentation level for the body of a function expression. 24 | > * `"CallExpression"` takes an object to define rules for function call expressions. 25 | > * `arguments` (default: 1) enforces indentation level for arguments in a call expression. This can either be a number indicating indentation level, or the string `"first"` indicating that all arguments of the expression must be aligned with the first argument. This can also be set to `"off"` to disable checking for CallExpression arguments. 26 | > * `"ArrayExpression"` (default: 1) enforces indentation level for elements in arrays. It can also be set to the string `"first"`, indicating that all the elements in the array should be aligned with the first element. This can also be set to `"off"` to disable checking for array elements. 27 | > * `"ObjectExpression"` (default: 1) enforces indentation level for properties in objects. It can be set to the string `"first"`, indicating that all properties in the object should be aligned with the first property. This can also be set to `"off"` to disable checking for object properties. 28 | > * `"ImportDeclaration"` (default: 1) enforces indentation level for import statements. It can be set to the string `"first"`, indicating that all imported members from a module should be aligned with the first member in the list. This can also be set to `"off"` to disable checking for imported module members. 29 | > * `"flatTernaryExpressions": true` (`false` by default) requires no indentation for ternary expressions which are nested in other ternary expressions. 30 | > * `"offsetTernaryExpressions": true` (`false` by default) requires indentation for values of ternary expressions. 31 | > * `"ignoredNodes"` accepts an array of [selectors](/docs/developer-guide/selectors.md). If an AST node is matched by any of the selectors, the indentation of tokens which are direct children of that node will be ignored. This can be used as an escape hatch to relax the rule if you disagree with the indentation that it enforces for a particular syntactic pattern. 32 | > * `"ignoreComments"` (default: false) can be used when comments do not need to be aligned with nodes on the previous or next line. 33 | 34 | Example (based on the code in the [document for the backend rule `indent`](https://eslint.org/docs/rules/indent)): 35 | 36 | ```javascript 37 | /*eslint editorconfig/indent: ["error", { "SwitchCase": 1 }]*/ 38 | 39 | switch(a){ 40 | case "a": 41 | break; 42 | case "b": 43 | break; 44 | } 45 | ``` 46 | 47 | Note: The third option parameter of the original backend `indent` rule should be passed as the second option parameter to this `editorconfig/indent` rule. 48 | -------------------------------------------------------------------------------- /docs/rules/linebreak-style.md: -------------------------------------------------------------------------------- 1 | # Enforce EditorConfig rules for linebreak style (`editorconfig/linebreak-style`) 2 | 3 | The corresponding EditorCongig property is `end_of_line`. 4 | The backend ESLint rule is [`linebreak-style`](https://eslint.org/docs/rules/linebreak-style) 5 | 6 | If you set `end_of_line = lf` in .editorconfig, the linebreak code **must** be LF. 7 | If you set `end_of_line = crlf` in .editorconfig, the linebreak code **must** be CRLF. 8 | If you don't set `end_of_line` in .editorconfig or set `end_of_line = unset`, this rule is disabled. 9 | 10 | `end_of_line = cr` is not supported. When `end_of_line = cr` is specified in .editorconfig, This plugin does nothing. 11 | 12 | ## Options 13 | 14 | None 15 | -------------------------------------------------------------------------------- /docs/rules/no-trailing-spaces.md: -------------------------------------------------------------------------------- 1 | # Enforce EditorConfig rules for trailing spaces (`editorconfig/no-trailing-spaces`) 2 | 3 | The corresponding EditorCongig property is `trim_trailing_whitespace`. 4 | The backend ESLint rule is [`no-trailing-spaces`](https://eslint.org/docs/rules/no-trailing-spaces) 5 | 6 | If you set `trim_trailing_whitespace = true` in .editorconfig, the trailing spaces **must** be removed. 7 | If you set `trim_trailing_whitespace = false` or `trim_trailing_whitespace = unset`, or you don't set `trim_trailing_whitespace` in .editorconfig, this rule is disabled. 8 | 9 | ## Options 10 | 11 | > * `"skipBlankLines": false` (default) disallows trailing whitespace on empty lines 12 | > * `"skipBlankLines": true` allows trailing whitespace on empty lines 13 | > * `"ignoreComments": false` (default) disallows trailing whitespace in comment blocks 14 | > * `"ignoreComments": true` allows trailing whitespace in comment blocks 15 | 16 | Example (based on the code in the [document for the backend rule `no-trailing-spaces`](https://eslint.org/docs/rules/no-trailing-spaces)): 17 | 18 | ```javascript 19 | /*eslint editorconfig/no-trailing-spaces: ["error", { "skipBlankLines": true }]*/ 20 | 21 | var foo = 0; 22 | var baz = 5; 23 | //••••• 24 | ``` 25 | -------------------------------------------------------------------------------- /examples/flat-config/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /examples/flat-config/README.md: -------------------------------------------------------------------------------- 1 | # Configuration example for Flat Config 2 | 3 | This directory is an example of using eslint-plugin-editorconfig with Flat Config. 4 | Unfortunately, it does not work properly with Flat Config yet, even if you use `@eslint/eslintrc` for compatibility. 5 | -------------------------------------------------------------------------------- /examples/flat-config/eslint.config.js: -------------------------------------------------------------------------------- 1 | import { dirname, join } from "node:path"; 2 | import { fileURLToPath } from "node:url"; 3 | import { FlatCompat } from "@eslint/eslintrc"; 4 | 5 | const __dirname = dirname(fileURLToPath(import.meta.url)); 6 | 7 | const compat = new FlatCompat({ 8 | baseDirectory: __dirname, 9 | resolvePluginsRelativeTo: __dirname, 10 | }); 11 | 12 | /** @type { import("eslint").Linter.FlatConfig[] } */ 13 | export default [ 14 | ...compat.config({ 15 | extends: [ "plugin:editorconfig/all" ], 16 | plugins: [ "editorconfig" ], 17 | }).map(config => ({ 18 | ...config, 19 | ignores: [ join(__dirname, "src/invalid.ts") ] 20 | })), 21 | ]; 22 | -------------------------------------------------------------------------------- /examples/flat-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "epe-flat-config-example", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "type": "module", 6 | "private": true, 7 | "scripts": { 8 | "lint": "eslint ." 9 | }, 10 | "devDependencies": { 11 | "@types/node": "^20.10.6", 12 | "eslint": "^8.56.0", 13 | "eslint-plugin-editorconfig": "^4.0.3", 14 | "undici": "^6.2.1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/flat-config/src/invalid.ts: -------------------------------------------------------------------------------- 1 | import { fetch } from "undici"; 2 | 3 | const res = await fetch("https://example.com"); 4 | 5 | try { 6 | const data = await res.json(); // This line should be warned by this plugin 7 | console.log(data); 8 | } catch (err) { 9 | console.error(err); 10 | process.exit(1); 11 | } 12 | -------------------------------------------------------------------------------- /examples/flat-config/src/valid.js: -------------------------------------------------------------------------------- 1 | import { fetch } from "undici"; 2 | 3 | const res = await fetch("https://example.com"); 4 | 5 | try { 6 | const data = await res.json(); 7 | console.log(data); 8 | } catch (err) { 9 | console.error(err); 10 | process.exit(1); 11 | } 12 | -------------------------------------------------------------------------------- /lib/base.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const editorconfig = require("editorconfig"); 4 | const { Linter } = require("eslint"); 5 | const { klona } = require("klona/lite"); 6 | const { clone } = require("./clone.js"); 7 | 8 | module.exports.buildRule = ({ baseRuleName, description, omitFirstOption, getESLintOption }) => { 9 | const jsBaseRule = klona(new Linter().getRules().get(baseRuleName)); 10 | 11 | // Remove first option 12 | if (omitFirstOption !== false) { 13 | jsBaseRule.meta.schema.shift(); 14 | } 15 | 16 | return { 17 | meta: { 18 | ...jsBaseRule.meta, 19 | 20 | docs: { 21 | ...jsBaseRule.meta.docs, 22 | description, 23 | url: `https://github.com/phanect/eslint-plugin-editorconfig/blob/main/docs/rules/${baseRuleName}.md`, 24 | }, 25 | }, 26 | 27 | create: function(context) { 28 | const filename = context.getFilename(); 29 | const ecParams = editorconfig.parseSync(context.getFilename(filename)); 30 | const { enabled, eslintOption } = getESLintOption(ecParams); 31 | 32 | let baseRule; 33 | 34 | if (filename.endsWith(".ts")) { 35 | try { 36 | const { rules } = require("@typescript-eslint/eslint-plugin"); 37 | baseRule = rules[baseRuleName] ? klona(rules[baseRuleName]) : jsBaseRule; 38 | } catch (err) { 39 | if (err.code === "MODULE_NOT_FOUND") { 40 | throw new Error("eslint-plugin-editorconfig requires typescript and @typescript-eslint/eslint-plugin to lint *.ts files. Run `npm install typescript @typescript-eslint/eslint-plugin`."); 41 | } else { 42 | throw err; 43 | } 44 | } 45 | } else { 46 | baseRule = jsBaseRule; 47 | } 48 | 49 | const _context = eslintOption ? clone(context, { options: [ eslintOption, ...context.options ]}) : context; 50 | 51 | return enabled ? baseRule.create(_context) : {}; 52 | }, 53 | }; 54 | }; 55 | -------------------------------------------------------------------------------- /lib/clone.js: -------------------------------------------------------------------------------- 1 | /* eslint no-use-before-define: "off" */ 2 | /* 3 | * This file is a modified version of klona/full (https://github.com/lukeed/klona). 4 | * Licensed under MIT. 5 | */ 6 | "use strict"; 7 | 8 | function set(obj, key, val, overwrite) { 9 | const overwriteKeys = Object.keys(overwrite); 10 | if (overwriteKeys.includes(key)) { 11 | obj[key] = overwrite[key]; 12 | return; 13 | } 14 | 15 | if (typeof val.value === "object") { 16 | val.value = klona(val.value); 17 | } 18 | if (!val.enumerable || val.get || val.set || !val.configurable || !val.writable || key === "__proto__") { 19 | Object.defineProperty(obj, key, val); 20 | } else { 21 | obj[key] = val.value; 22 | } 23 | } 24 | 25 | function klona(x, overwrite = {}) { 26 | if (typeof x !== "object") { 27 | return x; 28 | } 29 | 30 | const str = Object.prototype.toString.call(x); 31 | let i = 0; 32 | let k; 33 | let list; 34 | let tmp; 35 | 36 | if (str === "[object Object]") { 37 | tmp = Object.create(x.__proto__ || null); 38 | } else if (str === "[object Array]") { 39 | tmp = Array(x.length); 40 | } else if (str === "[object Set]") { 41 | tmp = new Set; 42 | x.forEach((val) => { 43 | tmp.add(klona(val)); 44 | }); 45 | } else if (str === "[object Map]") { 46 | tmp = new Map; 47 | x.forEach((val, key) => { 48 | tmp.set(klona(key), klona(val)); 49 | }); 50 | } else if (str === "[object Date]") { 51 | tmp = new Date(+x); 52 | } else if (str === "[object RegExp]") { 53 | tmp = new RegExp(x.source, x.flags); 54 | } else if (str === "[object DataView]") { 55 | tmp = new x.constructor(klona(x.buffer)); 56 | } else if (str === "[object ArrayBuffer]") { 57 | tmp = x.slice(0); 58 | } else if (str.slice(-6) === "Array]") { 59 | // ArrayBuffer.isView(x) 60 | // ~> `new` bcuz `Buffer.slice` => ref 61 | tmp = new x.constructor(x); 62 | } 63 | 64 | if (tmp) { 65 | for (list=Object.getOwnPropertySymbols(x); i < list.length; i++) { 66 | set(tmp, list[i], Object.getOwnPropertyDescriptor(x, list[i])); 67 | } 68 | 69 | for (i=0, list=Object.getOwnPropertyNames(x); i < list.length; i++) { 70 | if (Object.hasOwnProperty.call(tmp, k=list[i]) && tmp[k] === x[k]) { 71 | continue; 72 | } 73 | set(tmp, k, Object.getOwnPropertyDescriptor(x, k), overwrite); 74 | } 75 | } 76 | 77 | return tmp || x; 78 | } 79 | 80 | module.exports = { clone: klona }; 81 | -------------------------------------------------------------------------------- /lib/rules/charset.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { buildRule } = require("../base"); 4 | 5 | module.exports = buildRule({ 6 | baseRuleName: "unicode-bom", 7 | description: "Enforce EditorConfig rules for charset", 8 | getESLintOption: (ecParams) => { 9 | if (ecParams.charset === "utf-8") { 10 | return { enabled: true, eslintOption: "never" }; 11 | } else if (ecParams.charset === "utf-8-bom") { 12 | return { enabled: true, eslintOption: "always" }; 13 | } else { 14 | return { enabled: false }; 15 | } 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /lib/rules/eol-last.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { buildRule } = require("../base"); 4 | 5 | module.exports = buildRule({ 6 | baseRuleName: "eol-last", 7 | description: "Enforce EditorConfig rules for the newlines at the end of files", 8 | getESLintOption: (ecParams) => { 9 | if (ecParams.insert_final_newline === true) { 10 | return { enabled: true, eslintOption: "always" }; 11 | } else if (ecParams.insert_final_newline === false) { 12 | return { enabled: true, eslintOption: "never" }; 13 | } else { 14 | return { enabled: false }; 15 | } 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /lib/rules/indent.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { buildRule } = require("../base"); 4 | 5 | module.exports = buildRule({ 6 | baseRuleName: "indent", 7 | description: "Enforce EditorConfig rules for indentation", 8 | getESLintOption: (ecParams) => { 9 | if (ecParams.indent_style === "space") { 10 | return { enabled: true, eslintOption: ecParams.indent_size }; 11 | } else if (ecParams.indent_style === "tab") { 12 | return { enabled: true, eslintOption: "tab" }; 13 | } else { 14 | return { enabled: false }; 15 | } 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /lib/rules/linebreak-style.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { buildRule } = require("../base"); 4 | 5 | module.exports = buildRule({ 6 | baseRuleName: "linebreak-style", 7 | description: "Enforce EditorConfig rules for linebreak style", 8 | getESLintOption: (ecParams) => { 9 | if (ecParams.end_of_line === "lf") { 10 | return { enabled: true, eslintOption: "unix" }; 11 | } else if (ecParams.end_of_line === "crlf") { 12 | return { enabled: true, eslintOption: "windows" }; 13 | } else { 14 | return { enabled: false }; 15 | } 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /lib/rules/no-trailing-spaces.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { buildRule } = require("../base"); 4 | 5 | module.exports = buildRule({ 6 | baseRuleName: "no-trailing-spaces", 7 | description: "Enforce EditorConfig rules for trailing spaces", 8 | omitFirstOption: false, 9 | getESLintOption: (ecParams) => ({ enabled: ecParams.trim_trailing_whitespace }), 10 | }); 11 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | rules: { 5 | charset: require("./lib/rules/charset"), 6 | "eol-last": require("./lib/rules/eol-last"), 7 | indent: require("./lib/rules/indent"), 8 | "linebreak-style": require("./lib/rules/linebreak-style"), 9 | "no-trailing-spaces": require("./lib/rules/no-trailing-spaces"), 10 | }, 11 | configs: { 12 | noconflict: { 13 | rules: { 14 | "eol-last": "off", 15 | indent: "off", 16 | "linebreak-style": "off", 17 | "no-trailing-spaces": "off", 18 | "unicode-bom": "off", 19 | "@typescript-eslint/eol-last": "off", 20 | "@typescript-eslint/indent": "off", 21 | "@typescript-eslint/linebreak-style": "off", 22 | "@typescript-eslint/no-trailing-spaces": "off", 23 | "@typescript-eslint/unicode-bom": "off", 24 | }, 25 | }, 26 | all: { 27 | rules: { 28 | "eol-last": "off", 29 | indent: "off", 30 | "linebreak-style": "off", 31 | "no-trailing-spaces": "off", 32 | "unicode-bom": "off", 33 | "@typescript-eslint/eol-last": "off", 34 | "@typescript-eslint/indent": "off", 35 | "@typescript-eslint/linebreak-style": "off", 36 | "@typescript-eslint/no-trailing-spaces": "off", 37 | "@typescript-eslint/unicode-bom": "off", 38 | 39 | "editorconfig/charset": "error", 40 | "editorconfig/eol-last": "error", 41 | "editorconfig/indent": "error", 42 | "editorconfig/linebreak-style": "error", 43 | "editorconfig/no-trailing-spaces": "error", 44 | }, 45 | }, 46 | }, 47 | }; 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-plugin-editorconfig", 3 | "version": "4.0.3", 4 | "description": "An ESLint plugin to enforce EditorConfig rules", 5 | "main": "main.js", 6 | "scripts": { 7 | "test": "./test-packages/test.sh && mocha --reporter dot tests/lib/**/*.js", 8 | "lint": "eslint .", 9 | "release": "npm publish . --access public" 10 | }, 11 | "keywords": [ 12 | "eslint", 13 | "eslintplugin", 14 | "editorconfig" 15 | ], 16 | "author": "Jumpei Ogawa", 17 | "license": "MIT", 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/phanect/eslint-plugin-editorconfig.git" 21 | }, 22 | "bugs": { 23 | "url": "https://github.com/phanect/eslint-plugin-editorconfig/issues" 24 | }, 25 | "homepage": "https://github.com/phanect/eslint-plugin-editorconfig", 26 | "dependencies": { 27 | "editorconfig": "^1.0.2", 28 | "eslint": "^8.40.0", 29 | "klona": "^2.0.4" 30 | }, 31 | "devDependencies": { 32 | "@typescript-eslint/eslint-plugin": "^5.0.0", 33 | "@typescript-eslint/parser": "^5.0.0", 34 | "eslint-config-phanective": "latest", 35 | "mocha": "^10.2.0" 36 | }, 37 | "engines": { 38 | "node": ">=14", 39 | "npm": ">=8" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test-packages/failure/missing-ts-eslint/.eslintrc.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | root: true, 5 | extends: "plugin:editorconfig/noconflict", 6 | 7 | env: { 8 | es6: true, 9 | node: true, 10 | }, 11 | parser: "@typescript-eslint/parser", 12 | parserOptions: { 13 | sourceType: "module", 14 | }, 15 | plugins: [ "editorconfig" ], 16 | rules: { 17 | "editorconfig/charset": "error", 18 | "editorconfig/eol-last": "error", 19 | "editorconfig/indent": "error", 20 | "editorconfig/linebreak-style": "error", 21 | "editorconfig/no-trailing-spaces": "error", 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /test-packages/failure/missing-ts-eslint/main.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const bar: number = 0; 4 | 5 | console.log(bar); 6 | -------------------------------------------------------------------------------- /test-packages/failure/missing-ts-eslint/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@phanect/test-package-ts", 3 | "version": "1.0.0", 4 | "description": "a test package", 5 | "author": "Jumpei Ogawa (https://phanective.org)", 6 | "license": "MIT", 7 | "private": true, 8 | "main": "main.js", 9 | "scripts": { 10 | "lint": "eslint . --ext=.js,.ts", 11 | "test": "npm run lint" 12 | }, 13 | "devDependencies": { 14 | "@typescript-eslint/parser": "^5.0.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test-packages/success/base-rules-conflict/.eslintrc.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | root: true, 5 | extends: "plugin:editorconfig/noconflict", 6 | 7 | env: { 8 | es6: true, 9 | node: true, 10 | }, 11 | parserOptions: { 12 | ecmaVersion: 2020, 13 | }, 14 | plugins: [ "editorconfig" ], 15 | rules: { 16 | "unicode-bom": [ "error", "never" ], 17 | "eol-last": [ "error", "always" ], 18 | indent: [ "error", 2, { 19 | SwitchCase: 1, 20 | }], 21 | "linebreak-style": [ "error", "unix" ], 22 | "no-trailing-spaces": [ "error", { 23 | skipBlankLines: false, 24 | }], 25 | "editorconfig/charset": "error", 26 | "editorconfig/eol-last": "error", 27 | "editorconfig/indent": "error", 28 | "editorconfig/linebreak-style": "error", 29 | "editorconfig/no-trailing-spaces": "error", 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /test-packages/success/base-rules-conflict/main.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const foo = 0; 4 | 5 | console.log(foo); 6 | -------------------------------------------------------------------------------- /test-packages/success/base-rules-conflict/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@phanect/test-package-base-rules-conflict", 3 | "version": "1.0.0", 4 | "description": "a test package", 5 | "author": "Jumpei Ogawa (https://phanective.org)", 6 | "license": "MIT", 7 | "private": true, 8 | "main": "main.js", 9 | "scripts": { 10 | "lint": "eslint . --ext=.js,.ts", 11 | "test": "npm run lint" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test-packages/success/js/.eslintrc.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | root: true, 5 | extends: "plugin:editorconfig/noconflict", 6 | 7 | env: { 8 | es6: true, 9 | node: true, 10 | }, 11 | plugins: [ "editorconfig" ], 12 | rules: { 13 | "editorconfig/charset": "error", 14 | "editorconfig/eol-last": "error", 15 | "editorconfig/indent": "error", 16 | "editorconfig/linebreak-style": "error", 17 | "editorconfig/no-trailing-spaces": "error", 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /test-packages/success/js/main.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const foo = 0; 4 | 5 | console.log(foo); 6 | -------------------------------------------------------------------------------- /test-packages/success/js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@phanect/test-package-js", 3 | "version": "1.0.0", 4 | "description": "a test package", 5 | "author": "Jumpei Ogawa (https://phanective.org)", 6 | "license": "MIT", 7 | "private": true, 8 | "main": "main.js", 9 | "scripts": { 10 | "lint": "eslint .", 11 | "test": "npm run lint" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test-packages/success/ts/.eslintrc.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | root: true, 5 | extends: "plugin:editorconfig/noconflict", 6 | 7 | env: { 8 | es6: true, 9 | node: true, 10 | }, 11 | parser: "@typescript-eslint/parser", 12 | parserOptions: { 13 | sourceType: "module", 14 | }, 15 | plugins: [ "editorconfig" ], 16 | rules: { 17 | "editorconfig/charset": "error", 18 | "editorconfig/eol-last": "error", 19 | "editorconfig/indent": "error", 20 | "editorconfig/linebreak-style": "error", 21 | "editorconfig/no-trailing-spaces": "error", 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /test-packages/success/ts/main.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const bar: number = 0; 4 | 5 | console.log(bar); 6 | -------------------------------------------------------------------------------- /test-packages/success/ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@phanect/test-package-ts", 3 | "version": "1.0.0", 4 | "description": "a test package", 5 | "author": "Jumpei Ogawa (https://phanective.org)", 6 | "license": "MIT", 7 | "private": true, 8 | "main": "main.js", 9 | "scripts": { 10 | "lint": "eslint . --ext=.js,.ts", 11 | "test": "npm run lint" 12 | }, 13 | "devDependencies": { 14 | "@typescript-eslint/eslint-plugin": "^5.0.0", 15 | "@typescript-eslint/parser": "^5.0.0", 16 | "typescript": "^4.1.3" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test-packages/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eux 4 | 5 | TEST_PKGS_DIR="$(realpath "$(dirname "${BASH_SOURCE[0]}")")" 6 | PROJECT_ROOT="$(realpath "${TEST_PKGS_DIR}/..")" 7 | TMP="$(mktemp --directory)" 8 | 9 | # To run tests on local machine 10 | if [[ -z "${ESLINT_VERSION:-}" ]]; then 11 | ESLINT_VERSION=8 12 | fi 13 | 14 | cd "${PROJECT_ROOT}" 15 | PACKAGE="$(npm pack .)" 16 | 17 | cd "${TEST_PKGS_DIR}" 18 | git clean -Xd --force 19 | 20 | # If you test without copying, test package is affected by node_modules in the project root 21 | cp --recursive "${TEST_PKGS_DIR}" "${TMP}/" 22 | 23 | for PKGDIR in $(find "${TMP}/test-packages/success/" -maxdepth 1 -type d ! -path "${TMP}/test-packages/success/"); do 24 | cd "${PKGDIR}" 25 | npm install 26 | npm install --save-dev "eslint@${ESLINT_VERSION}" "${PROJECT_ROOT}/${PACKAGE}" 27 | npm run lint 28 | done 29 | 30 | for PKGDIR in $(find "${TMP}/test-packages/failure/" -maxdepth 1 -type d ! -path "${TMP}/test-packages/failure/"); do 31 | cd "${PKGDIR}" 32 | npm install 33 | npm install --save-dev "eslint@${ESLINT_VERSION}" "${PROJECT_ROOT}/${PACKAGE}" 34 | 35 | if [[ "${PKGDIR}" = "${TMP}/test-packages/failure/missing-ts-eslint" ]]; then 36 | # skip if Node.js version == 14.x 37 | if [[ -n "$(node --version | grep '^v14\.')" ]]; then 38 | continue 39 | fi 40 | 41 | if [[ -z "$((npm run lint 2>&1) | grep "eslint-plugin-editorconfig requires typescript and @typescript-eslint/eslint-plugin to lint \*.ts files. Run \`npm install typescript @typescript-eslint/eslint-plugin\`.")" ]]; then 42 | echo "Error message is not shown properly when @typescript-eslint/eslint-plugin is missing" >&2 43 | echo "ESLint's error message:" 44 | npm run lint 45 | exit 1 46 | fi 47 | fi 48 | done 49 | -------------------------------------------------------------------------------- /tests/configs/default/.editorconfig: -------------------------------------------------------------------------------- 1 | # Dummy .editorconfig for testing 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | indent_style = space 9 | indent_size = 2 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | -------------------------------------------------------------------------------- /tests/lib/rules/editorconfig-ts.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // ----------------------------------------------------------------------------- 4 | // Requirements 5 | // ----------------------------------------------------------------------------- 6 | 7 | const path = require("path"); 8 | const RuleTester = require("eslint").RuleTester; 9 | 10 | 11 | // ----------------------------------------------------------------------------- 12 | // Tests 13 | // ----------------------------------------------------------------------------- 14 | 15 | const ruleTester = new RuleTester({ 16 | parser: require.resolve("@typescript-eslint/parser"), 17 | parserOptions: { 18 | ecmaVersion: 2019, 19 | }, 20 | }); 21 | 22 | const commonValidTests = [ 23 | { 24 | filename: path.join(__dirname, "../../configs/default/target.ts"), 25 | code: `'use strict'; 26 | const foo: number = 0; 27 | `, 28 | }, 29 | ]; 30 | 31 | ruleTester.run("editorconfig/charset (typescript)", require("../../../lib/rules/charset"), { 32 | valid: commonValidTests, 33 | invalid: [], // TODO 34 | }); 35 | 36 | ruleTester.run("editorconfig/eol-last (typescript)", require("../../../lib/rules/eol-last"), { 37 | valid: commonValidTests, 38 | invalid: [{ 39 | filename: path.join(__dirname, "../../configs/default/target.ts"), 40 | code: `'use strict'; 41 | const foo: number = 0;`, 42 | output: `'use strict'; 43 | const foo: number = 0; 44 | `, 45 | errors: [{ 46 | message: "Newline required at end of file but not found.", 47 | line: 2, 48 | }], 49 | }], 50 | }); 51 | 52 | ruleTester.run("editorconfig/indent (typescript)", require("../../../lib/rules/indent"), { 53 | valid: [ 54 | ...commonValidTests, 55 | { 56 | // Passing Options (indent) 57 | filename: path.join(__dirname, "../../configs/default/target.ts"), 58 | options: [{ VariableDeclarator: { var: 2, let: 2, const: 3 }}], 59 | code: `'use strict'; 60 | const foo: string = 'foo', 61 | bar: string = 'bar', 62 | hoge: { fuga: string } = { 63 | fuga: 'fuga', 64 | }; 65 | let a: string = 'a', 66 | b: string = 'b', 67 | c: { d: string } = { 68 | d: 'd', 69 | }; 70 | var e: string = 'e', 71 | s: string = 's', 72 | l: { i: string, n: string, t: string } = { 73 | i: 'i', 74 | n: 'n', 75 | t: 't', 76 | }; 77 | `, 78 | }, 79 | ], 80 | invalid: [ 81 | { 82 | filename: path.join(__dirname, "../../configs/default/target.ts"), 83 | code: `'use strict'; 84 | const foo: number = 0; 85 | `, 86 | output: `'use strict'; 87 | const foo: number = 0; 88 | `, 89 | errors: [{ 90 | message: "Expected indentation of 0 spaces but found 4.", 91 | line: 2, 92 | column: 1, 93 | }], 94 | }, 95 | { 96 | // Passing Options 97 | filename: path.join(__dirname, "../../configs/default/target.ts"), 98 | options: [{ VariableDeclarator: { var: 2, let: 2, const: 3 }}], 99 | code: `'use strict'; 100 | const foo: string = 'foo', 101 | bar: string = 'bar'; 102 | let a: string = 'a', 103 | b: string = 'b'; 104 | var e: string = 'e', 105 | s: string = 's'; 106 | `, 107 | output: `'use strict'; 108 | const foo: string = 'foo', 109 | bar: string = 'bar'; 110 | let a: string = 'a', 111 | b: string = 'b'; 112 | var e: string = 'e', 113 | s: string = 's'; 114 | `, 115 | errors: [ 116 | { 117 | message: "Expected indentation of 6 spaces but found 2.", 118 | line: 3, 119 | }, 120 | { 121 | message: "Expected indentation of 4 spaces but found 2.", 122 | line: 5, 123 | }, 124 | { 125 | message: "Expected indentation of 4 spaces but found 2.", 126 | line: 7, 127 | }, 128 | ], 129 | }, 130 | ], 131 | }); 132 | 133 | ruleTester.run("editorconfig/linebreak-style (typescript)", require("../../../lib/rules/linebreak-style"), { 134 | valid: commonValidTests, 135 | invalid: [{ 136 | filename: path.join(__dirname, "../../configs/default/target.ts"), 137 | code: "'use strict';\r\nconst foo: number = 0;\n", 138 | output: "'use strict';\nconst foo: number = 0;\n", 139 | errors: [{ 140 | message: "Expected linebreaks to be 'LF' but found 'CRLF'.", 141 | line: 1, 142 | }], 143 | }], 144 | }); 145 | 146 | ruleTester.run("editorconfig/no-trailing-space (typescript)", require("../../../lib/rules/no-trailing-spaces"), { 147 | valid: [ 148 | ...commonValidTests, 149 | { 150 | filename: path.join(__dirname, "../../configs/default/target.ts"), 151 | code:`'use strict'; 152 | 153 | // comment 154 | const foo: string = 'foo';`, 155 | }, 156 | { 157 | // Passing Options 158 | filename: path.join(__dirname, "../../configs/default/target.ts"), 159 | options: [{ skipBlankLines: true, ignoreComments: true }], 160 | code: [ 161 | "'use strict';", 162 | " ", 163 | "// comment ", 164 | "const foo: string = 'foo';", 165 | ].join("\n"), 166 | }, 167 | ], 168 | invalid: [ 169 | { 170 | filename: path.join(__dirname, "../../configs/default/target.ts"), 171 | code: "'use strict';" + " \nconst foo: number = 0;\n", 172 | output: `'use strict'; 173 | const foo: number = 0; 174 | `, 175 | errors: [{ 176 | message: "Trailing spaces not allowed.", 177 | line: 1, 178 | }], 179 | }, 180 | { 181 | // Passing Options 182 | filename: path.join(__dirname, "../../configs/default/target.ts"), 183 | options: [{ skipBlankLines: true, ignoreComments: true }], 184 | code: [ 185 | "'use strict';", 186 | " ", 187 | "// comment ", 188 | "const foo: string = 'foo'; ", 189 | ].join("\n"), 190 | output: [ 191 | "'use strict';", 192 | " ", 193 | "// comment ", 194 | "const foo: string = 'foo';", 195 | ].join("\n"), 196 | errors: [{ 197 | message: "Trailing spaces not allowed.", 198 | line: 4, 199 | }], 200 | }, 201 | ], 202 | }); 203 | -------------------------------------------------------------------------------- /tests/lib/rules/editorconfig.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // ----------------------------------------------------------------------------- 4 | // Requirements 5 | // ----------------------------------------------------------------------------- 6 | 7 | const path = require("path"); 8 | const RuleTester = require("eslint").RuleTester; 9 | 10 | 11 | // ----------------------------------------------------------------------------- 12 | // Tests 13 | // ----------------------------------------------------------------------------- 14 | 15 | const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2019 }}); 16 | 17 | const commonValidTests = [ 18 | { 19 | filename: path.join(__dirname, "../../configs/default/target.js"), 20 | code: `'use strict'; 21 | const foo = 0; 22 | `, 23 | }, 24 | ]; 25 | 26 | ruleTester.run("editorconfig/charset (javascript)", require("../../../lib/rules/charset"), { 27 | valid: commonValidTests, 28 | invalid: [], // TODO 29 | }); 30 | 31 | ruleTester.run("editorconfig/eol-last (javascript)", require("../../../lib/rules/eol-last"), { 32 | valid: commonValidTests, 33 | invalid: [{ 34 | filename: path.join(__dirname, "../../configs/default/target.js"), 35 | code: `'use strict'; 36 | const foo = 0;`, 37 | output: `'use strict'; 38 | const foo = 0; 39 | `, 40 | errors: [{ 41 | message: "Newline required at end of file but not found.", 42 | line: 2, 43 | }], 44 | }], 45 | }); 46 | 47 | ruleTester.run("editorconfig/indent (javascript)", require("../../../lib/rules/indent"), { 48 | valid: [ 49 | ...commonValidTests, 50 | { 51 | // Passing Options (indent) 52 | filename: path.join(__dirname, "../../configs/default/target.js"), 53 | options: [{ VariableDeclarator: { var: 2, let: 2, const: 3 }}], 54 | code: `'use strict'; 55 | const foo = 'foo', 56 | bar = 'bar', 57 | hoge = { 58 | fuga: 'fuga', 59 | }; 60 | let a = 'a', 61 | b = 'b', 62 | c = { 63 | d: 'd', 64 | }; 65 | var e = 'e', 66 | s = 's', 67 | l = { 68 | i: 'i', 69 | n: 'n', 70 | t: 't', 71 | }; 72 | `, 73 | }, 74 | ], 75 | invalid: [ 76 | { 77 | filename: path.join(__dirname, "../../configs/default/target.js"), 78 | code: `'use strict'; 79 | const foo = 0; 80 | `, 81 | output: `'use strict'; 82 | const foo = 0; 83 | `, 84 | errors: [{ 85 | message: "Expected indentation of 0 spaces but found 4.", 86 | line: 2, 87 | column: 1, 88 | }], 89 | }, 90 | { 91 | // Passing Options 92 | filename: path.join(__dirname, "../../configs/default/target.js"), 93 | options: [{ VariableDeclarator: { var: 2, let: 2, const: 3 }}], 94 | code: `'use strict'; 95 | const foo = 'foo', 96 | bar = 'bar'; 97 | let a = 'a', 98 | b = 'b'; 99 | var e = 'e', 100 | s = 's'; 101 | `, 102 | output: `'use strict'; 103 | const foo = 'foo', 104 | bar = 'bar'; 105 | let a = 'a', 106 | b = 'b'; 107 | var e = 'e', 108 | s = 's'; 109 | `, 110 | errors: [ 111 | { 112 | message: "Expected indentation of 6 spaces but found 2.", 113 | line: 3, 114 | }, 115 | { 116 | message: "Expected indentation of 4 spaces but found 2.", 117 | line: 5, 118 | }, 119 | { 120 | message: "Expected indentation of 4 spaces but found 2.", 121 | line: 7, 122 | }, 123 | ], 124 | }, 125 | ], 126 | }); 127 | 128 | ruleTester.run("editorconfig/linebreak-style (javascript)", require("../../../lib/rules/linebreak-style"), { 129 | valid: commonValidTests, 130 | invalid: [{ 131 | filename: path.join(__dirname, "../../configs/default/target.js"), 132 | code: "'use strict';\r\nconst foo = 0;\n", 133 | output: "'use strict';\nconst foo = 0;\n", 134 | errors: [{ 135 | message: "Expected linebreaks to be 'LF' but found 'CRLF'.", 136 | line: 1, 137 | }], 138 | }], 139 | }); 140 | 141 | ruleTester.run("editorconfig/no-trailing-space (javascript)", require("../../../lib/rules/no-trailing-spaces"), { 142 | valid: [ 143 | ...commonValidTests, 144 | { 145 | filename: path.join(__dirname, "../../configs/default/target.js"), 146 | code:`'use strict'; 147 | 148 | // comment 149 | const foo = 'foo';`, 150 | }, 151 | { 152 | // Passing Options 153 | filename: path.join(__dirname, "../../configs/default/target.js"), 154 | options: [{ skipBlankLines: true, ignoreComments: true }], 155 | code: [ 156 | "'use strict';", 157 | " ", 158 | "// comment ", 159 | "const foo = 'foo';", 160 | ].join("\n"), 161 | }, 162 | ], 163 | invalid: [ 164 | { 165 | filename: path.join(__dirname, "../../configs/default/target.js"), 166 | code: "'use strict';" + " \nconst foo = 0;\n", 167 | output: `'use strict'; 168 | const foo = 0; 169 | `, 170 | errors: [{ 171 | message: "Trailing spaces not allowed.", 172 | line: 1, 173 | }], 174 | }, 175 | { 176 | // Passing Options 177 | filename: path.join(__dirname, "../../configs/default/target.js"), 178 | options: [{ skipBlankLines: true, ignoreComments: true }], 179 | code: [ 180 | "'use strict';", 181 | " ", 182 | "// comment ", 183 | "const foo = 'foo'; ", 184 | ].join("\n"), 185 | output: [ 186 | "'use strict';", 187 | " ", 188 | "// comment ", 189 | "const foo = 'foo';", 190 | ].join("\n"), 191 | errors: [{ 192 | message: "Trailing spaces not allowed.", 193 | line: 4, 194 | }], 195 | }, 196 | ], 197 | }); 198 | --------------------------------------------------------------------------------