From eb39fafa4f228271345b9d54d135caa9c0574361 Mon Sep 17 00:00:00 2001 From: Dominik Csapak Date: Thu, 2 Apr 2020 09:24:29 +0200 Subject: [PATCH] first commit includes a (minimal) working wrapper Signed-off-by: Dominik Csapak --- .gitignore | 2 + Makefile | 67 + debian/changelog | 6 + debian/compat | 1 + debian/control | 12 + debian/copyright | 40 + debian/rules | 4 + eslint/.codeclimate.yml | 9 + eslint/.editorconfig | 18 + eslint/.eslintignore | 12 + eslint/.eslintrc.js | 190 + eslint/.gitattributes | 8 + eslint/.github/FUNDING.yml | 2 + eslint/.github/ISSUE_TEMPLATE.md | 53 + eslint/.github/ISSUE_TEMPLATE/BUG_REPORT.md | 65 + eslint/.github/ISSUE_TEMPLATE/CHANGE.md | 32 + eslint/.github/ISSUE_TEMPLATE/NEW_RULE.md | 42 + eslint/.github/ISSUE_TEMPLATE/QUESTION.md | 25 + eslint/.github/ISSUE_TEMPLATE/RULE_CHANGE.md | 40 + eslint/.github/ISSUE_TEMPLATE/SECURITY.md | 22 + eslint/.github/PULL_REQUEST_TEMPLATE.md | 42 + eslint/.github/workflows/ci.yml | 67 + eslint/.markdownlint.yml | 21 + eslint/.npmrc | 1 + eslint/.nycrc | 13 + eslint/CHANGELOG.md | 5736 + eslint/CODE_OF_CONDUCT.md | 1 + eslint/CONTRIBUTING.md | 27 + eslint/LICENSE | 19 + eslint/Makefile.js | 1092 + eslint/README.md | 260 + eslint/SUPPORT.md | 1 + eslint/bin/eslint.js | 108 + eslint/conf/category-list.json | 40 + eslint/conf/config-schema.js | 81 + eslint/conf/default-cli-options.js | 31 + eslint/conf/environments.js | 168 + eslint/conf/replacements.json | 22 + eslint/docs/README.md | 17 + eslint/docs/about/index.md | 38 + eslint/docs/developer-guide/README.md | 47 + eslint/docs/developer-guide/architecture.md | 92 + .../architecture/dependency.svg | 52 + .../docs/developer-guide/code-conventions.md | 925 + .../developer-guide/code-path-analysis.md | 551 + .../code-path-analysis/README.md | 551 + .../example-dowhilestatement.svg | 100 + .../example-forinstatement.svg | 148 + .../example-forstatement-for-ever.svg | 63 + .../example-forstatement.svg | 201 + .../example-hello-world.svg | 48 + .../example-ifstatement-chain.svg | 203 + .../example-ifstatement.svg | 122 + .../example-switchstatement-has-default.svg | 279 + .../example-switchstatement.svg | 232 + ...example-trystatement-try-catch-finally.svg | 137 + .../example-trystatement-try-catch.svg | 186 + .../example-trystatement-try-finally.svg | 139 + .../example-when-there-is-a-function-f.svg | 99 + .../example-when-there-is-a-function-g.svg | 47 + .../example-whilestatement.svg | 172 + .../code-path-analysis/helo.svg | 113 + .../loop-event-example-for-1.svg | 84 + .../loop-event-example-for-2.svg | 110 + .../loop-event-example-for-3.svg | 115 + .../loop-event-example-for-4.svg | 115 + .../loop-event-example-for-5.svg | 149 + .../loop-event-example-while-1.svg | 82 + .../loop-event-example-while-2.svg | 87 + .../loop-event-example-while-3.svg | 121 + .../developer-guide/contributing/README.md | 41 + .../developer-guide/contributing/changes.md | 17 + .../developer-guide/contributing/new-rules.md | 42 + .../contributing/pull-requests.md | 182 + .../contributing/reporting-bugs.md | 7 + .../contributing/rule-changes.md | 21 + .../contributing/working-on-issues.md | 36 + .../development-environment.md | 107 + eslint/docs/developer-guide/nodejs-api.md | 1013 + .../scope-manager-interface.md | 386 + eslint/docs/developer-guide/selectors.md | 133 + .../docs/developer-guide/shareable-configs.md | 209 + eslint/docs/developer-guide/source-code.md | 40 + eslint/docs/developer-guide/unit-tests.md | 21 + .../working-with-custom-formatters.md | 368 + .../working-with-custom-parsers.md | 73 + .../developer-guide/working-with-plugins.md | 232 + .../working-with-rules-deprecated.md | 575 + .../developer-guide/working-with-rules.md | 737 + eslint/docs/maintainer-guide/README.md | 23 + eslint/docs/maintainer-guide/governance.md | 174 + eslint/docs/maintainer-guide/issues.md | 121 + eslint/docs/maintainer-guide/npm-2fa.md | 16 + eslint/docs/maintainer-guide/pullrequests.md | 94 + eslint/docs/maintainer-guide/releases.md | 59 + .../docs/maintainer-guide/working-groups.md | 22 + eslint/docs/rules/accessor-pairs.md | 289 + eslint/docs/rules/array-bracket-newline.md | 281 + eslint/docs/rules/array-bracket-spacing.md | 223 + eslint/docs/rules/array-callback-return.md | 154 + eslint/docs/rules/array-element-newline.md | 375 + eslint/docs/rules/arrow-body-style.md | 140 + eslint/docs/rules/arrow-parens.md | 223 + eslint/docs/rules/arrow-spacing.md | 93 + eslint/docs/rules/block-scoped-var.md | 79 + eslint/docs/rules/block-spacing.md | 59 + eslint/docs/rules/brace-style.md | 325 + eslint/docs/rules/callback-return.md | 174 + eslint/docs/rules/camelcase.md | 246 + eslint/docs/rules/capitalized-comments.md | 249 + eslint/docs/rules/class-methods-use-this.md | 124 + eslint/docs/rules/comma-dangle.md | 315 + eslint/docs/rules/comma-spacing.md | 129 + eslint/docs/rules/comma-style.md | 171 + eslint/docs/rules/complexity.md | 87 + .../docs/rules/computed-property-spacing.md | 165 + eslint/docs/rules/consistent-return.md | 145 + eslint/docs/rules/consistent-this.md | 84 + eslint/docs/rules/constructor-super.md | 60 + eslint/docs/rules/curly.md | 293 + eslint/docs/rules/default-case-last.md | 125 + eslint/docs/rules/default-case.md | 126 + eslint/docs/rules/default-param-last.md | 35 + eslint/docs/rules/dot-location.md | 85 + eslint/docs/rules/dot-notation.md | 67 + eslint/docs/rules/eol-last.md | 46 + eslint/docs/rules/eqeqeq.md | 125 + eslint/docs/rules/for-direction.md | 24 + eslint/docs/rules/func-call-spacing.md | 105 + eslint/docs/rules/func-name-matching.md | 140 + eslint/docs/rules/func-names.md | 210 + eslint/docs/rules/func-style.md | 131 + .../rules/function-call-argument-newline.md | 202 + eslint/docs/rules/function-paren-newline.md | 328 + eslint/docs/rules/generator-star-spacing.md | 207 + eslint/docs/rules/generator-star.md | 126 + eslint/docs/rules/getter-return.md | 97 + eslint/docs/rules/global-require.md | 89 + eslint/docs/rules/global-strict.md | 61 + eslint/docs/rules/grouped-accessor-pairs.md | 326 + eslint/docs/rules/guard-for-in.md | 52 + eslint/docs/rules/handle-callback-err.md | 81 + eslint/docs/rules/id-blacklist.md | 82 + eslint/docs/rules/id-length.md | 225 + eslint/docs/rules/id-match.md | 131 + eslint/docs/rules/implicit-arrow-linebreak.md | 101 + eslint/docs/rules/indent-legacy.md | 532 + eslint/docs/rules/indent.md | 795 + eslint/docs/rules/init-declarations.md | 131 + eslint/docs/rules/jsx-quotes.md | 73 + eslint/docs/rules/key-spacing.md | 330 + eslint/docs/rules/keyword-spacing.md | 272 + eslint/docs/rules/line-comment-position.md | 106 + eslint/docs/rules/linebreak-style.md | 86 + eslint/docs/rules/lines-around-comment.md | 498 + eslint/docs/rules/lines-around-directive.md | 322 + .../docs/rules/lines-between-class-members.md | 110 + eslint/docs/rules/max-classes-per-file.md | 49 + eslint/docs/rules/max-depth.md | 65 + eslint/docs/rules/max-len.md | 161 + eslint/docs/rules/max-lines-per-function.md | 191 + eslint/docs/rules/max-lines.md | 126 + eslint/docs/rules/max-nested-callbacks.md | 85 + eslint/docs/rules/max-params.md | 63 + eslint/docs/rules/max-statements-per-line.md | 87 + eslint/docs/rules/max-statements.md | 145 + eslint/docs/rules/multiline-comment-style.md | 123 + eslint/docs/rules/multiline-ternary.md | 149 + eslint/docs/rules/new-cap.md | 175 + eslint/docs/rules/new-parens.md | 59 + eslint/docs/rules/newline-after-var.md | 142 + eslint/docs/rules/newline-before-return.md | 116 + eslint/docs/rules/newline-per-chained-call.md | 118 + eslint/docs/rules/no-alert.md | 45 + eslint/docs/rules/no-array-constructor.md | 49 + eslint/docs/rules/no-arrow-condition.md | 48 + .../docs/rules/no-async-promise-executor.md | 62 + eslint/docs/rules/no-await-in-loop.md | 75 + eslint/docs/rules/no-bitwise.md | 86 + eslint/docs/rules/no-buffer-constructor.md | 41 + eslint/docs/rules/no-caller.md | 49 + eslint/docs/rules/no-case-declarations.md | 75 + eslint/docs/rules/no-catch-shadow.md | 73 + eslint/docs/rules/no-class-assign.md | 93 + eslint/docs/rules/no-comma-dangle.md | 52 + eslint/docs/rules/no-compare-neg-zero.md | 35 + eslint/docs/rules/no-cond-assign.md | 127 + eslint/docs/rules/no-confusing-arrow.md | 69 + eslint/docs/rules/no-console.md | 96 + eslint/docs/rules/no-const-assign.md | 68 + eslint/docs/rules/no-constant-condition.md | 101 + eslint/docs/rules/no-constructor-return.md | 50 + eslint/docs/rules/no-continue.md | 71 + eslint/docs/rules/no-control-regex.md | 34 + eslint/docs/rules/no-debugger.md | 41 + eslint/docs/rules/no-delete-var.md | 18 + eslint/docs/rules/no-div-regex.md | 32 + eslint/docs/rules/no-dupe-args.md | 37 + eslint/docs/rules/no-dupe-class-members.md | 74 + eslint/docs/rules/no-dupe-else-if.md | 178 + eslint/docs/rules/no-dupe-keys.md | 46 + eslint/docs/rules/no-duplicate-case.md | 91 + eslint/docs/rules/no-duplicate-imports.md | 60 + eslint/docs/rules/no-else-return.md | 157 + eslint/docs/rules/no-empty-character-class.md | 44 + eslint/docs/rules/no-empty-class.md | 33 + eslint/docs/rules/no-empty-function.md | 347 + eslint/docs/rules/no-empty-label.md | 40 + eslint/docs/rules/no-empty-pattern.md | 54 + eslint/docs/rules/no-empty.md | 89 + eslint/docs/rules/no-eq-null.md | 41 + eslint/docs/rules/no-eval.md | 147 + eslint/docs/rules/no-ex-assign.md | 36 + eslint/docs/rules/no-extend-native.md | 75 + eslint/docs/rules/no-extra-bind.md | 86 + eslint/docs/rules/no-extra-boolean-cast.md | 124 + eslint/docs/rules/no-extra-label.md | 82 + eslint/docs/rules/no-extra-parens.md | 267 + eslint/docs/rules/no-extra-semi.md | 42 + eslint/docs/rules/no-extra-strict.md | 50 + eslint/docs/rules/no-fallthrough.md | 169 + eslint/docs/rules/no-floating-decimal.md | 43 + eslint/docs/rules/no-func-assign.md | 51 + eslint/docs/rules/no-global-assign.md | 89 + eslint/docs/rules/no-implicit-coercion.md | 124 + eslint/docs/rules/no-implicit-globals.md | 226 + eslint/docs/rules/no-implied-eval.md | 63 + eslint/docs/rules/no-import-assign.md | 44 + eslint/docs/rules/no-inline-comments.md | 89 + eslint/docs/rules/no-inner-declarations.md | 144 + eslint/docs/rules/no-invalid-regexp.md | 64 + eslint/docs/rules/no-invalid-this.md | 251 + eslint/docs/rules/no-irregular-whitespace.md | 172 + eslint/docs/rules/no-iterator.md | 44 + eslint/docs/rules/no-label-var.md | 48 + eslint/docs/rules/no-labels.md | 123 + eslint/docs/rules/no-lone-blocks.md | 94 + eslint/docs/rules/no-lonely-if.md | 84 + eslint/docs/rules/no-loop-func.md | 98 + eslint/docs/rules/no-magic-numbers.md | 176 + .../rules/no-misleading-character-class.md | 73 + eslint/docs/rules/no-mixed-operators.md | 184 + eslint/docs/rules/no-mixed-requires.md | 124 + eslint/docs/rules/no-mixed-spaces-and-tabs.md | 65 + eslint/docs/rules/no-multi-assign.md | 48 + eslint/docs/rules/no-multi-spaces.md | 171 + eslint/docs/rules/no-multi-str.md | 31 + eslint/docs/rules/no-multiple-empty-lines.md | 96 + eslint/docs/rules/no-native-reassign.md | 91 + eslint/docs/rules/no-negated-condition.md | 58 + eslint/docs/rules/no-negated-in-lhs.md | 41 + eslint/docs/rules/no-nested-ternary.md | 44 + eslint/docs/rules/no-new-func.md | 36 + eslint/docs/rules/no-new-object.md | 50 + eslint/docs/rules/no-new-require.md | 46 + eslint/docs/rules/no-new-symbol.md | 48 + eslint/docs/rules/no-new-wrappers.md | 78 + eslint/docs/rules/no-new.md | 37 + eslint/docs/rules/no-obj-calls.md | 65 + eslint/docs/rules/no-octal-escape.md | 31 + eslint/docs/rules/no-octal.md | 36 + eslint/docs/rules/no-param-reassign.md | 160 + eslint/docs/rules/no-path-concat.md | 50 + eslint/docs/rules/no-plusplus.md | 100 + eslint/docs/rules/no-process-env.md | 39 + eslint/docs/rules/no-process-exit.md | 48 + eslint/docs/rules/no-proto.md | 42 + eslint/docs/rules/no-prototype-builtins.md | 39 + eslint/docs/rules/no-redeclare.md | 60 + eslint/docs/rules/no-regex-spaces.md | 46 + eslint/docs/rules/no-reserved-keys.md | 53 + eslint/docs/rules/no-restricted-exports.md | 106 + eslint/docs/rules/no-restricted-globals.md | 90 + eslint/docs/rules/no-restricted-imports.md | 185 + eslint/docs/rules/no-restricted-modules.md | 108 + eslint/docs/rules/no-restricted-properties.md | 125 + eslint/docs/rules/no-restricted-syntax.md | 84 + eslint/docs/rules/no-return-assign.md | 102 + eslint/docs/rules/no-return-await.md | 51 + eslint/docs/rules/no-script-url.md | 21 + eslint/docs/rules/no-self-assign.md | 86 + eslint/docs/rules/no-self-compare.md | 20 + eslint/docs/rules/no-sequences.md | 77 + eslint/docs/rules/no-setter-return.md | 101 + .../docs/rules/no-shadow-restricted-names.md | 47 + eslint/docs/rules/no-shadow.md | 173 + eslint/docs/rules/no-space-before-semi.md | 43 + eslint/docs/rules/no-spaced-func.md | 28 + eslint/docs/rules/no-sparse-arrays.md | 50 + eslint/docs/rules/no-sync.md | 59 + eslint/docs/rules/no-tabs.md | 61 + .../docs/rules/no-template-curly-in-string.md | 33 + eslint/docs/rules/no-ternary.md | 50 + eslint/docs/rules/no-this-before-super.md | 75 + eslint/docs/rules/no-throw-literal.md | 77 + eslint/docs/rules/no-trailing-spaces.md | 63 + eslint/docs/rules/no-undef-init.md | 111 + eslint/docs/rules/no-undef.md | 111 + eslint/docs/rules/no-undefined.md | 79 + eslint/docs/rules/no-underscore-dangle.md | 118 + eslint/docs/rules/no-unexpected-multiline.md | 74 + .../rules/no-unmodified-loop-condition.md | 84 + eslint/docs/rules/no-unneeded-ternary.md | 92 + eslint/docs/rules/no-unreachable.md | 75 + eslint/docs/rules/no-unsafe-finally.md | 144 + eslint/docs/rules/no-unsafe-negation.md | 107 + eslint/docs/rules/no-unused-expressions.md | 163 + eslint/docs/rules/no-unused-labels.md | 68 + eslint/docs/rules/no-unused-vars.md | 296 + eslint/docs/rules/no-use-before-define.md | 161 + eslint/docs/rules/no-useless-backreference.md | 130 + eslint/docs/rules/no-useless-call.md | 73 + eslint/docs/rules/no-useless-catch.md | 56 + eslint/docs/rules/no-useless-computed-key.md | 75 + eslint/docs/rules/no-useless-concat.md | 51 + eslint/docs/rules/no-useless-constructor.md | 71 + eslint/docs/rules/no-useless-escape.md | 54 + eslint/docs/rules/no-useless-rename.md | 124 + eslint/docs/rules/no-useless-return.md | 83 + eslint/docs/rules/no-var.md | 48 + eslint/docs/rules/no-void.md | 107 + eslint/docs/rules/no-warning-comments.md | 85 + .../rules/no-whitespace-before-property.md | 67 + eslint/docs/rules/no-with.md | 36 + eslint/docs/rules/no-wrap-func.md | 34 + .../rules/nonblock-statement-body-position.md | 158 + eslint/docs/rules/object-curly-newline.md | 524 + eslint/docs/rules/object-curly-spacing.md | 156 + eslint/docs/rules/object-property-newline.md | 270 + eslint/docs/rules/object-shorthand.md | 252 + .../rules/one-var-declaration-per-line.md | 89 + eslint/docs/rules/one-var.md | 542 + eslint/docs/rules/operator-assignment.md | 81 + eslint/docs/rules/operator-linebreak.md | 266 + eslint/docs/rules/padded-blocks.md | 399 + .../rules/padding-line-between-statements.md | 242 + eslint/docs/rules/prefer-arrow-callback.md | 100 + eslint/docs/rules/prefer-const.md | 205 + eslint/docs/rules/prefer-destructuring.md | 175 + .../rules/prefer-exponentiation-operator.md | 46 + .../docs/rules/prefer-named-capture-group.md | 43 + eslint/docs/rules/prefer-numeric-literals.md | 57 + eslint/docs/rules/prefer-object-spread.md | 49 + .../rules/prefer-promise-reject-errors.md | 79 + eslint/docs/rules/prefer-reflect.md | 339 + eslint/docs/rules/prefer-regex-literals.md | 94 + eslint/docs/rules/prefer-rest-params.md | 61 + eslint/docs/rules/prefer-spread.md | 78 + eslint/docs/rules/prefer-template.md | 53 + eslint/docs/rules/quote-props.md | 268 + eslint/docs/rules/quotes.md | 154 + eslint/docs/rules/radix.md | 95 + eslint/docs/rules/require-atomic-updates.md | 97 + eslint/docs/rules/require-await.md | 87 + eslint/docs/rules/require-jsdoc.md | 189 + eslint/docs/rules/require-unicode-regexp.md | 55 + eslint/docs/rules/require-yield.md | 45 + eslint/docs/rules/rest-spread-spacing.md | 140 + eslint/docs/rules/semi-spacing.md | 109 + eslint/docs/rules/semi-style.md | 96 + eslint/docs/rules/semi.md | 196 + eslint/docs/rules/sort-imports.md | 236 + eslint/docs/rules/sort-keys.md | 228 + eslint/docs/rules/sort-vars.md | 78 + .../docs/rules/space-after-function-name.md | 52 + eslint/docs/rules/space-after-keywords.md | 62 + eslint/docs/rules/space-before-blocks.md | 213 + .../docs/rules/space-before-function-paren.md | 368 + .../space-before-function-parentheses.md | 260 + eslint/docs/rules/space-before-keywords.md | 114 + eslint/docs/rules/space-in-brackets.md | 308 + eslint/docs/rules/space-in-parens.md | 279 + eslint/docs/rules/space-infix-ops.md | 79 + eslint/docs/rules/space-return-throw-case.md | 33 + eslint/docs/rules/space-unary-ops.md | 143 + eslint/docs/rules/space-unary-word-ops.md | 39 + eslint/docs/rules/spaced-comment.md | 271 + eslint/docs/rules/spaced-line-comment.md | 73 + eslint/docs/rules/strict.md | 272 + eslint/docs/rules/switch-colon-spacing.md | 80 + eslint/docs/rules/symbol-description.md | 61 + eslint/docs/rules/template-curly-spacing.md | 82 + eslint/docs/rules/template-tag-spacing.md | 76 + eslint/docs/rules/unicode-bom.md | 63 + eslint/docs/rules/use-isnan.md | 201 + eslint/docs/rules/valid-jsdoc.md | 393 + eslint/docs/rules/valid-typeof.md | 63 + eslint/docs/rules/vars-on-top.md | 88 + eslint/docs/rules/wrap-iife.md | 108 + eslint/docs/rules/wrap-regex.md | 33 + eslint/docs/rules/yield-star-spacing.md | 97 + eslint/docs/rules/yoda.md | 187 + eslint/docs/user-guide/README.md | 38 + .../docs/user-guide/command-line-interface.md | 494 + eslint/docs/user-guide/configuring.md | 1155 + eslint/docs/user-guide/getting-started.md | 78 + eslint/docs/user-guide/integrations.md | 63 + eslint/docs/user-guide/migrating-from-jscs.md | 171 + eslint/docs/user-guide/migrating-to-1.0.0.md | 209 + eslint/docs/user-guide/migrating-to-2.0.0.md | 356 + eslint/docs/user-guide/migrating-to-3.0.0.md | 81 + eslint/docs/user-guide/migrating-to-4.0.0.md | 226 + eslint/docs/user-guide/migrating-to-5.0.0.md | 270 + eslint/docs/user-guide/migrating-to-6.0.0.md | 330 + eslint/docs/user-guide/rule-deprecation.md | 13 + eslint/karma.conf.js | 101 + eslint/lib/api.js | 32 + .../cascading-config-array-factory.js | 478 + eslint/lib/cli-engine/cli-engine.js | 1021 + eslint/lib/cli-engine/config-array-factory.js | 1074 + .../cli-engine/config-array/config-array.js | 524 + .../config-array/config-dependency.js | 116 + .../config-array/extracted-config.js | 146 + .../cli-engine/config-array/ignore-pattern.js | 231 + eslint/lib/cli-engine/config-array/index.js | 20 + .../config-array/override-tester.js | 223 + eslint/lib/cli-engine/file-enumerator.js | 528 + .../lib/cli-engine/formatters/checkstyle.js | 60 + eslint/lib/cli-engine/formatters/codeframe.js | 138 + eslint/lib/cli-engine/formatters/compact.js | 60 + .../formatters/html-template-message.html | 8 + .../formatters/html-template-page.html | 115 + .../formatters/html-template-result.html | 6 + eslint/lib/cli-engine/formatters/html.js | 140 + .../lib/cli-engine/formatters/jslint-xml.js | 41 + .../formatters/json-with-metadata.js | 16 + eslint/lib/cli-engine/formatters/json.js | 13 + eslint/lib/cli-engine/formatters/junit.js | 82 + eslint/lib/cli-engine/formatters/stylish.js | 101 + eslint/lib/cli-engine/formatters/table.js | 159 + eslint/lib/cli-engine/formatters/tap.js | 95 + eslint/lib/cli-engine/formatters/unix.js | 58 + .../lib/cli-engine/formatters/visualstudio.js | 63 + eslint/lib/cli-engine/hash.js | 35 + eslint/lib/cli-engine/index.js | 7 + eslint/lib/cli-engine/lint-result-cache.js | 142 + eslint/lib/cli-engine/load-rules.js | 46 + eslint/lib/cli-engine/xml-escape.js | 34 + eslint/lib/cli.js | 240 + eslint/lib/init/autoconfig.js | 348 + eslint/lib/init/config-file.js | 143 + eslint/lib/init/config-initializer.js | 674 + eslint/lib/init/config-rule.js | 317 + eslint/lib/init/npm-utils.js | 178 + eslint/lib/init/source-code-utils.js | 109 + eslint/lib/linter/apply-disable-directives.js | 167 + .../code-path-analysis/code-path-analyzer.js | 684 + .../code-path-analysis/code-path-segment.js | 237 + .../code-path-analysis/code-path-state.js | 1399 + .../linter/code-path-analysis/code-path.js | 238 + .../code-path-analysis/debug-helpers.js | 196 + .../linter/code-path-analysis/fork-context.js | 249 + .../linter/code-path-analysis/id-generator.js | 46 + eslint/lib/linter/config-comment-parser.js | 141 + eslint/lib/linter/index.js | 13 + eslint/lib/linter/interpolate.js | 28 + eslint/lib/linter/linter.js | 1463 + eslint/lib/linter/node-event-generator.js | 311 + eslint/lib/linter/report-translator.js | 347 + eslint/lib/linter/rule-fixer.js | 140 + eslint/lib/linter/rules.js | 77 + eslint/lib/linter/safe-emitter.js | 52 + eslint/lib/linter/source-code-fixer.js | 152 + eslint/lib/linter/timing.js | 139 + eslint/lib/options.js | 263 + eslint/lib/rule-tester/index.js | 5 + eslint/lib/rule-tester/rule-tester.js | 879 + eslint/lib/rules/accessor-pairs.js | 367 + eslint/lib/rules/array-bracket-newline.js | 258 + eslint/lib/rules/array-bracket-spacing.js | 241 + eslint/lib/rules/array-callback-return.js | 302 + eslint/lib/rules/array-element-newline.js | 301 + eslint/lib/rules/arrow-body-style.js | 251 + eslint/lib/rules/arrow-parens.js | 184 + eslint/lib/rules/arrow-spacing.js | 161 + eslint/lib/rules/block-scoped-var.js | 122 + eslint/lib/rules/block-spacing.js | 147 + eslint/lib/rules/brace-style.js | 188 + eslint/lib/rules/callback-return.js | 182 + eslint/lib/rules/camelcase.js | 278 + eslint/lib/rules/capitalized-comments.js | 300 + eslint/lib/rules/class-methods-use-this.js | 125 + eslint/lib/rules/comma-dangle.js | 340 + eslint/lib/rules/comma-spacing.js | 195 + eslint/lib/rules/comma-style.js | 315 + eslint/lib/rules/complexity.js | 160 + eslint/lib/rules/computed-property-spacing.js | 204 + eslint/lib/rules/consistent-return.js | 196 + eslint/lib/rules/consistent-this.js | 151 + eslint/lib/rules/constructor-super.js | 395 + eslint/lib/rules/curly.js | 488 + eslint/lib/rules/default-case-last.js | 44 + eslint/lib/rules/default-case.js | 97 + eslint/lib/rules/default-param-last.js | 62 + eslint/lib/rules/dot-location.js | 99 + eslint/lib/rules/dot-notation.js | 173 + eslint/lib/rules/eol-last.js | 112 + eslint/lib/rules/eqeqeq.js | 174 + eslint/lib/rules/for-direction.js | 126 + eslint/lib/rules/func-call-spacing.js | 178 + eslint/lib/rules/func-name-matching.js | 252 + eslint/lib/rules/func-names.js | 190 + eslint/lib/rules/func-style.js | 98 + .../rules/function-call-argument-newline.js | 122 + eslint/lib/rules/function-paren-newline.js | 281 + eslint/lib/rules/generator-star-spacing.js | 206 + eslint/lib/rules/getter-return.js | 183 + eslint/lib/rules/global-require.js | 81 + eslint/lib/rules/grouped-accessor-pairs.js | 224 + eslint/lib/rules/guard-for-in.js | 76 + eslint/lib/rules/handle-callback-err.js | 95 + eslint/lib/rules/id-blacklist.js | 230 + eslint/lib/rules/id-length.js | 132 + eslint/lib/rules/id-match.js | 225 + eslint/lib/rules/implicit-arrow-linebreak.js | 81 + eslint/lib/rules/indent-legacy.js | 1125 + eslint/lib/rules/indent.js | 1686 + eslint/lib/rules/index.js | 293 + eslint/lib/rules/init-declarations.js | 139 + eslint/lib/rules/jsx-quotes.js | 95 + eslint/lib/rules/key-spacing.js | 670 + eslint/lib/rules/keyword-spacing.js | 566 + eslint/lib/rules/line-comment-position.js | 122 + eslint/lib/rules/linebreak-style.js | 99 + eslint/lib/rules/lines-around-comment.js | 404 + eslint/lib/rules/lines-around-directive.js | 201 + .../lib/rules/lines-between-class-members.js | 133 + eslint/lib/rules/max-classes-per-file.js | 65 + eslint/lib/rules/max-depth.js | 154 + eslint/lib/rules/max-len.js | 422 + eslint/lib/rules/max-lines-per-function.js | 214 + eslint/lib/rules/max-lines.js | 148 + eslint/lib/rules/max-nested-callbacks.js | 117 + eslint/lib/rules/max-params.js | 103 + eslint/lib/rules/max-statements-per-line.js | 196 + eslint/lib/rules/max-statements.js | 175 + eslint/lib/rules/multiline-comment-style.js | 435 + eslint/lib/rules/multiline-ternary.js | 95 + eslint/lib/rules/new-cap.js | 279 + eslint/lib/rules/new-parens.js | 99 + eslint/lib/rules/newline-after-var.js | 255 + eslint/lib/rules/newline-before-return.js | 217 + eslint/lib/rules/newline-per-chained-call.js | 110 + eslint/lib/rules/no-alert.js | 129 + eslint/lib/rules/no-array-constructor.js | 54 + eslint/lib/rules/no-async-promise-executor.js | 39 + eslint/lib/rules/no-await-in-loop.js | 106 + eslint/lib/rules/no-bitwise.js | 119 + eslint/lib/rules/no-buffer-constructor.js | 45 + eslint/lib/rules/no-caller.js | 46 + eslint/lib/rules/no-case-declarations.js | 64 + eslint/lib/rules/no-catch-shadow.js | 80 + eslint/lib/rules/no-class-assign.js | 61 + eslint/lib/rules/no-compare-neg-zero.js | 60 + eslint/lib/rules/no-cond-assign.js | 159 + eslint/lib/rules/no-confusing-arrow.js | 85 + eslint/lib/rules/no-console.js | 134 + eslint/lib/rules/no-const-assign.js | 54 + eslint/lib/rules/no-constant-condition.js | 253 + eslint/lib/rules/no-constructor-return.js | 62 + eslint/lib/rules/no-continue.js | 39 + eslint/lib/rules/no-control-regex.js | 113 + eslint/lib/rules/no-debugger.js | 43 + eslint/lib/rules/no-delete-var.js | 42 + eslint/lib/rules/no-div-regex.js | 53 + eslint/lib/rules/no-dupe-args.js | 80 + eslint/lib/rules/no-dupe-class-members.js | 103 + eslint/lib/rules/no-dupe-else-if.js | 122 + eslint/lib/rules/no-dupe-keys.js | 143 + eslint/lib/rules/no-duplicate-case.js | 52 + eslint/lib/rules/no-duplicate-imports.js | 142 + eslint/lib/rules/no-else-return.js | 404 + eslint/lib/rules/no-empty-character-class.js | 64 + eslint/lib/rules/no-empty-function.js | 167 + eslint/lib/rules/no-empty-pattern.js | 43 + eslint/lib/rules/no-empty.js | 86 + eslint/lib/rules/no-eq-null.js | 46 + eslint/lib/rules/no-eval.js | 307 + eslint/lib/rules/no-ex-assign.js | 52 + eslint/lib/rules/no-extend-native.js | 181 + eslint/lib/rules/no-extra-bind.js | 173 + eslint/lib/rules/no-extra-boolean-cast.js | 306 + eslint/lib/rules/no-extra-label.js | 149 + eslint/lib/rules/no-extra-parens.js | 1107 + eslint/lib/rules/no-extra-semi.js | 126 + eslint/lib/rules/no-fallthrough.js | 142 + eslint/lib/rules/no-floating-decimal.js | 70 + eslint/lib/rules/no-func-assign.js | 76 + eslint/lib/rules/no-global-assign.js | 94 + eslint/lib/rules/no-implicit-coercion.js | 300 + eslint/lib/rules/no-implicit-globals.js | 140 + eslint/lib/rules/no-implied-eval.js | 152 + eslint/lib/rules/no-import-assign.js | 238 + eslint/lib/rules/no-inline-comments.js | 89 + eslint/lib/rules/no-inner-declarations.js | 96 + eslint/lib/rules/no-invalid-regexp.js | 130 + eslint/lib/rules/no-invalid-this.js | 145 + eslint/lib/rules/no-irregular-whitespace.js | 251 + eslint/lib/rules/no-iterator.js | 52 + eslint/lib/rules/no-label-var.js | 79 + eslint/lib/rules/no-labels.js | 149 + eslint/lib/rules/no-lone-blocks.js | 128 + eslint/lib/rules/no-lonely-if.js | 89 + eslint/lib/rules/no-loop-func.js | 204 + eslint/lib/rules/no-magic-numbers.js | 213 + .../rules/no-misleading-character-class.js | 200 + eslint/lib/rules/no-mixed-operators.js | 242 + eslint/lib/rules/no-mixed-requires.js | 233 + eslint/lib/rules/no-mixed-spaces-and-tabs.js | 105 + eslint/lib/rules/no-multi-assign.js | 49 + eslint/lib/rules/no-multi-spaces.js | 138 + eslint/lib/rules/no-multi-str.js | 65 + eslint/lib/rules/no-multiple-empty-lines.js | 151 + eslint/lib/rules/no-native-reassign.js | 97 + eslint/lib/rules/no-negated-condition.js | 95 + eslint/lib/rules/no-negated-in-lhs.js | 46 + eslint/lib/rules/no-nested-ternary.js | 44 + eslint/lib/rules/no-new-func.js | 55 + eslint/lib/rules/no-new-object.js | 45 + eslint/lib/rules/no-new-require.js | 45 + eslint/lib/rules/no-new-symbol.js | 53 + eslint/lib/rules/no-new-wrappers.js | 48 + eslint/lib/rules/no-new.js | 43 + eslint/lib/rules/no-obj-calls.js | 81 + eslint/lib/rules/no-octal-escape.js | 56 + eslint/lib/rules/no-octal.js | 45 + eslint/lib/rules/no-param-reassign.js | 229 + eslint/lib/rules/no-path-concat.js | 59 + eslint/lib/rules/no-plusplus.js | 105 + eslint/lib/rules/no-process-env.js | 46 + eslint/lib/rules/no-process-exit.js | 42 + eslint/lib/rules/no-proto.js | 48 + eslint/lib/rules/no-prototype-builtins.js | 61 + eslint/lib/rules/no-redeclare.js | 172 + eslint/lib/rules/no-regex-spaces.js | 180 + eslint/lib/rules/no-restricted-exports.js | 84 + eslint/lib/rules/no-restricted-globals.js | 122 + eslint/lib/rules/no-restricted-imports.js | 268 + eslint/lib/rules/no-restricted-modules.js | 210 + eslint/lib/rules/no-restricted-properties.js | 181 + eslint/lib/rules/no-restricted-syntax.js | 70 + eslint/lib/rules/no-return-assign.js | 80 + eslint/lib/rules/no-return-await.js | 103 + eslint/lib/rules/no-script-url.js | 48 + eslint/lib/rules/no-self-assign.js | 233 + eslint/lib/rules/no-self-compare.js | 60 + eslint/lib/rules/no-sequences.js | 119 + eslint/lib/rules/no-setter-return.js | 227 + .../lib/rules/no-shadow-restricted-names.js | 64 + eslint/lib/rules/no-shadow.js | 192 + eslint/lib/rules/no-spaced-func.js | 83 + eslint/lib/rules/no-sparse-arrays.js | 50 + eslint/lib/rules/no-sync.js | 61 + eslint/lib/rules/no-tabs.js | 78 + .../lib/rules/no-template-curly-in-string.js | 44 + eslint/lib/rules/no-ternary.js | 41 + eslint/lib/rules/no-this-before-super.js | 304 + eslint/lib/rules/no-throw-literal.js | 51 + eslint/lib/rules/no-trailing-spaces.js | 190 + eslint/lib/rules/no-undef-init.js | 75 + eslint/lib/rules/no-undef.js | 78 + eslint/lib/rules/no-undefined.js | 84 + eslint/lib/rules/no-underscore-dangle.js | 232 + eslint/lib/rules/no-unexpected-multiline.js | 110 + .../lib/rules/no-unmodified-loop-condition.js | 360 + eslint/lib/rules/no-unneeded-ternary.js | 166 + eslint/lib/rules/no-unreachable.js | 218 + eslint/lib/rules/no-unsafe-finally.js | 111 + eslint/lib/rules/no-unsafe-negation.js | 127 + eslint/lib/rules/no-unused-expressions.js | 140 + eslint/lib/rules/no-unused-labels.js | 110 + eslint/lib/rules/no-unused-vars.js | 645 + eslint/lib/rules/no-use-before-define.js | 233 + eslint/lib/rules/no-useless-backreference.js | 193 + eslint/lib/rules/no-useless-call.js | 87 + eslint/lib/rules/no-useless-catch.js | 57 + eslint/lib/rules/no-useless-computed-key.js | 103 + eslint/lib/rules/no-useless-concat.js | 115 + eslint/lib/rules/no-useless-constructor.js | 181 + eslint/lib/rules/no-useless-escape.js | 252 + eslint/lib/rules/no-useless-rename.js | 168 + eslint/lib/rules/no-useless-return.js | 305 + eslint/lib/rules/no-var.js | 334 + eslint/lib/rules/no-void.js | 64 + eslint/lib/rules/no-warning-comments.js | 163 + .../rules/no-whitespace-before-property.js | 101 + eslint/lib/rules/no-with.js | 39 + .../rules/nonblock-statement-body-position.js | 124 + eslint/lib/rules/object-curly-newline.js | 306 + eslint/lib/rules/object-curly-spacing.js | 308 + eslint/lib/rules/object-property-newline.js | 99 + eslint/lib/rules/object-shorthand.js | 508 + .../lib/rules/one-var-declaration-per-line.js | 92 + eslint/lib/rules/one-var.js | 535 + eslint/lib/rules/operator-assignment.js | 243 + eslint/lib/rules/operator-linebreak.js | 250 + eslint/lib/rules/padded-blocks.js | 284 + .../rules/padding-line-between-statements.js | 632 + eslint/lib/rules/prefer-arrow-callback.js | 314 + eslint/lib/rules/prefer-const.js | 476 + eslint/lib/rules/prefer-destructuring.js | 272 + .../rules/prefer-exponentiation-operator.js | 189 + .../lib/rules/prefer-named-capture-group.js | 110 + eslint/lib/rules/prefer-numeric-literals.js | 147 + eslint/lib/rules/prefer-object-spread.js | 299 + .../lib/rules/prefer-promise-reject-errors.js | 133 + eslint/lib/rules/prefer-reflect.js | 127 + eslint/lib/rules/prefer-regex-literals.js | 125 + eslint/lib/rules/prefer-rest-params.js | 115 + eslint/lib/rules/prefer-spread.js | 91 + eslint/lib/rules/prefer-template.js | 283 + eslint/lib/rules/quote-props.js | 307 + eslint/lib/rules/quotes.js | 332 + eslint/lib/rules/radix.js | 178 + eslint/lib/rules/require-atomic-updates.js | 283 + eslint/lib/rules/require-await.js | 113 + eslint/lib/rules/require-jsdoc.js | 121 + eslint/lib/rules/require-unicode-regexp.js | 69 + eslint/lib/rules/require-yield.js | 78 + eslint/lib/rules/rest-spread-spacing.js | 123 + eslint/lib/rules/semi-spacing.js | 219 + eslint/lib/rules/semi-style.js | 151 + eslint/lib/rules/semi.js | 336 + eslint/lib/rules/sort-imports.js | 213 + eslint/lib/rules/sort-keys.js | 187 + eslint/lib/rules/sort-vars.js | 104 + eslint/lib/rules/space-before-blocks.js | 164 + .../lib/rules/space-before-function-paren.js | 161 + eslint/lib/rules/space-in-parens.js | 282 + eslint/lib/rules/space-infix-ops.js | 169 + eslint/lib/rules/space-unary-ops.js | 321 + eslint/lib/rules/spaced-comment.js | 382 + eslint/lib/rules/strict.js | 277 + eslint/lib/rules/switch-colon-spacing.js | 141 + eslint/lib/rules/symbol-description.js | 71 + eslint/lib/rules/template-curly-spacing.js | 141 + eslint/lib/rules/template-tag-spacing.js | 84 + eslint/lib/rules/unicode-bom.js | 73 + eslint/lib/rules/use-isnan.js | 138 + eslint/lib/rules/utils/ast-utils.js | 1497 + eslint/lib/rules/utils/fix-tracker.js | 114 + eslint/lib/rules/utils/keywords.js | 67 + .../lib/rules/utils/lazy-loading-rule-map.js | 115 + eslint/lib/rules/utils/patterns/letters.js | 36 + eslint/lib/rules/utils/unicode/index.js | 11 + .../utils/unicode/is-combining-character.js | 13 + .../rules/utils/unicode/is-emoji-modifier.js | 13 + .../unicode/is-regional-indicator-symbol.js | 13 + .../rules/utils/unicode/is-surrogate-pair.js | 14 + eslint/lib/rules/valid-jsdoc.js | 515 + eslint/lib/rules/valid-typeof.js | 85 + eslint/lib/rules/vars-on-top.js | 144 + eslint/lib/rules/wrap-iife.js | 197 + eslint/lib/rules/wrap-regex.js | 59 + eslint/lib/rules/yield-star-spacing.js | 127 + eslint/lib/rules/yoda.js | 355 + eslint/lib/shared/ajv.js | 34 + eslint/lib/shared/ast-utils.js | 29 + eslint/lib/shared/config-ops.js | 130 + eslint/lib/shared/config-validator.js | 326 + eslint/lib/shared/deprecation-warnings.js | 56 + eslint/lib/shared/logging.js | 30 + eslint/lib/shared/naming.js | 97 + eslint/lib/shared/relative-module-resolver.js | 43 + eslint/lib/shared/runtime-info.js | 163 + eslint/lib/shared/traverser.js | 195 + eslint/lib/shared/types.js | 143 + eslint/lib/source-code/index.js | 5 + eslint/lib/source-code/source-code.js | 585 + .../backward-token-comment-cursor.js | 57 + .../token-store/backward-token-cursor.js | 58 + eslint/lib/source-code/token-store/cursor.js | 76 + eslint/lib/source-code/token-store/cursors.js | 90 + .../token-store/decorative-cursor.js | 39 + .../source-code/token-store/filter-cursor.js | 43 + .../forward-token-comment-cursor.js | 57 + .../token-store/forward-token-cursor.js | 63 + eslint/lib/source-code/token-store/index.js | 627 + .../source-code/token-store/limit-cursor.js | 40 + .../token-store/padded-token-cursor.js | 38 + .../source-code/token-store/skip-cursor.js | 42 + eslint/lib/source-code/token-store/utils.js | 100 + eslint/messages/all-files-ignored.txt | 8 + eslint/messages/extend-config-missing.txt | 5 + eslint/messages/failed-to-read-json.txt | 3 + eslint/messages/file-not-found.txt | 2 + eslint/messages/no-config-found.txt | 7 + eslint/messages/plugin-conflict.txt | 7 + eslint/messages/plugin-missing.txt | 11 + .../print-config-with-directory-path.txt | 2 + eslint/messages/whitespace-found.txt | 3 + eslint/package.json | 145 + eslint/templates/blogpost.md.ejs | 108 + eslint/templates/bug-report.md | 26 + eslint/templates/formatter-examples.md.ejs | 59 + eslint/templates/rule-change-proposal.md | 20 + eslint/templates/rule-proposal.md | 18 + eslint/tests/bench/bench.js | 38 + eslint/tests/bench/large.js | 60571 ++++++ eslint/tests/bench/medium.js | 9111 + eslint/tests/bench/small.js | 4225 + eslint/tests/bin/eslint.js | 404 + eslint/tests/conf/config-schema.js | 27 + eslint/tests/fixtures/.eslintignore | 2 + eslint/tests/fixtures/.eslintignore2 | 3 + .../tests/fixtures/.eslintignore_twoGlobstar | 2 + eslint/tests/fixtures/.eslintrc | 2 + .../autoconfig/source-with-comments.js | 6 + eslint/tests/fixtures/autoconfig/source.js | 3 + .../left-pad-expected-quiet.js | 33 + .../autofix-integration/left-pad-expected.js | 33 + .../fixtures/autofix-integration/left-pad.js | 33 + .../return-conflicting-fixes.expected.js | 10 + .../autofix/return-conflicting-fixes.js | 10 + .../semicolon-conflicting-fixes.expected.js | 4 + .../autofix/semicolon-conflicting-fixes.js | 4 + eslint/tests/fixtures/bin/.eslintrc.yml | 5 + eslint/tests/fixtures/cache/src/fail-file.js | 2 + .../fixtures/cache/src/file-to-delete.js | 3 + eslint/tests/fixtures/cache/src/test-file.js | 3 + eslint/tests/fixtures/cache/src/test-file2.js | 3 + .../tests/fixtures/cli-engine/.eslintignore2 | 1 + eslint/tests/fixtures/cli-engine/console.js | 1 + .../deprecated-rule-config/.eslintrc.yml | 2 + eslint/tests/fixtures/cli-engine/empty/.keep | 0 .../hidden/.hiddenfolder/double-quotes.js | 2 + .../nested_node_modules/.eslintignore | 1 + .../cli-engine/nested_node_modules/passing.js | 1 + .../subdir/node_modules/text.js | 1 + .../fixtures/cli-engine/node_modules/foo.js | 3 + .../overrides-with-dot/.eslintrc.yml | 6 + .../overrides-with-dot/.test-target.js | 1 + eslint/tests/fixtures/cli/.eslintignore_foo | 1 + eslint/tests/fixtures/cli/foo/.bar/passing.js | 4 + eslint/tests/fixtures/cli/passing.js | 4 + eslint/tests/fixtures/cli/syntax-error.js | 1 + .../code-path-analysis/block-and-break-1.js | 30 + .../code-path-analysis/block-and-break-2.js | 46 + .../code-path-analysis/block-and-break-3.js | 23 + .../code-path-analysis/block-and-break-4.js | 16 + .../default-params--nest.js | 32 + .../default-params--simple.js | 30 + .../do-while--break-always.js | 22 + .../do-while--break-label.js | 42 + .../do-while--break-nest.js | 32 + .../do-while--break-simple.js | 28 + .../do-while--continue-always.js | 21 + .../do-while--continue-label.js | 42 + .../do-while--continue-nest.js | 33 + .../do-while--continue-simple.js | 27 + .../code-path-analysis/do-while--empty.js | 17 + .../code-path-analysis/do-while--simple.js | 18 + .../code-path-analysis/for--break-always.js | 24 + .../code-path-analysis/for--break-label.js | 52 + .../code-path-analysis/for--break-nest.js | 42 + .../for--break-simple-no-test.js | 27 + .../for--break-simple-no-update.js | 29 + .../code-path-analysis/for--break-simple.js | 30 + .../for--continue-always.js | 26 + .../code-path-analysis/for--continue-label.js | 48 + .../code-path-analysis/for--continue-nest.js | 42 + .../for--continue-simple-no-test.js | 26 + .../for--continue-simple-no-update.js | 31 + .../for--continue-simple.js | 32 + .../code-path-analysis/for--direct-nest.js | 30 + .../fixtures/code-path-analysis/for--empty.js | 16 + .../for--simple-fork-in-test-update.js | 30 + .../code-path-analysis/for--simple-no-test.js | 18 + .../for--simple-no-update.js | 19 + .../for--simple-test-true.js | 21 + .../code-path-analysis/for--simple.js | 20 + .../for-in--break-always.js | 28 + .../code-path-analysis/for-in--break-label.js | 52 + .../code-path-analysis/for-in--break-nest.js | 42 + .../for-in--break-simple.js | 34 + .../for-in--continue-always.js | 30 + .../for-in--continue-label.js | 60 + .../for-in--continue-nest.js | 48 + .../for-in--continue-simple.js | 34 + .../code-path-analysis/for-in--direct-nest.js | 30 + .../code-path-analysis/for-in--empty.js | 23 + .../code-path-analysis/for-in--simple.js | 24 + .../for-of--break-always.js | 28 + .../code-path-analysis/for-of--break-label.js | 52 + .../code-path-analysis/for-of--break-nest.js | 42 + .../for-of--break-simple.js | 34 + .../for-of--continue-always.js | 30 + .../for-of--continue-label.js | 60 + .../for-of--continue-nest.js | 48 + .../for-of--continue-simple.js | 34 + .../code-path-analysis/for-of--direct-nest.js | 30 + .../code-path-analysis/for-of--empty.js | 23 + .../code-path-analysis/for-of--simple.js | 24 + .../function--in-condition-expr.js | 42 + .../function--in-logical-right.js | 28 + .../code-path-analysis/function--simple.js | 28 + .../tests/fixtures/code-path-analysis/if-1.js | 20 + .../tests/fixtures/code-path-analysis/if-2.js | 23 + .../tests/fixtures/code-path-analysis/if-3.js | 38 + .../tests/fixtures/code-path-analysis/if-4.js | 31 + .../tests/fixtures/code-path-analysis/if-5.js | 25 + .../tests/fixtures/code-path-analysis/if-6.js | 37 + .../logical--do-while-and-1.js | 21 + .../logical--do-while-and-2.js | 27 + .../logical--do-while-mix-1.js | 24 + .../logical--do-while-mix-2.js | 24 + .../logical--do-while-or-1.js | 21 + .../logical--do-while-or-2.js | 27 + .../code-path-analysis/logical--for-and-1.js | 23 + .../code-path-analysis/logical--for-and-2.js | 29 + .../code-path-analysis/logical--for-and-3.js | 22 + .../code-path-analysis/logical--for-mix-1.js | 26 + .../code-path-analysis/logical--for-mix-2.js | 26 + .../code-path-analysis/logical--for-mix-3.js | 25 + .../code-path-analysis/logical--for-or-1.js | 23 + .../code-path-analysis/logical--for-or-2.js | 29 + .../code-path-analysis/logical--for-or-3.js | 22 + .../code-path-analysis/logical--if-and-1.js | 23 + .../code-path-analysis/logical--if-and-2.js | 28 + .../code-path-analysis/logical--if-and-3.js | 37 + .../code-path-analysis/logical--if-and-4.js | 34 + .../code-path-analysis/logical--if-and-5.js | 40 + .../code-path-analysis/logical--if-mix-1.js | 29 + .../code-path-analysis/logical--if-mix-2.js | 31 + .../code-path-analysis/logical--if-or-1.js | 23 + .../code-path-analysis/logical--if-or-2.js | 26 + .../code-path-analysis/logical--if-or-3.js | 38 + .../code-path-analysis/logical--if-or-4.js | 32 + .../code-path-analysis/logical--if-or-5.js | 40 + .../code-path-analysis/logical--simple-1.js | 24 + .../code-path-analysis/logical--simple-2.js | 24 + .../logical--while-and-1.js | 22 + .../logical--while-and-2.js | 28 + .../logical--while-mix-1.js | 25 + .../logical--while-mix-2.js | 25 + .../code-path-analysis/logical--while-or-1.js | 22 + .../code-path-analysis/logical--while-or-2.js | 28 + .../code-path-analysis/switch--cases-1.js | 53 + .../code-path-analysis/switch--cases-2.js | 61 + .../switch--cases-and-default-1.js | 54 + .../switch--cases-and-default-2.js | 54 + .../switch--cases-and-default-3.js | 52 + .../switch--default-only-1.js | 16 + .../switch--default-only-2.js | 22 + .../code-path-analysis/switch--empty.js | 15 + .../code-path-analysis/switch--precedence.js | 272 + .../switch--single-case-1.js | 17 + .../switch--single-case-2.js | 25 + .../code-path-analysis/try--try-catch-1.js | 26 + .../code-path-analysis/try--try-catch-2.js | 30 + .../code-path-analysis/try--try-catch-3.js | 43 + .../code-path-analysis/try--try-catch-4.js | 55 + .../try--try-catch-finally-1.js | 30 + .../try--try-catch-finally-2.js | 34 + .../try--try-catch-finally-3.js | 52 + .../code-path-analysis/try--try-finally-1.js | 33 + .../code-path-analysis/try--try-finally-2.js | 29 + .../code-path-analysis/try--try-finally-3.js | 31 + .../code-path-analysis/try--try-finally-4.js | 60 + .../code-path-analysis/try--try-finally-5.js | 55 + .../try--try-with-for-inof-1.js | 33 + .../try--try-with-for-inof-2.js | 32 + .../unreachable-controls.js | 42 + .../code-path-analysis/while--break-always.js | 23 + .../code-path-analysis/while--break-label.js | 50 + .../code-path-analysis/while--break-nest-2.js | 46 + .../code-path-analysis/while--break-nest.js | 40 + .../code-path-analysis/while--break-simple.js | 29 + .../while--continue-always.js | 25 + .../while--continue-label.js | 46 + .../while--continue-nest.js | 40 + .../while--continue-simple.js | 31 + .../code-path-analysis/while--direct-nest.js | 28 + .../code-path-analysis/while--empty.js | 18 + .../code-path-analysis/while--simple.js | 19 + .../tests/fixtures/config-extends/.eslintrc | 12 + .../fixtures/config-extends/array/.eslintrc | 14 + .../fixtures/config-extends/array/.eslintrc1 | 12 + .../fixtures/config-extends/array/.eslintrc2 | 9 + .../tests/fixtures/config-extends/deep.json | 11 + .../tests/fixtures/config-extends/error.json | 3 + .../fixtures/config-extends/js/.eslintrc | 12 + .../fixtures/config-extends/package.json | 7 + .../fixtures/config-extends/package/.eslintrc | 12 + .../config-extends/package2/.eslintrc | 12 + .../config-extends/package2/subdir/foo.js | 1 + .../config-extends/package3/.eslintrc | 12 + .../config-extends/package4/.eslintrc | 12 + .../resolving-relatively/.eslintrc.json | 3 + .../node_modules/a/index.js | 8 + .../node_modules/a/node_modules/b/index.js | 7 + .../config-extends/scoped-package/.eslintrc | 12 + .../config-extends/scoped-package2/.eslintrc | 12 + .../config-extends/scoped-package3/.eslintrc | 12 + .../config-extends/scoped-package3/foo.js | 1 + .../config-extends/scoped-package4/.eslintrc | 12 + .../config-extends/scoped-package5/.eslintrc | 12 + .../config-extends/scoped-package6/.eslintrc | 12 + .../config-extends/scoped-package7/.eslintrc | 12 + .../config-extends/scoped-package8/.eslintrc | 12 + .../config-extends/scoped-package9/.eslintrc | 12 + .../fixtures/config-extends/subdir/.eslintrc | 10 + .../subdir/subsubdir/deeper.json | 11 + .../subsubdir/subsubsubdir/deepest.json | 10 + .../fixtures/config-file/bom/.eslintrc.json | 5 + .../fixtures/config-file/bom/.eslintrc.yaml | 2 + .../fixtures/config-file/bom/package.json | 10 + .../broken-package-json/package.json | 6 + .../fixtures/config-file/cjs/.eslintrc.cjs | 5 + .../config-file/ecma-features/.eslintrc.yml | 1 + .../extends-chain-2/.eslintrc.json | 3 + .../extends-chain-2/parser.eslintrc.json | 3 + .../config-file/extends-chain-2/parser.js | 5 + .../extends-chain-2/relative.eslintrc.json | 3 + .../config-file/extends-chain/.eslintrc.json | 3 + .../config-file/extends/.eslintrc.yml | 3 + .../invalid/invalid-top-level-property.yml | 1 + .../tests/fixtures/config-file/js/.eslintrc | 1 + .../config-file/js/.eslintrc.broken.js | 5 + .../fixtures/config-file/js/.eslintrc.js | 5 + .../config-file/js/.eslintrc.parser.js | 6 + .../config-file/js/.eslintrc.parser2.js | 6 + .../config-file/js/.eslintrc.parser3.js | 6 + .../config-file/js/node_modules/foo/index.js | 1 + .../fixtures/config-file/js/not-a-config.js | 1 + .../tests/fixtures/config-file/json/.eslintrc | 1 + .../fixtures/config-file/json/.eslintrc.json | 5 + .../fixtures/config-file/legacy/.eslintrc | 5 + .../config-file/package-json/package.json | 7 + .../config-file/plugins/.eslintrc.yml | 8 + .../config-file/plugins/.eslintrc2.yml | 3 + .../tests/fixtures/config-file/yaml/.eslintrc | 1 + .../config-file/yaml/.eslintrc.empty.yaml | 1 + .../fixtures/config-file/yaml/.eslintrc.yaml | 2 + .../fixtures/config-file/yaml/.eslintrc.yml | 1 + .../tests/fixtures/config-file/yml/.eslintrc | 1 + .../fixtures/config-file/yml/.eslintrc.yml | 2 + .../config-hierarchy/broken/.eslintrc | 4 + .../config-hierarchy/broken/add-conf.yaml | 2 + .../broken/console-wrong-quotes-node.js | 4 + .../broken/console-wrong-quotes.js | 1 + .../broken/override-conf.yaml | 2 + .../broken/override-env-conf.yaml | 5 + .../config-hierarchy/broken/package.json | 5 + .../config-hierarchy/broken/plugins/.eslintrc | 3 + .../broken/plugins/console-wrong-quotes.js | 1 + .../broken/plugins2/.eslintrc | 3 + .../broken/plugins2/console-wrong-quotes.js | 1 + .../config-hierarchy/broken/process-exit.js | 1 + .../broken/subbroken/.eslintrc | 3 + .../broken/subbroken/console-wrong-quotes.js | 1 + .../broken/subbroken/subsubbroken/.eslintrc | 3 + .../subsubbroken/console-wrong-quotes.js | 1 + .../config-hierarchy/broken/wrong-quotes.js | 7 + .../config-hierarchy/envs/.eslintrc.json | 4 + .../config-hierarchy/envs/sub/.eslintrc.json | 3 + .../fixtures/config-hierarchy/envs/sub/foo.js | 1 + .../config-hierarchy/file-structure.json | 116 + .../config-hierarchy/fileexts/.eslintrc.js | 6 + .../fileexts/subdir/.eslintrc.yml | 2 + .../fileexts/subdir/subsubdir/.eslintrc.json | 5 + .../overwrite-ecmaFeatures/.eslintrc | 7 + .../overwrite-ecmaFeatures/child/.eslintrc | 5 + .../config-hierarchy/packagejson/.eslintrc | 2 + .../config-hierarchy/packagejson/package.json | 9 + .../packagejson/subdir/package.json | 9 + .../packagejson/subdir/subsubdir/package.json | 9 + .../subsubdir/subsubsubdir/package.json | 9 + .../subsubdir/subsubsubdir/wrong-quotes.js | 1 + .../subdir/subsubdir/wrong-quotes.js | 1 + .../packagejson/subdir/wrong-quotes.js | 1 + .../packagejson/wrong-quotes.js | 1 + .../home-folder-with-packagejson/package.json | 4 + .../home-folder/.eslintrc.json | 5 + .../home-folder/project/.eslintrc | 5 + .../home-folder/project/package.json | 1 + .../project-with-config/.eslintrc | 5 + .../project-with-config/package.json | 1 + .../project-with-config/subfolder/.eslintrc | 5 + .../project-without-config/package.json | 1 + .../root-true/parent/.eslintrc | 6 + .../root-true/parent/root/.eslintrc | 6 + .../root-true/parent/root/subdir/.eslintrc | 0 .../root-true/parent/root/wrong-semi.js | 1 + .../config-hierarchy/shared/a/.eslintrc | 6 + .../config-hierarchy/shared/a/index.js | 0 .../config-hierarchy/shared/b/.eslintrc | 3 + .../config-hierarchy/shared/b/index.js | 0 .../config-initializer/lib/doubleQuotes.js | 1 + .../config-initializer/lib/no-semi.js | 1 + .../new-es-features/new-es-features.js | 3 + .../parse-error/parse-error.js | 1 + .../config-initializer/singleQuotes.js | 1 + .../config-initializer/tests/console-log.js | 1 + .../config-initializer/tests/doubleQuotes.js | 1 + eslint/tests/fixtures/config-rule/schemas.js | 118 + .../fixtures/configurations/comments.json | 10 + .../fixtures/configurations/cwd/.eslintrc | 9 + .../fixtures/configurations/empty/.eslintrc | 0 .../fixtures/configurations/empty/empty.json | 0 .../fixtures/configurations/env-browser.json | 10 + .../fixtures/configurations/env-browser.yaml | 7 + .../fixtures/configurations/env-nashorn.json | 8 + .../fixtures/configurations/env-node.json | 10 + .../configurations/env-webextensions.json | 8 + eslint/tests/fixtures/configurations/es6.json | 5 + .../fixtures/configurations/my-awesome-config | 9 + .../configurations/parser/.eslintrc.json | 7 + .../fixtures/configurations/parser/custom.js | 3 + .../plugins-with-prefix-and-namespace.json | 10 + .../configurations/plugins-with-prefix.json | 10 + ...plugins-without-prefix-with-namespace.json | 10 + .../plugins-without-prefix.json | 10 + .../fixtures/configurations/processors.json | 12 + .../fixtures/configurations/quotes-error.json | 5 + .../fixtures/configurations/semi-error.json | 6 + .../configurations/single-quotes-error.json | 5 + .../configurations/single-quotes/.eslintrc | 10 + .../single-quotes/subdir/.eslintrc | 5 + .../fixtures/configurations/undef-error.json | 5 + .../tests/fixtures/disable-inline-config.js | 1 + .../tests/fixtures/environments/disable.yaml | 5 + eslint/tests/fixtures/environments/fake.yaml | 6 + .../tests/fixtures/environments/plugin.yaml | 5 + eslint/tests/fixtures/eslintrc/.eslintignore | 2 + eslint/tests/fixtures/eslintrc/.eslintrc | 5 + eslint/tests/fixtures/eslintrc/ignored.json | 1 + eslint/tests/fixtures/eslintrc/quotes.js | 1 + .../tests/fixtures/file-finder/.eslintignore | 2 + eslint/tests/fixtures/file-finder/empty | 0 .../fixtures/file-finder/package.json/empty | 0 .../tests/fixtures/file-finder/subdir/empty | 0 .../tests/fixtures/file-finder/subdir/empty2 | 0 .../file-finder/subdir/subsubdir/empty | 0 .../subdir/subsubdir/subsubsubdir/empty | 0 .../xvgRHtyH56756764535jkJ6jthty65tyhteHTEY | 0 .../xvgRHtyH56756764535jkJ6jthty65tyhteHTEY | 0 eslint/tests/fixtures/files/.bar.js | 2 + eslint/tests/fixtures/files/.eslintignore | 0 eslint/tests/fixtures/files/.eslintrc | 2 + eslint/tests/fixtures/files/foo.js | 2 + eslint/tests/fixtures/files/foo.js2 | 2 + ...semi-and-prefer-arrow-callback.expected.js | 7 + ...fix-both-semi-and-prefer-arrow-callback.js | 7 + ...fix-only-prefer-arrow-callback.expected.js | 7 + .../fix-only-prefer-arrow-callback.js | 7 + .../fix-types/fix-only-semi.expected.js | 7 + .../tests/fixtures/fix-types/fix-only-semi.js | 7 + .../fix-types/ignore-missing-meta.expected.js | 8 + .../fixtures/fix-types/ignore-missing-meta.js | 8 + eslint/tests/fixtures/fixmode/multipass.js | 1 + eslint/tests/fixtures/fixmode/ok.js | 1 + .../fixtures/fixmode/quotes-semi-eqeqeq.js | 4 + eslint/tests/fixtures/fixmode/quotes.js | 1 + eslint/tests/fixtures/fixture-parser.js | 16 + eslint/tests/fixtures/formatters/.ignoreme | 0 eslint/tests/fixtures/formatters/broken.js | 5 + eslint/tests/fixtures/formatters/simple.js | 16 + .../tests/fixtures/formatters/test/simple.js | 16 + eslint/tests/fixtures/glob-util/.eslintignore | 0 .../tests/fixtures/glob-util/empty/.gitkeep | 0 .../tests/fixtures/glob-util/hidden/.foo.js | 0 .../fixtures/glob-util/ignored/.eslintignore | 1 + .../tests/fixtures/glob-util/ignored/foo.js | 0 .../glob-util/node_modules/dependency.js | 1 + .../fixtures/glob-util/one-js-file/baz.js | 0 .../fixtures/glob-util/two-js-files/bar.js | 0 .../fixtures/glob-util/two-js-files/foo.js | 0 .../glob-util/unignored/.eslintignore | 3 + .../fixtures/glob-util/unignored/dir/foo.js | 0 eslint/tests/fixtures/globals-browser.js | 1 + eslint/tests/fixtures/globals-nashorn.js | 4 + eslint/tests/fixtures/globals-node.js | 2 + .../tests/fixtures/globals-webextensions.js | 3 + eslint/tests/fixtures/globals/conf.yaml | 5 + .../fixtures/ignored-paths/.eslintignore | 1 + .../.eslintignoreForDifferentCwd | 1 + .../ignored-paths/.eslintignoreWithComments | 7 + .../ignored-paths/.eslintignoreWithNegation | 2 + .../.eslintignoreWithUnignoredDefaults | 3 + .../bad-package-json-ignore/package.json | 11 + .../bower_components/package/file.js | 0 .../broken-package-json/package.json | 11 + .../fixtures/ignored-paths/crlf/.eslintignore | 2 + .../ignored-paths/custom-name/ignore-file | 1 + .../ignore-pattern/ignore-me.txt | 0 .../ignore-pattern/subdir/ignore-me.txt | 0 .../fixtures/ignored-paths/negation/ignore.js | 0 .../ignored-paths/negation/unignore.js | 0 .../node_modules/package/file.js | 0 .../package-json-ignore/package.json | 11 + .../tests/fixtures/ignored-paths/package.json | 11 + .../subdir/.eslintignoreInChildDir | 5 + .../subdir/bower_components/package/file.js | 0 .../subdir/node_modules/package/file.js | 0 .../fixtures/ignored-paths/subdir/undef.js | 2 + eslint/tests/fixtures/ignored-paths/undef.js | 2 + .../tests/fixtures/ignored-paths/unignored.js | 0 .../fixtures/lint-result-cache/.eslintrc.json | 7 + .../lint-result-cache/test-with-errors.js | 2 + eslint/tests/fixtures/max-warnings/.eslintrc | 6 + .../fixtures/max-warnings/six-warnings.js | 6 + eslint/tests/fixtures/missing-semicolon.js | 1 + .../fixtures/module-not-found/.eslintrc.yml | 1 + .../module-not-found/extends-js/.eslintrc.yml | 1 + .../extends-plugin/.eslintrc.yml | 1 + .../module-not-found/plugins/.eslintrc.yml | 1 + .../throw-in-config-itself/.eslintrc.js | 1 + .../throw-in-extends-js/.eslintrc.yml | 1 + .../throw-in-extends-plugin/.eslintrc.yml | 1 + .../throw-in-plugins/.eslintrc.yml | 1 + .../module-resolver/node_modules/foo.js | 1 + .../tests/fixtures/packagejson/package.json | 9 + eslint/tests/fixtures/packagejson/quotes.js | 1 + .../flow-destructuring-1.js | 567 + .../flow-destructuring-2.js | 567 + .../parsers/arrow-parens/identifer-type.js | 349 + .../parsers/arrow-parens/return-type.js | 350 + .../object-pattern-with-rest-element.js | 151 + .../destructuring-object-spread.js | 1726 + .../array-pattern-with-annotation.js | 389 + .../babel-eslint7/function-type-annotation.js | 525 + .../object-pattern-with-annotation.js | 438 + .../object-pattern-with-object-annotation.js | 439 + .../parsers/comma-dangle/object-pattern-1.js | 1148 + .../parsers/comma-dangle/object-pattern-2.js | 1148 + .../parsers/comma-dangle/return-type-1.js | 907 + .../parsers/comma-dangle/return-type-2.js | 907 + .../tests/fixtures/parsers/enhanced-parser.js | 18 + .../fixtures/parsers/enhanced-parser2.js | 23 + .../fixtures/parsers/enhanced-parser3.js | 63 + eslint/tests/fixtures/parsers/line-error.js | 5 + .../fixtures/parsers/linter-test-parsers.js | 14 + .../tests/fixtures/parsers/no-line-error.js | 5 + ...flow-stub-parser-multiline-type-literal.js | 821 + .../flow-stub-parser-multiline.js | 591 + ...low-stub-parser-singleline-type-literal.js | 818 + .../flow-stub-parser-singleline.js | 588 + .../flow-stub-parser-never-invalid.js | 607 + .../flow-stub-parser-never-valid.js | 607 + eslint/tests/fixtures/parsers/stub-parser.js | 11 + .../fixtures/parsers/throws-with-options.js | 10 + ...on-declaration-type-annotation-no-space.js | 486 + .../function-expression-type-annotation.js | 585 + .../function-parameter-type-annotation.js | 393 + .../function-return-type-annotation.js | 297 + ...claration-init-type-annotation-no-space.js | 293 + ...riable-declaration-init-type-annotation.js | 294 + .../parsers/typescript-parsers/declare-var.js | 215 + .../decorator-with-class-methods.js | 890 + .../decorator-with-class.js | 214 + .../decorator-with-keywords-class-method.js | 1183 + .../decorator-with-static-class-methods.js | 1187 + .../typescript-parsers/global-await.js | 162 + .../typescript-parsers/global-for-await-of.js | 470 + .../keyword-with-arrow-function.js | 180 + .../parsers/typescript-parsers/new-parens.js | 136 + .../object-assign-with-generic-1.js | 343 + .../object-assign-with-generic-2.js | 198 + .../object-with-arrow-fn-props.js | 8935 + .../tagged-template-with-generic-1.js | 245 + .../tagged-template-with-generic-2.js | 247 + .../tagged-template-with-generic-3.js | 245 + ...agged-template-with-generic-and-comment.js | 325 + .../parsers/typescript-parsers/type-alias.js | 155 + .../unknown-nodes/abstract-class-invalid.js | 1208 + .../unknown-nodes/abstract-class-valid.js | 1207 + .../functions-with-abstract-class-invalid.js | 1064 + .../functions-with-abstract-class-valid.js | 1064 + .../parsers/unknown-nodes/interface.js | 446 + .../unknown-nodes/namespace-invalid.js | 830 + .../parsers/unknown-nodes/namespace-valid.js | 830 + ...h-functions-with-abstract-class-invalid.js | 1200 + ...ith-functions-with-abstract-class-valid.js | 1201 + ...iable-declarator-type-indent-two-spaces.js | 395 + .../variable-declarator-type-no-indent.js | 395 + .../unknown-logical-operator-nested.js | 227 + .../unknown-logical-operator.js | 156 + eslint/tests/fixtures/passing-es7.js | 4 + eslint/tests/fixtures/passing.js | 4 + .../plugin-shorthand/basic/.eslintrc.json | 7 + .../plugin-shorthand/extends/.eslintrc.json | 4 + eslint/tests/fixtures/process-exit.js | 1 + .../fixtures/processors/custom-processor.js | 13 + .../fixtures/processors/pattern-processor.js | 63 + .../processors/test/test-processor.txt | 3 + eslint/tests/fixtures/rules/.eslintignore | 1 + eslint/tests/fixtures/rules/.jshintignore | 1 + eslint/tests/fixtures/rules/custom-rule.js | 15 + .../tests/fixtures/rules/dir1/no-strings.js | 14 + .../tests/fixtures/rules/dir2/no-literals.js | 11 + eslint/tests/fixtures/rules/eslint.json | 8 + .../rules/fix-types-test/no-program.js | 22 + eslint/tests/fixtures/rules/fixture-rule.js | 0 .../indent-legacy/indent-invalid-fixture-1.js | 530 + .../indent-legacy/indent-valid-fixture-1.js | 530 + .../rules/indent/indent-invalid-fixture-1.js | 530 + .../rules/indent/indent-valid-fixture-1.js | 530 + .../fixtures/rules/make-syntax-error-rule.js | 14 + eslint/tests/fixtures/rules/missing-rule.json | 5 + .../tests/fixtures/rules/multi-rulesdirs.json | 9 + eslint/tests/fixtures/rules/not-a-rule.yml | 1 + .../fixtures/rules/test-multi-rulesdirs.js | 1 + .../fixtures/rules/test/test-custom-rule.js | 6 + .../tests/fixtures/rules/wrong/custom-rule.js | 5 + eslint/tests/fixtures/shebang.js | 6 + eslint/tests/fixtures/single-quoted.js | 1 + .../fixtures/source-code-util/.eslintignore | 1 + .../fixtures/source-code-util/.eslintrc.json | 3 + eslint/tests/fixtures/source-code-util/bar.js | 1 + .../fixtures/source-code-util/ext/foo.abc | 1 + .../fixtures/source-code-util/ext/foo.js | 1 + eslint/tests/fixtures/source-code-util/foo.js | 1 + .../fixtures/source-code-util/ignored.js | 1 + .../fixtures/source-code-util/jsx/foo.jsx | 1 + .../fixtures/source-code-util/nested/bar.js | 1 + .../fixtures/source-code-util/nested/foo.js | 1 + .../parse-error/parse-error.js | 1 + eslint/tests/fixtures/syntax-error.js | 1 + .../testers/rule-tester/fixes-one-problem.js | 23 + .../fixtures/testers/rule-tester/messageId.js | 36 + .../rule-tester/modify-ast-at-first.js | 38 + .../testers/rule-tester/modify-ast-at-last.js | 38 + .../testers/rule-tester/modify-ast.js | 22 + .../fixtures/testers/rule-tester/no-eval.js | 22 + .../testers/rule-tester/no-invalid-args.js | 22 + .../testers/rule-tester/no-invalid-schema.js | 28 + .../rule-tester/no-schema-violation.js | 28 + .../testers/rule-tester/no-test-filename | 20 + .../testers/rule-tester/no-test-global.js | 27 + .../testers/rule-tester/no-test-settings.js | 20 + .../fixtures/testers/rule-tester/no-var.js | 33 + .../testers/rule-tester/suggestions.js | 60 + .../fixtures/traverse/.hidden_dir/dummy.js | 0 .../tests/fixtures/traverse/.hidden_file.js | 0 eslint/tests/fixtures/traverse/found.js | 1 + eslint/tests/fixtures/traverse/found.js2 | 1 + eslint/tests/fixtures/undef.js | 2 + .../fixtures/unmatched-patterns/failing.js | 3 + .../fixtures/unmatched-patterns/passing.js2 | 5 + eslint/tests/fixtures/utf8-bom.js | 3 + eslint/tests/lib/_utils.js | 76 + eslint/tests/lib/api.js | 28 + eslint/tests/lib/cli-engine/_utils.js | 446 + .../cascading-config-array-factory.js | 1601 + eslint/tests/lib/cli-engine/cli-engine.js | 6285 + .../lib/cli-engine/config-array-factory.js | 2417 + .../cli-engine/config-array/config-array.js | 739 + .../config-array/config-dependency.js | 120 + .../config-array/extracted-config.js | 139 + .../cli-engine/config-array/ignore-pattern.js | 125 + .../config-array/override-tester.js | 286 + .../tests/lib/cli-engine/file-enumerator.js | 487 + .../lib/cli-engine/formatters/checkstyle.js | 154 + .../lib/cli-engine/formatters/codeframe.js | 483 + .../lib/cli-engine/formatters/compact.js | 146 + .../tests/lib/cli-engine/formatters/html.js | 593 + .../lib/cli-engine/formatters/jslint-xml.js | 176 + .../formatters/json-with-metadata.js | 81 + .../tests/lib/cli-engine/formatters/json.js | 45 + .../tests/lib/cli-engine/formatters/junit.js | 218 + .../lib/cli-engine/formatters/stylish.js | 418 + .../tests/lib/cli-engine/formatters/table.js | 324 + eslint/tests/lib/cli-engine/formatters/tap.js | 235 + .../tests/lib/cli-engine/formatters/unix.js | 146 + .../lib/cli-engine/formatters/visualstudio.js | 146 + .../tests/lib/cli-engine/lint-result-cache.js | 335 + eslint/tests/lib/cli-engine/load-rules.js | 25 + eslint/tests/lib/cli.js | 1195 + eslint/tests/lib/init/autoconfig.js | 331 + eslint/tests/lib/init/config-file.js | 153 + eslint/tests/lib/init/config-initializer.js | 442 + eslint/tests/lib/init/config-rule.js | 329 + eslint/tests/lib/init/npm-utils.js | 228 + eslint/tests/lib/init/source-code-utils.js | 250 + .../lib/linter/apply-disable-directives.js | 889 + .../code-path-analysis/code-path-analyzer.js | 570 + .../linter/code-path-analysis/code-path.js | 306 + .../tests/lib/linter/config-comment-parser.js | 229 + eslint/tests/lib/linter/interpolate.js | 22 + eslint/tests/lib/linter/linter.js | 5348 + .../tests/lib/linter/node-event-generator.js | 322 + eslint/tests/lib/linter/report-translator.js | 814 + eslint/tests/lib/linter/rule-fixer.js | 141 + eslint/tests/lib/linter/rules.js | 97 + eslint/tests/lib/linter/safe-emitter.js | 43 + eslint/tests/lib/linter/source-code-fixer.js | 642 + eslint/tests/lib/options.js | 393 + .../tests/lib/rule-tester/no-test-runners.js | 32 + eslint/tests/lib/rule-tester/rule-tester.js | 1896 + eslint/tests/lib/rules/accessor-pairs.js | 1740 + .../tests/lib/rules/array-bracket-newline.js | 1585 + .../tests/lib/rules/array-bracket-spacing.js | 1130 + .../tests/lib/rules/array-callback-return.js | 204 + .../tests/lib/rules/array-element-newline.js | 885 + eslint/tests/lib/rules/arrow-body-style.js | 571 + eslint/tests/lib/rules/arrow-parens.js | 280 + eslint/tests/lib/rules/arrow-spacing.js | 313 + eslint/tests/lib/rules/block-scoped-var.js | 170 + eslint/tests/lib/rules/block-spacing.js | 403 + eslint/tests/lib/rules/brace-style.js | 669 + eslint/tests/lib/rules/callback-return.js | 483 + eslint/tests/lib/rules/camelcase.js | 951 + .../tests/lib/rules/capitalized-comments.js | 909 + .../tests/lib/rules/class-methods-use-this.js | 125 + eslint/tests/lib/rules/comma-dangle.js | 1756 + eslint/tests/lib/rules/comma-spacing.js | 511 + eslint/tests/lib/rules/comma-style.js | 624 + eslint/tests/lib/rules/complexity.js | 119 + .../lib/rules/computed-property-spacing.js | 1911 + eslint/tests/lib/rules/consistent-return.js | 287 + eslint/tests/lib/rules/consistent-this.js | 69 + eslint/tests/lib/rules/constructor-super.js | 228 + eslint/tests/lib/rules/curly.js | 1287 + eslint/tests/lib/rules/default-case-last.js | 128 + eslint/tests/lib/rules/default-case.js | 115 + eslint/tests/lib/rules/default-param-last.js | 106 + eslint/tests/lib/rules/dot-location.js | 260 + eslint/tests/lib/rules/dot-notation.js | 223 + eslint/tests/lib/rules/eol-last.js | 130 + eslint/tests/lib/rules/eqeqeq.js | 175 + eslint/tests/lib/rules/for-direction.js | 82 + eslint/tests/lib/rules/func-call-spacing.js | 721 + eslint/tests/lib/rules/func-name-matching.js | 462 + eslint/tests/lib/rules/func-names.js | 810 + eslint/tests/lib/rules/func-style.js | 131 + .../rules/function-call-argument-newline.js | 569 + .../tests/lib/rules/function-paren-newline.js | 1073 + .../tests/lib/rules/generator-star-spacing.js | 1026 + eslint/tests/lib/rules/getter-return.js | 119 + eslint/tests/lib/rules/global-require.js | 82 + .../tests/lib/rules/grouped-accessor-pairs.js | 440 + eslint/tests/lib/rules/guard-for-in.js | 39 + eslint/tests/lib/rules/handle-callback-err.js | 74 + eslint/tests/lib/rules/id-blacklist.js | 1351 + eslint/tests/lib/rules/id-length.js | 445 + eslint/tests/lib/rules/id-match.js | 604 + .../lib/rules/implicit-arrow-linebreak.js | 524 + eslint/tests/lib/rules/indent-legacy.js | 3876 + eslint/tests/lib/rules/indent.js | 11379 ++ eslint/tests/lib/rules/init-declarations.js | 337 + eslint/tests/lib/rules/jsx-quotes.js | 84 + eslint/tests/lib/rules/key-spacing.js | 2109 + eslint/tests/lib/rules/keyword-spacing.js | 3137 + .../tests/lib/rules/line-comment-position.js | 179 + eslint/tests/lib/rules/linebreak-style.js | 107 + .../tests/lib/rules/lines-around-comment.js | 1623 + .../tests/lib/rules/lines-around-directive.js | 1701 + .../lib/rules/lines-between-class-members.js | 146 + .../tests/lib/rules/max-classes-per-file.js | 59 + eslint/tests/lib/rules/max-depth.js | 46 + eslint/tests/lib/rules/max-len.js | 1104 + .../tests/lib/rules/max-lines-per-function.js | 440 + eslint/tests/lib/rules/max-lines.js | 163 + .../tests/lib/rules/max-nested-callbacks.js | 96 + eslint/tests/lib/rules/max-params.js | 128 + .../lib/rules/max-statements-per-line.js | 160 + eslint/tests/lib/rules/max-statements.js | 148 + .../lib/rules/multiline-comment-style.js | 1478 + eslint/tests/lib/rules/multiline-ternary.js | 561 + eslint/tests/lib/rules/new-cap.js | 165 + eslint/tests/lib/rules/new-parens.js | 185 + eslint/tests/lib/rules/newline-after-var.js | 349 + .../tests/lib/rules/newline-before-return.js | 284 + .../lib/rules/newline-per-chained-call.js | 196 + eslint/tests/lib/rules/no-alert.js | 129 + .../tests/lib/rules/no-array-constructor.js | 38 + .../lib/rules/no-async-promise-executor.js | 61 + eslint/tests/lib/rules/no-await-in-loop.js | 79 + eslint/tests/lib/rules/no-bitwise.js | 46 + .../tests/lib/rules/no-buffer-constructor.js | 74 + eslint/tests/lib/rules/no-caller.js | 32 + .../tests/lib/rules/no-case-declarations.js | 82 + eslint/tests/lib/rules/no-catch-shadow.js | 55 + eslint/tests/lib/rules/no-class-assign.js | 70 + eslint/tests/lib/rules/no-compare-neg-zero.js | 152 + eslint/tests/lib/rules/no-cond-assign.js | 71 + eslint/tests/lib/rules/no-confusing-arrow.js | 76 + eslint/tests/lib/rules/no-console.js | 63 + eslint/tests/lib/rules/no-const-assign.js | 87 + .../tests/lib/rules/no-constant-condition.js | 277 + .../tests/lib/rules/no-constructor-return.js | 60 + eslint/tests/lib/rules/no-continue.js | 57 + eslint/tests/lib/rules/no-control-regex.js | 46 + eslint/tests/lib/rules/no-debugger.js | 32 + eslint/tests/lib/rules/no-delete-var.js | 28 + eslint/tests/lib/rules/no-div-regex.js | 33 + eslint/tests/lib/rules/no-dupe-args.js | 39 + .../tests/lib/rules/no-dupe-class-members.js | 222 + eslint/tests/lib/rules/no-dupe-else-if.js | 317 + eslint/tests/lib/rules/no-dupe-keys.js | 51 + eslint/tests/lib/rules/no-duplicate-case.js | 133 + .../tests/lib/rules/no-duplicate-imports.js | 88 + eslint/tests/lib/rules/no-else-return.js | 523 + .../lib/rules/no-empty-character-class.js | 46 + eslint/tests/lib/rules/no-empty-function.js | 316 + eslint/tests/lib/rules/no-empty-pattern.js | 116 + eslint/tests/lib/rules/no-empty.js | 80 + eslint/tests/lib/rules/no-eq-null.js | 31 + eslint/tests/lib/rules/no-eval.js | 105 + eslint/tests/lib/rules/no-ex-assign.js | 34 + eslint/tests/lib/rules/no-extend-native.js | 141 + eslint/tests/lib/rules/no-extra-bind.js | 185 + .../tests/lib/rules/no-extra-boolean-cast.js | 2400 + eslint/tests/lib/rules/no-extra-label.js | 153 + eslint/tests/lib/rules/no-extra-parens.js | 2240 + eslint/tests/lib/rules/no-extra-semi.js | 162 + eslint/tests/lib/rules/no-fallthrough.js | 173 + eslint/tests/lib/rules/no-floating-decimal.js | 61 + eslint/tests/lib/rules/no-func-assign.js | 93 + eslint/tests/lib/rules/no-global-assign.js | 127 + .../tests/lib/rules/no-implicit-coercion.js | 360 + eslint/tests/lib/rules/no-implicit-globals.js | 1246 + eslint/tests/lib/rules/no-implied-eval.js | 240 + eslint/tests/lib/rules/no-import-assign.js | 315 + eslint/tests/lib/rules/no-inline-comments.js | 350 + .../tests/lib/rules/no-inner-declarations.js | 118 + eslint/tests/lib/rules/no-invalid-regexp.js | 82 + eslint/tests/lib/rules/no-invalid-this.js | 682 + .../lib/rules/no-irregular-whitespace.js | 545 + eslint/tests/lib/rules/no-iterator.js | 67 + eslint/tests/lib/rules/no-label-var.js | 49 + eslint/tests/lib/rules/no-labels.js | 303 + eslint/tests/lib/rules/no-lone-blocks.js | 240 + eslint/tests/lib/rules/no-lonely-if.js | 209 + eslint/tests/lib/rules/no-loop-func.js | 236 + eslint/tests/lib/rules/no-magic-numbers.js | 724 + .../rules/no-misleading-character-class.js | 298 + eslint/tests/lib/rules/no-mixed-operators.js | 368 + eslint/tests/lib/rules/no-mixed-requires.js | 119 + .../lib/rules/no-mixed-spaces-and-tabs.js | 240 + eslint/tests/lib/rules/no-multi-assign.js | 142 + eslint/tests/lib/rules/no-multi-spaces.js | 685 + eslint/tests/lib/rules/no-multi-str.js | 63 + .../lib/rules/no-multiple-empty-lines.js | 361 + eslint/tests/lib/rules/no-native-reassign.js | 62 + .../tests/lib/rules/no-negated-condition.js | 84 + eslint/tests/lib/rules/no-negated-in-lhs.js | 30 + eslint/tests/lib/rules/no-nested-ternary.js | 42 + eslint/tests/lib/rules/no-new-func.js | 42 + eslint/tests/lib/rules/no-new-object.js | 34 + eslint/tests/lib/rules/no-new-require.js | 43 + eslint/tests/lib/rules/no-new-symbol.js | 37 + eslint/tests/lib/rules/no-new-wrappers.js | 58 + eslint/tests/lib/rules/no-new.js | 35 + eslint/tests/lib/rules/no-obj-calls.js | 320 + eslint/tests/lib/rules/no-octal-escape.js | 129 + eslint/tests/lib/rules/no-octal.js | 109 + eslint/tests/lib/rules/no-param-reassign.js | 373 + eslint/tests/lib/rules/no-path-concat.js | 59 + eslint/tests/lib/rules/no-plusplus.js | 170 + eslint/tests/lib/rules/no-process-env.js | 52 + eslint/tests/lib/rules/no-process-exit.js | 51 + eslint/tests/lib/rules/no-proto.js | 34 + .../tests/lib/rules/no-prototype-builtins.js | 103 + eslint/tests/lib/rules/no-redeclare.js | 528 + eslint/tests/lib/rules/no-regex-spaces.js | 372 + .../tests/lib/rules/no-restricted-exports.js | 458 + .../tests/lib/rules/no-restricted-globals.js | 270 + .../tests/lib/rules/no-restricted-imports.js | 760 + .../tests/lib/rules/no-restricted-modules.js | 115 + .../lib/rules/no-restricted-properties.js | 535 + .../tests/lib/rules/no-restricted-syntax.js | 133 + eslint/tests/lib/rules/no-return-assign.js | 100 + eslint/tests/lib/rules/no-return-await.js | 290 + eslint/tests/lib/rules/no-script-url.js | 41 + eslint/tests/lib/rules/no-self-assign.js | 140 + eslint/tests/lib/rules/no-self-compare.js | 44 + eslint/tests/lib/rules/no-sequences.js | 80 + eslint/tests/lib/rules/no-setter-return.js | 510 + .../lib/rules/no-shadow-restricted-names.js | 127 + eslint/tests/lib/rules/no-shadow.js | 532 + eslint/tests/lib/rules/no-spaced-func.js | 107 + eslint/tests/lib/rules/no-sparse-arrays.js | 42 + eslint/tests/lib/rules/no-sync.js | 49 + eslint/tests/lib/rules/no-tabs.js | 157 + .../lib/rules/no-template-curly-in-string.js | 79 + eslint/tests/lib/rules/no-ternary.js | 30 + .../tests/lib/rules/no-this-before-super.js | 166 + eslint/tests/lib/rules/no-throw-literal.js | 150 + eslint/tests/lib/rules/no-trailing-spaces.js | 612 + eslint/tests/lib/rules/no-undef-init.js | 153 + eslint/tests/lib/rules/no-undef.js | 106 + eslint/tests/lib/rules/no-undefined.js | 127 + .../tests/lib/rules/no-underscore-dangle.js | 62 + .../lib/rules/no-unexpected-multiline.js | 267 + .../lib/rules/no-unmodified-loop-condition.js | 71 + eslint/tests/lib/rules/no-unneeded-ternary.js | 362 + eslint/tests/lib/rules/no-unreachable.js | 307 + eslint/tests/lib/rules/no-unsafe-finally.js | 111 + eslint/tests/lib/rules/no-unsafe-negation.js | 256 + .../tests/lib/rules/no-unused-expressions.js | 128 + eslint/tests/lib/rules/no-unused-labels.js | 86 + eslint/tests/lib/rules/no-unused-vars.js | 1026 + .../tests/lib/rules/no-use-before-define.js | 500 + .../lib/rules/no-useless-backreference.js | 513 + eslint/tests/lib/rules/no-useless-call.js | 175 + eslint/tests/lib/rules/no-useless-catch.js | 195 + .../lib/rules/no-useless-computed-key.js | 483 + eslint/tests/lib/rules/no-useless-concat.js | 98 + .../tests/lib/rules/no-useless-constructor.js | 79 + eslint/tests/lib/rules/no-useless-escape.js | 977 + eslint/tests/lib/rules/no-useless-rename.js | 536 + eslint/tests/lib/rules/no-useless-return.js | 448 + eslint/tests/lib/rules/no-var.js | 324 + eslint/tests/lib/rules/no-void.js | 65 + eslint/tests/lib/rules/no-warning-comments.js | 208 + .../rules/no-whitespace-before-property.js | 864 + eslint/tests/lib/rules/no-with.js | 28 + .../rules/nonblock-statement-body-position.js | 341 + .../tests/lib/rules/object-curly-newline.js | 1750 + .../tests/lib/rules/object-curly-spacing.js | 1411 + .../lib/rules/object-property-newline.js | 575 + eslint/tests/lib/rules/object-shorthand.js | 1180 + .../lib/rules/one-var-declaration-per-line.js | 89 + eslint/tests/lib/rules/one-var.js | 1859 + eslint/tests/lib/rules/operator-assignment.js | 403 + eslint/tests/lib/rules/operator-linebreak.js | 633 + eslint/tests/lib/rules/padded-blocks.js | 605 + .../rules/padding-line-between-statements.js | 4965 + .../tests/lib/rules/prefer-arrow-callback.js | 178 + eslint/tests/lib/rules/prefer-const.js | 551 + .../tests/lib/rules/prefer-destructuring.js | 342 + .../rules/prefer-exponentiation-operator.js | 351 + .../lib/rules/prefer-named-capture-group.js | 263 + .../lib/rules/prefer-numeric-literals.js | 323 + .../tests/lib/rules/prefer-object-spread.js | 996 + .../lib/rules/prefer-promise-reject-errors.js | 96 + eslint/tests/lib/rules/prefer-reflect.js | 285 + .../tests/lib/rules/prefer-regex-literals.js | 201 + eslint/tests/lib/rules/prefer-rest-params.js | 33 + eslint/tests/lib/rules/prefer-spread.js | 78 + eslint/tests/lib/rules/prefer-template.js | 223 + eslint/tests/lib/rules/quote-props.js | 384 + eslint/tests/lib/rules/quotes.js | 619 + eslint/tests/lib/rules/radix.js | 190 + .../tests/lib/rules/require-atomic-updates.js | 265 + eslint/tests/lib/rules/require-await.js | 162 + eslint/tests/lib/rules/require-jsdoc.js | 417 + .../tests/lib/rules/require-unicode-regexp.js | 102 + eslint/tests/lib/rules/require-yield.js | 67 + eslint/tests/lib/rules/rest-spread-spacing.js | 617 + eslint/tests/lib/rules/semi-spacing.js | 213 + eslint/tests/lib/rules/semi-style.js | 389 + eslint/tests/lib/rules/semi.js | 1180 + eslint/tests/lib/rules/sort-imports.js | 292 + eslint/tests/lib/rules/sort-keys.js | 1766 + eslint/tests/lib/rules/sort-vars.js | 259 + eslint/tests/lib/rules/space-before-blocks.js | 560 + .../lib/rules/space-before-function-paren.js | 604 + eslint/tests/lib/rules/space-in-parens.js | 568 + eslint/tests/lib/rules/space-infix-ops.js | 438 + eslint/tests/lib/rules/space-unary-ops.js | 810 + eslint/tests/lib/rules/spaced-comment.js | 706 + eslint/tests/lib/rules/strict.js | 578 + .../tests/lib/rules/switch-colon-spacing.js | 176 + eslint/tests/lib/rules/symbol-description.js | 48 + .../tests/lib/rules/template-curly-spacing.js | 354 + .../tests/lib/rules/template-tag-spacing.js | 221 + eslint/tests/lib/rules/unicode-bom.js | 64 + eslint/tests/lib/rules/use-isnan.js | 390 + eslint/tests/lib/rules/utils/ast-utils.js | 1480 + eslint/tests/lib/rules/utils/fix-tracker.js | 146 + eslint/tests/lib/rules/valid-jsdoc.js | 2116 + eslint/tests/lib/rules/valid-typeof.js | 172 + eslint/tests/lib/rules/vars-on-top.js | 432 + eslint/tests/lib/rules/wrap-iife.js | 608 + eslint/tests/lib/rules/wrap-regex.js | 49 + eslint/tests/lib/rules/yield-star-spacing.js | 277 + eslint/tests/lib/rules/yoda.js | 997 + eslint/tests/lib/shared/config-ops.js | 250 + eslint/tests/lib/shared/config-validator.js | 469 + eslint/tests/lib/shared/naming.js | 65 + eslint/tests/lib/shared/runtime-info.js | 216 + eslint/tests/lib/shared/traverser.js | 82 + eslint/tests/lib/source-code/source-code.js | 2741 + eslint/tests/lib/source-code/token-store.js | 1407 + eslint/tests/performance/jshint.js | 11293 ++ eslint/tests/tests.htm | 35 + eslint/tests/tools/code-sample-minimizer.js | 73 + .../internal-rules/consistent-docs-url.js | 101 + .../consistent-meta-messages.js | 38 + .../internal-rules/multiline-comment-style.js | 38 + .../tools/internal-rules/no-invalid-meta.js | 288 + eslint/tests/tools/loose-parser.js | 30 + eslint/tools/code-sample-minimizer.js | 207 + eslint/tools/fuzzer-runner.js | 81 + .../internal-rules/consistent-docs-url.js | 113 + .../consistent-meta-messages.js | 80 + eslint/tools/internal-rules/index.js | 13 + .../internal-rules/multiline-comment-style.js | 25 + .../tools/internal-rules/no-invalid-meta.js | 200 + eslint/tools/internal-rules/package.json | 9 + .../event-generator-tester.js | 62 + eslint/tools/internal-testers/test-parser.js | 48 + eslint/tools/rule-types.json | 280 + eslint/tools/update-readme.js | 127 + eslint/tools/update-rule-types.js | 76 + eslint/webpack.config.js | 49 + patches/0001-adapt-webpack-config.patch | 39 + patches/series | 1 + src/Makefile | 15 + src/app.js | 329 + src/eslint.js | 148541 +++++++++++++++ 1704 files changed, 580101 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 debian/changelog create mode 100644 debian/compat create mode 100644 debian/control create mode 100644 debian/copyright create mode 100755 debian/rules create mode 100644 eslint/.codeclimate.yml create mode 100644 eslint/.editorconfig create mode 100644 eslint/.eslintignore create mode 100644 eslint/.eslintrc.js create mode 100644 eslint/.gitattributes create mode 100644 eslint/.github/FUNDING.yml create mode 100644 eslint/.github/ISSUE_TEMPLATE.md create mode 100644 eslint/.github/ISSUE_TEMPLATE/BUG_REPORT.md create mode 100644 eslint/.github/ISSUE_TEMPLATE/CHANGE.md create mode 100644 eslint/.github/ISSUE_TEMPLATE/NEW_RULE.md create mode 100644 eslint/.github/ISSUE_TEMPLATE/QUESTION.md create mode 100644 eslint/.github/ISSUE_TEMPLATE/RULE_CHANGE.md create mode 100644 eslint/.github/ISSUE_TEMPLATE/SECURITY.md create mode 100644 eslint/.github/PULL_REQUEST_TEMPLATE.md create mode 100644 eslint/.github/workflows/ci.yml create mode 100644 eslint/.markdownlint.yml create mode 100644 eslint/.npmrc create mode 100644 eslint/.nycrc create mode 100644 eslint/CHANGELOG.md create mode 100644 eslint/CODE_OF_CONDUCT.md create mode 100644 eslint/CONTRIBUTING.md create mode 100644 eslint/LICENSE create mode 100644 eslint/Makefile.js create mode 100644 eslint/README.md create mode 100644 eslint/SUPPORT.md create mode 100755 eslint/bin/eslint.js create mode 100644 eslint/conf/category-list.json create mode 100644 eslint/conf/config-schema.js create mode 100644 eslint/conf/default-cli-options.js create mode 100644 eslint/conf/environments.js create mode 100644 eslint/conf/replacements.json create mode 100644 eslint/docs/README.md create mode 100644 eslint/docs/about/index.md create mode 100644 eslint/docs/developer-guide/README.md create mode 100644 eslint/docs/developer-guide/architecture.md create mode 100644 eslint/docs/developer-guide/architecture/dependency.svg create mode 100644 eslint/docs/developer-guide/code-conventions.md create mode 100644 eslint/docs/developer-guide/code-path-analysis.md create mode 100644 eslint/docs/developer-guide/code-path-analysis/README.md create mode 100644 eslint/docs/developer-guide/code-path-analysis/example-dowhilestatement.svg create mode 100644 eslint/docs/developer-guide/code-path-analysis/example-forinstatement.svg create mode 100644 eslint/docs/developer-guide/code-path-analysis/example-forstatement-for-ever.svg create mode 100644 eslint/docs/developer-guide/code-path-analysis/example-forstatement.svg create mode 100644 eslint/docs/developer-guide/code-path-analysis/example-hello-world.svg create mode 100644 eslint/docs/developer-guide/code-path-analysis/example-ifstatement-chain.svg create mode 100644 eslint/docs/developer-guide/code-path-analysis/example-ifstatement.svg create mode 100644 eslint/docs/developer-guide/code-path-analysis/example-switchstatement-has-default.svg create mode 100644 eslint/docs/developer-guide/code-path-analysis/example-switchstatement.svg create mode 100644 eslint/docs/developer-guide/code-path-analysis/example-trystatement-try-catch-finally.svg create mode 100644 eslint/docs/developer-guide/code-path-analysis/example-trystatement-try-catch.svg create mode 100644 eslint/docs/developer-guide/code-path-analysis/example-trystatement-try-finally.svg create mode 100644 eslint/docs/developer-guide/code-path-analysis/example-when-there-is-a-function-f.svg create mode 100644 eslint/docs/developer-guide/code-path-analysis/example-when-there-is-a-function-g.svg create mode 100644 eslint/docs/developer-guide/code-path-analysis/example-whilestatement.svg create mode 100644 eslint/docs/developer-guide/code-path-analysis/helo.svg create mode 100644 eslint/docs/developer-guide/code-path-analysis/loop-event-example-for-1.svg create mode 100644 eslint/docs/developer-guide/code-path-analysis/loop-event-example-for-2.svg create mode 100644 eslint/docs/developer-guide/code-path-analysis/loop-event-example-for-3.svg create mode 100644 eslint/docs/developer-guide/code-path-analysis/loop-event-example-for-4.svg create mode 100644 eslint/docs/developer-guide/code-path-analysis/loop-event-example-for-5.svg create mode 100644 eslint/docs/developer-guide/code-path-analysis/loop-event-example-while-1.svg create mode 100644 eslint/docs/developer-guide/code-path-analysis/loop-event-example-while-2.svg create mode 100644 eslint/docs/developer-guide/code-path-analysis/loop-event-example-while-3.svg create mode 100644 eslint/docs/developer-guide/contributing/README.md create mode 100644 eslint/docs/developer-guide/contributing/changes.md create mode 100644 eslint/docs/developer-guide/contributing/new-rules.md create mode 100644 eslint/docs/developer-guide/contributing/pull-requests.md create mode 100644 eslint/docs/developer-guide/contributing/reporting-bugs.md create mode 100644 eslint/docs/developer-guide/contributing/rule-changes.md create mode 100644 eslint/docs/developer-guide/contributing/working-on-issues.md create mode 100644 eslint/docs/developer-guide/development-environment.md create mode 100644 eslint/docs/developer-guide/nodejs-api.md create mode 100644 eslint/docs/developer-guide/scope-manager-interface.md create mode 100644 eslint/docs/developer-guide/selectors.md create mode 100644 eslint/docs/developer-guide/shareable-configs.md create mode 100644 eslint/docs/developer-guide/source-code.md create mode 100644 eslint/docs/developer-guide/unit-tests.md create mode 100644 eslint/docs/developer-guide/working-with-custom-formatters.md create mode 100644 eslint/docs/developer-guide/working-with-custom-parsers.md create mode 100644 eslint/docs/developer-guide/working-with-plugins.md create mode 100644 eslint/docs/developer-guide/working-with-rules-deprecated.md create mode 100644 eslint/docs/developer-guide/working-with-rules.md create mode 100644 eslint/docs/maintainer-guide/README.md create mode 100644 eslint/docs/maintainer-guide/governance.md create mode 100644 eslint/docs/maintainer-guide/issues.md create mode 100644 eslint/docs/maintainer-guide/npm-2fa.md create mode 100644 eslint/docs/maintainer-guide/pullrequests.md create mode 100644 eslint/docs/maintainer-guide/releases.md create mode 100644 eslint/docs/maintainer-guide/working-groups.md create mode 100644 eslint/docs/rules/accessor-pairs.md create mode 100644 eslint/docs/rules/array-bracket-newline.md create mode 100644 eslint/docs/rules/array-bracket-spacing.md create mode 100644 eslint/docs/rules/array-callback-return.md create mode 100644 eslint/docs/rules/array-element-newline.md create mode 100644 eslint/docs/rules/arrow-body-style.md create mode 100644 eslint/docs/rules/arrow-parens.md create mode 100644 eslint/docs/rules/arrow-spacing.md create mode 100644 eslint/docs/rules/block-scoped-var.md create mode 100644 eslint/docs/rules/block-spacing.md create mode 100644 eslint/docs/rules/brace-style.md create mode 100644 eslint/docs/rules/callback-return.md create mode 100644 eslint/docs/rules/camelcase.md create mode 100644 eslint/docs/rules/capitalized-comments.md create mode 100644 eslint/docs/rules/class-methods-use-this.md create mode 100644 eslint/docs/rules/comma-dangle.md create mode 100644 eslint/docs/rules/comma-spacing.md create mode 100644 eslint/docs/rules/comma-style.md create mode 100644 eslint/docs/rules/complexity.md create mode 100644 eslint/docs/rules/computed-property-spacing.md create mode 100644 eslint/docs/rules/consistent-return.md create mode 100644 eslint/docs/rules/consistent-this.md create mode 100644 eslint/docs/rules/constructor-super.md create mode 100644 eslint/docs/rules/curly.md create mode 100644 eslint/docs/rules/default-case-last.md create mode 100644 eslint/docs/rules/default-case.md create mode 100644 eslint/docs/rules/default-param-last.md create mode 100644 eslint/docs/rules/dot-location.md create mode 100644 eslint/docs/rules/dot-notation.md create mode 100644 eslint/docs/rules/eol-last.md create mode 100644 eslint/docs/rules/eqeqeq.md create mode 100644 eslint/docs/rules/for-direction.md create mode 100644 eslint/docs/rules/func-call-spacing.md create mode 100644 eslint/docs/rules/func-name-matching.md create mode 100644 eslint/docs/rules/func-names.md create mode 100644 eslint/docs/rules/func-style.md create mode 100644 eslint/docs/rules/function-call-argument-newline.md create mode 100644 eslint/docs/rules/function-paren-newline.md create mode 100644 eslint/docs/rules/generator-star-spacing.md create mode 100644 eslint/docs/rules/generator-star.md create mode 100644 eslint/docs/rules/getter-return.md create mode 100644 eslint/docs/rules/global-require.md create mode 100644 eslint/docs/rules/global-strict.md create mode 100644 eslint/docs/rules/grouped-accessor-pairs.md create mode 100644 eslint/docs/rules/guard-for-in.md create mode 100644 eslint/docs/rules/handle-callback-err.md create mode 100644 eslint/docs/rules/id-blacklist.md create mode 100644 eslint/docs/rules/id-length.md create mode 100644 eslint/docs/rules/id-match.md create mode 100644 eslint/docs/rules/implicit-arrow-linebreak.md create mode 100644 eslint/docs/rules/indent-legacy.md create mode 100644 eslint/docs/rules/indent.md create mode 100644 eslint/docs/rules/init-declarations.md create mode 100644 eslint/docs/rules/jsx-quotes.md create mode 100644 eslint/docs/rules/key-spacing.md create mode 100644 eslint/docs/rules/keyword-spacing.md create mode 100644 eslint/docs/rules/line-comment-position.md create mode 100644 eslint/docs/rules/linebreak-style.md create mode 100644 eslint/docs/rules/lines-around-comment.md create mode 100644 eslint/docs/rules/lines-around-directive.md create mode 100644 eslint/docs/rules/lines-between-class-members.md create mode 100644 eslint/docs/rules/max-classes-per-file.md create mode 100644 eslint/docs/rules/max-depth.md create mode 100644 eslint/docs/rules/max-len.md create mode 100644 eslint/docs/rules/max-lines-per-function.md create mode 100644 eslint/docs/rules/max-lines.md create mode 100644 eslint/docs/rules/max-nested-callbacks.md create mode 100644 eslint/docs/rules/max-params.md create mode 100644 eslint/docs/rules/max-statements-per-line.md create mode 100644 eslint/docs/rules/max-statements.md create mode 100644 eslint/docs/rules/multiline-comment-style.md create mode 100644 eslint/docs/rules/multiline-ternary.md create mode 100644 eslint/docs/rules/new-cap.md create mode 100644 eslint/docs/rules/new-parens.md create mode 100644 eslint/docs/rules/newline-after-var.md create mode 100644 eslint/docs/rules/newline-before-return.md create mode 100644 eslint/docs/rules/newline-per-chained-call.md create mode 100644 eslint/docs/rules/no-alert.md create mode 100644 eslint/docs/rules/no-array-constructor.md create mode 100644 eslint/docs/rules/no-arrow-condition.md create mode 100644 eslint/docs/rules/no-async-promise-executor.md create mode 100644 eslint/docs/rules/no-await-in-loop.md create mode 100644 eslint/docs/rules/no-bitwise.md create mode 100644 eslint/docs/rules/no-buffer-constructor.md create mode 100644 eslint/docs/rules/no-caller.md create mode 100644 eslint/docs/rules/no-case-declarations.md create mode 100644 eslint/docs/rules/no-catch-shadow.md create mode 100644 eslint/docs/rules/no-class-assign.md create mode 100644 eslint/docs/rules/no-comma-dangle.md create mode 100644 eslint/docs/rules/no-compare-neg-zero.md create mode 100644 eslint/docs/rules/no-cond-assign.md create mode 100644 eslint/docs/rules/no-confusing-arrow.md create mode 100644 eslint/docs/rules/no-console.md create mode 100644 eslint/docs/rules/no-const-assign.md create mode 100644 eslint/docs/rules/no-constant-condition.md create mode 100644 eslint/docs/rules/no-constructor-return.md create mode 100644 eslint/docs/rules/no-continue.md create mode 100644 eslint/docs/rules/no-control-regex.md create mode 100644 eslint/docs/rules/no-debugger.md create mode 100644 eslint/docs/rules/no-delete-var.md create mode 100644 eslint/docs/rules/no-div-regex.md create mode 100644 eslint/docs/rules/no-dupe-args.md create mode 100644 eslint/docs/rules/no-dupe-class-members.md create mode 100644 eslint/docs/rules/no-dupe-else-if.md create mode 100644 eslint/docs/rules/no-dupe-keys.md create mode 100644 eslint/docs/rules/no-duplicate-case.md create mode 100644 eslint/docs/rules/no-duplicate-imports.md create mode 100644 eslint/docs/rules/no-else-return.md create mode 100644 eslint/docs/rules/no-empty-character-class.md create mode 100644 eslint/docs/rules/no-empty-class.md create mode 100644 eslint/docs/rules/no-empty-function.md create mode 100644 eslint/docs/rules/no-empty-label.md create mode 100644 eslint/docs/rules/no-empty-pattern.md create mode 100644 eslint/docs/rules/no-empty.md create mode 100644 eslint/docs/rules/no-eq-null.md create mode 100644 eslint/docs/rules/no-eval.md create mode 100644 eslint/docs/rules/no-ex-assign.md create mode 100644 eslint/docs/rules/no-extend-native.md create mode 100644 eslint/docs/rules/no-extra-bind.md create mode 100644 eslint/docs/rules/no-extra-boolean-cast.md create mode 100644 eslint/docs/rules/no-extra-label.md create mode 100644 eslint/docs/rules/no-extra-parens.md create mode 100644 eslint/docs/rules/no-extra-semi.md create mode 100644 eslint/docs/rules/no-extra-strict.md create mode 100644 eslint/docs/rules/no-fallthrough.md create mode 100644 eslint/docs/rules/no-floating-decimal.md create mode 100644 eslint/docs/rules/no-func-assign.md create mode 100644 eslint/docs/rules/no-global-assign.md create mode 100644 eslint/docs/rules/no-implicit-coercion.md create mode 100644 eslint/docs/rules/no-implicit-globals.md create mode 100644 eslint/docs/rules/no-implied-eval.md create mode 100644 eslint/docs/rules/no-import-assign.md create mode 100644 eslint/docs/rules/no-inline-comments.md create mode 100644 eslint/docs/rules/no-inner-declarations.md create mode 100644 eslint/docs/rules/no-invalid-regexp.md create mode 100644 eslint/docs/rules/no-invalid-this.md create mode 100644 eslint/docs/rules/no-irregular-whitespace.md create mode 100644 eslint/docs/rules/no-iterator.md create mode 100644 eslint/docs/rules/no-label-var.md create mode 100644 eslint/docs/rules/no-labels.md create mode 100644 eslint/docs/rules/no-lone-blocks.md create mode 100644 eslint/docs/rules/no-lonely-if.md create mode 100644 eslint/docs/rules/no-loop-func.md create mode 100644 eslint/docs/rules/no-magic-numbers.md create mode 100644 eslint/docs/rules/no-misleading-character-class.md create mode 100644 eslint/docs/rules/no-mixed-operators.md create mode 100644 eslint/docs/rules/no-mixed-requires.md create mode 100644 eslint/docs/rules/no-mixed-spaces-and-tabs.md create mode 100644 eslint/docs/rules/no-multi-assign.md create mode 100644 eslint/docs/rules/no-multi-spaces.md create mode 100644 eslint/docs/rules/no-multi-str.md create mode 100644 eslint/docs/rules/no-multiple-empty-lines.md create mode 100644 eslint/docs/rules/no-native-reassign.md create mode 100644 eslint/docs/rules/no-negated-condition.md create mode 100644 eslint/docs/rules/no-negated-in-lhs.md create mode 100644 eslint/docs/rules/no-nested-ternary.md create mode 100644 eslint/docs/rules/no-new-func.md create mode 100644 eslint/docs/rules/no-new-object.md create mode 100644 eslint/docs/rules/no-new-require.md create mode 100644 eslint/docs/rules/no-new-symbol.md create mode 100644 eslint/docs/rules/no-new-wrappers.md create mode 100644 eslint/docs/rules/no-new.md create mode 100644 eslint/docs/rules/no-obj-calls.md create mode 100644 eslint/docs/rules/no-octal-escape.md create mode 100644 eslint/docs/rules/no-octal.md create mode 100644 eslint/docs/rules/no-param-reassign.md create mode 100644 eslint/docs/rules/no-path-concat.md create mode 100644 eslint/docs/rules/no-plusplus.md create mode 100644 eslint/docs/rules/no-process-env.md create mode 100644 eslint/docs/rules/no-process-exit.md create mode 100644 eslint/docs/rules/no-proto.md create mode 100644 eslint/docs/rules/no-prototype-builtins.md create mode 100644 eslint/docs/rules/no-redeclare.md create mode 100644 eslint/docs/rules/no-regex-spaces.md create mode 100644 eslint/docs/rules/no-reserved-keys.md create mode 100644 eslint/docs/rules/no-restricted-exports.md create mode 100644 eslint/docs/rules/no-restricted-globals.md create mode 100644 eslint/docs/rules/no-restricted-imports.md create mode 100644 eslint/docs/rules/no-restricted-modules.md create mode 100644 eslint/docs/rules/no-restricted-properties.md create mode 100644 eslint/docs/rules/no-restricted-syntax.md create mode 100644 eslint/docs/rules/no-return-assign.md create mode 100644 eslint/docs/rules/no-return-await.md create mode 100644 eslint/docs/rules/no-script-url.md create mode 100644 eslint/docs/rules/no-self-assign.md create mode 100644 eslint/docs/rules/no-self-compare.md create mode 100644 eslint/docs/rules/no-sequences.md create mode 100644 eslint/docs/rules/no-setter-return.md create mode 100644 eslint/docs/rules/no-shadow-restricted-names.md create mode 100644 eslint/docs/rules/no-shadow.md create mode 100644 eslint/docs/rules/no-space-before-semi.md create mode 100644 eslint/docs/rules/no-spaced-func.md create mode 100644 eslint/docs/rules/no-sparse-arrays.md create mode 100644 eslint/docs/rules/no-sync.md create mode 100644 eslint/docs/rules/no-tabs.md create mode 100644 eslint/docs/rules/no-template-curly-in-string.md create mode 100644 eslint/docs/rules/no-ternary.md create mode 100644 eslint/docs/rules/no-this-before-super.md create mode 100644 eslint/docs/rules/no-throw-literal.md create mode 100644 eslint/docs/rules/no-trailing-spaces.md create mode 100644 eslint/docs/rules/no-undef-init.md create mode 100644 eslint/docs/rules/no-undef.md create mode 100644 eslint/docs/rules/no-undefined.md create mode 100644 eslint/docs/rules/no-underscore-dangle.md create mode 100644 eslint/docs/rules/no-unexpected-multiline.md create mode 100644 eslint/docs/rules/no-unmodified-loop-condition.md create mode 100644 eslint/docs/rules/no-unneeded-ternary.md create mode 100644 eslint/docs/rules/no-unreachable.md create mode 100644 eslint/docs/rules/no-unsafe-finally.md create mode 100644 eslint/docs/rules/no-unsafe-negation.md create mode 100644 eslint/docs/rules/no-unused-expressions.md create mode 100644 eslint/docs/rules/no-unused-labels.md create mode 100644 eslint/docs/rules/no-unused-vars.md create mode 100644 eslint/docs/rules/no-use-before-define.md create mode 100644 eslint/docs/rules/no-useless-backreference.md create mode 100644 eslint/docs/rules/no-useless-call.md create mode 100644 eslint/docs/rules/no-useless-catch.md create mode 100644 eslint/docs/rules/no-useless-computed-key.md create mode 100644 eslint/docs/rules/no-useless-concat.md create mode 100644 eslint/docs/rules/no-useless-constructor.md create mode 100644 eslint/docs/rules/no-useless-escape.md create mode 100644 eslint/docs/rules/no-useless-rename.md create mode 100644 eslint/docs/rules/no-useless-return.md create mode 100644 eslint/docs/rules/no-var.md create mode 100644 eslint/docs/rules/no-void.md create mode 100644 eslint/docs/rules/no-warning-comments.md create mode 100644 eslint/docs/rules/no-whitespace-before-property.md create mode 100644 eslint/docs/rules/no-with.md create mode 100644 eslint/docs/rules/no-wrap-func.md create mode 100644 eslint/docs/rules/nonblock-statement-body-position.md create mode 100644 eslint/docs/rules/object-curly-newline.md create mode 100644 eslint/docs/rules/object-curly-spacing.md create mode 100644 eslint/docs/rules/object-property-newline.md create mode 100644 eslint/docs/rules/object-shorthand.md create mode 100644 eslint/docs/rules/one-var-declaration-per-line.md create mode 100644 eslint/docs/rules/one-var.md create mode 100644 eslint/docs/rules/operator-assignment.md create mode 100644 eslint/docs/rules/operator-linebreak.md create mode 100644 eslint/docs/rules/padded-blocks.md create mode 100644 eslint/docs/rules/padding-line-between-statements.md create mode 100644 eslint/docs/rules/prefer-arrow-callback.md create mode 100644 eslint/docs/rules/prefer-const.md create mode 100644 eslint/docs/rules/prefer-destructuring.md create mode 100644 eslint/docs/rules/prefer-exponentiation-operator.md create mode 100644 eslint/docs/rules/prefer-named-capture-group.md create mode 100644 eslint/docs/rules/prefer-numeric-literals.md create mode 100644 eslint/docs/rules/prefer-object-spread.md create mode 100644 eslint/docs/rules/prefer-promise-reject-errors.md create mode 100644 eslint/docs/rules/prefer-reflect.md create mode 100644 eslint/docs/rules/prefer-regex-literals.md create mode 100644 eslint/docs/rules/prefer-rest-params.md create mode 100644 eslint/docs/rules/prefer-spread.md create mode 100644 eslint/docs/rules/prefer-template.md create mode 100644 eslint/docs/rules/quote-props.md create mode 100644 eslint/docs/rules/quotes.md create mode 100644 eslint/docs/rules/radix.md create mode 100644 eslint/docs/rules/require-atomic-updates.md create mode 100644 eslint/docs/rules/require-await.md create mode 100644 eslint/docs/rules/require-jsdoc.md create mode 100644 eslint/docs/rules/require-unicode-regexp.md create mode 100644 eslint/docs/rules/require-yield.md create mode 100644 eslint/docs/rules/rest-spread-spacing.md create mode 100644 eslint/docs/rules/semi-spacing.md create mode 100644 eslint/docs/rules/semi-style.md create mode 100644 eslint/docs/rules/semi.md create mode 100644 eslint/docs/rules/sort-imports.md create mode 100644 eslint/docs/rules/sort-keys.md create mode 100644 eslint/docs/rules/sort-vars.md create mode 100644 eslint/docs/rules/space-after-function-name.md create mode 100644 eslint/docs/rules/space-after-keywords.md create mode 100644 eslint/docs/rules/space-before-blocks.md create mode 100644 eslint/docs/rules/space-before-function-paren.md create mode 100644 eslint/docs/rules/space-before-function-parentheses.md create mode 100644 eslint/docs/rules/space-before-keywords.md create mode 100644 eslint/docs/rules/space-in-brackets.md create mode 100644 eslint/docs/rules/space-in-parens.md create mode 100644 eslint/docs/rules/space-infix-ops.md create mode 100644 eslint/docs/rules/space-return-throw-case.md create mode 100644 eslint/docs/rules/space-unary-ops.md create mode 100644 eslint/docs/rules/space-unary-word-ops.md create mode 100644 eslint/docs/rules/spaced-comment.md create mode 100644 eslint/docs/rules/spaced-line-comment.md create mode 100644 eslint/docs/rules/strict.md create mode 100644 eslint/docs/rules/switch-colon-spacing.md create mode 100644 eslint/docs/rules/symbol-description.md create mode 100644 eslint/docs/rules/template-curly-spacing.md create mode 100644 eslint/docs/rules/template-tag-spacing.md create mode 100644 eslint/docs/rules/unicode-bom.md create mode 100644 eslint/docs/rules/use-isnan.md create mode 100644 eslint/docs/rules/valid-jsdoc.md create mode 100644 eslint/docs/rules/valid-typeof.md create mode 100644 eslint/docs/rules/vars-on-top.md create mode 100644 eslint/docs/rules/wrap-iife.md create mode 100644 eslint/docs/rules/wrap-regex.md create mode 100644 eslint/docs/rules/yield-star-spacing.md create mode 100644 eslint/docs/rules/yoda.md create mode 100644 eslint/docs/user-guide/README.md create mode 100644 eslint/docs/user-guide/command-line-interface.md create mode 100644 eslint/docs/user-guide/configuring.md create mode 100644 eslint/docs/user-guide/getting-started.md create mode 100644 eslint/docs/user-guide/integrations.md create mode 100644 eslint/docs/user-guide/migrating-from-jscs.md create mode 100644 eslint/docs/user-guide/migrating-to-1.0.0.md create mode 100644 eslint/docs/user-guide/migrating-to-2.0.0.md create mode 100644 eslint/docs/user-guide/migrating-to-3.0.0.md create mode 100644 eslint/docs/user-guide/migrating-to-4.0.0.md create mode 100644 eslint/docs/user-guide/migrating-to-5.0.0.md create mode 100644 eslint/docs/user-guide/migrating-to-6.0.0.md create mode 100644 eslint/docs/user-guide/rule-deprecation.md create mode 100644 eslint/karma.conf.js create mode 100644 eslint/lib/api.js create mode 100644 eslint/lib/cli-engine/cascading-config-array-factory.js create mode 100644 eslint/lib/cli-engine/cli-engine.js create mode 100644 eslint/lib/cli-engine/config-array-factory.js create mode 100644 eslint/lib/cli-engine/config-array/config-array.js create mode 100644 eslint/lib/cli-engine/config-array/config-dependency.js create mode 100644 eslint/lib/cli-engine/config-array/extracted-config.js create mode 100644 eslint/lib/cli-engine/config-array/ignore-pattern.js create mode 100644 eslint/lib/cli-engine/config-array/index.js create mode 100644 eslint/lib/cli-engine/config-array/override-tester.js create mode 100644 eslint/lib/cli-engine/file-enumerator.js create mode 100644 eslint/lib/cli-engine/formatters/checkstyle.js create mode 100644 eslint/lib/cli-engine/formatters/codeframe.js create mode 100644 eslint/lib/cli-engine/formatters/compact.js create mode 100644 eslint/lib/cli-engine/formatters/html-template-message.html create mode 100644 eslint/lib/cli-engine/formatters/html-template-page.html create mode 100644 eslint/lib/cli-engine/formatters/html-template-result.html create mode 100644 eslint/lib/cli-engine/formatters/html.js create mode 100644 eslint/lib/cli-engine/formatters/jslint-xml.js create mode 100644 eslint/lib/cli-engine/formatters/json-with-metadata.js create mode 100644 eslint/lib/cli-engine/formatters/json.js create mode 100644 eslint/lib/cli-engine/formatters/junit.js create mode 100644 eslint/lib/cli-engine/formatters/stylish.js create mode 100644 eslint/lib/cli-engine/formatters/table.js create mode 100644 eslint/lib/cli-engine/formatters/tap.js create mode 100644 eslint/lib/cli-engine/formatters/unix.js create mode 100644 eslint/lib/cli-engine/formatters/visualstudio.js create mode 100644 eslint/lib/cli-engine/hash.js create mode 100644 eslint/lib/cli-engine/index.js create mode 100644 eslint/lib/cli-engine/lint-result-cache.js create mode 100644 eslint/lib/cli-engine/load-rules.js create mode 100644 eslint/lib/cli-engine/xml-escape.js create mode 100644 eslint/lib/cli.js create mode 100644 eslint/lib/init/autoconfig.js create mode 100644 eslint/lib/init/config-file.js create mode 100644 eslint/lib/init/config-initializer.js create mode 100644 eslint/lib/init/config-rule.js create mode 100644 eslint/lib/init/npm-utils.js create mode 100644 eslint/lib/init/source-code-utils.js create mode 100644 eslint/lib/linter/apply-disable-directives.js create mode 100644 eslint/lib/linter/code-path-analysis/code-path-analyzer.js create mode 100644 eslint/lib/linter/code-path-analysis/code-path-segment.js create mode 100644 eslint/lib/linter/code-path-analysis/code-path-state.js create mode 100644 eslint/lib/linter/code-path-analysis/code-path.js create mode 100644 eslint/lib/linter/code-path-analysis/debug-helpers.js create mode 100644 eslint/lib/linter/code-path-analysis/fork-context.js create mode 100644 eslint/lib/linter/code-path-analysis/id-generator.js create mode 100644 eslint/lib/linter/config-comment-parser.js create mode 100644 eslint/lib/linter/index.js create mode 100644 eslint/lib/linter/interpolate.js create mode 100644 eslint/lib/linter/linter.js create mode 100644 eslint/lib/linter/node-event-generator.js create mode 100644 eslint/lib/linter/report-translator.js create mode 100644 eslint/lib/linter/rule-fixer.js create mode 100644 eslint/lib/linter/rules.js create mode 100644 eslint/lib/linter/safe-emitter.js create mode 100644 eslint/lib/linter/source-code-fixer.js create mode 100644 eslint/lib/linter/timing.js create mode 100644 eslint/lib/options.js create mode 100644 eslint/lib/rule-tester/index.js create mode 100644 eslint/lib/rule-tester/rule-tester.js create mode 100644 eslint/lib/rules/accessor-pairs.js create mode 100644 eslint/lib/rules/array-bracket-newline.js create mode 100644 eslint/lib/rules/array-bracket-spacing.js create mode 100644 eslint/lib/rules/array-callback-return.js create mode 100644 eslint/lib/rules/array-element-newline.js create mode 100644 eslint/lib/rules/arrow-body-style.js create mode 100644 eslint/lib/rules/arrow-parens.js create mode 100644 eslint/lib/rules/arrow-spacing.js create mode 100644 eslint/lib/rules/block-scoped-var.js create mode 100644 eslint/lib/rules/block-spacing.js create mode 100644 eslint/lib/rules/brace-style.js create mode 100644 eslint/lib/rules/callback-return.js create mode 100644 eslint/lib/rules/camelcase.js create mode 100644 eslint/lib/rules/capitalized-comments.js create mode 100644 eslint/lib/rules/class-methods-use-this.js create mode 100644 eslint/lib/rules/comma-dangle.js create mode 100644 eslint/lib/rules/comma-spacing.js create mode 100644 eslint/lib/rules/comma-style.js create mode 100644 eslint/lib/rules/complexity.js create mode 100644 eslint/lib/rules/computed-property-spacing.js create mode 100644 eslint/lib/rules/consistent-return.js create mode 100644 eslint/lib/rules/consistent-this.js create mode 100644 eslint/lib/rules/constructor-super.js create mode 100644 eslint/lib/rules/curly.js create mode 100644 eslint/lib/rules/default-case-last.js create mode 100644 eslint/lib/rules/default-case.js create mode 100644 eslint/lib/rules/default-param-last.js create mode 100644 eslint/lib/rules/dot-location.js create mode 100644 eslint/lib/rules/dot-notation.js create mode 100644 eslint/lib/rules/eol-last.js create mode 100644 eslint/lib/rules/eqeqeq.js create mode 100644 eslint/lib/rules/for-direction.js create mode 100644 eslint/lib/rules/func-call-spacing.js create mode 100644 eslint/lib/rules/func-name-matching.js create mode 100644 eslint/lib/rules/func-names.js create mode 100644 eslint/lib/rules/func-style.js create mode 100644 eslint/lib/rules/function-call-argument-newline.js create mode 100644 eslint/lib/rules/function-paren-newline.js create mode 100644 eslint/lib/rules/generator-star-spacing.js create mode 100644 eslint/lib/rules/getter-return.js create mode 100644 eslint/lib/rules/global-require.js create mode 100644 eslint/lib/rules/grouped-accessor-pairs.js create mode 100644 eslint/lib/rules/guard-for-in.js create mode 100644 eslint/lib/rules/handle-callback-err.js create mode 100644 eslint/lib/rules/id-blacklist.js create mode 100644 eslint/lib/rules/id-length.js create mode 100644 eslint/lib/rules/id-match.js create mode 100644 eslint/lib/rules/implicit-arrow-linebreak.js create mode 100644 eslint/lib/rules/indent-legacy.js create mode 100644 eslint/lib/rules/indent.js create mode 100644 eslint/lib/rules/index.js create mode 100644 eslint/lib/rules/init-declarations.js create mode 100644 eslint/lib/rules/jsx-quotes.js create mode 100644 eslint/lib/rules/key-spacing.js create mode 100644 eslint/lib/rules/keyword-spacing.js create mode 100644 eslint/lib/rules/line-comment-position.js create mode 100644 eslint/lib/rules/linebreak-style.js create mode 100644 eslint/lib/rules/lines-around-comment.js create mode 100644 eslint/lib/rules/lines-around-directive.js create mode 100644 eslint/lib/rules/lines-between-class-members.js create mode 100644 eslint/lib/rules/max-classes-per-file.js create mode 100644 eslint/lib/rules/max-depth.js create mode 100644 eslint/lib/rules/max-len.js create mode 100644 eslint/lib/rules/max-lines-per-function.js create mode 100644 eslint/lib/rules/max-lines.js create mode 100644 eslint/lib/rules/max-nested-callbacks.js create mode 100644 eslint/lib/rules/max-params.js create mode 100644 eslint/lib/rules/max-statements-per-line.js create mode 100644 eslint/lib/rules/max-statements.js create mode 100644 eslint/lib/rules/multiline-comment-style.js create mode 100644 eslint/lib/rules/multiline-ternary.js create mode 100644 eslint/lib/rules/new-cap.js create mode 100644 eslint/lib/rules/new-parens.js create mode 100644 eslint/lib/rules/newline-after-var.js create mode 100644 eslint/lib/rules/newline-before-return.js create mode 100644 eslint/lib/rules/newline-per-chained-call.js create mode 100644 eslint/lib/rules/no-alert.js create mode 100644 eslint/lib/rules/no-array-constructor.js create mode 100644 eslint/lib/rules/no-async-promise-executor.js create mode 100644 eslint/lib/rules/no-await-in-loop.js create mode 100644 eslint/lib/rules/no-bitwise.js create mode 100644 eslint/lib/rules/no-buffer-constructor.js create mode 100644 eslint/lib/rules/no-caller.js create mode 100644 eslint/lib/rules/no-case-declarations.js create mode 100644 eslint/lib/rules/no-catch-shadow.js create mode 100644 eslint/lib/rules/no-class-assign.js create mode 100644 eslint/lib/rules/no-compare-neg-zero.js create mode 100644 eslint/lib/rules/no-cond-assign.js create mode 100644 eslint/lib/rules/no-confusing-arrow.js create mode 100644 eslint/lib/rules/no-console.js create mode 100644 eslint/lib/rules/no-const-assign.js create mode 100644 eslint/lib/rules/no-constant-condition.js create mode 100644 eslint/lib/rules/no-constructor-return.js create mode 100644 eslint/lib/rules/no-continue.js create mode 100644 eslint/lib/rules/no-control-regex.js create mode 100644 eslint/lib/rules/no-debugger.js create mode 100644 eslint/lib/rules/no-delete-var.js create mode 100644 eslint/lib/rules/no-div-regex.js create mode 100644 eslint/lib/rules/no-dupe-args.js create mode 100644 eslint/lib/rules/no-dupe-class-members.js create mode 100644 eslint/lib/rules/no-dupe-else-if.js create mode 100644 eslint/lib/rules/no-dupe-keys.js create mode 100644 eslint/lib/rules/no-duplicate-case.js create mode 100644 eslint/lib/rules/no-duplicate-imports.js create mode 100644 eslint/lib/rules/no-else-return.js create mode 100644 eslint/lib/rules/no-empty-character-class.js create mode 100644 eslint/lib/rules/no-empty-function.js create mode 100644 eslint/lib/rules/no-empty-pattern.js create mode 100644 eslint/lib/rules/no-empty.js create mode 100644 eslint/lib/rules/no-eq-null.js create mode 100644 eslint/lib/rules/no-eval.js create mode 100644 eslint/lib/rules/no-ex-assign.js create mode 100644 eslint/lib/rules/no-extend-native.js create mode 100644 eslint/lib/rules/no-extra-bind.js create mode 100644 eslint/lib/rules/no-extra-boolean-cast.js create mode 100644 eslint/lib/rules/no-extra-label.js create mode 100644 eslint/lib/rules/no-extra-parens.js create mode 100644 eslint/lib/rules/no-extra-semi.js create mode 100644 eslint/lib/rules/no-fallthrough.js create mode 100644 eslint/lib/rules/no-floating-decimal.js create mode 100644 eslint/lib/rules/no-func-assign.js create mode 100644 eslint/lib/rules/no-global-assign.js create mode 100644 eslint/lib/rules/no-implicit-coercion.js create mode 100644 eslint/lib/rules/no-implicit-globals.js create mode 100644 eslint/lib/rules/no-implied-eval.js create mode 100644 eslint/lib/rules/no-import-assign.js create mode 100644 eslint/lib/rules/no-inline-comments.js create mode 100644 eslint/lib/rules/no-inner-declarations.js create mode 100644 eslint/lib/rules/no-invalid-regexp.js create mode 100644 eslint/lib/rules/no-invalid-this.js create mode 100644 eslint/lib/rules/no-irregular-whitespace.js create mode 100644 eslint/lib/rules/no-iterator.js create mode 100644 eslint/lib/rules/no-label-var.js create mode 100644 eslint/lib/rules/no-labels.js create mode 100644 eslint/lib/rules/no-lone-blocks.js create mode 100644 eslint/lib/rules/no-lonely-if.js create mode 100644 eslint/lib/rules/no-loop-func.js create mode 100644 eslint/lib/rules/no-magic-numbers.js create mode 100644 eslint/lib/rules/no-misleading-character-class.js create mode 100644 eslint/lib/rules/no-mixed-operators.js create mode 100644 eslint/lib/rules/no-mixed-requires.js create mode 100644 eslint/lib/rules/no-mixed-spaces-and-tabs.js create mode 100644 eslint/lib/rules/no-multi-assign.js create mode 100644 eslint/lib/rules/no-multi-spaces.js create mode 100644 eslint/lib/rules/no-multi-str.js create mode 100644 eslint/lib/rules/no-multiple-empty-lines.js create mode 100644 eslint/lib/rules/no-native-reassign.js create mode 100644 eslint/lib/rules/no-negated-condition.js create mode 100644 eslint/lib/rules/no-negated-in-lhs.js create mode 100644 eslint/lib/rules/no-nested-ternary.js create mode 100644 eslint/lib/rules/no-new-func.js create mode 100644 eslint/lib/rules/no-new-object.js create mode 100644 eslint/lib/rules/no-new-require.js create mode 100644 eslint/lib/rules/no-new-symbol.js create mode 100644 eslint/lib/rules/no-new-wrappers.js create mode 100644 eslint/lib/rules/no-new.js create mode 100644 eslint/lib/rules/no-obj-calls.js create mode 100644 eslint/lib/rules/no-octal-escape.js create mode 100644 eslint/lib/rules/no-octal.js create mode 100644 eslint/lib/rules/no-param-reassign.js create mode 100644 eslint/lib/rules/no-path-concat.js create mode 100644 eslint/lib/rules/no-plusplus.js create mode 100644 eslint/lib/rules/no-process-env.js create mode 100644 eslint/lib/rules/no-process-exit.js create mode 100644 eslint/lib/rules/no-proto.js create mode 100644 eslint/lib/rules/no-prototype-builtins.js create mode 100644 eslint/lib/rules/no-redeclare.js create mode 100644 eslint/lib/rules/no-regex-spaces.js create mode 100644 eslint/lib/rules/no-restricted-exports.js create mode 100644 eslint/lib/rules/no-restricted-globals.js create mode 100644 eslint/lib/rules/no-restricted-imports.js create mode 100644 eslint/lib/rules/no-restricted-modules.js create mode 100644 eslint/lib/rules/no-restricted-properties.js create mode 100644 eslint/lib/rules/no-restricted-syntax.js create mode 100644 eslint/lib/rules/no-return-assign.js create mode 100644 eslint/lib/rules/no-return-await.js create mode 100644 eslint/lib/rules/no-script-url.js create mode 100644 eslint/lib/rules/no-self-assign.js create mode 100644 eslint/lib/rules/no-self-compare.js create mode 100644 eslint/lib/rules/no-sequences.js create mode 100644 eslint/lib/rules/no-setter-return.js create mode 100644 eslint/lib/rules/no-shadow-restricted-names.js create mode 100644 eslint/lib/rules/no-shadow.js create mode 100644 eslint/lib/rules/no-spaced-func.js create mode 100644 eslint/lib/rules/no-sparse-arrays.js create mode 100644 eslint/lib/rules/no-sync.js create mode 100644 eslint/lib/rules/no-tabs.js create mode 100644 eslint/lib/rules/no-template-curly-in-string.js create mode 100644 eslint/lib/rules/no-ternary.js create mode 100644 eslint/lib/rules/no-this-before-super.js create mode 100644 eslint/lib/rules/no-throw-literal.js create mode 100644 eslint/lib/rules/no-trailing-spaces.js create mode 100644 eslint/lib/rules/no-undef-init.js create mode 100644 eslint/lib/rules/no-undef.js create mode 100644 eslint/lib/rules/no-undefined.js create mode 100644 eslint/lib/rules/no-underscore-dangle.js create mode 100644 eslint/lib/rules/no-unexpected-multiline.js create mode 100644 eslint/lib/rules/no-unmodified-loop-condition.js create mode 100644 eslint/lib/rules/no-unneeded-ternary.js create mode 100644 eslint/lib/rules/no-unreachable.js create mode 100644 eslint/lib/rules/no-unsafe-finally.js create mode 100644 eslint/lib/rules/no-unsafe-negation.js create mode 100644 eslint/lib/rules/no-unused-expressions.js create mode 100644 eslint/lib/rules/no-unused-labels.js create mode 100644 eslint/lib/rules/no-unused-vars.js create mode 100644 eslint/lib/rules/no-use-before-define.js create mode 100644 eslint/lib/rules/no-useless-backreference.js create mode 100644 eslint/lib/rules/no-useless-call.js create mode 100644 eslint/lib/rules/no-useless-catch.js create mode 100644 eslint/lib/rules/no-useless-computed-key.js create mode 100644 eslint/lib/rules/no-useless-concat.js create mode 100644 eslint/lib/rules/no-useless-constructor.js create mode 100644 eslint/lib/rules/no-useless-escape.js create mode 100644 eslint/lib/rules/no-useless-rename.js create mode 100644 eslint/lib/rules/no-useless-return.js create mode 100644 eslint/lib/rules/no-var.js create mode 100644 eslint/lib/rules/no-void.js create mode 100644 eslint/lib/rules/no-warning-comments.js create mode 100644 eslint/lib/rules/no-whitespace-before-property.js create mode 100644 eslint/lib/rules/no-with.js create mode 100644 eslint/lib/rules/nonblock-statement-body-position.js create mode 100644 eslint/lib/rules/object-curly-newline.js create mode 100644 eslint/lib/rules/object-curly-spacing.js create mode 100644 eslint/lib/rules/object-property-newline.js create mode 100644 eslint/lib/rules/object-shorthand.js create mode 100644 eslint/lib/rules/one-var-declaration-per-line.js create mode 100644 eslint/lib/rules/one-var.js create mode 100644 eslint/lib/rules/operator-assignment.js create mode 100644 eslint/lib/rules/operator-linebreak.js create mode 100644 eslint/lib/rules/padded-blocks.js create mode 100644 eslint/lib/rules/padding-line-between-statements.js create mode 100644 eslint/lib/rules/prefer-arrow-callback.js create mode 100644 eslint/lib/rules/prefer-const.js create mode 100644 eslint/lib/rules/prefer-destructuring.js create mode 100644 eslint/lib/rules/prefer-exponentiation-operator.js create mode 100644 eslint/lib/rules/prefer-named-capture-group.js create mode 100644 eslint/lib/rules/prefer-numeric-literals.js create mode 100644 eslint/lib/rules/prefer-object-spread.js create mode 100644 eslint/lib/rules/prefer-promise-reject-errors.js create mode 100644 eslint/lib/rules/prefer-reflect.js create mode 100644 eslint/lib/rules/prefer-regex-literals.js create mode 100644 eslint/lib/rules/prefer-rest-params.js create mode 100644 eslint/lib/rules/prefer-spread.js create mode 100644 eslint/lib/rules/prefer-template.js create mode 100644 eslint/lib/rules/quote-props.js create mode 100644 eslint/lib/rules/quotes.js create mode 100644 eslint/lib/rules/radix.js create mode 100644 eslint/lib/rules/require-atomic-updates.js create mode 100644 eslint/lib/rules/require-await.js create mode 100644 eslint/lib/rules/require-jsdoc.js create mode 100644 eslint/lib/rules/require-unicode-regexp.js create mode 100644 eslint/lib/rules/require-yield.js create mode 100644 eslint/lib/rules/rest-spread-spacing.js create mode 100644 eslint/lib/rules/semi-spacing.js create mode 100644 eslint/lib/rules/semi-style.js create mode 100644 eslint/lib/rules/semi.js create mode 100644 eslint/lib/rules/sort-imports.js create mode 100644 eslint/lib/rules/sort-keys.js create mode 100644 eslint/lib/rules/sort-vars.js create mode 100644 eslint/lib/rules/space-before-blocks.js create mode 100644 eslint/lib/rules/space-before-function-paren.js create mode 100644 eslint/lib/rules/space-in-parens.js create mode 100644 eslint/lib/rules/space-infix-ops.js create mode 100644 eslint/lib/rules/space-unary-ops.js create mode 100644 eslint/lib/rules/spaced-comment.js create mode 100644 eslint/lib/rules/strict.js create mode 100644 eslint/lib/rules/switch-colon-spacing.js create mode 100644 eslint/lib/rules/symbol-description.js create mode 100644 eslint/lib/rules/template-curly-spacing.js create mode 100644 eslint/lib/rules/template-tag-spacing.js create mode 100644 eslint/lib/rules/unicode-bom.js create mode 100644 eslint/lib/rules/use-isnan.js create mode 100644 eslint/lib/rules/utils/ast-utils.js create mode 100644 eslint/lib/rules/utils/fix-tracker.js create mode 100644 eslint/lib/rules/utils/keywords.js create mode 100644 eslint/lib/rules/utils/lazy-loading-rule-map.js create mode 100644 eslint/lib/rules/utils/patterns/letters.js create mode 100644 eslint/lib/rules/utils/unicode/index.js create mode 100644 eslint/lib/rules/utils/unicode/is-combining-character.js create mode 100644 eslint/lib/rules/utils/unicode/is-emoji-modifier.js create mode 100644 eslint/lib/rules/utils/unicode/is-regional-indicator-symbol.js create mode 100644 eslint/lib/rules/utils/unicode/is-surrogate-pair.js create mode 100644 eslint/lib/rules/valid-jsdoc.js create mode 100644 eslint/lib/rules/valid-typeof.js create mode 100644 eslint/lib/rules/vars-on-top.js create mode 100644 eslint/lib/rules/wrap-iife.js create mode 100644 eslint/lib/rules/wrap-regex.js create mode 100644 eslint/lib/rules/yield-star-spacing.js create mode 100644 eslint/lib/rules/yoda.js create mode 100644 eslint/lib/shared/ajv.js create mode 100644 eslint/lib/shared/ast-utils.js create mode 100644 eslint/lib/shared/config-ops.js create mode 100644 eslint/lib/shared/config-validator.js create mode 100644 eslint/lib/shared/deprecation-warnings.js create mode 100644 eslint/lib/shared/logging.js create mode 100644 eslint/lib/shared/naming.js create mode 100644 eslint/lib/shared/relative-module-resolver.js create mode 100644 eslint/lib/shared/runtime-info.js create mode 100644 eslint/lib/shared/traverser.js create mode 100644 eslint/lib/shared/types.js create mode 100644 eslint/lib/source-code/index.js create mode 100644 eslint/lib/source-code/source-code.js create mode 100644 eslint/lib/source-code/token-store/backward-token-comment-cursor.js create mode 100644 eslint/lib/source-code/token-store/backward-token-cursor.js create mode 100644 eslint/lib/source-code/token-store/cursor.js create mode 100644 eslint/lib/source-code/token-store/cursors.js create mode 100644 eslint/lib/source-code/token-store/decorative-cursor.js create mode 100644 eslint/lib/source-code/token-store/filter-cursor.js create mode 100644 eslint/lib/source-code/token-store/forward-token-comment-cursor.js create mode 100644 eslint/lib/source-code/token-store/forward-token-cursor.js create mode 100644 eslint/lib/source-code/token-store/index.js create mode 100644 eslint/lib/source-code/token-store/limit-cursor.js create mode 100644 eslint/lib/source-code/token-store/padded-token-cursor.js create mode 100644 eslint/lib/source-code/token-store/skip-cursor.js create mode 100644 eslint/lib/source-code/token-store/utils.js create mode 100644 eslint/messages/all-files-ignored.txt create mode 100644 eslint/messages/extend-config-missing.txt create mode 100644 eslint/messages/failed-to-read-json.txt create mode 100644 eslint/messages/file-not-found.txt create mode 100644 eslint/messages/no-config-found.txt create mode 100644 eslint/messages/plugin-conflict.txt create mode 100644 eslint/messages/plugin-missing.txt create mode 100644 eslint/messages/print-config-with-directory-path.txt create mode 100644 eslint/messages/whitespace-found.txt create mode 100644 eslint/package.json create mode 100644 eslint/templates/blogpost.md.ejs create mode 100644 eslint/templates/bug-report.md create mode 100644 eslint/templates/formatter-examples.md.ejs create mode 100644 eslint/templates/rule-change-proposal.md create mode 100644 eslint/templates/rule-proposal.md create mode 100644 eslint/tests/bench/bench.js create mode 100644 eslint/tests/bench/large.js create mode 100644 eslint/tests/bench/medium.js create mode 100644 eslint/tests/bench/small.js create mode 100644 eslint/tests/bin/eslint.js create mode 100644 eslint/tests/conf/config-schema.js create mode 100644 eslint/tests/fixtures/.eslintignore create mode 100644 eslint/tests/fixtures/.eslintignore2 create mode 100644 eslint/tests/fixtures/.eslintignore_twoGlobstar create mode 100644 eslint/tests/fixtures/.eslintrc create mode 100644 eslint/tests/fixtures/autoconfig/source-with-comments.js create mode 100644 eslint/tests/fixtures/autoconfig/source.js create mode 100644 eslint/tests/fixtures/autofix-integration/left-pad-expected-quiet.js create mode 100644 eslint/tests/fixtures/autofix-integration/left-pad-expected.js create mode 100644 eslint/tests/fixtures/autofix-integration/left-pad.js create mode 100644 eslint/tests/fixtures/autofix/return-conflicting-fixes.expected.js create mode 100644 eslint/tests/fixtures/autofix/return-conflicting-fixes.js create mode 100644 eslint/tests/fixtures/autofix/semicolon-conflicting-fixes.expected.js create mode 100644 eslint/tests/fixtures/autofix/semicolon-conflicting-fixes.js create mode 100644 eslint/tests/fixtures/bin/.eslintrc.yml create mode 100644 eslint/tests/fixtures/cache/src/fail-file.js create mode 100644 eslint/tests/fixtures/cache/src/file-to-delete.js create mode 100644 eslint/tests/fixtures/cache/src/test-file.js create mode 100644 eslint/tests/fixtures/cache/src/test-file2.js create mode 100644 eslint/tests/fixtures/cli-engine/.eslintignore2 create mode 100644 eslint/tests/fixtures/cli-engine/console.js create mode 100644 eslint/tests/fixtures/cli-engine/deprecated-rule-config/.eslintrc.yml create mode 100644 eslint/tests/fixtures/cli-engine/empty/.keep create mode 100644 eslint/tests/fixtures/cli-engine/hidden/.hiddenfolder/double-quotes.js create mode 100644 eslint/tests/fixtures/cli-engine/nested_node_modules/.eslintignore create mode 100644 eslint/tests/fixtures/cli-engine/nested_node_modules/passing.js create mode 100644 eslint/tests/fixtures/cli-engine/nested_node_modules/subdir/node_modules/text.js create mode 100644 eslint/tests/fixtures/cli-engine/node_modules/foo.js create mode 100644 eslint/tests/fixtures/cli-engine/overrides-with-dot/.eslintrc.yml create mode 100644 eslint/tests/fixtures/cli-engine/overrides-with-dot/.test-target.js create mode 100644 eslint/tests/fixtures/cli/.eslintignore_foo create mode 100644 eslint/tests/fixtures/cli/foo/.bar/passing.js create mode 100644 eslint/tests/fixtures/cli/passing.js create mode 100644 eslint/tests/fixtures/cli/syntax-error.js create mode 100644 eslint/tests/fixtures/code-path-analysis/block-and-break-1.js create mode 100644 eslint/tests/fixtures/code-path-analysis/block-and-break-2.js create mode 100644 eslint/tests/fixtures/code-path-analysis/block-and-break-3.js create mode 100644 eslint/tests/fixtures/code-path-analysis/block-and-break-4.js create mode 100644 eslint/tests/fixtures/code-path-analysis/default-params--nest.js create mode 100644 eslint/tests/fixtures/code-path-analysis/default-params--simple.js create mode 100644 eslint/tests/fixtures/code-path-analysis/do-while--break-always.js create mode 100644 eslint/tests/fixtures/code-path-analysis/do-while--break-label.js create mode 100644 eslint/tests/fixtures/code-path-analysis/do-while--break-nest.js create mode 100644 eslint/tests/fixtures/code-path-analysis/do-while--break-simple.js create mode 100644 eslint/tests/fixtures/code-path-analysis/do-while--continue-always.js create mode 100644 eslint/tests/fixtures/code-path-analysis/do-while--continue-label.js create mode 100644 eslint/tests/fixtures/code-path-analysis/do-while--continue-nest.js create mode 100644 eslint/tests/fixtures/code-path-analysis/do-while--continue-simple.js create mode 100644 eslint/tests/fixtures/code-path-analysis/do-while--empty.js create mode 100644 eslint/tests/fixtures/code-path-analysis/do-while--simple.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for--break-always.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for--break-label.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for--break-nest.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for--break-simple-no-test.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for--break-simple-no-update.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for--break-simple.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for--continue-always.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for--continue-label.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for--continue-nest.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for--continue-simple-no-test.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for--continue-simple-no-update.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for--continue-simple.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for--direct-nest.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for--empty.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for--simple-fork-in-test-update.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for--simple-no-test.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for--simple-no-update.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for--simple-test-true.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for--simple.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for-in--break-always.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for-in--break-label.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for-in--break-nest.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for-in--break-simple.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for-in--continue-always.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for-in--continue-label.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for-in--continue-nest.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for-in--continue-simple.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for-in--direct-nest.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for-in--empty.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for-in--simple.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for-of--break-always.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for-of--break-label.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for-of--break-nest.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for-of--break-simple.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for-of--continue-always.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for-of--continue-label.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for-of--continue-nest.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for-of--continue-simple.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for-of--direct-nest.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for-of--empty.js create mode 100644 eslint/tests/fixtures/code-path-analysis/for-of--simple.js create mode 100644 eslint/tests/fixtures/code-path-analysis/function--in-condition-expr.js create mode 100644 eslint/tests/fixtures/code-path-analysis/function--in-logical-right.js create mode 100644 eslint/tests/fixtures/code-path-analysis/function--simple.js create mode 100644 eslint/tests/fixtures/code-path-analysis/if-1.js create mode 100644 eslint/tests/fixtures/code-path-analysis/if-2.js create mode 100644 eslint/tests/fixtures/code-path-analysis/if-3.js create mode 100644 eslint/tests/fixtures/code-path-analysis/if-4.js create mode 100644 eslint/tests/fixtures/code-path-analysis/if-5.js create mode 100644 eslint/tests/fixtures/code-path-analysis/if-6.js create mode 100644 eslint/tests/fixtures/code-path-analysis/logical--do-while-and-1.js create mode 100644 eslint/tests/fixtures/code-path-analysis/logical--do-while-and-2.js create mode 100644 eslint/tests/fixtures/code-path-analysis/logical--do-while-mix-1.js create mode 100644 eslint/tests/fixtures/code-path-analysis/logical--do-while-mix-2.js create mode 100644 eslint/tests/fixtures/code-path-analysis/logical--do-while-or-1.js create mode 100644 eslint/tests/fixtures/code-path-analysis/logical--do-while-or-2.js create mode 100644 eslint/tests/fixtures/code-path-analysis/logical--for-and-1.js create mode 100644 eslint/tests/fixtures/code-path-analysis/logical--for-and-2.js create mode 100644 eslint/tests/fixtures/code-path-analysis/logical--for-and-3.js create mode 100644 eslint/tests/fixtures/code-path-analysis/logical--for-mix-1.js create mode 100644 eslint/tests/fixtures/code-path-analysis/logical--for-mix-2.js create mode 100644 eslint/tests/fixtures/code-path-analysis/logical--for-mix-3.js create mode 100644 eslint/tests/fixtures/code-path-analysis/logical--for-or-1.js create mode 100644 eslint/tests/fixtures/code-path-analysis/logical--for-or-2.js create mode 100644 eslint/tests/fixtures/code-path-analysis/logical--for-or-3.js create mode 100644 eslint/tests/fixtures/code-path-analysis/logical--if-and-1.js create mode 100644 eslint/tests/fixtures/code-path-analysis/logical--if-and-2.js create mode 100644 eslint/tests/fixtures/code-path-analysis/logical--if-and-3.js create mode 100644 eslint/tests/fixtures/code-path-analysis/logical--if-and-4.js create mode 100644 eslint/tests/fixtures/code-path-analysis/logical--if-and-5.js create mode 100644 eslint/tests/fixtures/code-path-analysis/logical--if-mix-1.js create mode 100644 eslint/tests/fixtures/code-path-analysis/logical--if-mix-2.js create mode 100644 eslint/tests/fixtures/code-path-analysis/logical--if-or-1.js create mode 100644 eslint/tests/fixtures/code-path-analysis/logical--if-or-2.js create mode 100644 eslint/tests/fixtures/code-path-analysis/logical--if-or-3.js create mode 100644 eslint/tests/fixtures/code-path-analysis/logical--if-or-4.js create mode 100644 eslint/tests/fixtures/code-path-analysis/logical--if-or-5.js create mode 100644 eslint/tests/fixtures/code-path-analysis/logical--simple-1.js create mode 100644 eslint/tests/fixtures/code-path-analysis/logical--simple-2.js create mode 100644 eslint/tests/fixtures/code-path-analysis/logical--while-and-1.js create mode 100644 eslint/tests/fixtures/code-path-analysis/logical--while-and-2.js create mode 100644 eslint/tests/fixtures/code-path-analysis/logical--while-mix-1.js create mode 100644 eslint/tests/fixtures/code-path-analysis/logical--while-mix-2.js create mode 100644 eslint/tests/fixtures/code-path-analysis/logical--while-or-1.js create mode 100644 eslint/tests/fixtures/code-path-analysis/logical--while-or-2.js create mode 100644 eslint/tests/fixtures/code-path-analysis/switch--cases-1.js create mode 100644 eslint/tests/fixtures/code-path-analysis/switch--cases-2.js create mode 100644 eslint/tests/fixtures/code-path-analysis/switch--cases-and-default-1.js create mode 100644 eslint/tests/fixtures/code-path-analysis/switch--cases-and-default-2.js create mode 100644 eslint/tests/fixtures/code-path-analysis/switch--cases-and-default-3.js create mode 100644 eslint/tests/fixtures/code-path-analysis/switch--default-only-1.js create mode 100644 eslint/tests/fixtures/code-path-analysis/switch--default-only-2.js create mode 100644 eslint/tests/fixtures/code-path-analysis/switch--empty.js create mode 100644 eslint/tests/fixtures/code-path-analysis/switch--precedence.js create mode 100644 eslint/tests/fixtures/code-path-analysis/switch--single-case-1.js create mode 100644 eslint/tests/fixtures/code-path-analysis/switch--single-case-2.js create mode 100644 eslint/tests/fixtures/code-path-analysis/try--try-catch-1.js create mode 100644 eslint/tests/fixtures/code-path-analysis/try--try-catch-2.js create mode 100644 eslint/tests/fixtures/code-path-analysis/try--try-catch-3.js create mode 100644 eslint/tests/fixtures/code-path-analysis/try--try-catch-4.js create mode 100644 eslint/tests/fixtures/code-path-analysis/try--try-catch-finally-1.js create mode 100644 eslint/tests/fixtures/code-path-analysis/try--try-catch-finally-2.js create mode 100644 eslint/tests/fixtures/code-path-analysis/try--try-catch-finally-3.js create mode 100644 eslint/tests/fixtures/code-path-analysis/try--try-finally-1.js create mode 100644 eslint/tests/fixtures/code-path-analysis/try--try-finally-2.js create mode 100644 eslint/tests/fixtures/code-path-analysis/try--try-finally-3.js create mode 100644 eslint/tests/fixtures/code-path-analysis/try--try-finally-4.js create mode 100644 eslint/tests/fixtures/code-path-analysis/try--try-finally-5.js create mode 100644 eslint/tests/fixtures/code-path-analysis/try--try-with-for-inof-1.js create mode 100644 eslint/tests/fixtures/code-path-analysis/try--try-with-for-inof-2.js create mode 100644 eslint/tests/fixtures/code-path-analysis/unreachable-controls.js create mode 100644 eslint/tests/fixtures/code-path-analysis/while--break-always.js create mode 100644 eslint/tests/fixtures/code-path-analysis/while--break-label.js create mode 100644 eslint/tests/fixtures/code-path-analysis/while--break-nest-2.js create mode 100644 eslint/tests/fixtures/code-path-analysis/while--break-nest.js create mode 100644 eslint/tests/fixtures/code-path-analysis/while--break-simple.js create mode 100644 eslint/tests/fixtures/code-path-analysis/while--continue-always.js create mode 100644 eslint/tests/fixtures/code-path-analysis/while--continue-label.js create mode 100644 eslint/tests/fixtures/code-path-analysis/while--continue-nest.js create mode 100644 eslint/tests/fixtures/code-path-analysis/while--continue-simple.js create mode 100644 eslint/tests/fixtures/code-path-analysis/while--direct-nest.js create mode 100644 eslint/tests/fixtures/code-path-analysis/while--empty.js create mode 100644 eslint/tests/fixtures/code-path-analysis/while--simple.js create mode 100644 eslint/tests/fixtures/config-extends/.eslintrc create mode 100644 eslint/tests/fixtures/config-extends/array/.eslintrc create mode 100644 eslint/tests/fixtures/config-extends/array/.eslintrc1 create mode 100644 eslint/tests/fixtures/config-extends/array/.eslintrc2 create mode 100644 eslint/tests/fixtures/config-extends/deep.json create mode 100644 eslint/tests/fixtures/config-extends/error.json create mode 100644 eslint/tests/fixtures/config-extends/js/.eslintrc create mode 100644 eslint/tests/fixtures/config-extends/package.json create mode 100644 eslint/tests/fixtures/config-extends/package/.eslintrc create mode 100644 eslint/tests/fixtures/config-extends/package2/.eslintrc create mode 100644 eslint/tests/fixtures/config-extends/package2/subdir/foo.js create mode 100644 eslint/tests/fixtures/config-extends/package3/.eslintrc create mode 100644 eslint/tests/fixtures/config-extends/package4/.eslintrc create mode 100644 eslint/tests/fixtures/config-extends/resolving-relatively/.eslintrc.json create mode 100644 eslint/tests/fixtures/config-extends/resolving-relatively/node_modules/a/index.js create mode 100644 eslint/tests/fixtures/config-extends/resolving-relatively/node_modules/a/node_modules/b/index.js create mode 100644 eslint/tests/fixtures/config-extends/scoped-package/.eslintrc create mode 100644 eslint/tests/fixtures/config-extends/scoped-package2/.eslintrc create mode 100644 eslint/tests/fixtures/config-extends/scoped-package3/.eslintrc create mode 100644 eslint/tests/fixtures/config-extends/scoped-package3/foo.js create mode 100644 eslint/tests/fixtures/config-extends/scoped-package4/.eslintrc create mode 100644 eslint/tests/fixtures/config-extends/scoped-package5/.eslintrc create mode 100644 eslint/tests/fixtures/config-extends/scoped-package6/.eslintrc create mode 100644 eslint/tests/fixtures/config-extends/scoped-package7/.eslintrc create mode 100644 eslint/tests/fixtures/config-extends/scoped-package8/.eslintrc create mode 100644 eslint/tests/fixtures/config-extends/scoped-package9/.eslintrc create mode 100644 eslint/tests/fixtures/config-extends/subdir/.eslintrc create mode 100644 eslint/tests/fixtures/config-extends/subdir/subsubdir/deeper.json create mode 100644 eslint/tests/fixtures/config-extends/subdir/subsubdir/subsubsubdir/deepest.json create mode 100644 eslint/tests/fixtures/config-file/bom/.eslintrc.json create mode 100644 eslint/tests/fixtures/config-file/bom/.eslintrc.yaml create mode 100644 eslint/tests/fixtures/config-file/bom/package.json create mode 100644 eslint/tests/fixtures/config-file/broken-package-json/package.json create mode 100644 eslint/tests/fixtures/config-file/cjs/.eslintrc.cjs create mode 100644 eslint/tests/fixtures/config-file/ecma-features/.eslintrc.yml create mode 100644 eslint/tests/fixtures/config-file/extends-chain-2/.eslintrc.json create mode 100644 eslint/tests/fixtures/config-file/extends-chain-2/parser.eslintrc.json create mode 100644 eslint/tests/fixtures/config-file/extends-chain-2/parser.js create mode 100644 eslint/tests/fixtures/config-file/extends-chain-2/relative.eslintrc.json create mode 100644 eslint/tests/fixtures/config-file/extends-chain/.eslintrc.json create mode 100644 eslint/tests/fixtures/config-file/extends/.eslintrc.yml create mode 100644 eslint/tests/fixtures/config-file/invalid/invalid-top-level-property.yml create mode 100644 eslint/tests/fixtures/config-file/js/.eslintrc create mode 100644 eslint/tests/fixtures/config-file/js/.eslintrc.broken.js create mode 100644 eslint/tests/fixtures/config-file/js/.eslintrc.js create mode 100644 eslint/tests/fixtures/config-file/js/.eslintrc.parser.js create mode 100644 eslint/tests/fixtures/config-file/js/.eslintrc.parser2.js create mode 100644 eslint/tests/fixtures/config-file/js/.eslintrc.parser3.js create mode 100644 eslint/tests/fixtures/config-file/js/node_modules/foo/index.js create mode 100644 eslint/tests/fixtures/config-file/js/not-a-config.js create mode 100644 eslint/tests/fixtures/config-file/json/.eslintrc create mode 100644 eslint/tests/fixtures/config-file/json/.eslintrc.json create mode 100644 eslint/tests/fixtures/config-file/legacy/.eslintrc create mode 100644 eslint/tests/fixtures/config-file/package-json/package.json create mode 100644 eslint/tests/fixtures/config-file/plugins/.eslintrc.yml create mode 100644 eslint/tests/fixtures/config-file/plugins/.eslintrc2.yml create mode 100644 eslint/tests/fixtures/config-file/yaml/.eslintrc create mode 100644 eslint/tests/fixtures/config-file/yaml/.eslintrc.empty.yaml create mode 100644 eslint/tests/fixtures/config-file/yaml/.eslintrc.yaml create mode 100644 eslint/tests/fixtures/config-file/yaml/.eslintrc.yml create mode 100644 eslint/tests/fixtures/config-file/yml/.eslintrc create mode 100644 eslint/tests/fixtures/config-file/yml/.eslintrc.yml create mode 100644 eslint/tests/fixtures/config-hierarchy/broken/.eslintrc create mode 100644 eslint/tests/fixtures/config-hierarchy/broken/add-conf.yaml create mode 100644 eslint/tests/fixtures/config-hierarchy/broken/console-wrong-quotes-node.js create mode 100644 eslint/tests/fixtures/config-hierarchy/broken/console-wrong-quotes.js create mode 100644 eslint/tests/fixtures/config-hierarchy/broken/override-conf.yaml create mode 100644 eslint/tests/fixtures/config-hierarchy/broken/override-env-conf.yaml create mode 100644 eslint/tests/fixtures/config-hierarchy/broken/package.json create mode 100644 eslint/tests/fixtures/config-hierarchy/broken/plugins/.eslintrc create mode 100644 eslint/tests/fixtures/config-hierarchy/broken/plugins/console-wrong-quotes.js create mode 100644 eslint/tests/fixtures/config-hierarchy/broken/plugins2/.eslintrc create mode 100644 eslint/tests/fixtures/config-hierarchy/broken/plugins2/console-wrong-quotes.js create mode 100644 eslint/tests/fixtures/config-hierarchy/broken/process-exit.js create mode 100644 eslint/tests/fixtures/config-hierarchy/broken/subbroken/.eslintrc create mode 100644 eslint/tests/fixtures/config-hierarchy/broken/subbroken/console-wrong-quotes.js create mode 100644 eslint/tests/fixtures/config-hierarchy/broken/subbroken/subsubbroken/.eslintrc create mode 100644 eslint/tests/fixtures/config-hierarchy/broken/subbroken/subsubbroken/console-wrong-quotes.js create mode 100644 eslint/tests/fixtures/config-hierarchy/broken/wrong-quotes.js create mode 100644 eslint/tests/fixtures/config-hierarchy/envs/.eslintrc.json create mode 100644 eslint/tests/fixtures/config-hierarchy/envs/sub/.eslintrc.json create mode 100644 eslint/tests/fixtures/config-hierarchy/envs/sub/foo.js create mode 100644 eslint/tests/fixtures/config-hierarchy/file-structure.json create mode 100644 eslint/tests/fixtures/config-hierarchy/fileexts/.eslintrc.js create mode 100644 eslint/tests/fixtures/config-hierarchy/fileexts/subdir/.eslintrc.yml create mode 100644 eslint/tests/fixtures/config-hierarchy/fileexts/subdir/subsubdir/.eslintrc.json create mode 100644 eslint/tests/fixtures/config-hierarchy/overwrite-ecmaFeatures/.eslintrc create mode 100644 eslint/tests/fixtures/config-hierarchy/overwrite-ecmaFeatures/child/.eslintrc create mode 100644 eslint/tests/fixtures/config-hierarchy/packagejson/.eslintrc create mode 100644 eslint/tests/fixtures/config-hierarchy/packagejson/package.json create mode 100644 eslint/tests/fixtures/config-hierarchy/packagejson/subdir/package.json create mode 100644 eslint/tests/fixtures/config-hierarchy/packagejson/subdir/subsubdir/package.json create mode 100644 eslint/tests/fixtures/config-hierarchy/packagejson/subdir/subsubdir/subsubsubdir/package.json create mode 100644 eslint/tests/fixtures/config-hierarchy/packagejson/subdir/subsubdir/subsubsubdir/wrong-quotes.js create mode 100644 eslint/tests/fixtures/config-hierarchy/packagejson/subdir/subsubdir/wrong-quotes.js create mode 100644 eslint/tests/fixtures/config-hierarchy/packagejson/subdir/wrong-quotes.js create mode 100644 eslint/tests/fixtures/config-hierarchy/packagejson/wrong-quotes.js create mode 100644 eslint/tests/fixtures/config-hierarchy/personal-config/home-folder-with-packagejson/package.json create mode 100644 eslint/tests/fixtures/config-hierarchy/personal-config/home-folder/.eslintrc.json create mode 100644 eslint/tests/fixtures/config-hierarchy/personal-config/home-folder/project/.eslintrc create mode 100644 eslint/tests/fixtures/config-hierarchy/personal-config/home-folder/project/package.json create mode 100644 eslint/tests/fixtures/config-hierarchy/personal-config/project-with-config/.eslintrc create mode 100644 eslint/tests/fixtures/config-hierarchy/personal-config/project-with-config/package.json create mode 100644 eslint/tests/fixtures/config-hierarchy/personal-config/project-with-config/subfolder/.eslintrc create mode 100644 eslint/tests/fixtures/config-hierarchy/personal-config/project-without-config/package.json create mode 100644 eslint/tests/fixtures/config-hierarchy/root-true/parent/.eslintrc create mode 100644 eslint/tests/fixtures/config-hierarchy/root-true/parent/root/.eslintrc create mode 100644 eslint/tests/fixtures/config-hierarchy/root-true/parent/root/subdir/.eslintrc create mode 100644 eslint/tests/fixtures/config-hierarchy/root-true/parent/root/wrong-semi.js create mode 100644 eslint/tests/fixtures/config-hierarchy/shared/a/.eslintrc create mode 100644 eslint/tests/fixtures/config-hierarchy/shared/a/index.js create mode 100644 eslint/tests/fixtures/config-hierarchy/shared/b/.eslintrc create mode 100644 eslint/tests/fixtures/config-hierarchy/shared/b/index.js create mode 100644 eslint/tests/fixtures/config-initializer/lib/doubleQuotes.js create mode 100644 eslint/tests/fixtures/config-initializer/lib/no-semi.js create mode 100644 eslint/tests/fixtures/config-initializer/new-es-features/new-es-features.js create mode 100644 eslint/tests/fixtures/config-initializer/parse-error/parse-error.js create mode 100644 eslint/tests/fixtures/config-initializer/singleQuotes.js create mode 100644 eslint/tests/fixtures/config-initializer/tests/console-log.js create mode 100644 eslint/tests/fixtures/config-initializer/tests/doubleQuotes.js create mode 100644 eslint/tests/fixtures/config-rule/schemas.js create mode 100644 eslint/tests/fixtures/configurations/comments.json create mode 100644 eslint/tests/fixtures/configurations/cwd/.eslintrc create mode 100644 eslint/tests/fixtures/configurations/empty/.eslintrc create mode 100644 eslint/tests/fixtures/configurations/empty/empty.json create mode 100644 eslint/tests/fixtures/configurations/env-browser.json create mode 100644 eslint/tests/fixtures/configurations/env-browser.yaml create mode 100644 eslint/tests/fixtures/configurations/env-nashorn.json create mode 100644 eslint/tests/fixtures/configurations/env-node.json create mode 100644 eslint/tests/fixtures/configurations/env-webextensions.json create mode 100644 eslint/tests/fixtures/configurations/es6.json create mode 100644 eslint/tests/fixtures/configurations/my-awesome-config create mode 100644 eslint/tests/fixtures/configurations/parser/.eslintrc.json create mode 100644 eslint/tests/fixtures/configurations/parser/custom.js create mode 100644 eslint/tests/fixtures/configurations/plugins-with-prefix-and-namespace.json create mode 100644 eslint/tests/fixtures/configurations/plugins-with-prefix.json create mode 100644 eslint/tests/fixtures/configurations/plugins-without-prefix-with-namespace.json create mode 100644 eslint/tests/fixtures/configurations/plugins-without-prefix.json create mode 100644 eslint/tests/fixtures/configurations/processors.json create mode 100644 eslint/tests/fixtures/configurations/quotes-error.json create mode 100644 eslint/tests/fixtures/configurations/semi-error.json create mode 100644 eslint/tests/fixtures/configurations/single-quotes-error.json create mode 100644 eslint/tests/fixtures/configurations/single-quotes/.eslintrc create mode 100644 eslint/tests/fixtures/configurations/single-quotes/subdir/.eslintrc create mode 100644 eslint/tests/fixtures/configurations/undef-error.json create mode 100644 eslint/tests/fixtures/disable-inline-config.js create mode 100644 eslint/tests/fixtures/environments/disable.yaml create mode 100644 eslint/tests/fixtures/environments/fake.yaml create mode 100644 eslint/tests/fixtures/environments/plugin.yaml create mode 100644 eslint/tests/fixtures/eslintrc/.eslintignore create mode 100644 eslint/tests/fixtures/eslintrc/.eslintrc create mode 100644 eslint/tests/fixtures/eslintrc/ignored.json create mode 100644 eslint/tests/fixtures/eslintrc/quotes.js create mode 100644 eslint/tests/fixtures/file-finder/.eslintignore create mode 100644 eslint/tests/fixtures/file-finder/empty create mode 100644 eslint/tests/fixtures/file-finder/package.json/empty create mode 100644 eslint/tests/fixtures/file-finder/subdir/empty create mode 100644 eslint/tests/fixtures/file-finder/subdir/empty2 create mode 100644 eslint/tests/fixtures/file-finder/subdir/subsubdir/empty create mode 100644 eslint/tests/fixtures/file-finder/subdir/subsubdir/subsubsubdir/empty create mode 100644 eslint/tests/fixtures/file-finder/subdir/xvgRHtyH56756764535jkJ6jthty65tyhteHTEY create mode 100644 eslint/tests/fixtures/file-finder/xvgRHtyH56756764535jkJ6jthty65tyhteHTEY create mode 100644 eslint/tests/fixtures/files/.bar.js create mode 100644 eslint/tests/fixtures/files/.eslintignore create mode 100644 eslint/tests/fixtures/files/.eslintrc create mode 100644 eslint/tests/fixtures/files/foo.js create mode 100644 eslint/tests/fixtures/files/foo.js2 create mode 100644 eslint/tests/fixtures/fix-types/fix-both-semi-and-prefer-arrow-callback.expected.js create mode 100644 eslint/tests/fixtures/fix-types/fix-both-semi-and-prefer-arrow-callback.js create mode 100644 eslint/tests/fixtures/fix-types/fix-only-prefer-arrow-callback.expected.js create mode 100644 eslint/tests/fixtures/fix-types/fix-only-prefer-arrow-callback.js create mode 100644 eslint/tests/fixtures/fix-types/fix-only-semi.expected.js create mode 100644 eslint/tests/fixtures/fix-types/fix-only-semi.js create mode 100644 eslint/tests/fixtures/fix-types/ignore-missing-meta.expected.js create mode 100644 eslint/tests/fixtures/fix-types/ignore-missing-meta.js create mode 100644 eslint/tests/fixtures/fixmode/multipass.js create mode 100644 eslint/tests/fixtures/fixmode/ok.js create mode 100644 eslint/tests/fixtures/fixmode/quotes-semi-eqeqeq.js create mode 100644 eslint/tests/fixtures/fixmode/quotes.js create mode 100644 eslint/tests/fixtures/fixture-parser.js create mode 100644 eslint/tests/fixtures/formatters/.ignoreme create mode 100644 eslint/tests/fixtures/formatters/broken.js create mode 100644 eslint/tests/fixtures/formatters/simple.js create mode 100644 eslint/tests/fixtures/formatters/test/simple.js create mode 100644 eslint/tests/fixtures/glob-util/.eslintignore create mode 100644 eslint/tests/fixtures/glob-util/empty/.gitkeep create mode 100644 eslint/tests/fixtures/glob-util/hidden/.foo.js create mode 100644 eslint/tests/fixtures/glob-util/ignored/.eslintignore create mode 100644 eslint/tests/fixtures/glob-util/ignored/foo.js create mode 100644 eslint/tests/fixtures/glob-util/node_modules/dependency.js create mode 100644 eslint/tests/fixtures/glob-util/one-js-file/baz.js create mode 100644 eslint/tests/fixtures/glob-util/two-js-files/bar.js create mode 100644 eslint/tests/fixtures/glob-util/two-js-files/foo.js create mode 100644 eslint/tests/fixtures/glob-util/unignored/.eslintignore create mode 100644 eslint/tests/fixtures/glob-util/unignored/dir/foo.js create mode 100644 eslint/tests/fixtures/globals-browser.js create mode 100644 eslint/tests/fixtures/globals-nashorn.js create mode 100644 eslint/tests/fixtures/globals-node.js create mode 100644 eslint/tests/fixtures/globals-webextensions.js create mode 100644 eslint/tests/fixtures/globals/conf.yaml create mode 100644 eslint/tests/fixtures/ignored-paths/.eslintignore create mode 100644 eslint/tests/fixtures/ignored-paths/.eslintignoreForDifferentCwd create mode 100644 eslint/tests/fixtures/ignored-paths/.eslintignoreWithComments create mode 100644 eslint/tests/fixtures/ignored-paths/.eslintignoreWithNegation create mode 100644 eslint/tests/fixtures/ignored-paths/.eslintignoreWithUnignoredDefaults create mode 100644 eslint/tests/fixtures/ignored-paths/bad-package-json-ignore/package.json create mode 100644 eslint/tests/fixtures/ignored-paths/bower_components/package/file.js create mode 100644 eslint/tests/fixtures/ignored-paths/broken-package-json/package.json create mode 100644 eslint/tests/fixtures/ignored-paths/crlf/.eslintignore create mode 100644 eslint/tests/fixtures/ignored-paths/custom-name/ignore-file create mode 100644 eslint/tests/fixtures/ignored-paths/ignore-pattern/ignore-me.txt create mode 100644 eslint/tests/fixtures/ignored-paths/ignore-pattern/subdir/ignore-me.txt create mode 100644 eslint/tests/fixtures/ignored-paths/negation/ignore.js create mode 100644 eslint/tests/fixtures/ignored-paths/negation/unignore.js create mode 100644 eslint/tests/fixtures/ignored-paths/node_modules/package/file.js create mode 100644 eslint/tests/fixtures/ignored-paths/package-json-ignore/package.json create mode 100644 eslint/tests/fixtures/ignored-paths/package.json create mode 100644 eslint/tests/fixtures/ignored-paths/subdir/.eslintignoreInChildDir create mode 100644 eslint/tests/fixtures/ignored-paths/subdir/bower_components/package/file.js create mode 100644 eslint/tests/fixtures/ignored-paths/subdir/node_modules/package/file.js create mode 100644 eslint/tests/fixtures/ignored-paths/subdir/undef.js create mode 100644 eslint/tests/fixtures/ignored-paths/undef.js create mode 100644 eslint/tests/fixtures/ignored-paths/unignored.js create mode 100644 eslint/tests/fixtures/lint-result-cache/.eslintrc.json create mode 100644 eslint/tests/fixtures/lint-result-cache/test-with-errors.js create mode 100644 eslint/tests/fixtures/max-warnings/.eslintrc create mode 100644 eslint/tests/fixtures/max-warnings/six-warnings.js create mode 100644 eslint/tests/fixtures/missing-semicolon.js create mode 100644 eslint/tests/fixtures/module-not-found/.eslintrc.yml create mode 100644 eslint/tests/fixtures/module-not-found/extends-js/.eslintrc.yml create mode 100644 eslint/tests/fixtures/module-not-found/extends-plugin/.eslintrc.yml create mode 100644 eslint/tests/fixtures/module-not-found/plugins/.eslintrc.yml create mode 100644 eslint/tests/fixtures/module-not-found/throw-in-config-itself/.eslintrc.js create mode 100644 eslint/tests/fixtures/module-not-found/throw-in-extends-js/.eslintrc.yml create mode 100644 eslint/tests/fixtures/module-not-found/throw-in-extends-plugin/.eslintrc.yml create mode 100644 eslint/tests/fixtures/module-not-found/throw-in-plugins/.eslintrc.yml create mode 100644 eslint/tests/fixtures/module-resolver/node_modules/foo.js create mode 100644 eslint/tests/fixtures/packagejson/package.json create mode 100644 eslint/tests/fixtures/packagejson/quotes.js create mode 100644 eslint/tests/fixtures/parsers/array-bracket-spacing/flow-destructuring-1.js create mode 100644 eslint/tests/fixtures/parsers/array-bracket-spacing/flow-destructuring-2.js create mode 100644 eslint/tests/fixtures/parsers/arrow-parens/identifer-type.js create mode 100644 eslint/tests/fixtures/parsers/arrow-parens/return-type.js create mode 100644 eslint/tests/fixtures/parsers/babel-eslint10/object-pattern-with-rest-element.js create mode 100644 eslint/tests/fixtures/parsers/babel-eslint5/destructuring-object-spread.js create mode 100644 eslint/tests/fixtures/parsers/babel-eslint7/array-pattern-with-annotation.js create mode 100644 eslint/tests/fixtures/parsers/babel-eslint7/function-type-annotation.js create mode 100644 eslint/tests/fixtures/parsers/babel-eslint7/object-pattern-with-annotation.js create mode 100644 eslint/tests/fixtures/parsers/babel-eslint7/object-pattern-with-object-annotation.js create mode 100644 eslint/tests/fixtures/parsers/comma-dangle/object-pattern-1.js create mode 100644 eslint/tests/fixtures/parsers/comma-dangle/object-pattern-2.js create mode 100644 eslint/tests/fixtures/parsers/comma-dangle/return-type-1.js create mode 100644 eslint/tests/fixtures/parsers/comma-dangle/return-type-2.js create mode 100644 eslint/tests/fixtures/parsers/enhanced-parser.js create mode 100644 eslint/tests/fixtures/parsers/enhanced-parser2.js create mode 100644 eslint/tests/fixtures/parsers/enhanced-parser3.js create mode 100644 eslint/tests/fixtures/parsers/line-error.js create mode 100644 eslint/tests/fixtures/parsers/linter-test-parsers.js create mode 100644 eslint/tests/fixtures/parsers/no-line-error.js create mode 100644 eslint/tests/fixtures/parsers/object-curly-newline/flow-stub-parser-multiline-type-literal.js create mode 100644 eslint/tests/fixtures/parsers/object-curly-newline/flow-stub-parser-multiline.js create mode 100644 eslint/tests/fixtures/parsers/object-curly-newline/flow-stub-parser-singleline-type-literal.js create mode 100644 eslint/tests/fixtures/parsers/object-curly-newline/flow-stub-parser-singleline.js create mode 100644 eslint/tests/fixtures/parsers/object-curly-spacing/flow-stub-parser-never-invalid.js create mode 100644 eslint/tests/fixtures/parsers/object-curly-spacing/flow-stub-parser-never-valid.js create mode 100644 eslint/tests/fixtures/parsers/stub-parser.js create mode 100644 eslint/tests/fixtures/parsers/throws-with-options.js create mode 100644 eslint/tests/fixtures/parsers/type-annotations/function-declaration-type-annotation-no-space.js create mode 100644 eslint/tests/fixtures/parsers/type-annotations/function-expression-type-annotation.js create mode 100644 eslint/tests/fixtures/parsers/type-annotations/function-parameter-type-annotation.js create mode 100644 eslint/tests/fixtures/parsers/type-annotations/function-return-type-annotation.js create mode 100644 eslint/tests/fixtures/parsers/type-annotations/variable-declaration-init-type-annotation-no-space.js create mode 100644 eslint/tests/fixtures/parsers/type-annotations/variable-declaration-init-type-annotation.js create mode 100644 eslint/tests/fixtures/parsers/typescript-parsers/declare-var.js create mode 100644 eslint/tests/fixtures/parsers/typescript-parsers/decorator-with-class-methods.js create mode 100644 eslint/tests/fixtures/parsers/typescript-parsers/decorator-with-class.js create mode 100644 eslint/tests/fixtures/parsers/typescript-parsers/decorator-with-keywords-class-method.js create mode 100644 eslint/tests/fixtures/parsers/typescript-parsers/decorator-with-static-class-methods.js create mode 100644 eslint/tests/fixtures/parsers/typescript-parsers/global-await.js create mode 100644 eslint/tests/fixtures/parsers/typescript-parsers/global-for-await-of.js create mode 100644 eslint/tests/fixtures/parsers/typescript-parsers/keyword-with-arrow-function.js create mode 100644 eslint/tests/fixtures/parsers/typescript-parsers/new-parens.js create mode 100644 eslint/tests/fixtures/parsers/typescript-parsers/object-assign-with-generic/object-assign-with-generic-1.js create mode 100644 eslint/tests/fixtures/parsers/typescript-parsers/object-assign-with-generic/object-assign-with-generic-2.js create mode 100644 eslint/tests/fixtures/parsers/typescript-parsers/object-with-arrow-fn-props.js create mode 100644 eslint/tests/fixtures/parsers/typescript-parsers/tagged-template-with-generic/tagged-template-with-generic-1.js create mode 100644 eslint/tests/fixtures/parsers/typescript-parsers/tagged-template-with-generic/tagged-template-with-generic-2.js create mode 100644 eslint/tests/fixtures/parsers/typescript-parsers/tagged-template-with-generic/tagged-template-with-generic-3.js create mode 100644 eslint/tests/fixtures/parsers/typescript-parsers/tagged-template-with-generic/tagged-template-with-generic-and-comment.js create mode 100644 eslint/tests/fixtures/parsers/typescript-parsers/type-alias.js create mode 100644 eslint/tests/fixtures/parsers/unknown-nodes/abstract-class-invalid.js create mode 100644 eslint/tests/fixtures/parsers/unknown-nodes/abstract-class-valid.js create mode 100644 eslint/tests/fixtures/parsers/unknown-nodes/functions-with-abstract-class-invalid.js create mode 100644 eslint/tests/fixtures/parsers/unknown-nodes/functions-with-abstract-class-valid.js create mode 100644 eslint/tests/fixtures/parsers/unknown-nodes/interface.js create mode 100644 eslint/tests/fixtures/parsers/unknown-nodes/namespace-invalid.js create mode 100644 eslint/tests/fixtures/parsers/unknown-nodes/namespace-valid.js create mode 100644 eslint/tests/fixtures/parsers/unknown-nodes/namespace-with-functions-with-abstract-class-invalid.js create mode 100644 eslint/tests/fixtures/parsers/unknown-nodes/namespace-with-functions-with-abstract-class-valid.js create mode 100644 eslint/tests/fixtures/parsers/unknown-nodes/variable-declarator-type-indent-two-spaces.js create mode 100644 eslint/tests/fixtures/parsers/unknown-nodes/variable-declarator-type-no-indent.js create mode 100644 eslint/tests/fixtures/parsers/unknown-operators/unknown-logical-operator-nested.js create mode 100644 eslint/tests/fixtures/parsers/unknown-operators/unknown-logical-operator.js create mode 100644 eslint/tests/fixtures/passing-es7.js create mode 100644 eslint/tests/fixtures/passing.js create mode 100644 eslint/tests/fixtures/plugin-shorthand/basic/.eslintrc.json create mode 100644 eslint/tests/fixtures/plugin-shorthand/extends/.eslintrc.json create mode 100644 eslint/tests/fixtures/process-exit.js create mode 100644 eslint/tests/fixtures/processors/custom-processor.js create mode 100644 eslint/tests/fixtures/processors/pattern-processor.js create mode 100644 eslint/tests/fixtures/processors/test/test-processor.txt create mode 100644 eslint/tests/fixtures/rules/.eslintignore create mode 100644 eslint/tests/fixtures/rules/.jshintignore create mode 100644 eslint/tests/fixtures/rules/custom-rule.js create mode 100644 eslint/tests/fixtures/rules/dir1/no-strings.js create mode 100644 eslint/tests/fixtures/rules/dir2/no-literals.js create mode 100644 eslint/tests/fixtures/rules/eslint.json create mode 100644 eslint/tests/fixtures/rules/fix-types-test/no-program.js create mode 100644 eslint/tests/fixtures/rules/fixture-rule.js create mode 100644 eslint/tests/fixtures/rules/indent-legacy/indent-invalid-fixture-1.js create mode 100644 eslint/tests/fixtures/rules/indent-legacy/indent-valid-fixture-1.js create mode 100644 eslint/tests/fixtures/rules/indent/indent-invalid-fixture-1.js create mode 100644 eslint/tests/fixtures/rules/indent/indent-valid-fixture-1.js create mode 100644 eslint/tests/fixtures/rules/make-syntax-error-rule.js create mode 100644 eslint/tests/fixtures/rules/missing-rule.json create mode 100644 eslint/tests/fixtures/rules/multi-rulesdirs.json create mode 100644 eslint/tests/fixtures/rules/not-a-rule.yml create mode 100644 eslint/tests/fixtures/rules/test-multi-rulesdirs.js create mode 100644 eslint/tests/fixtures/rules/test/test-custom-rule.js create mode 100644 eslint/tests/fixtures/rules/wrong/custom-rule.js create mode 100755 eslint/tests/fixtures/shebang.js create mode 100644 eslint/tests/fixtures/single-quoted.js create mode 100644 eslint/tests/fixtures/source-code-util/.eslintignore create mode 100644 eslint/tests/fixtures/source-code-util/.eslintrc.json create mode 100644 eslint/tests/fixtures/source-code-util/bar.js create mode 100644 eslint/tests/fixtures/source-code-util/ext/foo.abc create mode 100644 eslint/tests/fixtures/source-code-util/ext/foo.js create mode 100644 eslint/tests/fixtures/source-code-util/foo.js create mode 100644 eslint/tests/fixtures/source-code-util/ignored.js create mode 100644 eslint/tests/fixtures/source-code-util/jsx/foo.jsx create mode 100644 eslint/tests/fixtures/source-code-util/nested/bar.js create mode 100644 eslint/tests/fixtures/source-code-util/nested/foo.js create mode 100644 eslint/tests/fixtures/source-code-util/parse-error/parse-error.js create mode 100644 eslint/tests/fixtures/syntax-error.js create mode 100644 eslint/tests/fixtures/testers/rule-tester/fixes-one-problem.js create mode 100644 eslint/tests/fixtures/testers/rule-tester/messageId.js create mode 100644 eslint/tests/fixtures/testers/rule-tester/modify-ast-at-first.js create mode 100644 eslint/tests/fixtures/testers/rule-tester/modify-ast-at-last.js create mode 100644 eslint/tests/fixtures/testers/rule-tester/modify-ast.js create mode 100644 eslint/tests/fixtures/testers/rule-tester/no-eval.js create mode 100644 eslint/tests/fixtures/testers/rule-tester/no-invalid-args.js create mode 100644 eslint/tests/fixtures/testers/rule-tester/no-invalid-schema.js create mode 100644 eslint/tests/fixtures/testers/rule-tester/no-schema-violation.js create mode 100644 eslint/tests/fixtures/testers/rule-tester/no-test-filename create mode 100644 eslint/tests/fixtures/testers/rule-tester/no-test-global.js create mode 100644 eslint/tests/fixtures/testers/rule-tester/no-test-settings.js create mode 100644 eslint/tests/fixtures/testers/rule-tester/no-var.js create mode 100644 eslint/tests/fixtures/testers/rule-tester/suggestions.js create mode 100644 eslint/tests/fixtures/traverse/.hidden_dir/dummy.js create mode 100644 eslint/tests/fixtures/traverse/.hidden_file.js create mode 100644 eslint/tests/fixtures/traverse/found.js create mode 100644 eslint/tests/fixtures/traverse/found.js2 create mode 100644 eslint/tests/fixtures/undef.js create mode 100644 eslint/tests/fixtures/unmatched-patterns/failing.js create mode 100644 eslint/tests/fixtures/unmatched-patterns/passing.js2 create mode 100644 eslint/tests/fixtures/utf8-bom.js create mode 100644 eslint/tests/lib/_utils.js create mode 100644 eslint/tests/lib/api.js create mode 100644 eslint/tests/lib/cli-engine/_utils.js create mode 100644 eslint/tests/lib/cli-engine/cascading-config-array-factory.js create mode 100644 eslint/tests/lib/cli-engine/cli-engine.js create mode 100644 eslint/tests/lib/cli-engine/config-array-factory.js create mode 100644 eslint/tests/lib/cli-engine/config-array/config-array.js create mode 100644 eslint/tests/lib/cli-engine/config-array/config-dependency.js create mode 100644 eslint/tests/lib/cli-engine/config-array/extracted-config.js create mode 100644 eslint/tests/lib/cli-engine/config-array/ignore-pattern.js create mode 100644 eslint/tests/lib/cli-engine/config-array/override-tester.js create mode 100644 eslint/tests/lib/cli-engine/file-enumerator.js create mode 100644 eslint/tests/lib/cli-engine/formatters/checkstyle.js create mode 100644 eslint/tests/lib/cli-engine/formatters/codeframe.js create mode 100644 eslint/tests/lib/cli-engine/formatters/compact.js create mode 100644 eslint/tests/lib/cli-engine/formatters/html.js create mode 100644 eslint/tests/lib/cli-engine/formatters/jslint-xml.js create mode 100644 eslint/tests/lib/cli-engine/formatters/json-with-metadata.js create mode 100644 eslint/tests/lib/cli-engine/formatters/json.js create mode 100644 eslint/tests/lib/cli-engine/formatters/junit.js create mode 100644 eslint/tests/lib/cli-engine/formatters/stylish.js create mode 100644 eslint/tests/lib/cli-engine/formatters/table.js create mode 100644 eslint/tests/lib/cli-engine/formatters/tap.js create mode 100644 eslint/tests/lib/cli-engine/formatters/unix.js create mode 100644 eslint/tests/lib/cli-engine/formatters/visualstudio.js create mode 100644 eslint/tests/lib/cli-engine/lint-result-cache.js create mode 100644 eslint/tests/lib/cli-engine/load-rules.js create mode 100644 eslint/tests/lib/cli.js create mode 100644 eslint/tests/lib/init/autoconfig.js create mode 100644 eslint/tests/lib/init/config-file.js create mode 100644 eslint/tests/lib/init/config-initializer.js create mode 100644 eslint/tests/lib/init/config-rule.js create mode 100644 eslint/tests/lib/init/npm-utils.js create mode 100644 eslint/tests/lib/init/source-code-utils.js create mode 100644 eslint/tests/lib/linter/apply-disable-directives.js create mode 100644 eslint/tests/lib/linter/code-path-analysis/code-path-analyzer.js create mode 100644 eslint/tests/lib/linter/code-path-analysis/code-path.js create mode 100644 eslint/tests/lib/linter/config-comment-parser.js create mode 100644 eslint/tests/lib/linter/interpolate.js create mode 100644 eslint/tests/lib/linter/linter.js create mode 100644 eslint/tests/lib/linter/node-event-generator.js create mode 100644 eslint/tests/lib/linter/report-translator.js create mode 100644 eslint/tests/lib/linter/rule-fixer.js create mode 100644 eslint/tests/lib/linter/rules.js create mode 100644 eslint/tests/lib/linter/safe-emitter.js create mode 100644 eslint/tests/lib/linter/source-code-fixer.js create mode 100644 eslint/tests/lib/options.js create mode 100644 eslint/tests/lib/rule-tester/no-test-runners.js create mode 100644 eslint/tests/lib/rule-tester/rule-tester.js create mode 100644 eslint/tests/lib/rules/accessor-pairs.js create mode 100644 eslint/tests/lib/rules/array-bracket-newline.js create mode 100644 eslint/tests/lib/rules/array-bracket-spacing.js create mode 100644 eslint/tests/lib/rules/array-callback-return.js create mode 100644 eslint/tests/lib/rules/array-element-newline.js create mode 100644 eslint/tests/lib/rules/arrow-body-style.js create mode 100644 eslint/tests/lib/rules/arrow-parens.js create mode 100644 eslint/tests/lib/rules/arrow-spacing.js create mode 100644 eslint/tests/lib/rules/block-scoped-var.js create mode 100644 eslint/tests/lib/rules/block-spacing.js create mode 100644 eslint/tests/lib/rules/brace-style.js create mode 100644 eslint/tests/lib/rules/callback-return.js create mode 100644 eslint/tests/lib/rules/camelcase.js create mode 100644 eslint/tests/lib/rules/capitalized-comments.js create mode 100644 eslint/tests/lib/rules/class-methods-use-this.js create mode 100644 eslint/tests/lib/rules/comma-dangle.js create mode 100644 eslint/tests/lib/rules/comma-spacing.js create mode 100644 eslint/tests/lib/rules/comma-style.js create mode 100644 eslint/tests/lib/rules/complexity.js create mode 100644 eslint/tests/lib/rules/computed-property-spacing.js create mode 100644 eslint/tests/lib/rules/consistent-return.js create mode 100644 eslint/tests/lib/rules/consistent-this.js create mode 100644 eslint/tests/lib/rules/constructor-super.js create mode 100644 eslint/tests/lib/rules/curly.js create mode 100644 eslint/tests/lib/rules/default-case-last.js create mode 100644 eslint/tests/lib/rules/default-case.js create mode 100644 eslint/tests/lib/rules/default-param-last.js create mode 100644 eslint/tests/lib/rules/dot-location.js create mode 100644 eslint/tests/lib/rules/dot-notation.js create mode 100644 eslint/tests/lib/rules/eol-last.js create mode 100644 eslint/tests/lib/rules/eqeqeq.js create mode 100644 eslint/tests/lib/rules/for-direction.js create mode 100644 eslint/tests/lib/rules/func-call-spacing.js create mode 100644 eslint/tests/lib/rules/func-name-matching.js create mode 100644 eslint/tests/lib/rules/func-names.js create mode 100644 eslint/tests/lib/rules/func-style.js create mode 100644 eslint/tests/lib/rules/function-call-argument-newline.js create mode 100644 eslint/tests/lib/rules/function-paren-newline.js create mode 100644 eslint/tests/lib/rules/generator-star-spacing.js create mode 100644 eslint/tests/lib/rules/getter-return.js create mode 100644 eslint/tests/lib/rules/global-require.js create mode 100644 eslint/tests/lib/rules/grouped-accessor-pairs.js create mode 100644 eslint/tests/lib/rules/guard-for-in.js create mode 100644 eslint/tests/lib/rules/handle-callback-err.js create mode 100644 eslint/tests/lib/rules/id-blacklist.js create mode 100644 eslint/tests/lib/rules/id-length.js create mode 100644 eslint/tests/lib/rules/id-match.js create mode 100644 eslint/tests/lib/rules/implicit-arrow-linebreak.js create mode 100644 eslint/tests/lib/rules/indent-legacy.js create mode 100644 eslint/tests/lib/rules/indent.js create mode 100644 eslint/tests/lib/rules/init-declarations.js create mode 100644 eslint/tests/lib/rules/jsx-quotes.js create mode 100644 eslint/tests/lib/rules/key-spacing.js create mode 100644 eslint/tests/lib/rules/keyword-spacing.js create mode 100644 eslint/tests/lib/rules/line-comment-position.js create mode 100644 eslint/tests/lib/rules/linebreak-style.js create mode 100644 eslint/tests/lib/rules/lines-around-comment.js create mode 100644 eslint/tests/lib/rules/lines-around-directive.js create mode 100644 eslint/tests/lib/rules/lines-between-class-members.js create mode 100644 eslint/tests/lib/rules/max-classes-per-file.js create mode 100644 eslint/tests/lib/rules/max-depth.js create mode 100644 eslint/tests/lib/rules/max-len.js create mode 100644 eslint/tests/lib/rules/max-lines-per-function.js create mode 100644 eslint/tests/lib/rules/max-lines.js create mode 100644 eslint/tests/lib/rules/max-nested-callbacks.js create mode 100644 eslint/tests/lib/rules/max-params.js create mode 100644 eslint/tests/lib/rules/max-statements-per-line.js create mode 100644 eslint/tests/lib/rules/max-statements.js create mode 100644 eslint/tests/lib/rules/multiline-comment-style.js create mode 100644 eslint/tests/lib/rules/multiline-ternary.js create mode 100644 eslint/tests/lib/rules/new-cap.js create mode 100644 eslint/tests/lib/rules/new-parens.js create mode 100644 eslint/tests/lib/rules/newline-after-var.js create mode 100644 eslint/tests/lib/rules/newline-before-return.js create mode 100644 eslint/tests/lib/rules/newline-per-chained-call.js create mode 100644 eslint/tests/lib/rules/no-alert.js create mode 100644 eslint/tests/lib/rules/no-array-constructor.js create mode 100644 eslint/tests/lib/rules/no-async-promise-executor.js create mode 100644 eslint/tests/lib/rules/no-await-in-loop.js create mode 100644 eslint/tests/lib/rules/no-bitwise.js create mode 100644 eslint/tests/lib/rules/no-buffer-constructor.js create mode 100644 eslint/tests/lib/rules/no-caller.js create mode 100644 eslint/tests/lib/rules/no-case-declarations.js create mode 100644 eslint/tests/lib/rules/no-catch-shadow.js create mode 100644 eslint/tests/lib/rules/no-class-assign.js create mode 100644 eslint/tests/lib/rules/no-compare-neg-zero.js create mode 100644 eslint/tests/lib/rules/no-cond-assign.js create mode 100644 eslint/tests/lib/rules/no-confusing-arrow.js create mode 100644 eslint/tests/lib/rules/no-console.js create mode 100644 eslint/tests/lib/rules/no-const-assign.js create mode 100644 eslint/tests/lib/rules/no-constant-condition.js create mode 100644 eslint/tests/lib/rules/no-constructor-return.js create mode 100644 eslint/tests/lib/rules/no-continue.js create mode 100644 eslint/tests/lib/rules/no-control-regex.js create mode 100644 eslint/tests/lib/rules/no-debugger.js create mode 100644 eslint/tests/lib/rules/no-delete-var.js create mode 100644 eslint/tests/lib/rules/no-div-regex.js create mode 100644 eslint/tests/lib/rules/no-dupe-args.js create mode 100644 eslint/tests/lib/rules/no-dupe-class-members.js create mode 100644 eslint/tests/lib/rules/no-dupe-else-if.js create mode 100644 eslint/tests/lib/rules/no-dupe-keys.js create mode 100644 eslint/tests/lib/rules/no-duplicate-case.js create mode 100644 eslint/tests/lib/rules/no-duplicate-imports.js create mode 100644 eslint/tests/lib/rules/no-else-return.js create mode 100644 eslint/tests/lib/rules/no-empty-character-class.js create mode 100644 eslint/tests/lib/rules/no-empty-function.js create mode 100644 eslint/tests/lib/rules/no-empty-pattern.js create mode 100644 eslint/tests/lib/rules/no-empty.js create mode 100644 eslint/tests/lib/rules/no-eq-null.js create mode 100644 eslint/tests/lib/rules/no-eval.js create mode 100644 eslint/tests/lib/rules/no-ex-assign.js create mode 100644 eslint/tests/lib/rules/no-extend-native.js create mode 100644 eslint/tests/lib/rules/no-extra-bind.js create mode 100644 eslint/tests/lib/rules/no-extra-boolean-cast.js create mode 100644 eslint/tests/lib/rules/no-extra-label.js create mode 100644 eslint/tests/lib/rules/no-extra-parens.js create mode 100644 eslint/tests/lib/rules/no-extra-semi.js create mode 100644 eslint/tests/lib/rules/no-fallthrough.js create mode 100644 eslint/tests/lib/rules/no-floating-decimal.js create mode 100644 eslint/tests/lib/rules/no-func-assign.js create mode 100644 eslint/tests/lib/rules/no-global-assign.js create mode 100644 eslint/tests/lib/rules/no-implicit-coercion.js create mode 100644 eslint/tests/lib/rules/no-implicit-globals.js create mode 100644 eslint/tests/lib/rules/no-implied-eval.js create mode 100644 eslint/tests/lib/rules/no-import-assign.js create mode 100644 eslint/tests/lib/rules/no-inline-comments.js create mode 100644 eslint/tests/lib/rules/no-inner-declarations.js create mode 100644 eslint/tests/lib/rules/no-invalid-regexp.js create mode 100644 eslint/tests/lib/rules/no-invalid-this.js create mode 100644 eslint/tests/lib/rules/no-irregular-whitespace.js create mode 100644 eslint/tests/lib/rules/no-iterator.js create mode 100644 eslint/tests/lib/rules/no-label-var.js create mode 100644 eslint/tests/lib/rules/no-labels.js create mode 100644 eslint/tests/lib/rules/no-lone-blocks.js create mode 100644 eslint/tests/lib/rules/no-lonely-if.js create mode 100644 eslint/tests/lib/rules/no-loop-func.js create mode 100644 eslint/tests/lib/rules/no-magic-numbers.js create mode 100644 eslint/tests/lib/rules/no-misleading-character-class.js create mode 100644 eslint/tests/lib/rules/no-mixed-operators.js create mode 100644 eslint/tests/lib/rules/no-mixed-requires.js create mode 100644 eslint/tests/lib/rules/no-mixed-spaces-and-tabs.js create mode 100644 eslint/tests/lib/rules/no-multi-assign.js create mode 100644 eslint/tests/lib/rules/no-multi-spaces.js create mode 100644 eslint/tests/lib/rules/no-multi-str.js create mode 100644 eslint/tests/lib/rules/no-multiple-empty-lines.js create mode 100644 eslint/tests/lib/rules/no-native-reassign.js create mode 100644 eslint/tests/lib/rules/no-negated-condition.js create mode 100644 eslint/tests/lib/rules/no-negated-in-lhs.js create mode 100644 eslint/tests/lib/rules/no-nested-ternary.js create mode 100644 eslint/tests/lib/rules/no-new-func.js create mode 100644 eslint/tests/lib/rules/no-new-object.js create mode 100644 eslint/tests/lib/rules/no-new-require.js create mode 100644 eslint/tests/lib/rules/no-new-symbol.js create mode 100644 eslint/tests/lib/rules/no-new-wrappers.js create mode 100644 eslint/tests/lib/rules/no-new.js create mode 100644 eslint/tests/lib/rules/no-obj-calls.js create mode 100644 eslint/tests/lib/rules/no-octal-escape.js create mode 100644 eslint/tests/lib/rules/no-octal.js create mode 100644 eslint/tests/lib/rules/no-param-reassign.js create mode 100644 eslint/tests/lib/rules/no-path-concat.js create mode 100644 eslint/tests/lib/rules/no-plusplus.js create mode 100644 eslint/tests/lib/rules/no-process-env.js create mode 100644 eslint/tests/lib/rules/no-process-exit.js create mode 100644 eslint/tests/lib/rules/no-proto.js create mode 100644 eslint/tests/lib/rules/no-prototype-builtins.js create mode 100644 eslint/tests/lib/rules/no-redeclare.js create mode 100644 eslint/tests/lib/rules/no-regex-spaces.js create mode 100644 eslint/tests/lib/rules/no-restricted-exports.js create mode 100644 eslint/tests/lib/rules/no-restricted-globals.js create mode 100644 eslint/tests/lib/rules/no-restricted-imports.js create mode 100644 eslint/tests/lib/rules/no-restricted-modules.js create mode 100644 eslint/tests/lib/rules/no-restricted-properties.js create mode 100644 eslint/tests/lib/rules/no-restricted-syntax.js create mode 100644 eslint/tests/lib/rules/no-return-assign.js create mode 100644 eslint/tests/lib/rules/no-return-await.js create mode 100644 eslint/tests/lib/rules/no-script-url.js create mode 100644 eslint/tests/lib/rules/no-self-assign.js create mode 100644 eslint/tests/lib/rules/no-self-compare.js create mode 100644 eslint/tests/lib/rules/no-sequences.js create mode 100644 eslint/tests/lib/rules/no-setter-return.js create mode 100644 eslint/tests/lib/rules/no-shadow-restricted-names.js create mode 100644 eslint/tests/lib/rules/no-shadow.js create mode 100644 eslint/tests/lib/rules/no-spaced-func.js create mode 100644 eslint/tests/lib/rules/no-sparse-arrays.js create mode 100644 eslint/tests/lib/rules/no-sync.js create mode 100644 eslint/tests/lib/rules/no-tabs.js create mode 100644 eslint/tests/lib/rules/no-template-curly-in-string.js create mode 100644 eslint/tests/lib/rules/no-ternary.js create mode 100644 eslint/tests/lib/rules/no-this-before-super.js create mode 100644 eslint/tests/lib/rules/no-throw-literal.js create mode 100644 eslint/tests/lib/rules/no-trailing-spaces.js create mode 100644 eslint/tests/lib/rules/no-undef-init.js create mode 100644 eslint/tests/lib/rules/no-undef.js create mode 100644 eslint/tests/lib/rules/no-undefined.js create mode 100644 eslint/tests/lib/rules/no-underscore-dangle.js create mode 100644 eslint/tests/lib/rules/no-unexpected-multiline.js create mode 100644 eslint/tests/lib/rules/no-unmodified-loop-condition.js create mode 100644 eslint/tests/lib/rules/no-unneeded-ternary.js create mode 100644 eslint/tests/lib/rules/no-unreachable.js create mode 100644 eslint/tests/lib/rules/no-unsafe-finally.js create mode 100644 eslint/tests/lib/rules/no-unsafe-negation.js create mode 100644 eslint/tests/lib/rules/no-unused-expressions.js create mode 100644 eslint/tests/lib/rules/no-unused-labels.js create mode 100644 eslint/tests/lib/rules/no-unused-vars.js create mode 100644 eslint/tests/lib/rules/no-use-before-define.js create mode 100644 eslint/tests/lib/rules/no-useless-backreference.js create mode 100644 eslint/tests/lib/rules/no-useless-call.js create mode 100644 eslint/tests/lib/rules/no-useless-catch.js create mode 100644 eslint/tests/lib/rules/no-useless-computed-key.js create mode 100644 eslint/tests/lib/rules/no-useless-concat.js create mode 100644 eslint/tests/lib/rules/no-useless-constructor.js create mode 100644 eslint/tests/lib/rules/no-useless-escape.js create mode 100644 eslint/tests/lib/rules/no-useless-rename.js create mode 100644 eslint/tests/lib/rules/no-useless-return.js create mode 100644 eslint/tests/lib/rules/no-var.js create mode 100644 eslint/tests/lib/rules/no-void.js create mode 100644 eslint/tests/lib/rules/no-warning-comments.js create mode 100644 eslint/tests/lib/rules/no-whitespace-before-property.js create mode 100644 eslint/tests/lib/rules/no-with.js create mode 100644 eslint/tests/lib/rules/nonblock-statement-body-position.js create mode 100644 eslint/tests/lib/rules/object-curly-newline.js create mode 100644 eslint/tests/lib/rules/object-curly-spacing.js create mode 100644 eslint/tests/lib/rules/object-property-newline.js create mode 100644 eslint/tests/lib/rules/object-shorthand.js create mode 100644 eslint/tests/lib/rules/one-var-declaration-per-line.js create mode 100644 eslint/tests/lib/rules/one-var.js create mode 100644 eslint/tests/lib/rules/operator-assignment.js create mode 100644 eslint/tests/lib/rules/operator-linebreak.js create mode 100644 eslint/tests/lib/rules/padded-blocks.js create mode 100644 eslint/tests/lib/rules/padding-line-between-statements.js create mode 100644 eslint/tests/lib/rules/prefer-arrow-callback.js create mode 100644 eslint/tests/lib/rules/prefer-const.js create mode 100644 eslint/tests/lib/rules/prefer-destructuring.js create mode 100644 eslint/tests/lib/rules/prefer-exponentiation-operator.js create mode 100644 eslint/tests/lib/rules/prefer-named-capture-group.js create mode 100644 eslint/tests/lib/rules/prefer-numeric-literals.js create mode 100644 eslint/tests/lib/rules/prefer-object-spread.js create mode 100644 eslint/tests/lib/rules/prefer-promise-reject-errors.js create mode 100644 eslint/tests/lib/rules/prefer-reflect.js create mode 100644 eslint/tests/lib/rules/prefer-regex-literals.js create mode 100644 eslint/tests/lib/rules/prefer-rest-params.js create mode 100644 eslint/tests/lib/rules/prefer-spread.js create mode 100644 eslint/tests/lib/rules/prefer-template.js create mode 100644 eslint/tests/lib/rules/quote-props.js create mode 100644 eslint/tests/lib/rules/quotes.js create mode 100644 eslint/tests/lib/rules/radix.js create mode 100644 eslint/tests/lib/rules/require-atomic-updates.js create mode 100644 eslint/tests/lib/rules/require-await.js create mode 100644 eslint/tests/lib/rules/require-jsdoc.js create mode 100644 eslint/tests/lib/rules/require-unicode-regexp.js create mode 100644 eslint/tests/lib/rules/require-yield.js create mode 100644 eslint/tests/lib/rules/rest-spread-spacing.js create mode 100644 eslint/tests/lib/rules/semi-spacing.js create mode 100644 eslint/tests/lib/rules/semi-style.js create mode 100644 eslint/tests/lib/rules/semi.js create mode 100644 eslint/tests/lib/rules/sort-imports.js create mode 100644 eslint/tests/lib/rules/sort-keys.js create mode 100644 eslint/tests/lib/rules/sort-vars.js create mode 100644 eslint/tests/lib/rules/space-before-blocks.js create mode 100644 eslint/tests/lib/rules/space-before-function-paren.js create mode 100644 eslint/tests/lib/rules/space-in-parens.js create mode 100644 eslint/tests/lib/rules/space-infix-ops.js create mode 100644 eslint/tests/lib/rules/space-unary-ops.js create mode 100644 eslint/tests/lib/rules/spaced-comment.js create mode 100644 eslint/tests/lib/rules/strict.js create mode 100644 eslint/tests/lib/rules/switch-colon-spacing.js create mode 100644 eslint/tests/lib/rules/symbol-description.js create mode 100644 eslint/tests/lib/rules/template-curly-spacing.js create mode 100644 eslint/tests/lib/rules/template-tag-spacing.js create mode 100644 eslint/tests/lib/rules/unicode-bom.js create mode 100644 eslint/tests/lib/rules/use-isnan.js create mode 100644 eslint/tests/lib/rules/utils/ast-utils.js create mode 100644 eslint/tests/lib/rules/utils/fix-tracker.js create mode 100644 eslint/tests/lib/rules/valid-jsdoc.js create mode 100644 eslint/tests/lib/rules/valid-typeof.js create mode 100644 eslint/tests/lib/rules/vars-on-top.js create mode 100644 eslint/tests/lib/rules/wrap-iife.js create mode 100644 eslint/tests/lib/rules/wrap-regex.js create mode 100644 eslint/tests/lib/rules/yield-star-spacing.js create mode 100644 eslint/tests/lib/rules/yoda.js create mode 100644 eslint/tests/lib/shared/config-ops.js create mode 100644 eslint/tests/lib/shared/config-validator.js create mode 100644 eslint/tests/lib/shared/naming.js create mode 100644 eslint/tests/lib/shared/runtime-info.js create mode 100644 eslint/tests/lib/shared/traverser.js create mode 100644 eslint/tests/lib/source-code/source-code.js create mode 100644 eslint/tests/lib/source-code/token-store.js create mode 100644 eslint/tests/performance/jshint.js create mode 100644 eslint/tests/tests.htm create mode 100644 eslint/tests/tools/code-sample-minimizer.js create mode 100644 eslint/tests/tools/internal-rules/consistent-docs-url.js create mode 100644 eslint/tests/tools/internal-rules/consistent-meta-messages.js create mode 100644 eslint/tests/tools/internal-rules/multiline-comment-style.js create mode 100644 eslint/tests/tools/internal-rules/no-invalid-meta.js create mode 100644 eslint/tests/tools/loose-parser.js create mode 100644 eslint/tools/code-sample-minimizer.js create mode 100644 eslint/tools/fuzzer-runner.js create mode 100644 eslint/tools/internal-rules/consistent-docs-url.js create mode 100644 eslint/tools/internal-rules/consistent-meta-messages.js create mode 100644 eslint/tools/internal-rules/index.js create mode 100644 eslint/tools/internal-rules/multiline-comment-style.js create mode 100644 eslint/tools/internal-rules/no-invalid-meta.js create mode 100644 eslint/tools/internal-rules/package.json create mode 100644 eslint/tools/internal-testers/event-generator-tester.js create mode 100644 eslint/tools/internal-testers/test-parser.js create mode 100644 eslint/tools/rule-types.json create mode 100644 eslint/tools/update-readme.js create mode 100644 eslint/tools/update-rule-types.js create mode 100644 eslint/webpack.config.js create mode 100644 patches/0001-adapt-webpack-config.patch create mode 100644 patches/series create mode 100644 src/Makefile create mode 100644 src/app.js create mode 100755 src/eslint.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ffa69a8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.tmp +eslint-* diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d264323 --- /dev/null +++ b/Makefile @@ -0,0 +1,67 @@ +include /usr/share/dpkg/pkg-info.mk + +PACKAGE=pve-eslint + +GITVERSION:=$(shell git rev-parse HEAD) +BUILDDIR ?= ${PACKAGE}-${DEB_VERSION_UPSTREAM} + +DEB=${PACKAGE}_${DEB_VERSION_UPSTREAM_REVISION}_all.deb + +SRCDIR=src +UPSTREAM=eslint +UPSTREAMTAG=v7.0.0-alpha.3 +BUILDSRC=${UPSTREAM}-${UPSTREAMTAG} + +all: ${DEB} + @echo ${DEB} + +.PHONY: deb +deb: ${DEB} +${DEB}: ${SRCDIR} + rm -rf ${BUILDDIR} + mkdir ${BUILDDIR} + cp -a debian ${BUILDDIR}/ + cp -a ${SRCDIR}/* ${BUILDDIR}/ + echo "git clone git://git.proxmox.com/git/pve-eslint.git\\ngit checkout ${GITVERSION}" > ${BUILDDIR}/debian/SOURCE + cd ${BUILDDIR}; dpkg-buildpackage -rfakeroot -b -uc -us + lintian ${DEB} + @echo ${DEB} + +.PHONY: download +download: + rm -rf ${UPSTREAM}.tmp ${UPSTREAM} + git clone -b ${UPSTREAMTAG} --depth 1 https://github.com/eslint/eslint ${UPSTREAM}.tmp + rm -rf ${UPSTREAM}.tmp/.git + find ${UPSTREAM}.tmp/ -type f -name '.gitignore' -delete + mv ${UPSTREAM}.tmp ${UPSTREAM} + +# NOTE: needs npm installed, downloads packages from npm +.PHONY: buildupstream +buildupstream: ${BUILDSRC} + cp ${BUILDSRC}/build/eslint.js ${SRCDIR}/eslint.js + +${BUILDSRC}: ${UPSTREAM} patches + rm -rf $@ + mkdir $@.tmp + rsync -ra ${UPSTREAM}/ $@.tmp + cd $@.tmp; ln -s ../patches patches + cd $@.tmp; quilt push -a + cd $@.tmp; rm -rf .pc ./patches + mv $@.tmp $@ + cd $@; npm install + cd $@; npm run webpack + +.PHONY: upload +upload: ${DEB} + tar cf - ${DEB} | ssh -X repoman@repo.proxmox.com -- upload --product pmg,pve --dist buster + +.PHONY: distclean +distclean: clean + +.PHONY: clean +clean: + rm -rf *~ debian/*~ *.deb ${BUILDSRC} ${BUILDSRC}.tmp ${UPSTREAM}.tmp ${BUILDDIR} *.changes *.dsc *.buildinfo + +.PHONY: dinstall +dinstall: deb + dpkg -i ${DEB} diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..d211385 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,6 @@ +pve-eslint (1.0-1) unstable; urgency=low + + * first version + + -- Proxmox Support Team Thu, 02 Apr 2020 09:16:33 +0200 + diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..f599e28 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +10 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..6a49d6b --- /dev/null +++ b/debian/control @@ -0,0 +1,12 @@ +Source: pve-eslint +Section: devel +Priority: optional +Maintainer: Proxmox Support Team +Build-Depends: debhelper (>= 7.0.0~) + +Package: pve-eslint +Architecture: all +Depends: node-commander, node-colors, nodejs +Description: ESLint for Proxmox Virtual Environment development + This package contains a version of eslint used to develop the + Proxmox Virtual Environment GUI. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..50e7951 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,40 @@ +Copyright (C) 2020 Proxmox Server Solutions GmbH + +This software is written by Proxmox Server Solutions GmbH + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . + +------------- + +eslint + +Copyright JS Foundation and other contributors, https://js.foundation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..2d33f6a --- /dev/null +++ b/debian/rules @@ -0,0 +1,4 @@ +#!/usr/bin/make -f + +%: + dh $@ diff --git a/eslint/.codeclimate.yml b/eslint/.codeclimate.yml new file mode 100644 index 0000000..093f5ca --- /dev/null +++ b/eslint/.codeclimate.yml @@ -0,0 +1,9 @@ +languages: + JavaScript: true + +exclude_paths: + - tests/** + +engines: + eslint: + enabled: true diff --git a/eslint/.editorconfig b/eslint/.editorconfig new file mode 100644 index 0000000..646b08e --- /dev/null +++ b/eslint/.editorconfig @@ -0,0 +1,18 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true +end_of_line = lf +insert_final_newline = true + +[docs/rules/linebreak-style.md] +end_of_line = disabled + +[{docs/rules/{indent.md,no-mixed-spaces-and-tabs.md}] +indent_style = disabled +indent_size = disabled + +[docs/rules/no-trailing-spaces.md] +trim_trailing_whitespace = false diff --git a/eslint/.eslintignore b/eslint/.eslintignore new file mode 100644 index 0000000..b8414c9 --- /dev/null +++ b/eslint/.eslintignore @@ -0,0 +1,12 @@ +/build/** +/coverage/** +/docs/** +/jsdoc/** +/templates/** +/tests/bench/** +/tests/fixtures/** +/tests/performance/** +/tmp/** +/tools/internal-rules/node_modules/** +test.js +!.eslintrc.js diff --git a/eslint/.eslintrc.js b/eslint/.eslintrc.js new file mode 100644 index 0000000..6ab4510 --- /dev/null +++ b/eslint/.eslintrc.js @@ -0,0 +1,190 @@ +"use strict"; + +const internalFiles = [ + "**/cli-engine/**/*", + "**/init/**/*", + "**/linter/**/*", + "**/rule-tester/**/*", + "**/rules/**/*", + "**/source-code/**/*" +]; + +module.exports = { + root: true, + plugins: [ + "eslint-plugin", + "internal-rules" + ], + extends: [ + "eslint", + "plugin:eslint-plugin/recommended" + ], + parserOptions: { + ecmaVersion: 2020 + }, + rules: { + "eslint-plugin/consistent-output": "error", + "eslint-plugin/no-deprecated-context-methods": "error", + "eslint-plugin/prefer-output-null": "error", + "eslint-plugin/prefer-placeholders": "error", + "eslint-plugin/report-message-format": ["error", "[^a-z].*\\.$"], + "eslint-plugin/require-meta-docs-description": "error", + "eslint-plugin/require-meta-type": "error", + "eslint-plugin/test-case-property-ordering": [ + "error", + + // https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/issues/79 + [ + "filename", + "code", + "output", + "options", + "parser", + "parserOptions", + "globals", + "env", + "errors" + ] + ], + "eslint-plugin/test-case-shorthand-strings": "error", + "internal-rules/multiline-comment-style": "error" + }, + overrides: [ + { + files: ["lib/rules/*", "tools/internal-rules/*"], + excludedFiles: ["index.js"], + rules: { + "internal-rules/no-invalid-meta": "error" + + /* + * TODO: enable it when all the rules using meta.messages + * "internal-rules/consistent-meta-messages": "error" + */ + } + }, + { + files: ["lib/rules/*"], + excludedFiles: ["index.js"], + rules: { + "internal-rules/consistent-docs-url": "error" + } + }, + { + files: ["tests/**/*"], + env: { mocha: true }, + rules: { + "no-restricted-syntax": ["error", { + selector: "CallExpression[callee.object.name='assert'][callee.property.name='doesNotThrow']", + message: "`assert.doesNotThrow()` should be replaced with a comment next to the code." + }] + } + }, + + // Restrict relative path imports + { + files: ["lib/*"], + rules: { + "no-restricted-modules": ["error", { + patterns: [ + ...internalFiles + ] + }] + } + }, + { + files: ["lib/cli-engine/**/*"], + rules: { + "no-restricted-modules": ["error", { + patterns: [ + ...internalFiles, + "**/init" + ] + }] + } + }, + { + files: ["lib/init/**/*"], + rules: { + "no-restricted-modules": ["error", { + patterns: [ + ...internalFiles, + "**/rule-tester" + ] + }] + } + }, + { + files: ["lib/linter/**/*"], + rules: { + "no-restricted-modules": ["error", { + patterns: [ + ...internalFiles, + "fs", + "**/cli-engine", + "**/init", + "**/rule-tester" + ] + }] + } + }, + { + files: ["lib/rules/**/*"], + rules: { + "no-restricted-modules": ["error", { + patterns: [ + ...internalFiles, + "fs", + "**/cli-engine", + "**/init", + "**/linter", + "**/rule-tester", + "**/source-code" + ] + }] + } + }, + { + files: ["lib/shared/**/*"], + rules: { + "no-restricted-modules": ["error", { + patterns: [ + ...internalFiles, + "**/cli-engine", + "**/init", + "**/linter", + "**/rule-tester", + "**/source-code" + ] + }] + } + }, + { + files: ["lib/source-code/**/*"], + rules: { + "no-restricted-modules": ["error", { + patterns: [ + ...internalFiles, + "fs", + "**/cli-engine", + "**/init", + "**/linter", + "**/rule-tester", + "**/rules" + ] + }] + } + }, + { + files: ["lib/rule-tester/**/*"], + rules: { + "no-restricted-modules": ["error", { + patterns: [ + ...internalFiles, + "**/cli-engine", + "**/init" + ] + }] + } + } + ] +}; diff --git a/eslint/.gitattributes b/eslint/.gitattributes new file mode 100644 index 0000000..1df2587 --- /dev/null +++ b/eslint/.gitattributes @@ -0,0 +1,8 @@ +# Convert text file line endings to lf +* text=auto + +*.js text eol=lf + +# The test fixtures are text files. +/tests/fixtures/**/* text eol=lf +/tests/fixtures/ignored-paths/crlf/.eslintignore text eol=crlf diff --git a/eslint/.github/FUNDING.yml b/eslint/.github/FUNDING.yml new file mode 100644 index 0000000..fec5535 --- /dev/null +++ b/eslint/.github/FUNDING.yml @@ -0,0 +1,2 @@ +github: eslint +open_collective: eslint diff --git a/eslint/.github/ISSUE_TEMPLATE.md b/eslint/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..2e492e4 --- /dev/null +++ b/eslint/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,53 @@ + + +**Tell us about your environment** + + + +* **ESLint Version:** +* **Node Version:** +* **npm Version:** + +**What parser (default, Babel-ESLint, etc.) are you using?** + +**Please show your full configuration:** + +
+Configuration + + +```js + +``` + +
+ +**What did you do? Please include the actual source code causing the issue, as well as the command that you used to run ESLint.** + + +```js + +``` + + +```bash + +``` + +**What did you expect to happen?** + +**What actually happened? Please include the actual, raw output from ESLint.** + diff --git a/eslint/.github/ISSUE_TEMPLATE/BUG_REPORT.md b/eslint/.github/ISSUE_TEMPLATE/BUG_REPORT.md new file mode 100644 index 0000000..dd7c10d --- /dev/null +++ b/eslint/.github/ISSUE_TEMPLATE/BUG_REPORT.md @@ -0,0 +1,65 @@ +--- +name: "\U0001F41E Bug report" +about: Report an issue with ESLint or rules bundled with ESLint +title: '' +labels: bug, triage +assignees: '' + +--- + + + +**Tell us about your environment** + + + +* **ESLint Version:** +* **Node Version:** +* **npm Version:** + +**What parser (default, Babel-ESLint, etc.) are you using?** + +**Please show your full configuration:** + +
+Configuration + + +```js + +``` + +
+ +**What did you do? Please include the actual source code causing the issue, as well as the command that you used to run ESLint.** + + +```js + +``` + + +```bash + +``` + +**What did you expect to happen?** + + +**What actually happened? Please include the actual, raw output from ESLint.** + + +**Are you willing to submit a pull request to fix this bug?** diff --git a/eslint/.github/ISSUE_TEMPLATE/CHANGE.md b/eslint/.github/ISSUE_TEMPLATE/CHANGE.md new file mode 100644 index 0000000..6cf6425 --- /dev/null +++ b/eslint/.github/ISSUE_TEMPLATE/CHANGE.md @@ -0,0 +1,32 @@ +--- +name: "\U0001F4DD Non-rule change request" +about: Request a change that is not a bug fix, rule change, or new rule +title: '' +labels: enhancement, triage, core +assignees: '' + +--- + + + +**The version of ESLint you are using.** + + +**The problem you want to solve.** + + +**Your take on the correct solution to problem.** + + +**Are you willing to submit a pull request to implement this change?** diff --git a/eslint/.github/ISSUE_TEMPLATE/NEW_RULE.md b/eslint/.github/ISSUE_TEMPLATE/NEW_RULE.md new file mode 100644 index 0000000..598e89c --- /dev/null +++ b/eslint/.github/ISSUE_TEMPLATE/NEW_RULE.md @@ -0,0 +1,42 @@ +--- +name: "\U0001F680 New rule proposal" +about: Propose a new rule to be added to ESLint +title: '' +labels: triage, rule, feature +assignees: '' + +--- + + + +**Please describe what the rule should do:** + +**What category of rule is this? (place an "X" next to just one item)** + +[ ] Warns about a potential error (problem) +[ ] Suggests an alternate way of doing something (suggestion) +[ ] Enforces code style (layout) +[ ] Other (please specify:) + +**Provide 2-3 code examples that this rule will warn about:** + + +```js + +``` + +**Why should this rule be included in ESLint (instead of a plugin)?** + + +**Are you willing to submit a pull request to implement this rule?** diff --git a/eslint/.github/ISSUE_TEMPLATE/QUESTION.md b/eslint/.github/ISSUE_TEMPLATE/QUESTION.md new file mode 100644 index 0000000..375e217 --- /dev/null +++ b/eslint/.github/ISSUE_TEMPLATE/QUESTION.md @@ -0,0 +1,25 @@ +--- +name: "⛔ Question" +about: Please go to https://gitter.im/eslint/eslint +title: '' +labels: '' +assignees: '' + +--- + + diff --git a/eslint/.github/ISSUE_TEMPLATE/RULE_CHANGE.md b/eslint/.github/ISSUE_TEMPLATE/RULE_CHANGE.md new file mode 100644 index 0000000..d735010 --- /dev/null +++ b/eslint/.github/ISSUE_TEMPLATE/RULE_CHANGE.md @@ -0,0 +1,40 @@ +--- +name: "\U0001F4DD Rule change request" +about: Request a change to an existing rule +title: '' +labels: enhancement, triage, rule +assignees: '' + +--- + + + +**What rule do you want to change?** + +**Does this change cause the rule to produce more or fewer warnings?** + +**How will the change be implemented? (New option, new default behavior, etc.)?** + +**Please provide some example code that this change will affect:** + + +```js + +``` + +**What does the rule currently do for this code?** + +**What will the rule do after it's changed?** + +**Are you willing to submit a pull request to implement this change?** diff --git a/eslint/.github/ISSUE_TEMPLATE/SECURITY.md b/eslint/.github/ISSUE_TEMPLATE/SECURITY.md new file mode 100644 index 0000000..c04f525 --- /dev/null +++ b/eslint/.github/ISSUE_TEMPLATE/SECURITY.md @@ -0,0 +1,22 @@ +--- +name: "⛔ Security issue" +about: Please file security issues at https://hackerone.com/eslint +title: '' +labels: '' +assignees: '' + +--- + + diff --git a/eslint/.github/PULL_REQUEST_TEMPLATE.md b/eslint/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..792638e --- /dev/null +++ b/eslint/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,42 @@ + + +#### Prerequisites checklist + +- [ ] I have read the [contributing guidelines](https://github.com/eslint/eslint/blob/master/CONTRIBUTING.md). +- [ ] The team has reached consensus on the changes proposed in this pull request. If not, I understand that the evaluation process will begin with this pull request and won't be merged until the team has reached consensus. + +#### What is the purpose of this pull request? (put an "X" next to an item) + +[ ] Documentation update +[ ] Bug fix ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/bug-report.md)) +[ ] New rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-proposal.md)) +[ ] Changes an existing rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-change-proposal.md)) +[ ] Add autofixing to a rule +[ ] Add a CLI option +[ ] Add something to the core +[ ] Other, please explain: + + + + + + + +#### What changes did you make? (Give an overview) + + +#### Is there anything you'd like reviewers to focus on? diff --git a/eslint/.github/workflows/ci.yml b/eslint/.github/workflows/ci.yml new file mode 100644 index 0000000..f1da882 --- /dev/null +++ b/eslint/.github/workflows/ci.yml @@ -0,0 +1,67 @@ +name: CI +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + verify_files: + name: Verify Files + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + - name: Install Packages + run: npm install + - name: Lint Files + run: node Makefile lint + - name: Check Rule Files + run: node Makefile checkRuleFiles + - name: Check Licenses + run: node Makefile checkLicenses + + test_on_node: + name: Test + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macOS-latest] + node: [13.x, 12.x, 10.x, "10.12.0"] + exclude: + - os: windows-latest + node: "10.12.0" + - os: windows-latest + node: 10.x + - os: windows-latest + node: 13.x + - os: macOS-latest + node: "10.12.0" + - os: macOS-latest + node: 10.x + - os: macOS-latest + node: 13.x + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node }} + - name: Install Packages + run: npm install + - name: Test + run: node Makefile mocha + - name: Fuzz Test + run: node Makefile fuzz + + test_on_browser: + name: Browser Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + - name: Install Packages + run: npm install + - name: Test + run: node Makefile karma + - name: Fuzz Test + run: node Makefile fuzz diff --git a/eslint/.markdownlint.yml b/eslint/.markdownlint.yml new file mode 100644 index 0000000..975106d --- /dev/null +++ b/eslint/.markdownlint.yml @@ -0,0 +1,21 @@ +default: true + +# Exclusions for deliberate/widespread violations +MD001: false # Header levels should only increment by one level at a time +MD002: false # First header should be a h1 header +MD007: # Unordered list indentation + indent: 4 +MD012: false # Multiple consecutive blank lines +MD013: false # Line length +MD014: false # Dollar signs used before commands without showing output +MD019: false # Multiple spaces after hash on atx style header +MD021: false # Multiple spaces inside hashes on closed atx style header +MD024: false # Multiple headers with the same content +MD026: false # Trailing punctuation in header +MD029: false # Ordered list item prefix +MD030: false # Spaces after list markers +MD033: false # Allow inline HTML +MD034: false # Bare URL used +MD040: false # Fenced code blocks should have a language specified +MD041: false # First line in file should be a top level header +MD046: false # Code block style diff --git a/eslint/.npmrc b/eslint/.npmrc new file mode 100644 index 0000000..c1ca392 --- /dev/null +++ b/eslint/.npmrc @@ -0,0 +1 @@ +package-lock = false diff --git a/eslint/.nycrc b/eslint/.nycrc new file mode 100644 index 0000000..040f203 --- /dev/null +++ b/eslint/.nycrc @@ -0,0 +1,13 @@ +{ + "include": [ + "bin/**/*.js", + "conf/**/*.js", + "lib/**/*.js" + ], + "reporter": [ + "lcov", + "text-summary", + "cobertura" + ], + "sourceMap": true +} diff --git a/eslint/CHANGELOG.md b/eslint/CHANGELOG.md new file mode 100644 index 0000000..feaeee4 --- /dev/null +++ b/eslint/CHANGELOG.md @@ -0,0 +1,5736 @@ +v7.0.0-alpha.3 - March 27, 2020 + +* [`78c8cda`](https://github.com/eslint/eslint/commit/78c8cda5a5d82f5f8548c4528a6438d29756bb71) Breaking: RuleTester Improvements (refs eslint/rfcs#25) (#12955) (Milos Djermanovic) +* [`e0f1b6c`](https://github.com/eslint/eslint/commit/e0f1b6c3d62f725b99b8c07654603b559ba43ba9) Update: stricter array index check in no-magic-numbers (fixes #12845) (#12851) (Milos Djermanovic) +* [`362713c`](https://github.com/eslint/eslint/commit/362713c04aa89092b2b98a77fa94a75b3c933fc6) Update: Improve report location for template-curly-spacing (#12813) (Milos Djermanovic) +* [`29f32db`](https://github.com/eslint/eslint/commit/29f32db68c921a857e17ae627923d87b9c8708de) Fix: Change error message logic for implicit file ignore (fixes #12873) (#12878) (Scott Hardin) +* [`eb1a43c`](https://github.com/eslint/eslint/commit/eb1a43ce3113c906800192c3ef766d2ff188776f) Fix: require-await ignore async generators (fixes #12459) (#13048) (Anix) +* [`920465b`](https://github.com/eslint/eslint/commit/920465b5d8d291df8bce7eef8a066b1dd43d8cae) Fix: getNameLocationInGlobalDirectiveComment end location (refs #12334) (#13086) (Milos Djermanovic) +* [`ae14a02`](https://github.com/eslint/eslint/commit/ae14a021bbea5117fe366ae4ed235e8f08dc65a8) Fix: add end location to report in no-extra-bind (refs #12334) (#13083) (Milos Djermanovic) +* [`105384c`](https://github.com/eslint/eslint/commit/105384ccc11dcd7303104fb5a347eda1d4d48357) Update: report operator location in operator-linebreak (refs #12334) (#13102) (Milos Djermanovic) +* [`081e240`](https://github.com/eslint/eslint/commit/081e24022a40d9a026ddd2a85c68cb8c3f18dc53) Update: support globalThis in no-implied-eval (fixes #12670) (#13105) (YeonJuan) +* [`185982d`](https://github.com/eslint/eslint/commit/185982d5615d325ae8b45c2360d5847df4098bda) Breaking: improve plugin resolving (refs eslint/rfcs#47) (#12922) (Toru Nagashima) +* [`0c20bc0`](https://github.com/eslint/eslint/commit/0c20bc068e608869981a10711bba88ffde1539d8) Fix: check assignment property target in camelcase (fixes #13025) (#13027) (YeonJuan) +* [`8d50a7d`](https://github.com/eslint/eslint/commit/8d50a7d82244d4912f3eab62a66c81c76c44a9da) Fix: add end location to report in no-prototype-builtins (refs #12334) (#13087) (Milos Djermanovic) +* [`3e4e7f8`](https://github.com/eslint/eslint/commit/3e4e7f8d429dc70b78c0aefaa37f9c22a1e5fc0f) Fix: incorrect logic for required parens in no-extra-boolean-cast fixer (#13061) (Milos Djermanovic) +* [`6c069f9`](https://github.com/eslint/eslint/commit/6c069f907a04268b671c7f949c04a508df9d42a3) Docs: Add comments to code block in example (#13089) (Kibeom Kwon) +* [`ee1f053`](https://github.com/eslint/eslint/commit/ee1f0531aa534ef9182cf8586f55ad82aaa55e75) Docs: Fix typo (#13092) (Max Coplan) +* [`76324ac`](https://github.com/eslint/eslint/commit/76324ace67893c3d7e38a369114d6128df9ffb65) Docs: Add further reading to rule (#13084) (Max Coplan) +* [`a1370ab`](https://github.com/eslint/eslint/commit/a1370abed72e1fb93e601816d981fa6e46204afb) Update: Report constructor calls in no-obj-calls (#12909) (Milos Djermanovic) +* [`2111c52`](https://github.com/eslint/eslint/commit/2111c52443e7641caad291e0daaea8e2fe6c4562) Upgrade: esquery@1.2.0 (#13076) (Milos Djermanovic) +* [`3f7c9bf`](https://github.com/eslint/eslint/commit/3f7c9bf19615122fb776cdd13da532d860bd945a) Docs: clarify variables option in no-use-before-define (fixes #12986) (#13017) (Anix) +* [`aef9488`](https://github.com/eslint/eslint/commit/aef9488c07d3da4becff6e8d6918824b53086d86) Fix: allow references to external globals in id-blacklist (fixes #12567) (#12987) (Milos Djermanovic) +* [`4955c50`](https://github.com/eslint/eslint/commit/4955c50dc9e89b4077b28e35f065d45e89bdccd7) Fix: remove type arguments in prefer-object-spread (fixes #13058) (#13063) (Milos Djermanovic) +* [`48b122f`](https://github.com/eslint/eslint/commit/48b122f450b14dd27afef4c8115c69fca4f02be1) Breaking: change relative paths with --config (refs eslint/rfcs#37) (#12887) (Toru Nagashima) +* [`085979f`](https://github.com/eslint/eslint/commit/085979fed9a5e24a87e4d92ee79272b59211d03f) Update: consider env in no-implied-eval (fixes #12733) (#12757) (YeonJuan) +* [`9ac5b9e`](https://github.com/eslint/eslint/commit/9ac5b9edf06d16a9216c2c9b02bb20b6aa8ed0ab) Docs: Clarify node_modules is ignored by default (fixes #13006) (#13054) (Mika Kuijpers) +* [`0de91f3`](https://github.com/eslint/eslint/commit/0de91f39a97cdf530cb64edbadde57a2bb41ca86) Docs: removed correct code from incorrect eg (#13060) (Anix) +* [`dbe357d`](https://github.com/eslint/eslint/commit/dbe357de199620675446464f6fd0e35064c4d247) Fix: check template literal in prefer-numeric-literals (fixes #13045) (#13046) (YeonJuan) +* [`2260611`](https://github.com/eslint/eslint/commit/2260611e616bdc2a0bf16d508b60a50772ce7fbb) Fix: added async in allow method in no-empty-function (fixes #12768) (#13036) (Anix) +* [`f3788af`](https://github.com/eslint/eslint/commit/f3788aff615edfbfb7afc4c491bb07d20737531b) Sponsors: Sync README with website (ESLint Jenkins) +* [`e90b29b`](https://github.com/eslint/eslint/commit/e90b29bb1f41d4e5767e33d03db5984f036586ed) Update: Allow testing Suggestions with data in RuleTester (fixes #12606) (#12635) (Milos Djermanovic) +* [`7224eee`](https://github.com/eslint/eslint/commit/7224eee3ff4b4378d3439deb038bf34b116fa48b) Fix: no-plusplus allow comma operands in for afterthought (fixes #13005) (#13024) (Milos Djermanovic) +* [`7598cf8`](https://github.com/eslint/eslint/commit/7598cf816bd854de1dd7d96cf00dec6ecc4564ac) Fix: Newline before eof when creating config via --init (#12952) (Andreas Lind) +* [`183e300`](https://github.com/eslint/eslint/commit/183e3006841c29efdd245c45a72e6cefac86ae35) Update: support globalThis (refs #12670) (#12774) (YeonJuan) +* [`af7af9d`](https://github.com/eslint/eslint/commit/af7af9d32ea8073d2d0d726cc8551351261a170f) Docs: Update governance (#13055) (Nicholas C. Zakas) +* [`31d5eb3`](https://github.com/eslint/eslint/commit/31d5eb3e60b6c2ee26976721f07cc89d60867659) Sponsors: Sync README with website (ESLint Jenkins) +* [`95613d4`](https://github.com/eslint/eslint/commit/95613d46b7900b3d9757a7f6959d5dfb262f29fc) Upgrade: espree@6.2.1 (#13026) (Kai Cataldo) +* [`f1525dc`](https://github.com/eslint/eslint/commit/f1525dc45dfdbbe31e724671270785b41cffc6bd) Sponsors: Sync README with website (ESLint Jenkins) +* [`0243549`](https://github.com/eslint/eslint/commit/0243549db4d237cb78e720d55a9cae89b91f6830) Fix: camelcase false positive with computed property (fixes #13022) (#13023) (Milos Djermanovic) +* [`bc0c02c`](https://github.com/eslint/eslint/commit/bc0c02cd0368559c7a7b1510eb4620022a4cc31c) Chore: added lock files to gitignore (#13015) (Anix) +* [`79ac6cd`](https://github.com/eslint/eslint/commit/79ac6cd2d8e4c32e03dfea10a957806845058573) Docs: added less confusing explanation for func-style (fixes #12900) (#13004) (Anix) +* [`26267ed`](https://github.com/eslint/eslint/commit/26267ed70270ef746b785c09e267f815bf7c596a) Chore: update GitHub Actions (#12984) (Pig Fang) +* [`1299705`](https://github.com/eslint/eslint/commit/12997058626b5167ba4b9d2ae0d0ea965a01c4be) Update: acorn version (#13016) (Idan Avrahami) +* [`6cef0d5`](https://github.com/eslint/eslint/commit/6cef0d50a0d131bc8897799a54e1af1d38606db4) Fix: Check division operator in astUtils.canTokensBeAdjacent (#12879) (Milos Djermanovic) +* [`fd8e1f5`](https://github.com/eslint/eslint/commit/fd8e1f52110cada542a120750236fd1ec8779336) Sponsors: Sync README with website (ESLint Jenkins) +* [`472025f`](https://github.com/eslint/eslint/commit/472025f2814d0360fe8d4cddbcba049982e1cd43) Chore: update space-before-function-paren in eslint-config-eslint (#12966) (Kai Cataldo) +* [`fd8c42a`](https://github.com/eslint/eslint/commit/fd8c42ada52f0ae2488ad96ee8fee675f63134ce) Sponsors: Sync README with website (ESLint Jenkins) + +v7.0.0-alpha.2 - February 28, 2020 + +* [`a5b41a7`](https://github.com/eslint/eslint/commit/a5b41a75b57572e97476b06ad39b768e15b9d844) Update: no-restricted-modules handle TemplateLiteral (fixes #12926) (#12927) (Michal Piechowiak) +* [`051567a`](https://github.com/eslint/eslint/commit/051567adca7ca56d691bcda76f54ed72e3eae367) Update: check identifier in array pattern in id-length (fixes #12832) (#12839) (YeonJuan) +* [`4af06fc`](https://github.com/eslint/eslint/commit/4af06fc49029dac5c9acfd53f01fd9527bfbb4aa) Breaking: Test with an unknown error property should fail in RuleTester (#12096) (Milos Djermanovic) +* [`9038a29`](https://github.com/eslint/eslint/commit/9038a29569548c0563c29dbe9f7dae280ff3addd) Update: func-names `as-needed` false negative with AssignmentPattern (#12932) (Milos Djermanovic) +* [`afde78b`](https://github.com/eslint/eslint/commit/afde78b125747ce5ad9e5f871122a0d370dd0152) Fix: curly removes necessary braces between if and else (fixes #12928) (#12943) (Milos Djermanovic) +* [`4797fb2`](https://github.com/eslint/eslint/commit/4797fb2c29db97bc5cd23b40e5a9235fef1ea06a) Fix: arrow-body-style crash with object literal body (fixes #12884) (#12886) (Milos Djermanovic) +* [`afa9aac`](https://github.com/eslint/eslint/commit/afa9aac6de9444e935a55b46311e5b5a58f86063) Breaking: class default `true` computed-property-spacing (fixes #12812) (#12915) (Milos Djermanovic) +* [`b8e20d3`](https://github.com/eslint/eslint/commit/b8e20d33b7d6645266beef09cd231afaf5054328) Docs: Mention TypeScript's compiler check (#12903) (Benny Neugebauer) +* [`de14d1c`](https://github.com/eslint/eslint/commit/de14d1ce0cf422b4100a686abb906f53fbf905b3) Fix: wrap-iife autofix removes mandatory parentheses (#12905) (Milos Djermanovic) +* [`5775b06`](https://github.com/eslint/eslint/commit/5775b06a74573cbe068bea56b1d2376421f5e831) Fix: Optionally allow underscores in member names (#11972) (Edgardo Avilés) +* [`e997f32`](https://github.com/eslint/eslint/commit/e997f32b936463ac38e8b0034f764c47502e56a8) Docs: Updated arrow-parens for minor grammar issue (#12962) (Tom) +* [`7d52151`](https://github.com/eslint/eslint/commit/7d52151bcd5d5524f240588436a8808162be187f) Breaking: classes default `true` in accessor-pairs (fixes #12811) (#12919) (Milos Djermanovic) +* [`cf14355`](https://github.com/eslint/eslint/commit/cf14355e34a6757e15806f8e493553bd7110fb36) Docs: Fix links to custom parsers doc (#12965) (Brandon Mills) +* [`0dfc3ff`](https://github.com/eslint/eslint/commit/0dfc3ff9fb228e1d9b1df99de50033ce9140ac24) Fix: add end location to report in no-eval (#12960) (Milos Djermanovic) +* [`f479f6f`](https://github.com/eslint/eslint/commit/f479f6fe2eb95156e22bebfccb39a7fc1f19e9c0) Docs: References correct config file name (#12885) (Patrick Kilgore) +* [`78182e4`](https://github.com/eslint/eslint/commit/78182e45e0178d9eac2591944ef4daee21d2cb44) Breaking: Add new rules to eslint:recommended (fixes #12911) (#12920) (Milos Djermanovic) +* [`8d5c434`](https://github.com/eslint/eslint/commit/8d5c434f721142be74c7515aaa935668a15b79b1) Docs: fix category descriptions for site generation (fixes #12894) (#12930) (Kai Cataldo) +* [`05380e6`](https://github.com/eslint/eslint/commit/05380e6e7e19a79d26ea6d6b44a8d5ee7cde51c8) Docs: Remove claim about semicolons from docs (#12944) (Luke Sikina) +* [`aa9d725`](https://github.com/eslint/eslint/commit/aa9d72525054e641231a2960a2e37b3716228056) Sponsors: Sync README with website (ESLint Jenkins) +* [`7747177`](https://github.com/eslint/eslint/commit/7747177f8504961059b7c56bdb70a820bd1114c1) Update: report rename id destructuring in id-blacklist (fixes #12807) (#12923) (YeonJuan) +* [`6423e11`](https://github.com/eslint/eslint/commit/6423e11c0bedd3b4e661ab554316bdeb1fc1ee3c) Breaking: check unnamed default export in func-names (fixes #12194) (#12195) (Chiawen Chen) +* [`77df505`](https://github.com/eslint/eslint/commit/77df505d9a08496a8eaefeca4f885f54a21d5c5e) Update: check renaming identifier in object destructuring (fixes 12827) (#12881) (YeonJuan) +* [`41de9df`](https://github.com/eslint/eslint/commit/41de9df41a30a4300243bfe4ca26f716a787b2fc) Update: enforceForLogicalOperands no-extra-boolean-cast (fixes #12137) (#12734) (jmoore914) + +v7.0.0-alpha.1 - February 14, 2020 + +* [`f702b1a`](https://github.com/eslint/eslint/commit/f702b1a54820d2b4e4993dcded99f551a98b490f) Add missing plugin reference (#12796) (Eduard Bardají Puig) +* [`1f1424c`](https://github.com/eslint/eslint/commit/1f1424cb200e609d58645f6c54739e11469e6265) Fix: fix inconsistently works option in no-extra-parens (fixes #12717) (#12843) (YeonJuan) +* [`b5adcaa`](https://github.com/eslint/eslint/commit/b5adcaab93f388f1d8e9d35d6f5e8c2994240850) Fix: make YieldExpression throwable (fixes #12880) (#12897) (YeonJuan) +* [`4293229`](https://github.com/eslint/eslint/commit/4293229709dde105692347241513766e953664dd) Breaking: use-isnan enforceForSwitchCase default `true` (fixes #12810) (#12913) (Milos Djermanovic) +* [`cf38d0d`](https://github.com/eslint/eslint/commit/cf38d0d939b62f3670cdd59f0143fd896fccd771) Breaking: change default ignore pattern (refs eslint/rfcs#51) (#12888) (Toru Nagashima) +* [`bfe1dc4`](https://github.com/eslint/eslint/commit/bfe1dc4e614640cb69032afbb5851c1493f537e3) Breaking: no-dupe-class-members checks some computed keys (fixes #12808) (#12837) (Milos Djermanovic) +* [`1ee6b63`](https://github.com/eslint/eslint/commit/1ee6b6388305a8671c8d4c3cf30c2dbf18a1ff7e) Update: check template literal in yoda (fixes #12863) (#12876) (YeonJuan) +* [`0ae7041`](https://github.com/eslint/eslint/commit/0ae70417af70ad565450d9e779ae78c05f6a51e2) Sponsors: Sync README with website (ESLint Jenkins) +* [`1907e57`](https://github.com/eslint/eslint/commit/1907e57362f7d5f7a02a5a78f24ac3347f868e93) Chore: add Twitter and Open Collective badge (#12877) (Kai Cataldo) +* [`95e0586`](https://github.com/eslint/eslint/commit/95e0586c95e6953d11983d1d11891ed30318109a) Fix: id-blacklist false positives on renamed imports (#12831) (Milos Djermanovic) +* [`b7f0d20`](https://github.com/eslint/eslint/commit/b7f0d200c125b3d233ccafaabdaa61c66dc60e3c) Chore: Use consistent badge style (#12825) (fisker Cheung) +* [`3734a66`](https://github.com/eslint/eslint/commit/3734a669983de7d5107ba8f39b291c6e3116489f) Chore: use ids for messages (#12859) (Gareth Jones) +* [`824d235`](https://github.com/eslint/eslint/commit/824d23585c205f2993716585cb6f55dfbe4a33f0) Docs: add errorOnUnmatchedPattern option to CLIEngine (#12834) (Arthur Denner) +* [`439c833`](https://github.com/eslint/eslint/commit/439c83342c364ba3ce5168d54e165b1fe3e35630) Update: array-callback-return checks Array.forEach (fixes #12551) (#12646) (Gabriel R Sezefredo) +* [`33efd71`](https://github.com/eslint/eslint/commit/33efd71d7c3496b4b9cbfe006280527064940826) Docs: Fix spelling mistakes (#12861) (Bryan Mishkin) +* [`a5b3c5f`](https://github.com/eslint/eslint/commit/a5b3c5fa4edc2312534af0d9f0911f68144f8baf) Docs: Update README team and sponsors (ESLint Jenkins) +* [`0cae920`](https://github.com/eslint/eslint/commit/0cae9203a8077184ad6beb00028fd376cc806f34) Chore: rename shadowed global (#12862) (Tony Brix) +* [`055b80d`](https://github.com/eslint/eslint/commit/055b80dc89bba2a5ab22f7a27deb40135b5cacfa) Chore: Fix typo in complexity.js (#12864) (Kyle Shevlin) +* [`d6c313d`](https://github.com/eslint/eslint/commit/d6c313de794ea0671d35b5027288cd2ea456c0b5) Docs: add missing eslint comments in prefer-regex-literals examples (#12858) (Milos Djermanovic) +* [`7d551ab`](https://github.com/eslint/eslint/commit/7d551ab8cbf2d3a802b0d0685379aa075fe9d7c0) Sponsors: Sync README with website (ESLint Jenkins) +* [`540de8e`](https://github.com/eslint/eslint/commit/540de8e34d08f4b17b66b06d13927acb7552357a) Sponsors: Sync README with website (ESLint Jenkins) +* [`ac5d515`](https://github.com/eslint/eslint/commit/ac5d515252c226f030fa646bf7635a12a3b856fe) Sponsors: Sync README with website (ESLint Jenkins) +* [`dadc892`](https://github.com/eslint/eslint/commit/dadc8927820576c60b48bcbc7d5a9056a6279d30) Fix: operator-assignment crash on adjacent division assignment (#12844) (Milos Djermanovic) +* [`9f39ef0`](https://github.com/eslint/eslint/commit/9f39ef0d4b398c7c09ceef89128da448680d587c) Chore: typo in PULL_REQUEST_TEMPLATE.md (#12848) (Balázs Orbán) +* [`a60d5cd`](https://github.com/eslint/eslint/commit/a60d5cd2325ca72fa1b272b0b90ccd7904b92062) Chore: typo in no-irregular-whitespace.js (#12847) (Balázs Orbán) +* [`691d19a`](https://github.com/eslint/eslint/commit/691d19a2872bffab50c0024d488b8cb33504cc83) Chore: add missing `ecmaVersion` 2020/11 type value (#12833) (Piotr Błażejewicz (Peter Blazejewicz)) +* [`516ddb3`](https://github.com/eslint/eslint/commit/516ddb37d39502e5a8c88a017ae3bad05046f41d) Sponsors: Sync README with website (ESLint Jenkins) +* [`a9d92f9`](https://github.com/eslint/eslint/commit/a9d92f991d69902a9150db373590e2ed54dec988) Fix: radix rule crash on disabled globals (#12824) (Milos Djermanovic) +* [`03a69db`](https://github.com/eslint/eslint/commit/03a69dbe86d5b5768a310105416ae726822e3c1c) Update: check template literal in no-proto, no-iterator (fixes #12801) (#12806) (YeonJuan) +* [`562e784`](https://github.com/eslint/eslint/commit/562e7845946a490f2e173a0bcd1af631070a4eef) Update: fix no-magic-numbers false negative with ignoreArrayIndexes (#12805) (Milos Djermanovic) +* [`f5b9656`](https://github.com/eslint/eslint/commit/f5b96564f732962f46755adbb33c49fae9af6a92) Chore: add test for no-constant-condition (#12836) (Milos Djermanovic) +* [`533c114`](https://github.com/eslint/eslint/commit/533c1140dc98bebdc3ae8334ab2e6c27c7df0c21) Fix: multiline-comment-style rule add extra space after * (fixes #12785) (#12823) (Karthik Priyadarshan) +* [`0460748`](https://github.com/eslint/eslint/commit/0460748cda67ddc4a4cb0db3cdf187a742d09bf8) Update: check template literal in no-constant-condition (fixes #12815) (#12816) (YeonJuan) +* [`80309c3`](https://github.com/eslint/eslint/commit/80309c3791188ac5d1c4eebc99ede323a55336e6) Fix: no-constant-condition doesn't introspect arrays (fixes #12225) (#12307) (Sean Gray) +* [`10a79a6`](https://github.com/eslint/eslint/commit/10a79a672b42d51539bcd6ace482be7afa5f34f8) Chore: Adopt `eslint-plugin/require-meta-docs-description` internally (#12762) (Bryan Mishkin) +* [`aea1729`](https://github.com/eslint/eslint/commit/aea172998ec4e2af1d9186b6767c3f34428945f4) Docs: Fix anchor links in Node.js API TOC (#12821) (Matija Marohnić) +* [`1b8a3ce`](https://github.com/eslint/eslint/commit/1b8a3ce15237b9085f2761dcf73655207e6169a6) Sponsors: Sync README with website (ESLint Jenkins) +* [`c2217c0`](https://github.com/eslint/eslint/commit/c2217c04d6c82b160a21b00fca39c8acec543403) Breaking: make `radix` rule stricter (#12608) (fisker Cheung) + +v7.0.0-alpha.0 - January 17, 2020 + +* [`1aa021d`](https://github.com/eslint/eslint/commit/1aa021d77fdd2c68d7b7d2f4603252110c414b32) Breaking: lint `overrides` files (fixes #10828, refs eslint/rfcs#20) (#12677) (Toru Nagashima) +* [`e59d775`](https://github.com/eslint/eslint/commit/e59d77536bd8db57e8a75cd5245f6f320aa699f8) Update: Separate pattern/expression options for array-element-newline (#11796) (jacobparish) +* [`f8f115a`](https://github.com/eslint/eslint/commit/f8f115af6e10539e6cad485588187cb11917f8c9) Update: treat comment tokens in template-curly-spacing (fixes #12744) (#12775) (YeonJuan) +* [`b50179d`](https://github.com/eslint/eslint/commit/b50179def3fedbd95fdeab25e32c2511867eb760) Breaking: Check assignment targets in no-extra-parens (#12490) (Milos Djermanovic) +* [`de4fa7c`](https://github.com/eslint/eslint/commit/de4fa7c65c7befefa64d1605550267071ee56a5d) Fix: wrong indent at tagged template in indent (fixes #12122) (#12596) (YeonJuan) +* [`d86a5bb`](https://github.com/eslint/eslint/commit/d86a5bbb1987d858d4963f647b0af5c1fd924b4f) Breaking: Check flatMap in array-callback-return (fixes #12235) (#12765) (Milos Djermanovic) +* [`cf46df7`](https://github.com/eslint/eslint/commit/cf46df70158a4ed4c09d5c9d655c07dc6df3ff5e) Breaking: description in directive comments (refs eslint/rfcs#33) (#12699) (Toru Nagashima) +* [`7350589`](https://github.com/eslint/eslint/commit/7350589a5bdfc9d75d1ff19364f476eec44c3911) Breaking: some rules recognize bigint literals (fixes #11803) (#12701) (Toru Nagashima) +* [`16a1c1f`](https://github.com/eslint/eslint/commit/16a1c1f79bc0a4cc1c3a87e98d220041de88bb93) Fix: prefer-object-spread false positives with accessors (fixes #12086) (#12784) (Milos Djermanovic) +* [`f9774ec`](https://github.com/eslint/eslint/commit/f9774ec11b0ebe63fb16a97b97890efb84699548) New: Add default-case-last rule (fixes #12665) (#12668) (Milos Djermanovic) +* [`9a93d9e`](https://github.com/eslint/eslint/commit/9a93d9ef389c49a133c4df4f9843927f5f806423) Update: fix no-restricted-imports export * false negative (fixes #12737) (#12798) (Milos Djermanovic) +* [`0d8c0af`](https://github.com/eslint/eslint/commit/0d8c0affe1ae7ecf228cdf91b490921f7e9d1fdb) Fix: improve report location for computed-property-spacing (#12795) (Milos Djermanovic) +* [`756b95d`](https://github.com/eslint/eslint/commit/756b95d59fb97cd9b3f3adf98cebf529fe4842a2) Fix: id-blacklist should ignore ObjectPatterns (fixes #12787) (#12792) (JP Ramassini) +* [`561b6d4`](https://github.com/eslint/eslint/commit/561b6d4726f3e77dd40ba0d340ca7f08429cd2eb) Chore: add prerequisites checklist to PR template (#12790) (Kai Cataldo) +* [`01ff791`](https://github.com/eslint/eslint/commit/01ff7910af86fc45b76e883bc9ab00c9be3b50ac) Fix: Display pipe character correctly in test output (#12771) (Brad Zacher) +* [`68becbd`](https://github.com/eslint/eslint/commit/68becbd84e8a0693409d36f2be10679c483e233a) Update: fix no-restricted-imports importNames reporting (fixes #12282) (#12711) (Andrey Alexandrov) +* [`ae959b6`](https://github.com/eslint/eslint/commit/ae959b691fb148ac8b474c924c8cb01ef61c436d) Update: report double extra parens in no-extra-parens (fixes #12127) (#12697) (YeonJuan) +* [`02fcc05`](https://github.com/eslint/eslint/commit/02fcc055710e8d69d986f1e682cae2014ad881e2) Docs: Improve sort-keys (#12791) (Steven Vachon) +* [`35cd958`](https://github.com/eslint/eslint/commit/35cd95893be0afd8c954cbcf9268c2aa045b7d5b) Sponsors: Sync README with website (ESLint Jenkins) +* [`a1d999c`](https://github.com/eslint/eslint/commit/a1d999c6b4e51c317ad409110be7be214ff9f7c6) New: Add no-useless-backreference rule (fixes #12673) (#12690) (Milos Djermanovic) +* [`b2c6209`](https://github.com/eslint/eslint/commit/b2c62096a8b318765d618cab222240f87d73063b) Update: fix no-extra-parens CallExpression#callee false negatives (#12743) (Milos Djermanovic) +* [`14b42c3`](https://github.com/eslint/eslint/commit/14b42c386be3387c415267b789f277e1294d4567) Update: fix counting jsx comment len in max-len (fixes #12213) (#12661) (YeonJuan) +* [`e632c31`](https://github.com/eslint/eslint/commit/e632c31d7e5363f1347b787702ecd4a85f5c11a2) Upgrade: several dependencies (#12753) (Toru Nagashima) +* [`25eb703`](https://github.com/eslint/eslint/commit/25eb703c8758563988ffb497a53f89a3ed345399) Docs: fix dead link in max-lines rule docs (#12766) (Christian Bundy) +* [`9dfc850`](https://github.com/eslint/eslint/commit/9dfc8501fb1956c90dc11e6377b4cb38a6bea65d) Chore: Refactor to use messageId in a number of rules (#12715) (Brad Zacher) +* [`1118fce`](https://github.com/eslint/eslint/commit/1118fceb49af3436b8dcd0c6089f913cedf9a329) Breaking: runtime-deprecation on '~/.eslintrc' (refs eslint/rfcs#32) (#12678) (Toru Nagashima) +* [`2c28fbb`](https://github.com/eslint/eslint/commit/2c28fbbb563a44282bef0c9fcc9be29d611cc83b) Breaking: drop Node.js 8 support (refs eslint/rfcs#44) (#12700) (Toru Nagashima) +* [`098b67d`](https://github.com/eslint/eslint/commit/098b67d04a4b4dc8ef4faa6434c6ef5abbde3ed3) Docs: fix minor typo in brace-style.md (#12749) (Marko Kaznovac) +* [`313f70a`](https://github.com/eslint/eslint/commit/313f70ac9a3cf5d1558d2427b00dd75666e18cf4) Update: add outerIIFEBody: "off" to indent rule (fixes #11377) (#12706) (Kai Cataldo) +* [`b77b858`](https://github.com/eslint/eslint/commit/b77b8585e33fc4bb438a0e11ca8177c7eb91dbd8) Chore: fix separateRequires tests for one-var rule (#12709) (Milos Djermanovic) +* [`e4df7df`](https://github.com/eslint/eslint/commit/e4df7dfb0199badb61d2c03ff4f7e4be735279d9) Chore: add JSDoc types for RuleTester test cases (#12325) (Chiawen Chen) +* [`b23ad0d`](https://github.com/eslint/eslint/commit/b23ad0d789a909baf8d7c41a35bc53df932eaf30) Docs: change a broken link in working-with-rules.md (#12732) (Damien Cassou) +* [`3fa39a6`](https://github.com/eslint/eslint/commit/3fa39a633b37544fec7cedfc1f2b0e62e9312a72) Update: Handle locally unsupported regex in computed property keys (#12056) (Milos Djermanovic) +* [`4744397`](https://github.com/eslint/eslint/commit/474439720258b1a64b305c31588f803104fa4aaf) Chore: remove unused code in max-lines-per-function (#12659) (YeonJuan) +* [`4e16957`](https://github.com/eslint/eslint/commit/4e169576a526023ee297d5bc8b37eedba229f63d) Build: update browser build (#12693) (Toru Nagashima) +* [`00ddfff`](https://github.com/eslint/eslint/commit/00ddfffe6b4b4244e4680b0f92f2c6697fad136f) Fix: Windows path parsing for JUnit (fixes #12507) (#12509) (Michael Wall) +* [`985dac3`](https://github.com/eslint/eslint/commit/985dac35e3c367f0f99d1f0e766e06a1d9818dd4) New: Add no-restricted-exports rule (fixes #10428) (#12546) (Milos Djermanovic) +* [`1aff21b`](https://github.com/eslint/eslint/commit/1aff21bb54da44cef0b6e378a34a74265863b930) Fix: no-mixed-spaces-and-tabs reports multiline strings (#12566) (Milos Djermanovic) +* [`8f1020f`](https://github.com/eslint/eslint/commit/8f1020ff711b0c57d590bf666e2841f64186d083) Update: no-void add an option to allow void as a statement (#12613) (Brad Zacher) +* [`bb6cf50`](https://github.com/eslint/eslint/commit/bb6cf5082623ffb67bb1495fee52c0610ee5f421) Update: Add offsetTernaryExpressions option for indent rule (#12556) (Adam Stankiewicz) +* [`39f5a45`](https://github.com/eslint/eslint/commit/39f5a453579b2ad732212edeb71f84ecb0991f97) Chore: add test cases for for-direction (#12698) (YeonJuan) +* [`b340304`](https://github.com/eslint/eslint/commit/b3403045e535921df6d34785a4ce053e14ba27fd) Chore: Add extra test, improve docs (#12492) (Kevin Partington) +* [`827259e`](https://github.com/eslint/eslint/commit/827259ea009f98a0fdf3f7ebf1bfb6cd661ce28d) Build: package.json update for eslint-config-eslint release (ESLint Jenkins) + +v6.8.0 - December 20, 2019 + +* [`c5c7086`](https://github.com/eslint/eslint/commit/c5c708666b450fb69522a55aa375626f9297dc6f) Fix: ignore aligning single line in key-spacing (fixes #11414) (#12652) (YeonJuan) +* [`9986d9e`](https://github.com/eslint/eslint/commit/9986d9e0baed0d3586bbee472fe2fae2ed625f5d) Chore: add object option test cases in yield-star-spacing (#12679) (YeonJuan) +* [`1713d07`](https://github.com/eslint/eslint/commit/1713d0758b083f3840d724505f997a7cb20ff384) New: Add no-error-on-unmatched-pattern flag (fixes #10587) (#12377) (ncraley) +* [`5c25a26`](https://github.com/eslint/eslint/commit/5c25a26608fbd9a1d0127c9a3653609aa4b63e86) Update: autofix bug in lines-between-class-members (fixes #12391) (#12632) (YeonJuan) +* [`4b3cc5c`](https://github.com/eslint/eslint/commit/4b3cc5cd2459f04eae149faea0651785d7f9db0b) Chore: enable prefer-regex-literals in eslint codebase (#12268) (薛定谔的猫) +* [`05faebb`](https://github.com/eslint/eslint/commit/05faebb943456ad2b20117f3c8b3eccbe2e2fb03) Update: improve suggestion testing experience (#12602) (Brad Zacher) +* [`05f7dd5`](https://github.com/eslint/eslint/commit/05f7dd53ed91a6e3be9eb40825fb6d2207f82209) Update: Add suggestions for no-unsafe-negation (fixes #12591) (#12609) (Milos Djermanovic) +* [`d3e43f1`](https://github.com/eslint/eslint/commit/d3e43f1c10c5e19f40e7b3d3944b87f1b0c9c075) Docs: Update no-multi-assign explanation (#12615) (Yuping Zuo) +* [`272e4db`](https://github.com/eslint/eslint/commit/272e4db6074283bc01cc6ec72c9e396bb3c110e6) Fix: no-multiple-empty-lines: Adjust reported `loc` (#12594) (Tobias Bieniek) +* [`a258039`](https://github.com/eslint/eslint/commit/a258039e556075d7d1f955a79d094ea103ec165a) Fix: no-restricted-imports schema allows multiple paths/patterns objects (#12639) (Milos Djermanovic) +* [`51f9620`](https://github.com/eslint/eslint/commit/51f9620cc55cc091fe38dbe68e4633de06297b8c) Fix: improve report location for array-bracket-spacing (#12653) (Milos Djermanovic) +* [`45364af`](https://github.com/eslint/eslint/commit/45364afc9c7f0251348cd1a7a13656c3816435d7) Fix: prefer-numeric-literals doesn't check types of literal arguments (#12655) (Milos Djermanovic) +* [`e3c570e`](https://github.com/eslint/eslint/commit/e3c570eaf3d1d44fb57bf42f1870887856e4c5a0) Docs: Add example for expression option (#12694) (Arnaud Barré) +* [`6b774ef`](https://github.com/eslint/eslint/commit/6b774ef0d849ccf5c1127b25e1fe7c3e438d586b) Docs: Add spacing in comments for no-console rule (#12696) (Nikki Nikkhoui) +* [`7171fca`](https://github.com/eslint/eslint/commit/7171fca6ef4e0e8f267658fc7d8f603f00eddd84) Chore: refactor regex in config comment parser (#12662) (Milos Djermanovic) +* [`1600648`](https://github.com/eslint/eslint/commit/1600648d2880ffb1e9e414b31ff0f66ead7167f9) Update: Allow $schema in config (#12612) (Yordis Prieto) +* [`acc0e47`](https://github.com/eslint/eslint/commit/acc0e47572a9390292b4e313b4a4bf360d236358) Update: support .eslintrc.cjs (refs eslint/rfcs#43) (#12321) (Evan Plaice) +* [`49c1658`](https://github.com/eslint/eslint/commit/49c1658544ace24b9aaaa301af0fc07a2ef3bf30) Chore: remove bundling of ESLint during release (#12676) (Kai Cataldo) +* [`257f3d6`](https://github.com/eslint/eslint/commit/257f3d67905a52bf8602a5a5707c893cc90d7ca7) Chore: complete to move to GitHub Actions (#12625) (Toru Nagashima) +* [`ab912f0`](https://github.com/eslint/eslint/commit/ab912f0ef709a916ab9a27ea09d9d7adf046fb2d) Docs: 1tbs with allowSingleLine edge cases (refs #12284) (#12314) (Ari Kardasis) +* [`dd1c30e`](https://github.com/eslint/eslint/commit/dd1c30e35f05ed332e2abbd3d4d53635efde74b8) Sponsors: Sync README with website (ESLint Jenkins) +* [`a230f84`](https://github.com/eslint/eslint/commit/a230f8404e4f2423dd79378b065d24c12776775b) Update: include node version in cache (#12582) (Eric Wang) +* [`8b65f17`](https://github.com/eslint/eslint/commit/8b65f175dfb4fac11ed7184537be400ed14996fb) Chore: remove references to parser demo (#12644) (Kai Cataldo) +* [`e9cef99`](https://github.com/eslint/eslint/commit/e9cef99e6ebec1faefdb576ca597e81ae4f04afd) Docs: wrap {{}} in raw liquid tags to prevent interpolation (#12643) (Kai Cataldo) +* [`e707453`](https://github.com/eslint/eslint/commit/e70745325ff9e085acc6843dd8bfae5550645d4f) Docs: Fix configuration example in no-restricted-imports (fixes #11717) (#12638) (Milos Djermanovic) +* [`19194ce`](https://github.com/eslint/eslint/commit/19194cec724e016df02376bbeae31171be6f0bdf) Chore: Add tests to cover default object options in comma-dangle (#12627) (YeonJuan) +* [`6e36d12`](https://github.com/eslint/eslint/commit/6e36d12d95e76022172fd0ec8a5e85c22fde6a8a) Update: do not recommend require-atomic-updates (refs #11899) (#12599) (Kai Cataldo) + +v6.7.2 - November 30, 2019 + +* [`bc435a9`](https://github.com/eslint/eslint/commit/bc435a93afd6ba4def1b53993ef7cf8220f3f070) Fix: isSpaceBetweenTokens() recognizes spaces in JSXText (fixes #12614) (#12616) (Toru Nagashima) +* [`4928d51`](https://github.com/eslint/eslint/commit/4928d513b4fe716c7ed958c294a10ef8517be25e) Fix: don't ignore the entry directory (fixes #12604) (#12607) (Toru Nagashima) +* [`b41677a`](https://github.com/eslint/eslint/commit/b41677ae2a143790b19b0e70391a46ec6c8f5de1) Docs: Clarify suggestion's data in Working with Rules (refs #12606) (#12617) (Milos Djermanovic) +* [`ea16de4`](https://github.com/eslint/eslint/commit/ea16de4e7c6f661398b0b7843f95e5f307c89551) Fix: Support tagged template literal generics in no-unexpected-multiline (#11698) (Brad Zacher) +* [`fa6415d`](https://github.com/eslint/eslint/commit/fa6415d5b877370374a6a530a5190ab5a411b4dc) Sponsors: Sync README with website (ESLint Jenkins) +* [`e1e158b`](https://github.com/eslint/eslint/commit/e1e158b4d7bd61e812723b378d2c391295da43a5) Sponsors: Sync README with website (ESLint Jenkins) + +v6.7.1 - November 24, 2019 + +* [`dd1e9f4`](https://github.com/eslint/eslint/commit/dd1e9f4df2103c43509a54b0ad5f9106557997f9) Fix: revert changes to key-spacing due to regression (#12598) (Kai Cataldo) +* [`c644b54`](https://github.com/eslint/eslint/commit/c644b5429e5bc8a050afd70c99ec82035eb611fa) Docs: Update README team and sponsors (ESLint Jenkins) + +v6.7.0 - November 22, 2019 + +* [`312a88f`](https://github.com/eslint/eslint/commit/312a88f2230082d898b7d8d82f8af63cb352e55a) New: Add grouped-accessor-pairs rule (fixes #12277) (#12331) (Milos Djermanovic) +* [`5c68f5f`](https://github.com/eslint/eslint/commit/5c68f5feeb4a6c0cb53ff76b2fd255b5bfa69c93) Update: Add 'lexicalBindings' to no-implicit-globals and change messages (#11996) (Milos Djermanovic) +* [`6eaad96`](https://github.com/eslint/eslint/commit/6eaad964ff159d0a38de96c1104782ffe6858c78) New: Add suggestions API (#12384) (Will Douglas) +* [`b336fbe`](https://github.com/eslint/eslint/commit/b336fbedecd85731611fdc2dfd8edb635a8b1c39) Fix: indent rule with JSX spread props (#12581) (Nathan Woltman) +* [`97c745d`](https://github.com/eslint/eslint/commit/97c745dc277febbea82552a4d9186e3df847f860) Update: Report assignment expression location in no-cond-assign (#12465) (Milos Djermanovic) +* [`0f01f3d`](https://github.com/eslint/eslint/commit/0f01f3d0807c580631c2fdcff29192a64a870637) Update: Check member expressions with `this` in operator-assignment (#12495) (Milos Djermanovic) +* [`62c7038`](https://github.com/eslint/eslint/commit/62c7038a493d89e4a7b14ac673a063d09d04057b) Fix: invalid token checking in computed-property-spacing (fixes #12198) (#12533) (YeonJuan) +* [`4f8a1ee`](https://github.com/eslint/eslint/commit/4f8a1ee1c26ccb5882e5e83ea7eab2f406c7476b) Update: Add enforceForClassMembers option to no-useless-computed-key (#12110) (ark120202) +* [`1a2eb99`](https://github.com/eslint/eslint/commit/1a2eb99f11c65813bba11d6576a06cff2b823cc9) New: new rule no-constructor-return (fixes #12481) (#12529) (Pig Fang) +* [`ca3b2a6`](https://github.com/eslint/eslint/commit/ca3b2a62c9e829dc4534bca3643d6bc729b46df0) New: ignorePatterns in config files (refs eslint/rfcs#22) (#12274) (Toru Nagashima) +* [`60204a3`](https://github.com/eslint/eslint/commit/60204a3620e33a078c1c35fa2e5d839a16c627ff) Docs: Added another Textmate 2 bundle. (#12580) (Ryan Fitzer) +* [`62623f9`](https://github.com/eslint/eslint/commit/62623f9f611a3adb79696304760a2fd14be8afbc) Fix: preserve whitespace in multiline-comment-style (fixes #12312) (#12316) (Kai Cataldo) +* [`17a8849`](https://github.com/eslint/eslint/commit/17a8849491a983f6cb8e98da8c0c9d52ff5f2aa6) New: Add no-dupe-else-if rule (fixes #12469) (#12504) (Milos Djermanovic) +* [`41a78fd`](https://github.com/eslint/eslint/commit/41a78fd7ce245cad8ff6a96c42f5840688849427) Update: improve location for semi and comma-dangle (#12380) (Chiawen Chen) +* [`0a480f8`](https://github.com/eslint/eslint/commit/0a480f8307a0e438032f484254941e6426748143) Docs: Change "Code Conventions" link in pull-requests.md (#12401) (Denis Sikuler) +* [`fed20bb`](https://github.com/eslint/eslint/commit/fed20bb039cf9f53adfcf93e467f418c5e958f45) Fix: require-await crash on global await (#12571) (Brad Zacher) +* [`b8030fc`](https://github.com/eslint/eslint/commit/b8030fc23e88f57a04d955b3befd1ab0fc2c5d10) Update: deprecate personal config (fixes #11914, refs eslint/rfcs#32) (#12426) (Toru Nagashima) +* [`40c8c32`](https://github.com/eslint/eslint/commit/40c8c3264c7c383d98c9faf9c4cb4f8b75aee40f) Fix: improve report location for object-curly-spacing (#12563) (Milos Djermanovic) +* [`1110045`](https://github.com/eslint/eslint/commit/1110045e0d28a461e75d2f57d5f01533d59ef239) Fix: ignore marker-only comments in spaced-comment (fixes #12036) (#12558) (Milos Djermanovic) +* [`6503cb8`](https://github.com/eslint/eslint/commit/6503cb8d99e549fece53b80b110e890a7978b9fd) Update: Fix uglified object align in key-spacing (fixes #11414) (#12472) (YeonJuan) +* [`40791af`](https://github.com/eslint/eslint/commit/40791af69efde1701690637603ad37d41e15a727) Docs: clarify ignoreDestructuring option in the camelcase rule (#12553) (Milos Djermanovic) +* [`07d398d`](https://github.com/eslint/eslint/commit/07d398d91d5b6d0247e58b1f8ea64bb5acd570a8) Chore: Add GitHub organization to Sponsor button (#12562) (Brandon Mills) +* [`a477707`](https://github.com/eslint/eslint/commit/a47770706ac59633dcd73e886d1a7282b324ee06) Chore: Format style guide links so they can be clicked (#12189) (Ivan V) +* [`0f7edef`](https://github.com/eslint/eslint/commit/0f7edefdc1576d5e3e7ef89083002b0a4a31f039) Update: add react plugin config for eslint init (#12446) (Ibrahim Rouis) +* [`448ff1e`](https://github.com/eslint/eslint/commit/448ff1e53734c503fb9e7e6802c1c7e441d4c019) Update: Report '\08' and '\09' in no-octal-escape (fixes #12080) (#12526) (Milos Djermanovic) +* [`45aa6a3`](https://github.com/eslint/eslint/commit/45aa6a3ba3486f1b116c5daab6432d144e5ea574) New: Add no-setter-return rule (fixes #12285) (#12346) (Milos Djermanovic) +* [`0afb518`](https://github.com/eslint/eslint/commit/0afb518d1f139376245613dddd8eaef32b52d619) Fix: invalid autofix in function-call-argument-newline (fixes #12454) (#12539) (YeonJuan) +* [`90305e0`](https://github.com/eslint/eslint/commit/90305e017c2c5fba0b4b62f41b180910b4baeedb) Update: Depcrecate isSpaceBetweenTokens() (#12519) (Kai Cataldo) +* [`41b1e43`](https://github.com/eslint/eslint/commit/41b1e4308c1cb01c8b00cc8adc36440e77854117) New: add option for camelcase (fixes #12527) (#12528) (Pig Fang) +* [`f49f1e0`](https://github.com/eslint/eslint/commit/f49f1e0a69afa49f6548af7b2c0e6347e1ea022d) Upgrade: upgrade optionator to avoid license issue (fixes #11536) (#12537) (Pig Fang) +* [`0286b57`](https://github.com/eslint/eslint/commit/0286b5730501b391c74e069db46849f0de0885d2) Docs: Clean up Getting Started Guide (#12544) (Nicholas C. Zakas) +* [`575a98d`](https://github.com/eslint/eslint/commit/575a98d724b2688f1e9c83744c5dc9ffe9a7bfb4) Chore: Add funding field to package.json (#12543) (Nicholas C. Zakas) +* [`9e29e18`](https://github.com/eslint/eslint/commit/9e29e189752f06362fd1956659e07834efb746a5) Fix: sourceCode#isSpaceBetweenTokens() checks non-adjacent tokens (#12491) (Kai Cataldo) +* [`5868550`](https://github.com/eslint/eslint/commit/586855060afb3201f4752be8820dc85703b523a6) Docs: add notice about `function` keyword in keyword-spacing (#12524) (Pig Fang) +* [`bb556d5`](https://github.com/eslint/eslint/commit/bb556d5fd735ad2dcea322082edcc07a58105ce9) Fix: curly `multi` reports single lexical declarations (fixes #11908) (#12513) (Milos Djermanovic) +* [`ac60621`](https://github.com/eslint/eslint/commit/ac606217d4beebc35b865d14a7f9723fd21faa48) Fix: unexpected autofix in prefer-const (fixes #12514) (#12521) (YeonJuan) +* [`990065e`](https://github.com/eslint/eslint/commit/990065e5f58b6cc6922ab6cee5b97bfc56a6237a) Update: curly multi-or-nest flagging semis on next line (fixes #12370) (#12378) (cherryblossom000) +* [`084a8a6`](https://github.com/eslint/eslint/commit/084a8a63a749232681fefe9bdac6802efdcdc8a8) Fix: no-cond-assign with `always` option reports switch case clauses (#12470) (Milos Djermanovic) +* [`7e41355`](https://github.com/eslint/eslint/commit/7e41355b19a8ef347620dd7c0dde491c3460937b) Update: improve report location for space-infix-ops (#12324) (Chiawen Chen) +* [`94ff921`](https://github.com/eslint/eslint/commit/94ff921689115f856578159564ee1968b4b914be) Update: Add capIsConstructor option to no-invalid-this (fixes #12271) (#12308) (Milos Djermanovic) +* [`de65de6`](https://github.com/eslint/eslint/commit/de65de6e488112a602949e6a5d27dd4c754b003c) New: Add prefer-exponentiation-operator rule (fixes #10482) (#12360) (Milos Djermanovic) +* [`c78f4a7`](https://github.com/eslint/eslint/commit/c78f4a73de68f81cd41132b46d4840b91599d599) Update: Allow JSX exception in no-inline-comments (fixes #11270) (#12388) (Milos Djermanovic) +* [`e17fb90`](https://github.com/eslint/eslint/commit/e17fb90f5817d16081e690eb06b7720afcb9fa2a) New: allowAfterThisConstructor for no-underscore-dangle (fixes #11488) (#11489) (sripberger) +* [`287ca56`](https://github.com/eslint/eslint/commit/287ca562811d037bde09a47af7f5b9c7b741e022) Build: update CI for Node.js 13 (#12496) (Toru Nagashima) +* [`98e1d50`](https://github.com/eslint/eslint/commit/98e1d50273f31c2a7b59772298280ed7305274c8) Upgrade: globals to v12.1.0 (#12296) (Tony Brix) +* [`8ac71a3`](https://github.com/eslint/eslint/commit/8ac71a3c89a9db13706a44b23d1b509b65185113) Sponsors: Sync README with website (ESLint Jenkins) +* [`4e142ea`](https://github.com/eslint/eslint/commit/4e142ea411dfb692b6e2a69cd5f1204ade4dd58a) Docs: Update README team and sponsors (ESLint Jenkins) + +v6.6.0 - October 25, 2019 + +* [`39dfe08`](https://github.com/eslint/eslint/commit/39dfe0880fa934e287e8ea1f7b56d5cba8d43765) Update: false positives in function-call-argument-newline (fixes #12123) (#12280) (Scott O'Hara) +* [`4d84210`](https://github.com/eslint/eslint/commit/4d842105c9c82026be668d7425213138903d4d41) Update: improve report location for no-trailing-spaces (fixes #12315) (#12477) (Milos Djermanovic) +* [`c6a7745`](https://github.com/eslint/eslint/commit/c6a7745a1371a85932bfae5fec039d1b6fcfc128) Update: no-trailing-spaces false negatives after comments (fixes #12479) (#12480) (Milos Djermanovic) +* [`0bffe95`](https://github.com/eslint/eslint/commit/0bffe953d2752dd2d3045f2f8771c96b6cee8fc4) Fix: no-misleading-character-class crash on invalid regex (fixes #12169) (#12347) (Milos Djermanovic) +* [`c6a9a3b`](https://github.com/eslint/eslint/commit/c6a9a3bc58b69dbf9be9cd09b0283c081ca211e7) Update: Add enforceForIndexOf option to use-isnan (fixes #12207) (#12379) (Milos Djermanovic) +* [`364877b`](https://github.com/eslint/eslint/commit/364877b2504e8f7ece04770b93d517e2f27458d0) Update: measure plugin loading time and output in debug message (#12395) (Victor Homyakov) +* [`1744fab`](https://github.com/eslint/eslint/commit/1744faba3c93c869f7dbbf0a704d32e2692d6856) Fix: operator-assignment removes and duplicates comments (#12485) (Milos Djermanovic) +* [`52ca11a`](https://github.com/eslint/eslint/commit/52ca11a66ab6c2fb5a71d8b9869482f14f98cb9d) Fix: operator-assignment invalid autofix with adjacent tokens (#12483) (Milos Djermanovic) +* [`0f6d0dc`](https://github.com/eslint/eslint/commit/0f6d0dcdf5adc30079a7379bbf605a4ef3887a85) Fix: CLIEngine#addPlugin reset lastConfigArrays (fixes #12425) (#12468) (Toru Nagashima) +* [`923a8cb`](https://github.com/eslint/eslint/commit/923a8cb752b8dee1e622c5fd36f3f53288e30602) Chore: Fix lint failure in JSDoc comment (#12489) (Brandon Mills) +* [`aac3be4`](https://github.com/eslint/eslint/commit/aac3be435cccc241781150fcac728df04d086fa8) Update: Add ignored prop regex no-param-reassign (#11275) (Luke Bennett) +* [`e5382d6`](https://github.com/eslint/eslint/commit/e5382d6e4eb1344f537b6f107535269e9939fcb8) Chore: Remove unused parameter in dot-location (#12464) (Milos Djermanovic) +* [`49faefb`](https://github.com/eslint/eslint/commit/49faefbee3fc7daaf2482d9d7d23513d6ffda9e8) Fix: no-obj-calls false positive (fixes #12437) (#12467) (Toru Nagashima) +* [`b3dbd96`](https://github.com/eslint/eslint/commit/b3dbd9657bbeac6571111a4429b03fc085ba6655) Fix: problematic installation issue (fixes #11018) (#12309) (Toru Nagashima) +* [`cd7c29b`](https://github.com/eslint/eslint/commit/cd7c29b17085c14c9cf6345201c72a192c0d0e0c) Sponsors: Sync README with website (ESLint Jenkins) +* [`8233873`](https://github.com/eslint/eslint/commit/8233873b8e5facd80ab7b172bff1e896a9c5fd39) Docs: Add note about Node.js requiring SSL support (fixes #11413) (#12475) (Nicholas C. Zakas) +* [`89e8aaf`](https://github.com/eslint/eslint/commit/89e8aafcc622a4763bed6b9d62f148ef95798f38) Fix: improve report location for no-tabs (#12471) (Milos Djermanovic) +* [`7dffe48`](https://github.com/eslint/eslint/commit/7dffe482d646d4e5f94fa87a22f3b5b2e0a4b189) Update: Enable function string option in comma-dangle (fixes #12058) (#12462) (YeonJuan) +* [`e15e1f9`](https://github.com/eslint/eslint/commit/e15e1f933f287d274a726e7f0f0a1dd80f0964af) Docs: fix doc for no-unneeded-ternary rule (fixes #12098) (#12410) (Sam Rae) +* [`b1dc58f`](https://github.com/eslint/eslint/commit/b1dc58f0a717cb3d19300c845ca23a21ceb610d3) Sponsors: Sync README with website (ESLint Jenkins) +* [`61749c9`](https://github.com/eslint/eslint/commit/61749c94bd8a2ebcdfb89e0cd48c4a029a945079) Chore: Provide debug log for parser errors (#12474) (Brad Zacher) +* [`7c8bbe0`](https://github.com/eslint/eslint/commit/7c8bbe0391944e1f92e083a04715bf4b3fe6be5d) Update: enforceForOrderingRelations no-unsafe-negation (fixes #12163) (#12414) (Sam Rae) +* [`349ed67`](https://github.com/eslint/eslint/commit/349ed6700e1155384597e1e6035550a96cb8a42d) Update: improve report location for no-mixed-operators (#12328) (Chiawen Chen) +* [`a102eaa`](https://github.com/eslint/eslint/commit/a102eaa9ac19e1c6d92f79a4033e9048cfb64c0d) Fix: prefer-numeric-literals invalid autofix with adjacent tokens (#12387) (Milos Djermanovic) +* [`6e7c18d`](https://github.com/eslint/eslint/commit/6e7c18ddb30b32ee5b2e842cc8258aa7aebb7445) Update: enforceForNewInMemberExpressions no-extra-parens (fixes #12428) (#12436) (Milos Djermanovic) +* [`51fbbd7`](https://github.com/eslint/eslint/commit/51fbbd78f98f223d17071650f5117d07f60dadc2) Fix: array-bracket-newline consistent error with comments (fixes #12416) (#12441) (Milos Djermanovic) +* [`e657d4c`](https://github.com/eslint/eslint/commit/e657d4ccb9f3dd5cacceaaa40ffe24ac29a1349a) Fix: report full dot location in dot-location (#12452) (Milos Djermanovic) +* [`2d6e345`](https://github.com/eslint/eslint/commit/2d6e345e3c2626b0f2252f22cfaffdf53ea0871a) Update: make isSpaceBetweenTokens() ignore newline in comments (#12407) (YeonJuan) +* [`84f71de`](https://github.com/eslint/eslint/commit/84f71de0e686e0fe37b83d6728ce1825caaa44fb) Update: remove default overrides in keyword-spacing (fixes #12369) (#12411) (YeonJuan) +* [`18a0b0e`](https://github.com/eslint/eslint/commit/18a0b0e3df927428a22b5b5295f9faee4bd57246) Update: improve report location for no-space-in-parens (#12364) (Chiawen Chen) +* [`d61c8a5`](https://github.com/eslint/eslint/commit/d61c8a5a75447a36276f2d4f84afb3e1129618da) Update: improve report location for no-multi-spaces (#12329) (Chiawen Chen) +* [`561093f`](https://github.com/eslint/eslint/commit/561093fc4267a4ae317d63bc9f103020fad88802) Upgrade: bump inquirer to ^7.0.0 (#12440) (Joe Graham) +* [`fb633b2`](https://github.com/eslint/eslint/commit/fb633b2bbd0a390b247047524fdd1f612dbab803) Chore: Add a script for testing with more control (#12444) (Eric Wang) +* [`012ec51`](https://github.com/eslint/eslint/commit/012ec5151113a2be06fc0e4cd208d714e52dbc57) Sponsors: Sync README with website (ESLint Jenkins) +* [`874fe16`](https://github.com/eslint/eslint/commit/874fe1642a10a0fb937ccccdd4d22343b84f80dc) New: pass cwd from cli engine (#12389) (Eric Wang) +* [`b962775`](https://github.com/eslint/eslint/commit/b962775b8cb7c90985a5ab63e56744bb2ba79644) Update: no-self-assign should detect member expression with this (#12279) (Tibor Blenessy) +* [`02977f2`](https://github.com/eslint/eslint/commit/02977f25a922dd0b8617c16116bb4364d0f30e94) Docs: Clarify `eslint:recommended` semver policy (#12429) (Kevin Partington) +* [`97045ae`](https://github.com/eslint/eslint/commit/97045ae0805e6503887eef0b131dcb9e70b6d185) Docs: Fixes object type for `rules` in "Use a Plugin" (#12409) (Daisy Develops) +* [`24ca088`](https://github.com/eslint/eslint/commit/24ca088fdc901feef8f10b050414fbde64b55c7d) Docs: Fix typo in v6 migration guide (#12412) (Benjamim Sonntag) +* [`b094008`](https://github.com/eslint/eslint/commit/b094008fb196dc1de5b4c27b7dbf0bcbb4b7b352) Chore: update version parameter name (#12402) (Toru Nagashima) +* [`e5637ba`](https://github.com/eslint/eslint/commit/e5637badd42f087d115f81575b832097fe6fe554) Chore: enable jsdoc/require-description (#12365) (Kai Cataldo) +* [`d31f337`](https://github.com/eslint/eslint/commit/d31f3370396ec4868722bdc044aa697b135ac183) Sponsors: Sync README with website (ESLint Jenkins) +* [`7ffb22f`](https://github.com/eslint/eslint/commit/7ffb22f61cf1622511a7fe42b5ead7c3b216df5e) Chore: Clean up inline directive parsing (#12375) (Jordan Eldredge) +* [`84467c0`](https://github.com/eslint/eslint/commit/84467c07461cc47ee43807ba9014e13700473c5c) Docs: fix wrong max-depth example (fixes #11991) (#12358) (Gabriel R Sezefredo) +* [`3642342`](https://github.com/eslint/eslint/commit/364234262efabd91fa8bd53161d9d3e1e37e7944) Docs: Fix minor formatting/grammar errors (#12371) (cherryblossom000) +* [`c47fa0d`](https://github.com/eslint/eslint/commit/c47fa0dfc76211b3b0e5649c63acdd9606ce0eca) Docs: Fix missing word in sentence (#12361) (Dan Boulet) +* [`8108f49`](https://github.com/eslint/eslint/commit/8108f49f9fa0c2de80b3b66c847551beff585951) Chore: enable additional eslint-plugin-jsdoc rules (#12336) (Kai Cataldo) +* [`b718d2e`](https://github.com/eslint/eslint/commit/b718d2e6c9fe3fc56aa7cfc68b1a40b5cd8a7c01) Chore: update issue template with --eslint-fix flag (#12352) (James George) +* [`20ba14d`](https://github.com/eslint/eslint/commit/20ba14dc78fc2654b2920d14877dde21c6c10da4) Sponsors: Sync README with website (ESLint Jenkins) +* [`566a947`](https://github.com/eslint/eslint/commit/566a947f67c8038a50e204d68723519778a78a0f) Sponsors: Sync README with website (ESLint Jenkins) +* [`070cbd0`](https://github.com/eslint/eslint/commit/070cbd0a2ec07831962a25c4276d08e097302416) Sponsors: Sync README with website (ESLint Jenkins) + +v6.5.1 - September 30, 2019 + +* [`0d3d7d9`](https://github.com/eslint/eslint/commit/0d3d7d9cdd83a7f0e035c95f716a91b9ecc4868b) Docs: fix typo in no-magic-numbers (#12345) (Josiah Rooney) +* [`447ac87`](https://github.com/eslint/eslint/commit/447ac877e8ca2858d61b1e983f72d39e3e2ca74d) Fix: no-useless-rename handles ExperimentalRestProperty (fixes #12335) (#12339) (Kai Cataldo) +* [`b6ff73c`](https://github.com/eslint/eslint/commit/b6ff73cad13282fbfc91186cf4bc2f20278a8936) Sponsors: Sync README with website (ESLint Jenkins) + +v6.5.0 - September 29, 2019 + +* [`73596cb`](https://github.com/eslint/eslint/commit/73596cbdf0a12e2878b2994783f9b969b0c5fbeb) Update: Add enforceForSwitchCase option to use-isnan (#12106) (Milos Djermanovic) +* [`d592a24`](https://github.com/eslint/eslint/commit/d592a248d67920f7200925c003f10853d29f1f8d) Fix: exclude `\u000d` so new line won't convert to text (fixes #12027) (#12031) (zamboney) +* [`e85d27a`](https://github.com/eslint/eslint/commit/e85d27af427d6185ac553a0d801b5103153426d4) Fix: no-regex-spaces false positives and invalid autofix (fixes #12226) (#12231) (Milos Djermanovic) +* [`b349bf7`](https://github.com/eslint/eslint/commit/b349bf79ad56dded826bc99cb52c3551af34fa63) Fix: prefer-named-capture-group incorrect locations (fixes #12233) (#12247) (Milos Djermanovic) +* [`7dc1ea9`](https://github.com/eslint/eslint/commit/7dc1ea9a1b9a21daaffcf712ba9c0e91af81b906) Fix: no-useless-return autofix removes comments (#12292) (Milos Djermanovic) +* [`0e68677`](https://github.com/eslint/eslint/commit/0e68677ec0aaf060a071ecf71e4af954dddb6af0) Fix: no-extra-bind autofix removes comments (#12293) (Milos Djermanovic) +* [`6ad7e86`](https://github.com/eslint/eslint/commit/6ad7e864303e56a39c89569d50c6caf80752ee21) Fix: no-extra-label autofix removes comments (#12298) (Milos Djermanovic) +* [`acec201`](https://github.com/eslint/eslint/commit/acec201f06df780791179ad92cfc484f9b6d23d4) Fix: no-undef-init autofix removes comments (#12299) (Milos Djermanovic) +* [`d89390b`](https://github.com/eslint/eslint/commit/d89390b75e3e9993f347387a49b0ac5550f45c7f) Fix: use async reading of stdin in bin/eslint.js (fixes #12212) (#12230) (Barrie Treloar) +* [`334ca7c`](https://github.com/eslint/eslint/commit/334ca7c8b9c18ac097849c1cefaa43097a4e51dc) Update: no-useless-rename also reports default values (fixes #12301) (#12322) (Kai Cataldo) +* [`41bfe91`](https://github.com/eslint/eslint/commit/41bfe919c06932b7e58cd9ead20157e06656160a) Update: Fix handling of chained new expressions in new-parens (#12303) (Milos Djermanovic) +* [`160b7c4`](https://github.com/eslint/eslint/commit/160b7c46b556ccb6023eb411a8be8801a4bda6df) Chore: add autofix npm script (#12330) (Kai Cataldo) +* [`04b6adb`](https://github.com/eslint/eslint/commit/04b6adb7f1bcb2b6cb3fa377b1ca4cecd810630e) Chore: enable eslint-plugin-jsdoc (refs #11146) (#12332) (Kai Cataldo) +* [`9b86167`](https://github.com/eslint/eslint/commit/9b86167e6f053e4a72bf68ebc79db53903f7f8c3) Docs: Add new ES environments to Configuring ESLint (#12289) (Milos Djermanovic) +* [`c9aeab2`](https://github.com/eslint/eslint/commit/c9aeab21a71c6743f51163b7a8fdf4f0cbfcdbde) Docs: Add supported ECMAScript version to README (#12290) (Milos Djermanovic) +* [`8316e7b`](https://github.com/eslint/eslint/commit/8316e7be5a9429513d7ecf2ee2afc40ab4415b8f) Fix: no-useless-rename autofix removes comments (#12300) (Milos Djermanovic) +* [`29c12f1`](https://github.com/eslint/eslint/commit/29c12f18726a3afb21fc89ab1bdacc6972d49e68) Chore: cache results in runtime-info (#12320) (Kai Cataldo) +* [`f5537b2`](https://github.com/eslint/eslint/commit/f5537b2ed0b0b5e51a34c22cdd4ebfd024eaea3d) Fix: prefer-numeric-literals autofix removes comments (#12313) (Milos Djermanovic) +* [`11ae6fc`](https://github.com/eslint/eslint/commit/11ae6fcb5d5503e5dea41c02780369efe51f0bb9) Update: Fix call, new and member expressions in no-extra-parens (#12302) (Milos Djermanovic) +* [`a7894eb`](https://github.com/eslint/eslint/commit/a7894ebb43523152d36720efa770bb1fe8b58c07) New: add --env-info flag to CLI (#12270) (Kai Cataldo) +* [`61392ff`](https://github.com/eslint/eslint/commit/61392ff5ec660bfc01ac2ff0e9660d259cf88fd6) Sponsors: Sync README with website (ESLint Jenkins) +* [`2c6bf8e`](https://github.com/eslint/eslint/commit/2c6bf8ea9c8a8f94746f980bd5bea0a8c5c4d6b7) Docs: English fix (#12306) (Daniel Nixon) +* [`6f11877`](https://github.com/eslint/eslint/commit/6f118778366613fc53036cb6a7537e1b4c6e7af8) Sponsors: Sync README with website (ESLint Jenkins) +* [`2e202ca`](https://github.com/eslint/eslint/commit/2e202ca2228846e6226aa8dd99c614d572fb86a8) Docs: fix links in array-callback-return (#12288) (Milos Djermanovic) +* [`e39c631`](https://github.com/eslint/eslint/commit/e39c6318af0fd27edd5fd2aaf2b24a3e204005dd) Docs: add example for CLIEngine#executeOnText 3rd arg (#12286) (Kai Cataldo) +* [`d4f9a16`](https://github.com/eslint/eslint/commit/d4f9a16af7e00021e2ed63823d9c2f149bc985d6) Update: add support for JSXFragments in indent rule (fixes #12208) (#12210) (Kai Cataldo) +* [`c6af95f`](https://github.com/eslint/eslint/commit/c6af95f5bf1ef10f08545d54fd52b98e85fdf7f7) Sponsors: Sync README with website (ESLint Jenkins) +* [`8cadd52`](https://github.com/eslint/eslint/commit/8cadd5229b7372aed0d4785dcae15532a399bf55) Sponsors: Sync README with website (ESLint Jenkins) +* [`f9fc695`](https://github.com/eslint/eslint/commit/f9fc695d77c19cd5ecb3f0e97e1ea124c8543409) Chore: enable default-param-last (#12244) (薛定谔的猫) +* [`9984c3e`](https://github.com/eslint/eslint/commit/9984c3e27c92de76b8c05a58525dbcea12b10b83) Docs: Update README team and sponsors (ESLint Jenkins) + +v6.4.0 - September 13, 2019 + +* [`e915fff`](https://github.com/eslint/eslint/commit/e915fffb6089a23ff1cae926cc607f9b87dc1819) Docs: Improve examples and clarify default option (#12067) (Yuping Zuo) +* [`540296f`](https://github.com/eslint/eslint/commit/540296fcecd232a09dc873a5a22f5839b59b7842) Update: enforceForClassMembers option to accessor-pairs (fixes #12063) (#12192) (Milos Djermanovic) +* [`d3c2334`](https://github.com/eslint/eslint/commit/d3c2334646eae9287d5be9e457d041e445efb512) Update: flag nested block with declaration as error (#12193) (David Waller) +* [`b2498d2`](https://github.com/eslint/eslint/commit/b2498d284b9c30ed1543429c2f45d9014e12fe22) Update: Fix handling of property names in no-self-assign (#12105) (Milos Djermanovic) +* [`1ee61b0`](https://github.com/eslint/eslint/commit/1ee61b06715fcc750be2c923034a1e59ba663287) Update: enforceForClassMembers computed-property-spacing (fixes #12049) (#12214) (Milos Djermanovic) +* [`520c922`](https://github.com/eslint/eslint/commit/520c92270eed6e90c1a796e8af275980f01705e0) Docs: Added naming convention details to plugin usage (#12202) (Henrique Barcelos) +* [`f826eab`](https://github.com/eslint/eslint/commit/f826eabbeecddb047f58f4e7308a14c18148d369) Fix: Allow line comment exception in object-curly-spacing (fixes #11902) (#12216) (Milos Djermanovic) +* [`db2a29b`](https://github.com/eslint/eslint/commit/db2a29beb0fa28183f65bf9e659c66c03a8918b5) Update: indentation of comment followed by semicolon (fixes #12232) (#12243) (Kai Cataldo) +* [`ae17d1c`](https://github.com/eslint/eslint/commit/ae17d1ca59dd466aa64da0680ec2453c2dc3b80d) Fix: no-sequences is reporting incorrect locations (#12241) (Milos Djermanovic) +* [`365331a`](https://github.com/eslint/eslint/commit/365331a42e22af5a77ac9cfa9673d6a8f653eb5a) Fix: object-shorthand providing invalid fixes for typescript (#12260) (Brad Zacher) +* [`1c921c6`](https://github.com/eslint/eslint/commit/1c921c6dfd7ddfb0308c8103e53d32c1241475f0) New: add no-import-assign (fixes #12237) (#12252) (Toru Nagashima) +* [`3be04fd`](https://github.com/eslint/eslint/commit/3be04fd6a4e7b3f5a5ecb845a29cf29b71fe2dfb) New: Add prefer-regex-literals rule (fixes #12238) (#12254) (Milos Djermanovic) +* [`37c0fde`](https://github.com/eslint/eslint/commit/37c0fdeb87b92a0b779b125adf45535b79b65757) Update: Report global Atomics calls in no-obj-calls (fixes #12234) (#12258) (Milos Djermanovic) +* [`985c9e5`](https://github.com/eslint/eslint/commit/985c9e5eba351965a8a1491a41dbdcc78154b8f4) Fix: space-before-function-paren autofix removes comments (fixes #12259) (#12264) (Milos Djermanovic) +* [`01da7d0`](https://github.com/eslint/eslint/commit/01da7d04c4e5a7376cf241ec02db7971726a1bf9) Fix: eqeqeq rule reports incorrect locations (#12265) (Milos Djermanovic) +* [`319e4d8`](https://github.com/eslint/eslint/commit/319e4d8386ea846928f0f906c251b46043a53491) Docs: adding finally example (#12256) (Jens Melgaard) +* [`d52328f`](https://github.com/eslint/eslint/commit/d52328f012f3704c7d1ce39427e63f80531c7979) Docs: fix no-sequences `with` examples (#12239) (Milos Djermanovic) +* [`a41fdc0`](https://github.com/eslint/eslint/commit/a41fdc07404a7675d14183fab245fb8f49dcb858) Fix: Remove autofixer for no-unsafe-negation (#12157) (Milos Djermanovic) +* [`e38f5fd`](https://github.com/eslint/eslint/commit/e38f5fdfc786363a3eae642f1a69a8725600aa61) Update: fix no-octal-escape false negatives after \0 (#12079) (Milos Djermanovic) +* [`9418fbe`](https://github.com/eslint/eslint/commit/9418fbe0eb31cace3debe27b620709628df2fad7) Sponsors: Sync README with website (ESLint Jenkins) +* [`acc5ec5`](https://github.com/eslint/eslint/commit/acc5ec5082aed466a29899f651e6767b39155aec) Sponsors: Sync README with website (ESLint Jenkins) +* [`460c5ad`](https://github.com/eslint/eslint/commit/460c5ad176eaf39ff579cd96b3bcbe0539093f8f) Sponsors: Sync README with website (ESLint Jenkins) +* [`0313441`](https://github.com/eslint/eslint/commit/0313441d016c8aa0674c135f9da67a676e766ec5) New: add rule default-param-last (fixes #11361) (#12188) (Chiawen Chen) +* [`7621f5d`](https://github.com/eslint/eslint/commit/7621f5d2aa7d87e798b75ca47d6889c280597e99) Update: add more specific linting messages to space-in-parens (#11121) (Che Fisher) +* [`21eb904`](https://github.com/eslint/eslint/commit/21eb9044135c01b6c12188517bba840614483fc6) Fix: basePath of OverrideTester (fixes #12032) (#12205) (Toru Nagashima) +* [`86e5e65`](https://github.com/eslint/eslint/commit/86e5e657ea3fbf12b10524abcbc197afd215a060) Sponsors: Sync README with website (ESLint Jenkins) +* [`2b1a13f`](https://github.com/eslint/eslint/commit/2b1a13fa0de8360586857f3ced8da514c971297d) Fix: no-extra-boolean-cast reports wrong negation node (fixes #11324) (#12197) (Milos Djermanovic) +* [`ba8c2aa`](https://github.com/eslint/eslint/commit/ba8c2aa0154561fbeca33db0343cb39a7fbd9b4f) Sponsors: Sync README with website (ESLint Jenkins) +* [`a0a9746`](https://github.com/eslint/eslint/commit/a0a9746724ccd22c721ddc1b25c566aa9acea154) Docs: Fix link in no-irregular-whitespace.md (#12196) (Timo Tijhof) +* [`e10eeba`](https://github.com/eslint/eslint/commit/e10eebab4abd193dee697c4de7fb2d95bbab2d8c) Fix: quotes autofix produces syntax error with octal escape sequences (#12118) (Milos Djermanovic) + +v6.3.0 - August 30, 2019 + +* [`0acdefb`](https://github.com/eslint/eslint/commit/0acdefb97f35bb09db2910540c70dc377a01ad62) Chore: refactor code (#12113) (James George) +* [`52e2cf5`](https://github.com/eslint/eslint/commit/52e2cf50b35d57fb8466e0bcd0581eff1590fb4c) New: reportUnusedDisableDirectives in config (refs eslint/rfcs#22) (#12151) (Toru Nagashima) +* [`020f952`](https://github.com/eslint/eslint/commit/020f9526b618a191566acea3e17e20815d484c58) Update: enforceForSequenceExpressions to no-extra-parens (fixes #11916) (#12142) (Milos Djermanovic) +* [`aab1b84`](https://github.com/eslint/eslint/commit/aab1b840f9cffb2a76a5c9fe1852961be71dc184) Fix: reset to the default color (#12174) (Ricardo Gobbo de Souza) +* [`4009d39`](https://github.com/eslint/eslint/commit/4009d39aa59451510aa24911e758d664f216289a) Fix: yoda rule produces invalid autofix with preceding yield (#12166) (Milos Djermanovic) +* [`febb660`](https://github.com/eslint/eslint/commit/febb6605d350c936d64cb73e694482cfbb20b29c) Fix: no-extra-boolean-cast invalid autofix with yield before negation (#12164) (Milos Djermanovic) +* [`4c0b70b`](https://github.com/eslint/eslint/commit/4c0b70b869c16647f7af6de9d5c5479fc19f49db) New: support TypeScript at config initializer (fixes #11789) (#12172) (Pig Fang) +* [`94e39d9`](https://github.com/eslint/eslint/commit/94e39d9f782f45db86a079e07508d63040118ef1) Chore: use GitHub Actions (#12144) (Toru Nagashima) +* [`e88f305`](https://github.com/eslint/eslint/commit/e88f305df9d454868624c559fd93b981a680c215) Chore: support es2020 in fuzz (#12180) (薛定谔的猫) +* [`00d2c5b`](https://github.com/eslint/eslint/commit/00d2c5be9a89efd90135c4368a9589f33df3f7ba) Docs: corrected class extension example (#12176) (Marius M) +* [`31e5428`](https://github.com/eslint/eslint/commit/31e542819967b2aa1191e1abaa1c4a49fddbe3cf) Chore: Fix wrong error object keys in test files (#12162) (Milos Djermanovic) +* [`197f443`](https://github.com/eslint/eslint/commit/197f4432fca70a574028e5568c48afad12213224) Fix: func-name-matching crash on descriptor-like arguments (#12100) (Milos Djermanovic) +* [`644ce33`](https://github.com/eslint/eslint/commit/644ce3306748a33b74fc6a94be0267c2c9f19348) Fix: no-self-assign false positive with rest and spread in array (#12099) (Milos Djermanovic) +* [`a81d263`](https://github.com/eslint/eslint/commit/a81d2636ce41fb34d6826c2e9857814e11cb9c30) Fix: fix message of function-paren-newline (#12136) (Pig Fang) +* [`77f8ed1`](https://github.com/eslint/eslint/commit/77f8ed1ad9656c526217ce54a6717fa232d522c8) Chore: update blogpost template (#12154) (Toru Nagashima) +* [`6abc7b7`](https://github.com/eslint/eslint/commit/6abc7b72dfb824a372379708ca39340b2c7abc03) Docs: Document the exception in no-unsafe-negation (#12161) (Milos Djermanovic) + +v6.2.2 - August 23, 2019 + +* [`0e0b784`](https://github.com/eslint/eslint/commit/0e0b784b922051c2a1d39dd8160382114b645800) Upgrade: espree@^6.1.1 (#12158) (Kevin Partington) +* [`04e859f`](https://github.com/eslint/eslint/commit/04e859f228d081efd3af6edb22563dbc775f8d1d) Sponsors: Sync README with website (ESLint Jenkins) +* [`34783d1`](https://github.com/eslint/eslint/commit/34783d10ff9b58a3c1e39a36e10864caeb9f66e0) Sponsors: Sync README with website (ESLint Jenkins) +* [`b809e72`](https://github.com/eslint/eslint/commit/b809e72221bc658e5a42bfd4b8723d3771571f9e) Docs: Update README team and sponsors (ESLint Jenkins) + +v6.2.1 - August 20, 2019 + +* [`8c021b5`](https://github.com/eslint/eslint/commit/8c021b5917b3aa3c578ffe3972106d0a6bcf0838) Upgrade: eslint-utils 1.4.2 (#12131) (Toru Nagashima) +* [`e82388b`](https://github.com/eslint/eslint/commit/e82388bd87717430200ec554634cc08806e38d3c) Sponsors: Sync README with website (ESLint Jenkins) +* [`4aeeeed`](https://github.com/eslint/eslint/commit/4aeeeedb656ee3519ea82ebf0cb41ca801215046) Docs: update docs for ecmaVersion 2020 (#12120) (silverwind) +* [`6886148`](https://github.com/eslint/eslint/commit/6886148d1f528659ec3e125f61ef7a5f4c67556d) Docs: Add duplicate keys limitation to accessor-pairs (#12124) (Milos Djermanovic) + +v6.2.0 - August 18, 2019 + +* [`fee6acb`](https://github.com/eslint/eslint/commit/fee6acbe13cecd4c028e681e185fc6a6d6ba9452) Update: support bigint and dynamic import (refs #11803) (#11983) (Toru Nagashima) +* [`afd8012`](https://github.com/eslint/eslint/commit/afd8012c2797f2f5bf3c360cb241ea2ba6e1a489) New: noInlineConfig setting (refs eslint/rfcs#22) (#12091) (Toru Nagashima) +* [`3d12378`](https://github.com/eslint/eslint/commit/3d12378221961439c27ddae0ecda9845ac575107) Update: Fix accessor-pairs to enforce pairs per property in literals (#12062) (Milos Djermanovic) +* [`8cd00b3`](https://github.com/eslint/eslint/commit/8cd00b308987e0db0bdb2e242bf13b2b07b350bd) New: function-call-argument-newline (#12024) (finico) +* [`30ebf92`](https://github.com/eslint/eslint/commit/30ebf929f60684520b1201c1adfd86214c19d614) Fix: prefer-template autofix produces syntax error with octal escapes (#12085) (Milos Djermanovic) +* [`13c3988`](https://github.com/eslint/eslint/commit/13c3988a4001ae368ea7b6c8d3dd0abfa7c6cf64) Fix: Check literal type explicitly in dot-notation (#12095) (Milos Djermanovic) +* [`3e5ceca`](https://github.com/eslint/eslint/commit/3e5ceca4d2284b55a2292a1d3de9aa4cdf6fa213) Fix: Handle empty string property names in getFunctionNameWithKind (#12104) (Milos Djermanovic) +* [`9a043ff`](https://github.com/eslint/eslint/commit/9a043ffbb864fc65baeb16fe5668435e3b7cfe34) Fix: no-duplicate-case false positives on Object.prototype keys (#12107) (Milos Djermanovic) +* [`fe631af`](https://github.com/eslint/eslint/commit/fe631afee59641876598d19b1935967099cc6fa0) Chore: minor typo fix (#12112) (James George) +* [`4cb7877`](https://github.com/eslint/eslint/commit/4cb78774f6cc687a3c8701462f8c7f7b587ecaf0) Fix: fix no-extra-parens ignores some nodes (#11909) (Pig Fang) +* [`2dc23b8`](https://github.com/eslint/eslint/commit/2dc23b81e54defbce7a70a7f26c2e4c7b692cf58) Update: fix no-dupe-keys false negatives on empty string names (#12069) (Milos Djermanovic) +* [`19ab666`](https://github.com/eslint/eslint/commit/19ab6666e8e4142a183bdee2be96e5bafbac0e21) Fix: yoda exceptRange false positives on empty string property names (#12071) (Milos Djermanovic) +* [`d642150`](https://github.com/eslint/eslint/commit/d642150fe016608e71a1df2a72960e915b3cfbad) Update: Check empty string property names in sort-keys (#12073) (Milos Djermanovic) +* [`acce6de`](https://github.com/eslint/eslint/commit/acce6de940e2b089ff5ba59e4518a54af1682d5e) Fix: class-methods-use-this reports 'undefined' names (#12103) (Milos Djermanovic) +* [`92ec2cb`](https://github.com/eslint/eslint/commit/92ec2cb1731b7b6e0ac66336d583fbb782504290) Fix: Allow bind call with a single spread element in no-extra-bind (#12088) (Milos Djermanovic) +* [`bfdb0c9`](https://github.com/eslint/eslint/commit/bfdb0c97003fc0e045aa6ed10b177c35305a6e46) Fix: no-extra-boolean-cast invalid autofix for Boolean() without args (#12076) (Milos Djermanovic) +* [`34ccc0c`](https://github.com/eslint/eslint/commit/34ccc0cd81f495190e585c6efa8ae233d45bd3ed) Chore: Remove TDZ scope type condition from no-unused-vars (#12055) (Milos Djermanovic) +* [`01d38ce`](https://github.com/eslint/eslint/commit/01d38ce2faf0abbc9dd5d25694baeee131036165) Docs: Remove TDZ scope from the scope manager interface documentation (#12054) (Milos Djermanovic) +* [`1aff8fc`](https://github.com/eslint/eslint/commit/1aff8fc4f9394cd9126654a55f7f3a43ab1cf8f0) Update: warn about mixing ternary and logical operators (fixes #11704) (#12001) (Karthik Priyadarshan) +* [`11be2f8`](https://github.com/eslint/eslint/commit/11be2f8513bd61499f6247392a33ac0a26901c90) Docs: do not recommend global-installed usage (#12016) (薛定谔的猫) +* [`cf31dab`](https://github.com/eslint/eslint/commit/cf31dab5d5982151e0cfcc32879e69a83180ec70) Fix: no-restricted-syntax - correct the schema (#12051) (Brad Zacher) +* [`fbec99e`](https://github.com/eslint/eslint/commit/fbec99ea3e39316791685652c66e522d698f52d8) Update: fix class-methods-use-this false negatives with exceptMethods (#12077) (Milos Djermanovic) +* [`fb08b7c`](https://github.com/eslint/eslint/commit/fb08b7c9d28bc68864eb940e26df274059228b6a) Docs: Remove readonly/writable global logic from no-undef (fixes #11963) (#12053) (Milos Djermanovic) +* [`5b5934b`](https://github.com/eslint/eslint/commit/5b5934b9513f9114f5bf8e12ff4f4981590d64d3) Sponsors: Sync README with website (ESLint Jenkins) +* [`9156760`](https://github.com/eslint/eslint/commit/915676022a100ae5dba788fa3329d34b3c1f18d3) Sponsors: Sync README with website (ESLint Jenkins) +* [`f5e0cc4`](https://github.com/eslint/eslint/commit/f5e0cc40795f175692acb05daaadb91e9e5ae5d3) Update: Check computed method keys in no-extra-parens (#11973) (Milos Djermanovic) +* [`d961438`](https://github.com/eslint/eslint/commit/d9614388df8cfb977842ed7ac4725d76a3e05df3) Docs: Fix Incorrect Documentation (#12045) (Michael Miceli) +* [`887d08c`](https://github.com/eslint/eslint/commit/887d08c244e32f1fc18359e63380e2cdb0cb3797) Sponsors: Sync README with website (ESLint Jenkins) +* [`d90183f`](https://github.com/eslint/eslint/commit/d90183ff6757cff854f4ca4d25b835143dfb4b21) Docs: add a case to func-names (#12038) (Chiawen Chen) +* [`8a5b62d`](https://github.com/eslint/eslint/commit/8a5b62de2ae574f416c0f8ad91205da9b1837275) Docs: no use eslint.linter in code example (#12037) (薛定谔的猫) +* [`5831767`](https://github.com/eslint/eslint/commit/58317673210e48be3975e317c2c566fae155c94f) Update: report location of func-names (fixes #12022) (#12028) (Pig Fang) + +v6.1.0 - July 20, 2019 + +* [`8f86cca`](https://github.com/eslint/eslint/commit/8f86ccaa89daf10123370868c5dcb48c1fcbef7d) Upgrade: eslint-scope@^5.0.0 (#12011) (Kevin Partington) +* [`d08683e`](https://github.com/eslint/eslint/commit/d08683e3c807f92daf266894093c70f8d5ac6afa) Fix: glob processing (fixes #11940) (#11986) (Toru Nagashima) +* [`bfcf8b2`](https://github.com/eslint/eslint/commit/bfcf8b21011466b570b536ca31ec10fd228b3dca) Fix: dot-location errors with parenthesized objects (fixes #11868) (#11933) (Milos Djermanovic) +* [`79e8d09`](https://github.com/eslint/eslint/commit/79e8d099bbbebfa4d804484eeeeea9c074ede870) Fix: add parens for sequence expr in arrow-body-style (fixes #11917) (#11918) (Pig Fang) +* [`105c098`](https://github.com/eslint/eslint/commit/105c098f3cece8b83ab8d1566b8ea41dd94a60b9) Docs: update docs for object-curly-spacing (fixes #11634) (#12009) (Chiawen Chen) +* [`c90a12c`](https://github.com/eslint/eslint/commit/c90a12c283698befcafd2c86f8bd8942428fe80b) Chore: update release script for new website repo (#12006) (Kai Cataldo) +* [`e2c08a9`](https://github.com/eslint/eslint/commit/e2c08a9c8d86238955ecc8fd5a626584ee91eba5) Sponsors: Sync README with website (ESLint Jenkins) +* [`b974fcb`](https://github.com/eslint/eslint/commit/b974fcbd3321ab382a914520018d4c051b2e5c62) Update: Check computed property keys in no-extra-parens (#11952) (Milos Djermanovic) +* [`222d27c`](https://github.com/eslint/eslint/commit/222d27c32a6d6d8828233b3b99e93ecefa94c603) Update: Add for-in and for-of checks for props in no-param-reassign (#11941) (Milos Djermanovic) +* [`e4c450f`](https://github.com/eslint/eslint/commit/e4c450febc9bd77b33f6473667afa9f955be6b71) Fix: no-extra-parens autofix with `in` in a for-loop init (fixes #11706) (#11848) (Milos Djermanovic) +* [`2dafe2d`](https://github.com/eslint/eslint/commit/2dafe2d288d1e0d353bb938d12a5da888091cfdb) Fix: prefer-const produces invalid autofix (fixes #11699) (#11827) (Milos Djermanovic) +* [`cb475fd`](https://github.com/eslint/eslint/commit/cb475fd8c0bbfcb00340459966b6780f39ea87a7) Fix: Cache file error handling on read-only file system. (fixes #11945) (#11946) (Cuki) +* [`89412c3`](https://github.com/eslint/eslint/commit/89412c3cbc52e556dba590fa94e10bf40faf1fdf) Docs: Fixed a typo (fixes #11999) (#12000) (Eddie Olson) +* [`6669f78`](https://github.com/eslint/eslint/commit/6669f78a3dd305aef6191e7eea24fae2ae4fd2e8) Fix: --init with Vue.js failed (fixes #11970) (#11985) (Toru Nagashima) +* [`93633c2`](https://github.com/eslint/eslint/commit/93633c2b3716b17816bcb3dc221c49b75db41317) Upgrade: Upgrade lodash dependency (fixes #11992) (#11994) (Cyd La Luz) +* [`776dae7`](https://github.com/eslint/eslint/commit/776dae71f2f5c7b5f0650ea3c277eca26e324e41) Docs: fix wrong Node.js version in getting started (#11993) (Toru Nagashima) +* [`4448261`](https://github.com/eslint/eslint/commit/4448261f5d217d8a06eb0ef898401928b54a34e3) Docs: some typos and optimization points (#11960) (Jason Lee) +* [`2a10856`](https://github.com/eslint/eslint/commit/2a10856d1ed5880a09a5ba452bd80d49c1be4e6c) Chore: Add temporary test files to .gitignore (#11978) (Milos Djermanovic) +* [`d83b233`](https://github.com/eslint/eslint/commit/d83b23382de3b80056a7e6330ed5846316c94147) Chore: update path for release bundles (#11977) (Kai Cataldo) +* [`1fb3620`](https://github.com/eslint/eslint/commit/1fb362093a65b99456a11029967d9ee0c31fd697) Fix: creating of enabledGlobals object without prototype (fixes #11929) (#11935) (finico) +* [`c2f2db9`](https://github.com/eslint/eslint/commit/c2f2db97c6d6a415b78ee7b3e8924853d465e757) Docs: Replace global true and false with writable and readonly in rules (#11956) (Milos Djermanovic) +* [`19335b8`](https://github.com/eslint/eslint/commit/19335b8f47029b2f742d5507ba39484eaf68d07b) Fix: actual messageId and expected messageId are switched in rule tester (#11928) (Milos Djermanovic) +* [`8b216e0`](https://github.com/eslint/eslint/commit/8b216e04fb0dd0a1a4d3730ebe4b24780020b09c) Docs: Fix incorrect example comments for unicode-bom rule (fixes #11937) (#11938) (Brandon Yeager) +* [`cc3885b`](https://github.com/eslint/eslint/commit/cc3885b028e29ebc575c900f43af81cb0dabffb6) Chore: add v8-compile-cache to speed up instantiation time (#11921) (薛定谔的猫) +* [`d8f2688`](https://github.com/eslint/eslint/commit/d8f26886f19a17f2e1cdcb91e2db84fc7ba3fdfb) Upgrade: deps (#11904) (薛定谔的猫) +* [`e5f1ccc`](https://github.com/eslint/eslint/commit/e5f1ccc9e2d07ad0acf149027ffc382021d54da1) Docs: add 'stricter rule config validating' in migrating docs (#11905) (薛定谔的猫) + +v6.0.1 - June 24, 2019 + +* [`b5bde06`](https://github.com/eslint/eslint/commit/b5bde0669bd6a7a6b8e38cdf204d8d4b932cea63) Fix: --rulesdir option didn't work (fixes #11888) (#11890) (Toru Nagashima) +* [`13f0418`](https://github.com/eslint/eslint/commit/13f041897ee31982808a57b4d06450b57c9b27dc) Fix: improve error message on --print-config (fixes #11874) (#11885) (Toru Nagashima) +* [`056c2aa`](https://github.com/eslint/eslint/commit/056c2aaf39a5f8d06de24f06946dda95032a0361) Fix: improve diagnostics for shareable-config-missing errors (#11880) (Teddy Katz) +* [`566b7aa`](https://github.com/eslint/eslint/commit/566b7aa5d61fb31cd47fe4da7820b07cf9bde1ec) Docs: Update no-confusing-arrow with the new default option (#11886) (Yuping Zuo) +* [`d07f3fa`](https://github.com/eslint/eslint/commit/d07f3fae19b901c30cf4998f10722cb3182bd237) Fix: CLIEngine#getRules() contains plugin rules (fixes #11871) (#11872) (Toru Nagashima) +* [`21f4a80`](https://github.com/eslint/eslint/commit/21f4a8057ccc941f72bb617ae3b5c135a774f6c0) Docs: Fix inconsistent linking in migration guide (#11881) (Teddy Katz) +* [`f3a0774`](https://github.com/eslint/eslint/commit/f3a0774a8879b08777a4aedc76677f13d5156242) Docs: Fix typo in 6.0.0 migration guide (#11870) (Kevin Partington) + +v6.0.0 - June 21, 2019 + +* [`81aa06b`](https://github.com/eslint/eslint/commit/81aa06b4cc49e9c15234a2c4d27659a03fea53d8) Upgrade: espree@6.0.0 (#11869) (Teddy Katz) +* [`5f022bc`](https://github.com/eslint/eslint/commit/5f022bc91d0d93d140876ceb1ee4e08b1b7cfd49) Fix: no-else-return autofix produces name collisions (fixes #11069) (#11867) (Milos Djermanovic) +* [`ded9548`](https://github.com/eslint/eslint/commit/ded9548d881b15e771ca79b844e8159601f30f70) Fix: multiline-comment-style incorrect message (#11864) (golopot) +* [`cad074d`](https://github.com/eslint/eslint/commit/cad074d4ddb34a59183b5965ca50170713b5a711) Docs: Add JSHint W047 compat to no-floating-decimal (#11861) (Timo Tijhof) +* [`41f6304`](https://github.com/eslint/eslint/commit/41f6304ce641a82ee729251b448dceb9fb0d501d) Upgrade: sinon (#11855) (Toru Nagashima) +* [`167ce87`](https://github.com/eslint/eslint/commit/167ce87e908ec04b0d3d79960278d45c883c4285) Chore: remove unuseable profile command (#11854) (Toru Nagashima) +* [`c844c6f`](https://github.com/eslint/eslint/commit/c844c6f2ff314cfa8c6ca0e35a1ef58b7e297b79) Fix: max-len properly ignore trailing comments (fixes #11838) (#11841) (ZYSzys) +* [`1b5661a`](https://github.com/eslint/eslint/commit/1b5661ae467c227c0239e06cc1466480004aa799) Fix: no-var should not fix variables named 'let' (fixes #11830) (#11832) (Milos Djermanovic) +* [`4d75956`](https://github.com/eslint/eslint/commit/4d75956147b6fd662ee90eb21d3f762816463b88) Build: CI with Azure Pipelines (#11845) (Toru Nagashima) +* [`1db3462`](https://github.com/eslint/eslint/commit/1db346220889305a427b45a00afcf362b81b3767) Chore: rm superfluous argument & fix perf-multifiles-targets (#11834) (薛定谔的猫) +* [`c57a4a4`](https://github.com/eslint/eslint/commit/c57a4a4a993193c4208c6419df331a7bc644a536) Upgrade: @babel/polyfill => core-js v3 (#11833) (薛定谔的猫) +* [`65faa04`](https://github.com/eslint/eslint/commit/65faa04e8b42eecd4505111bbff296951179f033) Docs: Clarify prefer-destructuring array/object difference (fixes #9970) (#11851) (Oliver Sieweke) +* [`81c3823`](https://github.com/eslint/eslint/commit/81c382378923a45015bafe58362f6c8faa5c3d5f) Fix: require-atomic-updates reports parameters (fixes #11723) (#11774) (Toru Nagashima) +* [`aef8ea1`](https://github.com/eslint/eslint/commit/aef8ea1a44b9f13d468f48536c4c93f79f201d9b) Sponsors: Sync README with website (ESLint Jenkins) + +v6.0.0-rc.0 - June 9, 2019 + +* [`f403b07`](https://github.com/eslint/eslint/commit/f403b07283f91f1285d8318d6acea851dd765755) Update: introduce minKeys option to sort-keys rule (fixes #11624) (#11625) (Christian) +* [`87451f4`](https://github.com/eslint/eslint/commit/87451f4779bc4c0ec874042b6854920f947ee258) Fix: no-octal should report NonOctalDecimalIntegerLiteral (fixes #11794) (#11805) (Milos Djermanovic) +* [`e4ab053`](https://github.com/eslint/eslint/commit/e4ab0531c4e44c23494c6a802aa2329d15ac90e5) Update: support "bigint" in valid-typeof rule (#11802) (Colin Ihrig) +* [`e0fafc8`](https://github.com/eslint/eslint/commit/e0fafc8ef59a80a6137f4170bbe46582d6fbcafc) Chore: removes unnecessary assignment in loop (#11780) (Dimitri Mitropoulos) +* [`20908a3`](https://github.com/eslint/eslint/commit/20908a38f489c285abf8fceef4d9d13bf8b87f22) Docs: removed '>' prefix from from docs/working-with-rules (#11818) (Alok Takshak) +* [`1c43eef`](https://github.com/eslint/eslint/commit/1c43eef605a9cf02a157881424ea62dcae747f69) Sponsors: Sync README with website (ESLint Jenkins) +* [`21f3131`](https://github.com/eslint/eslint/commit/21f3131aa1636afa8e5c01053e0e870f968425b1) Fix: `overrides` handle relative paths as expected (fixes #11577) (#11799) (Toru Nagashima) +* [`5509cdf`](https://github.com/eslint/eslint/commit/5509cdfa1b3d575248eef89a935f79c82e3f3071) Fix: fails the test case if autofix made syntax error (fixes #11615) (#11798) (Toru Nagashima) +* [`cb1922b`](https://github.com/eslint/eslint/commit/cb1922bdc07e58de0e55c13fd992dd8faf3292a4) Fix: show custom message for namespace import (fixes #11580) (#11791) (Pig Fang) +* [`37e5193`](https://github.com/eslint/eslint/commit/37e5193102d7544f155cdcb09c7c50dc602914d4) Update: add `endColumn` to no-useless-escape (fixes #11629) (#11790) (Pig Fang) +* [`ad4b048`](https://github.com/eslint/eslint/commit/ad4b048c6d034cbd7fd97deb4390d059bde8803f) Build: Fix typo in blog post template (fixes #11614) (#11782) (Kai Cataldo) +* [`9590587`](https://github.com/eslint/eslint/commit/9590587cef74c936ef9b7ce2d22a71e2fd0fbbc4) Update: improve reported location of arrow-parens (fixes #11773) (#11775) (Pig Fang) +* [`d662b17`](https://github.com/eslint/eslint/commit/d662b178c7dad193201564d16f7977af2f81ebcf) New: Add classname attribute to JUnit testcase (refs #11068) (#11683) (Fabio Pitino) +* [`8eaa9b2`](https://github.com/eslint/eslint/commit/8eaa9b259dc08dfb48269b1e4413d0d47698ed87) Chore: remove incorrect comment (#11769) (薛定谔的猫) +* [`4039a49`](https://github.com/eslint/eslint/commit/4039a49177f2fefacd747050b420c0c4560b7d4b) Chore: add .github/funding.yml (#11764) (Toru Nagashima) + +v6.0.0-alpha.2 - May 25, 2019 + +* [`9b87fee`](https://github.com/eslint/eslint/commit/9b87fee9dc7b1d99a50b924cb6b81255ebb5c4a2) Chore: Fix formatter documentation generation (#11767) (Ilya Volodin) +* [`f116208`](https://github.com/eslint/eslint/commit/f11620848733a3a6f58811d9bb2c3e748d6135ac) Chore: Fix site generation script for releases (#11766) (Ilya Volodin) +* [`cf9cce8`](https://github.com/eslint/eslint/commit/cf9cce81aa68e9bc23840530cb33f4c3f551fb1e) Update: Add never option for new-parens (refs #10034) (#11379) (pfgithub) +* [`b5fa149`](https://github.com/eslint/eslint/commit/b5fa1491d2371a721e4b5029e797ae98fd30fed2) New: multiple processors support (fixes #11035, fixes #11725) (#11552) (Toru Nagashima) +* [`2d32a9e`](https://github.com/eslint/eslint/commit/2d32a9e8dd10a5927576bd50d184876c775da4af) Breaking: stricter rule config validating (fixes #9505) (#11742) (薛定谔的猫) +* [`71716eb`](https://github.com/eslint/eslint/commit/71716eba3155266d777b994a38af524952e97696) Update: add fixer for no-div-regex rule (fixes #11355) (#11744) (joe-re) +* [`53f7f4c`](https://github.com/eslint/eslint/commit/53f7f4cf8d8b66a1911db56e4f72764388a21cc4) Update: Uniform messages for the rules in "complexity" section (#11759) (Igor Novozhilov) +* [`0a801d7`](https://github.com/eslint/eslint/commit/0a801d702dc41dae7eac0c802b822493ddc3ac41) Chore: improve perf test (#11756) (薛定谔的猫) +* [`45bd336`](https://github.com/eslint/eslint/commit/45bd336e647a6fa8a502488e5cbd27ba02712083) Docs: add about RuleTester's parser to migration guide (fixes #11728) (#11761) (Toru Nagashima) +* [`1374be4`](https://github.com/eslint/eslint/commit/1374be44f7ec4b8c913c52cc84debc4012c4f3ea) Docs: add table of contents in readme (#11765) (薛定谔的猫) +* [`54e6eda`](https://github.com/eslint/eslint/commit/54e6edaa2f881aab530fa14e63d92e5e0e2ca55c) New: extends in glob-based config (fixes #8813) (#11554) (Toru Nagashima) +* [`ec105b2`](https://github.com/eslint/eslint/commit/ec105b24f7e036ecdc4267f018529ac3765e29d5) Chore: typo in JSDoc on timing.display's return value (#11755) (Dimitri Mitropoulos) +* [`e45cc3f`](https://github.com/eslint/eslint/commit/e45cc3f3dc44f3a5b6b713a1bf5ce6e46d87ca49) Docs: updated no-proto rule (fixes #11743) (#11746) (Francesco Trotta) +* [`15c6c63`](https://github.com/eslint/eslint/commit/15c6c6374c0425d5402142d012a541fa208bc9da) Chore: eslint-config-eslint require node >= 8 (#11718) (薛定谔的猫) +* [`f9790ca`](https://github.com/eslint/eslint/commit/f9790ca1baec1275f3c946586766a5713258ac32) Fix: typo: missing word in docs (#11750) (Dimitri Mitropoulos) +* [`219aecb`](https://github.com/eslint/eslint/commit/219aecb78bc646d44bad27dc775a9b3d3dc58232) Chore: restructure files (#11555) (Toru Nagashima) +* [`5dad0b1`](https://github.com/eslint/eslint/commit/5dad0b1d80c9cf380c49f46266c35d461d3cecad) Fix: Unignoring directories in .eslintignore (fixes #11684) (#11685) (Mykola Bilochub) +* [`4625090`](https://github.com/eslint/eslint/commit/462509058e46770cf70307cf8dba279f0e73b967) Docs: small fix about the migration guide (#11729) (Toru Nagashima) +* [`0e89c73`](https://github.com/eslint/eslint/commit/0e89c73177398eaf978a50d5b0f79ff8e43512f2) Sponsors: Sync README with website (ESLint Jenkins) +* [`5a296fa`](https://github.com/eslint/eslint/commit/5a296fa0c9345ad1a55e2b257e5f6c9f05fff362) Sponsors: Sync README with website (ESLint Jenkins) +* [`7c8e86b`](https://github.com/eslint/eslint/commit/7c8e86bf2c900cec7cd1dfd529a8c77cc81ef34c) Fix: wrong 'plugin-missing' error on Node.js 12 (fixes #11720) (#11722) (Toru Nagashima) +* [`67c671f`](https://github.com/eslint/eslint/commit/67c671fdc1c8b08cb8d263a9bb2151e3108c88b4) Chore: ignore deprecated rules in fuzz tests (#11710) (Pig Fang) +* [`af81cb3`](https://github.com/eslint/eslint/commit/af81cb3ecc5e6bf43a6a2d8f326103350513a1b8) Chore: make fuzzer produce minimal reproducible examples of bugs (#11700) (Teddy Katz) + +v6.0.0-alpha.1 - May 10, 2019 + +* [`e84b6f8`](https://github.com/eslint/eslint/commit/e84b6f8b171ba4266164688f76d5ee45d278e5c2) Docs: fix example in object-curly-newline docs (#11633) (golopot) +* [`252efd3`](https://github.com/eslint/eslint/commit/252efd337b1441debb6d2cc8f51a625549b2c535) Fix: delete unnecessary duplicated question (fixes #11617) (#11618) (HelloRusk) +* [`21dd211`](https://github.com/eslint/eslint/commit/21dd2116c70b93aa8dd50d2b15e202724b11486a) New: add --resolve-plugins-relative-to flag (#11696) (Teddy Katz) +* [`1a3a88d`](https://github.com/eslint/eslint/commit/1a3a88df2f952c34631d8e1d83de47178826fce0) Fix: Curly rule incorrectly flagging lexical declarations (fixes #11663) (#11675) (Brian Kurek) +* [`f42d0af`](https://github.com/eslint/eslint/commit/f42d0afd89874b459fce1eb1998247d53f9aa42b) Chore: lazy loading for rules (#11705) (Toru Nagashima) +* [`f47d72c`](https://github.com/eslint/eslint/commit/f47d72ce2f2edb80cd38810894b9d4bda896bb29) Fix: not set ecmaVersion to 6 when sourceType=module (fixes #9687) (#11649) (薛定谔的猫) +* [`9484e9e`](https://github.com/eslint/eslint/commit/9484e9ea188ff70683c3112e397c7fddcc3f8095) Fix: ignore return statements in dead code (fixes #11647) (#11688) (Toru Nagashima) +* [`aae6f65`](https://github.com/eslint/eslint/commit/aae6f6525557ba06e73f051511646056313fcf91) Fix: don't use deprecated API (#11689) (Toru Nagashima) +* [`483239e`](https://github.com/eslint/eslint/commit/483239ec74a0c13529fc99547a784b749f41dd54) Docs: updated ImportDeclaration in docs-rules-indent (#11679) (Alok Takshak) +* [`f5bae78`](https://github.com/eslint/eslint/commit/f5bae78c19d5359f67969a2ff344025082e253f4) Chore: fix CLIEngine tests when os.tmpdir is a symlink (#11697) (Teddy Katz) +* [`e4400b5`](https://github.com/eslint/eslint/commit/e4400b5a02602bba7f67ea4cb874c231903c546a) Fix: require-atomic-updates false positive (fixes #11194, fixes #11687) (#11224) (Toru Nagashima) +* [`6ae21a4`](https://github.com/eslint/eslint/commit/6ae21a4bfe5a1566f787fbad798182a524b96d28) Breaking: fix config loading (fixes #11510, fixes #11559, fixes #11586) (#11546) (Toru Nagashima) +* [`bc0819c`](https://github.com/eslint/eslint/commit/bc0819c94aad14f7fad3cbc2338ea15658b0f272) Sponsors: Sync README with website (ESLint Jenkins) +* [`036a188`](https://github.com/eslint/eslint/commit/036a188143677384f720ff18071fc4206c54500b) Sponsors: Sync README with website (ESLint Jenkins) +* [`4b3b036`](https://github.com/eslint/eslint/commit/4b3b036d6240cdbc2d52e670de36b1117f5f34d7) Docs: replace `var` with `const` in code examples (#11655) (Niyaz Akhmetov) +* [`e4a08ba`](https://github.com/eslint/eslint/commit/e4a08bae82788136b6899262cb8b9ed4fe7964e6) Chore: update eslint-plugin-node to 9.0.0 (#11670) (Toru Nagashima) +* [`f2e7828`](https://github.com/eslint/eslint/commit/f2e78281d057f38b18cc160e81ed1bb54a5b9565) Docs: Fix Node 6 LTS EOL date (#11676) (James Ross) +* [`4052bfe`](https://github.com/eslint/eslint/commit/4052bfebb87850b901f2eb8687edfbe49c01d68f) Sponsors: Sync README with website (ESLint Jenkins) +* [`f6fc045`](https://github.com/eslint/eslint/commit/f6fc0450e749707bed44118c1205fb4e73e65628) Sponsors: Sync README with website (ESLint Jenkins) +* [`1ebf21b`](https://github.com/eslint/eslint/commit/1ebf21bc18769956366110bb62ff677639e633ae) Sponsors: Sync README with website (ESLint Jenkins) +* [`776b0fe`](https://github.com/eslint/eslint/commit/776b0fe3d93da958517ac7752682091f22eb30b4) Fix: update rule message of no-throw-literal (fixes #11637) (#11638) (Pig Fang) +* [`67c08b6`](https://github.com/eslint/eslint/commit/67c08b67509c54acd96aab2cec22efb53bfe6265) Fix: consider comments in object-curly-spacing (fixes #11656) (#11657) (Pig Fang) +* [`b6d41cb`](https://github.com/eslint/eslint/commit/b6d41cbe28a8b28b1c1d9aa36cb4c349c73f6f1d) Fix: check token before using in no-cond-assign (fixes #11611) (#11619) (Pig Fang) +* [`7f290a9`](https://github.com/eslint/eslint/commit/7f290a9044ca795884ac2e495cd31b2a35f109a6) Chore: add eslint as a devDependeny (#11654) (Toru Nagashima) +* [`139fd2f`](https://github.com/eslint/eslint/commit/139fd2f1254bcc524738f8c2645e0847df95e0d0) Chore: add markdownlint-cli (#11653) (Toru Nagashima) +* [`adc6585`](https://github.com/eslint/eslint/commit/adc6585ce074e03fc8a842e8ebc5b082a0ed0b65) Docs: update status of breaking changes in migration guide (#11652) (Teddy Katz) +* [`eef71e4`](https://github.com/eslint/eslint/commit/eef71e455e67040168c8df8a6c9c2b4fbe805a50) Docs: add missing items to migration guide (#11628) (Teddy Katz) +* [`0fc8e62`](https://github.com/eslint/eslint/commit/0fc8e62818bc8d0a0a804b59c6110818844df5f3) Breaking: eslint:recommended changes (fixes #10768) (#11518) (薛定谔的猫) +* [`1c34d4a`](https://github.com/eslint/eslint/commit/1c34d4a6313c399761281282fff3a1bbe5e17b6d) Sponsors: Sync README with website (ESLint Jenkins) +* [`33695e7`](https://github.com/eslint/eslint/commit/33695e7f7048306ac196eff6e5a16e165ad03090) Sponsors: Sync README with website (ESLint Jenkins) +* [`c94cf21`](https://github.com/eslint/eslint/commit/c94cf212d31513fde74e0ea88b79e5e0f89a18a4) Sponsors: Sync README with website (ESLint Jenkins) +* [`f62a451`](https://github.com/eslint/eslint/commit/f62a4510b007172c7160f007a6ec2aa2c9a80dd7) Build: add node 12 (#11648) (薛定谔的猫) +* [`20364cc`](https://github.com/eslint/eslint/commit/20364cc4f7fe0423adce0dd44fb24fc48e1cae4b) Breaking: make no-redeclare stricter (fixes #11370, fixes #11405) (#11509) (Toru Nagashima) +* [`ed675a6`](https://github.com/eslint/eslint/commit/ed675a6e5ac42898555c51a7cf771b79695ba591) Sponsors: Sync README with website (ESLint Jenkins) +* [`8b4dba6`](https://github.com/eslint/eslint/commit/8b4dba606f8306f8ad0a37e2174a6e3236f7ebe7) Chore: Add linting to git commit (#11556) (Nicholas C. Zakas) +* [`8684f46`](https://github.com/eslint/eslint/commit/8684f4646da33bfe81e8f7f8c2d1af8b31065564) Sponsors: Sync README with website (ESLint Jenkins) +* [`1bdacc9`](https://github.com/eslint/eslint/commit/1bdacc9b703158d5ca3563e4a9b67bb8453ac316) Sponsors: Sync README with website (ESLint Jenkins) +* [`e62c8af`](https://github.com/eslint/eslint/commit/e62c8af6a86e35dc05f30713faf87a18cc77714d) Sponsors: Sync README with website (ESLint Jenkins) +* [`1dfe077`](https://github.com/eslint/eslint/commit/1dfe077b7e47c6090277eb984e08bd472bb5595e) Fix: autofix of no-unneeded-ternary made syntax error (fixes #11579) (#11616) (Toru Nagashima) +* [`bebd079`](https://github.com/eslint/eslint/commit/bebd0793eaf122b013cca501ff2c6b0fc05d5493) Docs: fix grammar mistake in no-caller docs (#11623) (Daniel Lemay) +* [`f570be1`](https://github.com/eslint/eslint/commit/f570be17b339cb7622c512331b1653013279855a) Sponsors: Sync README with website (ESLint Jenkins) +* [`7c13a1c`](https://github.com/eslint/eslint/commit/7c13a1c144a6a7c99cd9338a24166da9f7439cd0) Sponsors: Sync README with website (ESLint Jenkins) +* [`b7bd432`](https://github.com/eslint/eslint/commit/b7bd432e1161feba8dbb81f62cf03cafad42c3d4) Sponsors: Sync README with website (ESLint Jenkins) +* [`412a76b`](https://github.com/eslint/eslint/commit/412a76b316e05ca85334c1d6bc6372df536da2db) Sponsors: Sync README with website (ESLint Jenkins) + +v6.0.0-alpha.0 - April 12, 2019 + +* [`3d9e137`](https://github.com/eslint/eslint/commit/3d9e1372aad1e174b5438e3d6bd75fdefba06bad) Chore: fix test that fails when the CWD contains a space (#11612) (Teddy Katz) +* [`8bfd1d1`](https://github.com/eslint/eslint/commit/8bfd1d138001d4493180b2fcff3330b42d0bb7cb) Docs: add v6.0.0 migration guide (#11515) (Teddy Katz) +* [`9e49b56`](https://github.com/eslint/eslint/commit/9e49b56c08fd0e449fddab45dfeb0da8d918b460) Breaking: upgrade espree to 6.0.0-alpha.0 (fixes #9687) (#11610) (Teddy Katz) +* [`0127d10`](https://github.com/eslint/eslint/commit/0127d107590acabfdea4a68b56acbeee6a7b9daa) Fix: no-var fixed to incorrect code (fixes #11441) (#11443) (薛定谔的猫) +* [`5cfdc2d`](https://github.com/eslint/eslint/commit/5cfdc2d08307c63bec487e76d2f470ef84166867) Update: Improve no-loop-func rule description and message (#11046) (Pedro Lopes) +* [`608a02c`](https://github.com/eslint/eslint/commit/608a02c60656b96c3219d342eee7e98b55bdd580) Fix: object-shorthand ignoreConstructors option (fixes #11595) (#11596) (overlookmotel) +* [`eeea893`](https://github.com/eslint/eslint/commit/eeea89361d48494995446ddb6ee6f049457911ec) Upgrade: update js-yaml package to 3.13.1 version (#11607) (Pobegaylo Maksim) +* [`e70d5f7`](https://github.com/eslint/eslint/commit/e70d5f7573a9641d7b63394df53a3ef86183445c) Upgrade: compatible deps (#11608) (薛定谔的猫) +* [`a55913d`](https://github.com/eslint/eslint/commit/a55913d6c6fd1a7c684b8b4d7ab380cf7dc83eb8) Sponsors: Sync README with website (ESLint Jenkins) +* [`9a6e8fe`](https://github.com/eslint/eslint/commit/9a6e8fe4b025d52275f7ad2959361587f476cc58) Sponsors: Sync README with website (ESLint Jenkins) +* [`cbdee62`](https://github.com/eslint/eslint/commit/cbdee6230d22522c37259449467ace16f28ea8e8) Docs: README updates to reflect JSCS compat project is finished (#11568) (Kevin Partington) +* [`b92ca6e`](https://github.com/eslint/eslint/commit/b92ca6ea8ae46b92c258921e8b5b40f5035dbc43) Fix: getErrorResults function to not mutate passed parameter (#11592) (danielamaia) +* [`ef7801e`](https://github.com/eslint/eslint/commit/ef7801ea510e12a9ca4963bcc8ec7e3aacc75ff0) Breaking: disallow invalid rule defaults in RuleTester (fixes #11473) (#11599) (Teddy Katz) +* [`c021117`](https://github.com/eslint/eslint/commit/c021117915d5d23399233f761a237e138f1854af) Sponsors: Sync README with website (ESLint Jenkins) +* [`4e7cdca`](https://github.com/eslint/eslint/commit/4e7cdca571632a3f3c32b39eb03022fe88ca8b30) Breaking: comma-dangle enable functions: "never" (fixes #11502) (#11519) (薛定谔的猫) +* [`12f256f`](https://github.com/eslint/eslint/commit/12f256f22534c4a4e1ca0ba54c37c6db81441461) Breaking: no-confusing-arrow enable allowParens: true (fixes #11503) (#11520) (薛定谔的猫) +* [`25cc63d`](https://github.com/eslint/eslint/commit/25cc63d47e6c0aea8b88589a088c1f0de5f6f1cc) Breaking: simplify config/plugin/parser resolution (fixes #10125) (#11388) (Teddy Katz) +* [`63fead8`](https://github.com/eslint/eslint/commit/63fead86f8cf4e6b33d5424fb7db1e76a66d4cce) Sponsors: Sync README with website (ESLint Jenkins) +* [`595de40`](https://github.com/eslint/eslint/commit/595de4074fac1b5839f56b29fe0106a7fda7e3e0) Docs: edit arrow-parens as-needed explanation (fixes #11202) (#11569) (Logan Lowder) +* [`3396c3e`](https://github.com/eslint/eslint/commit/3396c3e2231b5b6e16da8751c22c86c256590f6b) Upgrade: karma@^4.0.1, drops Node 6 support, fixes vulnerability (#11570) (Kevin Partington) +* [`2f8ae13`](https://github.com/eslint/eslint/commit/2f8ae1397eef3625fe66636e95b0b312c6ff8a37) Update: support single argument on newline with function-paren-newline (#11406) (Vladlen Grachev) +* [`fd1c91b`](https://github.com/eslint/eslint/commit/fd1c91b00e8d8c3a83d21e60668d5f1fa61cb214) Breaking: throw an error for invalid global configs (refs #11338) (#11517) (Teddy Katz) +* [`be83322`](https://github.com/eslint/eslint/commit/be833229b355eafb90f3e0bbc29bb106e100bed0) Breaking: Remove extra rules from eslint:recommended (fixes #10873) (#11357) (Kevin Partington) +* [`2543f11`](https://github.com/eslint/eslint/commit/2543f11dfe8069ed5096073169cf6791d42454db) Breaking: remove deprecated experimentalObjectRestSpread option (#11420) (Teddy Katz) +* [`19248e0`](https://github.com/eslint/eslint/commit/19248e0838425748d75518fe9f0a985587793378) Fix: make `overrides[].files` matching dotfiles (fixes #11201) (#11225) (Toru Nagashima) +* [`0fb5fd4`](https://github.com/eslint/eslint/commit/0fb5fd402334098dc44cbfbb8ab25919da04843d) Breaking: interpret rule options as unicode regexes (fixes #11423) (#11516) (Teddy Katz) +* [`6e7da57`](https://github.com/eslint/eslint/commit/6e7da57dddc41830df4aee77e31c4320c1557350) Breaking: drop Node.js 6 support (fixes #11456) (#11557) (Toru Nagashima) +* [`a73b4b8`](https://github.com/eslint/eslint/commit/a73b4b8d6398b00bdaf90599d9e6d1c80f000f88) Docs: Update README team and sponsors (ESLint Jenkins) + +v5.16.0 - March 29, 2019 + +* [`dfef227`](https://github.com/eslint/eslint/commit/dfef227091955a2f8f3fa8c76ad79de8a77e7955) Build: gensite passes rulesMeta to formatter rendering (#11567) (Kevin Partington) +* [`c06d38c`](https://github.com/eslint/eslint/commit/c06d38c81bd9203c904587396a65d3c8cc7f2944) Fix: Allow HTML formatter to handle no meta data (#11566) (Ilya Volodin) +* [`87a5c03`](https://github.com/eslint/eslint/commit/87a5c034977cf4538ff3539d2f8776a987c5942a) Docs: `func-style`: clarify when `allowArrowFunctions` is used (#11548) (Oliver Joseph Ash) +* [`bc3e427`](https://github.com/eslint/eslint/commit/bc3e427ee8875c53eac6b6762884b50074f1adfc) Update: pass rule meta to formatters RFC 10 (#11551) (Chris Meyer) +* [`b452f27`](https://github.com/eslint/eslint/commit/b452f270bc0b523d88d5d827c95be3096f82e99d) Chore: Update README to pull in reviewer data (#11506) (Nicholas C. Zakas) +* [`afe3d25`](https://github.com/eslint/eslint/commit/afe3d25f8afb88caee43f7202d0eb96f33a92a6b) Upgrade: Bump js-yaml dependency to fix Denial of Service vulnerability (#11550) (Vernon de Goede) +* [`4fe7eb7`](https://github.com/eslint/eslint/commit/4fe7eb7cecdc2395cf1eeaa20921bda8460b00c2) Chore: use nyc instead of istanbul (#11532) (Toru Nagashima) +* [`f16af43`](https://github.com/eslint/eslint/commit/f16af439694aab473c647d8fae47c402bd489447) Chore: fix formatters/table test (#11534) (Toru Nagashima) +* [`78358a8`](https://github.com/eslint/eslint/commit/78358a8f66e95c4fcc921f2497e8a5ec5f1537ec) Docs: fix duplicate punctuation in CLI docs (#11528) (Teddy Katz) + +v5.15.3 - March 18, 2019 + +* [`71adc66`](https://github.com/eslint/eslint/commit/71adc665b9649b173adc76f80723b8de20664ae1) Fix: avoid moving comments in implicit-arrow-linebreak (fixes #11521) (#11522) (Teddy Katz) +* [`1f715a2`](https://github.com/eslint/eslint/commit/1f715a20c145d8ccc38f3310afccd838495d09d4) Chore: make test-case-property-ordering reasonable (#11511) (Toru Nagashima) + +v5.15.2 - March 15, 2019 + +* [`29dbca7`](https://github.com/eslint/eslint/commit/29dbca73d762a809adb2f457b527e144426d54a7) Fix: implicit-arrow-linebreak adds extra characters (fixes #11268) (#11407) (Mark de Dios) +* [`5d2083f`](https://github.com/eslint/eslint/commit/5d2083fa3e14c024197f6c386ff72237a145e258) Upgrade: eslint-scope@4.0.3 (#11513) (Teddy Katz) +* [`a5dae7c`](https://github.com/eslint/eslint/commit/a5dae7c3d30231c2f5f075d98c2c8825899bab16) Fix: Empty glob pattern incorrectly expands to "/**" (#11476) (Ben Chauvette) +* [`448e8da`](https://github.com/eslint/eslint/commit/448e8da94d09b397e98ffcb6f22b55a578ef79c1) Chore: improve crash reporting (fixes #11304) (#11463) (Alex Zherdev) +* [`0f56dc6`](https://github.com/eslint/eslint/commit/0f56dc6d9eadad05dc3d5c9d1d9ddef94e10c5d3) Chore: make config validator params more consistent (#11435) (薛定谔的猫) +* [`d6c1122`](https://github.com/eslint/eslint/commit/d6c112289f0f16ade070865c8786831b7940ca79) Docs: Add working groups to maintainer guide (#11400) (Nicholas C. Zakas) +* [`5fdb4d3`](https://github.com/eslint/eslint/commit/5fdb4d3fb01b9d8a4c2dff71ed9cddb2f8feefb0) Build: compile deps to ES5 when generating browser file (fixes #11504) (#11505) (Teddy Katz) +* [`06fa165`](https://github.com/eslint/eslint/commit/06fa1655c3da8394ed9144d727115fc434b0416f) Build: update CI testing configuration (#11500) (Reece Dunham) +* [`956e883`](https://github.com/eslint/eslint/commit/956e883c21fd9f393bf6718d032a4e2e53b33f22) Docs: Fix example in no-restricted-modules docs (#11454) (Paul O’Shannessy) +* [`2c7431d`](https://github.com/eslint/eslint/commit/2c7431d6b32063f74e3837ee727f26af215eada7) Docs: fix json schema example dead link (#11498) (kazuya kawaguchi) +* [`e7266c2`](https://github.com/eslint/eslint/commit/e7266c2478aff5d66e7859313feb49e3a129f85e) Docs: Fix invalid JSON in "Specifying Parser Options" (#11492) (Mihira Jayasekera) +* [`6693161`](https://github.com/eslint/eslint/commit/6693161978a83e0730d5ea0fecdb627c5a2acdfd) Sponsors: Sync README with website (ESLint Jenkins) +* [`62fee4a`](https://github.com/eslint/eslint/commit/62fee4a976897d158c8c137339728cd280333286) Chore: eslint-config-eslint enable comma-dangle functions: "never" (#11434) (薛定谔的猫) +* [`34a5382`](https://github.com/eslint/eslint/commit/34a53829e7a63ff2f6b371d77ce283bbdd373b91) Build: copy bundled espree to website directory (#11478) (Pig Fang) +* [`f078f9a`](https://github.com/eslint/eslint/commit/f078f9a9e094ec00c61a6ef1c9550d017631e69a) Chore: use "file:" dependencies for internal rules/config (#11465) (Teddy Katz) +* [`0756128`](https://github.com/eslint/eslint/commit/075612871f85aa04cef8137bd32247e128ad600b) Docs: Add `visualstudio` to formatter list (#11480) (Patrick Eriksson) +* [`44de9d7`](https://github.com/eslint/eslint/commit/44de9d7e1aa2fcae475a97b8f597b7d8094566b2) Docs: Fix typo in func-name-matching rule docs (#11484) (Iulian Onofrei) + +v5.15.1 - March 4, 2019 + +* [`fe1a892`](https://github.com/eslint/eslint/commit/fe1a892f85b09c3d2fea05bef011530a678a6af5) Build: bundle espree (fixes eslint/eslint.github.io#546) (#11467) (薛定谔的猫) +* [`458053b`](https://github.com/eslint/eslint/commit/458053b0b541f857bf233dacbde5ba80681820f8) Fix: avoid creating invalid regex in no-warning-comments (fixes #11471) (#11472) (Teddy Katz) + +v5.15.0 - March 1, 2019 + +* [`4088c6c`](https://github.com/eslint/eslint/commit/4088c6c9d4578cd581ce8ff4385d90b58a75b755) Build: Remove path.resolve in webpack build (#11462) (Kevin Partington) +* [`ec59ec0`](https://github.com/eslint/eslint/commit/ec59ec09c8d001b8c04f9edc09994e2b0d0af0f9) New: add rule "prefer-named-capture-group" (fixes #11381) (#11392) (Pig Fang) +* [`a44f750`](https://github.com/eslint/eslint/commit/a44f75073306e5ea4e6722654009a99884fbca4f) Upgrade: eslint-scope@4.0.2 (#11461) (Teddy Katz) +* [`d3ce611`](https://github.com/eslint/eslint/commit/d3ce611e1c705440ccbcae357f2194134d026541) Sponsors: Sync README with website (ESLint Jenkins) +* [`ee88475`](https://github.com/eslint/eslint/commit/ee884754e4111e11994ff0df3f0c29e43e1dc3f2) Chore: add utils for rule tests (#11453) (薛定谔的猫) +* [`d4824e4`](https://github.com/eslint/eslint/commit/d4824e46d7a6ca1618454d3c6198403382108123) Sponsors: Sync README with website (ESLint Jenkins) +* [`6489518`](https://github.com/eslint/eslint/commit/64895185bde5233223648bcaf46f8deb72c9fb55) Fix: no-extra-parens crash when code is "((let))" (#11444) (Teddy Katz) +* [`9d20de2`](https://github.com/eslint/eslint/commit/9d20de2b0ac756bd62888119b8e08c7441d8a5aa) Sponsors: Sync README with website (ESLint Jenkins) +* [`3f14de4`](https://github.com/eslint/eslint/commit/3f14de458ba120e9c013f5fc7c6fe3e9b40c1460) Sponsors: Sync README with website (ESLint Jenkins) +* [`3d6c770`](https://github.com/eslint/eslint/commit/3d6c7709d47e047b25d91ca1a77d6dab92313061) Sponsors: Sync README with website (ESLint Jenkins) +* [`de5cbc5`](https://github.com/eslint/eslint/commit/de5cbc526b30405e742b35d85d04361529d49ed4) Update: remove invalid defaults from core rules (fixes #11415) (#11427) (Teddy Katz) +* [`eb0650b`](https://github.com/eslint/eslint/commit/eb0650ba20cf9f9ad78dbaccfeb7e0e7ab56e31d) Build: fix linting errors on master (#11428) (Teddy Katz) +* [`5018378`](https://github.com/eslint/eslint/commit/5018378131fd5190bbccca902c0cf4276ee1581a) Chore: enable require-unicode-regexp on ESLint codebase (#11422) (Teddy Katz) +* [`f6ba633`](https://github.com/eslint/eslint/commit/f6ba633f56eca6be20fc4b0d9496a78b9498d578) Chore: lint all files in the repo at the same time (#11425) (Teddy Katz) +* [`8f3d717`](https://github.com/eslint/eslint/commit/8f3d71754932669332ad7623bcc4c1aef3897125) Docs: Add non-attending TSC member info (#11411) (Nicholas C. Zakas) +* [`ce0777d`](https://github.com/eslint/eslint/commit/ce0777da5bc167fe0c529158fd8216d3eaf11565) Docs: use more common spelling (#11417) (薛定谔的猫) +* [`b9aabe3`](https://github.com/eslint/eslint/commit/b9aabe34311f6189b87c9d8a1aa40f3513fed773) Chore: run fuzzer along with unit tests (#11404) (Teddy Katz) +* [`db0c5e2`](https://github.com/eslint/eslint/commit/db0c5e2a7f894b7cda71007b0ba43d7814b3fb2e) Build: switch from browserify to webpack (fixes #11366) (#11398) (Pig Fang) + +v5.14.1 - February 18, 2019 + +* [`1d6e639`](https://github.com/eslint/eslint/commit/1d6e63930073e79e52890f552cc6e9a0646b7fb4) Fix: sort-keys throws Error at SpreadElement (fixes #11402) (#11403) (Krist Wongsuphasawat) + +v5.14.0 - February 15, 2019 + +* [`85a04b3`](https://github.com/eslint/eslint/commit/85a04b319e6dfde1458174cd1d8c9e7d33da0871) Fix: adds conditional for separateRequires in one-var (fixes #10179) (#10980) (Scott Stern) +* [`0c02932`](https://github.com/eslint/eslint/commit/0c02932f1b2e2a85809e84617efa1b8836c19cfb) Upgrade: espree@5.0.1 (#11401) (Ilya Volodin) +* [`104ae88`](https://github.com/eslint/eslint/commit/104ae881d0b21e9c64e006b2a2c21535cef0ad28) Docs: Update governance doc with reviewers status (#11399) (Nicholas C. Zakas) +* [`ab8ac6a`](https://github.com/eslint/eslint/commit/ab8ac6adaaf7a88e160899e7f438a4cfd655eb6e) Fix: Support boundary spread elements in sort-keys (#11158) (Jakub Rożek) +* [`a23d197`](https://github.com/eslint/eslint/commit/a23d1975d48841eafdead1a1357e2af842f688bc) New: add allowSingleLineBlocks opt. to padded-blocks rule (fixes #7145) (#11243) (richie3366) +* [`e25e7aa`](https://github.com/eslint/eslint/commit/e25e7aa3ea1e8c9b3cd3242acda6d4a5572c2c6a) Fix: comma-spacing ignore comma before closing paren (fixes #11295) (#11374) (Pig Fang) +* [`a1f7c44`](https://github.com/eslint/eslint/commit/a1f7c44ea9efbd9393889c1cc91b74260e0a8e02) Docs: fix space-before-blocks correct code for "classes": "never" (#11391) (PoziWorld) +* [`14f58a2`](https://github.com/eslint/eslint/commit/14f58a2bec4d6aade0de22771c378b86b1e51959) Docs: fix grammar in object-curly-spacing docs (#11389) (PoziWorld) +* [`d3e9a27`](https://github.com/eslint/eslint/commit/d3e9a27bbba30008a610df59e82b7192f0ecc3a3) Docs: fix grammar in “those who says” (#11390) (PoziWorld) +* [`ea8e804`](https://github.com/eslint/eslint/commit/ea8e8045ba0e6c1e1015104346af962f3e16fd81) Docs: Add note about support for object spread (fixes #11136) (#11395) (Steven Thomas) +* [`95aa3fd`](https://github.com/eslint/eslint/commit/95aa3fdb392d265e6c3d813d54076458e88e7ad8) Docs: Update README team and sponsors (ESLint Jenkins) +* [`51c4972`](https://github.com/eslint/eslint/commit/51c497298a15ad296a2b1f8fc397df687976b836) Update: Behavior of --init (fixes #11105) (#11332) (Nicholas C. Zakas) +* [`ad7a380`](https://github.com/eslint/eslint/commit/ad7a38097c32a91e5a831ef1bc8933601532576c) Docs: Update README team and sponsors (ESLint Jenkins) +* [`550de1e`](https://github.com/eslint/eslint/commit/550de1e611a1e9af873bcb18d74cf2056e8d2e1b) Update: use `default` keyword in JSON schema (fixes #9929) (#11288) (Pig Fang) +* [`983c520`](https://github.com/eslint/eslint/commit/983c5201210d7a4ffab0b3d05ab9919c0754e5ca) Update: Use 'readonly' and 'writable' for globals (fixes #11359) (#11384) (Nicholas C. Zakas) +* [`f1d3a7e`](https://github.com/eslint/eslint/commit/f1d3a7ee7c82365989e219b1dae379f08f6dd526) Upgrade: some deps (fixes #11372) (#11373) (薛定谔的猫) +* [`3e0c417`](https://github.com/eslint/eslint/commit/3e0c4176eff085498b813f8ba1732d7ed6ee44f8) Docs: Fix grammar in “there’s nothing prevent you” (#11385) (PoziWorld) +* [`de988bc`](https://github.com/eslint/eslint/commit/de988bc909b491366ad0cd9bc83f4d6de42d041a) Docs: Fix grammar: Spacing improve -> Spacing improves (#11386) (PoziWorld) +* [`1309dfd`](https://github.com/eslint/eslint/commit/1309dfdebb5595460b79dcac20df6a1f109e7566) Revert "Build: fix test failure on Node 11 (#11100)" (#11375) (薛定谔的猫) +* [`1e56897`](https://github.com/eslint/eslint/commit/1e56897db3e254e0aef6d2fe3274157fc379c79e) Docs: “the function actually use”: use -> uses (#11380) (PoziWorld) +* [`5a71bc9`](https://github.com/eslint/eslint/commit/5a71bc95a7e961b1b1b77022645e0bd9cdd08dc0) Docs: Update README team and sponsors (ESLint Jenkins) +* [`82a58ce`](https://github.com/eslint/eslint/commit/82a58ce26b282fd80335b3ac4fc88f21266c3ba1) Docs: Update README team and sponsors (ESLint Jenkins) +* [`546d355`](https://github.com/eslint/eslint/commit/546d355ace65631e27de859baea3ffcc50e0ad2c) Docs: Update README with latest sponsors/team data (#11378) (Nicholas C. Zakas) +* [`c0df9fe`](https://github.com/eslint/eslint/commit/c0df9febb7c7e045ababc10b88dbcbb3f28c724c) Docs: `...` is not an operator (#11232) (Felix Kling) +* [`7ecfdef`](https://github.com/eslint/eslint/commit/7ecfdefaeadb772f8b96ffe37c4a2c97fde0da16) Docs: update typescript parser (refs #11368) (#11369) (薛定谔的猫) +* [`3c90dd7`](https://github.com/eslint/eslint/commit/3c90dd7e25cf97833deddb11cfbc107a5663ac08) Update: remove prefer-spread autofix (fixes #11330) (#11365) (薛定谔的猫) +* [`5eb3121`](https://github.com/eslint/eslint/commit/5eb3121b82c1837da0c3021b7d9384bb30832e36) Update: add fixer for `prefer-destructuring` (fixes #11151) (#11301) (golopot) +* [`173eb38`](https://github.com/eslint/eslint/commit/173eb38cdb3e4673cba947521f27158828186d77) Docs: Clarify ecmaVersion doesn't imply globals (refs #9812) (#11364) (Keith Maxwell) +* [`84ce72f`](https://github.com/eslint/eslint/commit/84ce72fdeba082b7b132e4ac6b714fb1a93831b7) Fix: Remove extraneous linefeeds in `one-var` fixer (fixes #10741) (#10955) (st-sloth) +* [`389362a`](https://github.com/eslint/eslint/commit/389362a06ac6601512b872d3e843c7371f2a1bcc) Docs: clarify motivation for no-prototype-builtins (#11356) (Teddy Katz) +* [`533d240`](https://github.com/eslint/eslint/commit/533d240b0811f663494cb213b06cc9e51e1ff2d0) Update: no-shadow-restricted-names lets unassigned vars shadow undefined (#11341) (Teddy Katz) +* [`d0e823a`](https://github.com/eslint/eslint/commit/d0e823aef196a6564c87a78b72c1ef980ce67af9) Update: Make --init run js config files through linter (fixes #9947) (#11337) (Brian Kurek) +* [`92fc2f4`](https://github.com/eslint/eslint/commit/92fc2f4f3faf8aeaae8a8e71db0de405404fb6c3) Fix: CircularJSON dependency warning (fixes #11052) (#11314) (Terry) +* [`4dd19a3`](https://github.com/eslint/eslint/commit/4dd19a3c4c037adc860a65e96f2ba3eeccace1de) Docs: mention 'prefer-spread' in docs of 'no-useless-call' (#11348) (Klaus Meinhardt) +* [`4fd83d5`](https://github.com/eslint/eslint/commit/4fd83d5ec47a6a7b81cd8801c3bd63d27ea1c7c4) Docs: fix a misleading example in one-var (#11350) (薛定谔的猫) +* [`9441ce7`](https://github.com/eslint/eslint/commit/9441ce77b7228f2c4562e158a10905afe11f31f2) Chore: update incorrect tests to fix build failing (#11354) (薛定谔的猫) + +v5.13.0 - February 1, 2019 + +* [`91c8884`](https://github.com/eslint/eslint/commit/91c8884971f5e57f5f7490d8daf92c4a9a489836) Chore: use local function to append "s" instead of a package (#11293) (Timo Tijhof) +* [`b5143bf`](https://github.com/eslint/eslint/commit/b5143bfc09e53d8da8f63421ade093b7593f4f51) Update: for-direction detection false positives/negatives (#11254) (Ruben Bridgewater) +* [`9005e63`](https://github.com/eslint/eslint/commit/9005e632d13476880c55f7e3c8a6e450762a5171) Chore: increase camelcase test coverage (#11299) (Redmond Tran) +* [`5b14ad1`](https://github.com/eslint/eslint/commit/5b14ad1003c7df9a37621dea55c6d6d0484adc05) Fix: false positive in no-constant-condition (fixes #11306) (#11308) (Pig Fang) +* [`6567c4f`](https://github.com/eslint/eslint/commit/6567c4f6665df85c3347388b29d8193cc8208d63) Fix: only remove arrow before body in object-shorthand (fixes #11305) (#11307) (Pig Fang) +* [`fa2f370`](https://github.com/eslint/eslint/commit/fa2f370affa4814dbdda278f9859d0172d4b7aa2) Docs: update rule configuration values in examples (#11323) (Kai Cataldo) +* [`0a3c3ff`](https://github.com/eslint/eslint/commit/0a3c3ff1d91e8f39943efc4a7d2bf6927d68d37e) New: Allow globals to be disabled/configured with strings (fixes #9940) (#11338) (Teddy Katz) +* [`dccee63`](https://github.com/eslint/eslint/commit/dccee63cf41234180c71bf0fe01b165c9078fc69) Chore: avoid hard-coding the list of core rules in eslint:recommended (#11336) (Teddy Katz) +* [`c1fd6f5`](https://github.com/eslint/eslint/commit/c1fd6f54d92efe615bcae529006221e122dbe9e6) Chore: remove undocumented `Linter#rules` property (refs #9161) (#11335) (Teddy Katz) +* [`36e3356`](https://github.com/eslint/eslint/commit/36e335681d61cbe3c83b653b7cc5f95730f1d86e) Chore: remove dead code for loading rules (#11334) (Teddy Katz) +* [`c464e27`](https://github.com/eslint/eslint/commit/c464e2744ec76e7e9c6c5af0f6162c92187f1ece) Docs: Rename `result` -> `foo` (#11210) (Alexis Tyler) + +v5.12.1 - January 18, 2019 + +* [`eb5c401`](https://github.com/eslint/eslint/commit/eb5c4014f16be1c2003ed46ce9560d0d8a567d0f) Chore: use meta.messages in some rules (2/4) (refs #9870) (#10773) (薛定谔的猫) +* [`aa56247`](https://github.com/eslint/eslint/commit/aa56247746a0095996a41dd03bdbbf659f0f93b6) Fix: avoid loading core rules dynamically from FS in Linter (#11278) (Peter Metz) +* [`04450bb`](https://github.com/eslint/eslint/commit/04450bb7ed20f2412102538b238119d9764b4dc9) Docs: clarify process for adding committers (#11272) (Kai Cataldo) +* [`3ffcf26`](https://github.com/eslint/eslint/commit/3ffcf26c1c83efe7d7cf2d87f1063695ae653709) Docs: add @g-plane as committer (#11277) (Kai Cataldo) +* [`c403445`](https://github.com/eslint/eslint/commit/c40344566eff2e77a6ae2b2d2dbdbd4ad3e76b67) Fix: warn constant on RHS of || in no-constant-condition (fixes #11181) (#11253) (Merlin Mason) +* [`9194f45`](https://github.com/eslint/eslint/commit/9194f45ac7d521119a53773bf02b81670bad526e) Fix: Manage severity of 1 with TAP reporter (fixes #11110) (#11221) (Gabriel Cousin) +* [`000f495`](https://github.com/eslint/eslint/commit/000f4952ae6a4311fbbc3ed36c481235fcb0b64b) Docs: fix example for sort-imports ignoreDeclarationSort (#11242) (Remco Haszing) +* [`7c0bf2c`](https://github.com/eslint/eslint/commit/7c0bf2ca92d83125a1fa000c9c4250bae6b4fc21) Docs: Add `npx` usage to Getting Started guide (#11249) (eyal0803) +* [`da9174e`](https://github.com/eslint/eslint/commit/da9174e0798c1d785ddabb3ae405860fc5b89311) Docs: fixes typo peerDepencies (#11252) (Christian Kühl) +* [`9c31625`](https://github.com/eslint/eslint/commit/9c31625f19176664ef76dcf088ce50703c41c324) Docs: Improve custom formatter docs (#11258) (Nicholas C. Zakas) + +v5.12.0 - January 4, 2019 + +* [`0d91e7d`](https://github.com/eslint/eslint/commit/0d91e7d28e5eba79a6032165cdef5d4549d26462) Update: Add sort-imports ignoreDeclarationSort (fixes #11019) (#11040) (Remco Haszing) +* [`f92d6f0`](https://github.com/eslint/eslint/commit/f92d6f05c4dcd4a3a0616871e10b31edae9dfad5) Build: Add karma-chrome-launcher support (#11027) (薛定谔的猫) +* [`166853d`](https://github.com/eslint/eslint/commit/166853d9c59db493f0b1bb68a67ad868662a4205) Upgrade: eslint-plugin-eslint-plugin@2.0.1 (#11220) (薛定谔的猫) +* [`bfff77a`](https://github.com/eslint/eslint/commit/bfff77ad4eaa02e2e62481c986634df38d5db6e5) Fix: no-param-reassign parameter in ternary operator (fixes #11236) (#11239) (周昊宇) +* [`258b654`](https://github.com/eslint/eslint/commit/258b6541f61dc3a9ae64e200680766a11c3dd316) Upgrade: require-uncached renamed to import-fresh (#11066) (薛定谔的猫) + +v5.11.1 - December 26, 2018 + +* [`de79f10`](https://github.com/eslint/eslint/commit/de79f1026b7035f0296d7876f1db64f225cca1b8) Fix: handle optional catch bindings in no-useless-catch (#11205) (Colin Ihrig) + +v5.11.0 - December 22, 2018 + +* [`b4395f6`](https://github.com/eslint/eslint/commit/b4395f671442a7e0be956382c24cce38025a6df6) New: add option `first` for VariableDeclarator in indent (fixes #8976) (#11193) (Pig Fang) +* [`2b5a602`](https://github.com/eslint/eslint/commit/2b5a60284670a3ab1281b206941ed38faf2ea10c) New: no-useless-catch rule (fixes #11174) (#11198) (Alexander Grasley) +* [`06b3b5b`](https://github.com/eslint/eslint/commit/06b3b5bfcf0429c5078d4f4af3c03bb777e4f022) Fix: Account for comments in implicit-arrow-linebreak (#10545) (Mark de Dios) +* [`4242314`](https://github.com/eslint/eslint/commit/4242314215a6f35e432860433906f47af1a29724) Update: handle computed properties in camelcase (fixes #11084) (#11113) (Bence Dányi) +* [`1009304`](https://github.com/eslint/eslint/commit/100930493d9ab802a94dac5c761515b12241ddd2) Docs: add a note for no-unused-expressions (fixes #11169) (#11192) (Pig Fang) +* [`88f99d3`](https://github.com/eslint/eslint/commit/88f99d31b88a4cde4563bc4a6f4c41f0cc557885) Docs: clarify how to use configs in plugins (#11199) (Kai Cataldo) +* [`bcf558b`](https://github.com/eslint/eslint/commit/bcf558b2f7036f487af2bdb2b2d34b6cdf7fc174) Docs: Clarify the no-unused-vars docs (#11195) (Jed Fox) +* [`a470eb7`](https://github.com/eslint/eslint/commit/a470eb73d52fae0f0bc48de5a487e23cf78fcfa9) Docs: Fix no-irregular-whitespace description (#11196) (Jed Fox) +* [`8abc8af`](https://github.com/eslint/eslint/commit/8abc8afe71691b747cbd1819a13d896e8aa5b92a) Docs: Remove a misleading example (#11204) (Bogdan Gradinariu) +* [`733d936`](https://github.com/eslint/eslint/commit/733d93618a99758a05453ab94505a9f1330950e0) Docs: link to JSDoc EOL blogpost in valid-jsdoc and require-jsdoc (#11191) (Nathan Diddle) +* [`d5eb108`](https://github.com/eslint/eslint/commit/d5eb108e17f676d0e4fcddeb1211b4bdfac760c1) Docs: Ensure `triage` label is added to new issues (#11182) (Teddy Katz) +* [`617a287`](https://github.com/eslint/eslint/commit/617a2874ed085bca36ca289aac55e3b7f7ce937e) Docs: add missing deprecation notices for jsdoc rules (#11171) (Teddy Katz) + +v5.10.0 - December 8, 2018 + +* [`4b0f517`](https://github.com/eslint/eslint/commit/4b0f517cd317e5f1b99a1e8a0392332bd8a2e231) Upgrade: single- and multiline const, let, var statements (fixes #10721) (#10919) (Tom Panier) +* [`9666aba`](https://github.com/eslint/eslint/commit/9666abaf46c841fba7b5d4e53c6998cd25b9bc33) Update: space-infix-ops reports violating operator (#10934) (Bence Dányi) +* [`c14f717`](https://github.com/eslint/eslint/commit/c14f717f4c32860766185da47f64f8eb0c2d2998) Fix: Update all-files-ignored.txt message to be less confusing (#11075) (z.ky) +* [`9f3573d`](https://github.com/eslint/eslint/commit/9f3573dda3dc35bc220e945686cc835eaad0ac2c) Docs: Clarify the CLIEngine options (#10995) (Ed Morley) +* [`dd7b0cb`](https://github.com/eslint/eslint/commit/dd7b0cb019d94964930d30fec36f7b22ef072822) Chore: refactor template literal feature detection in 'quotes' rule (#11125) (Bryan) +* [`3bf0332`](https://github.com/eslint/eslint/commit/3bf0332508b921cb660c2e8a1ab7ddf46a2013b6) Fix: fix the fixer of lone comma with comments (fixes #10632) (#11154) (Pig Fang) +* [`f850726`](https://github.com/eslint/eslint/commit/f8507260c2091d18488fde20e466639d1a7f913c) Upgrade: Espree v5.0.0 (#11161) (Kai Cataldo) +* [`4490d7a`](https://github.com/eslint/eslint/commit/4490d7af529d4ecc18b6874f1d838869656da58a) Update: deprecate valid-jsdoc and require-jsdoc (#11145) (Teddy Katz) +* [`60dfb6c`](https://github.com/eslint/eslint/commit/60dfb6c623dfe829e5350dabe507e7850c1beacf) Docs: Update issue templates (#11163) (Teddy Katz) +* [`958987a`](https://github.com/eslint/eslint/commit/958987aa6f5630faa051d8f822f0200faff41924) Docs: Fix link to rule no-useless-rename (#11165) (Brian) +* [`62fd2b9`](https://github.com/eslint/eslint/commit/62fd2b93448966331db3eb2dfbe4e1273eb032b2) Update: Amend keyword-spacing to validate `default` keywords (#11097) (Bin Ury) +* [`4bcdfd0`](https://github.com/eslint/eslint/commit/4bcdfd07d514fd7a6b8672d33703d0b6c606f214) Chore: fix some jsdoc-related issues (#11148) (薛定谔的猫) +* [`c6471ed`](https://github.com/eslint/eslint/commit/c6471ed6feb3e71e239379a7042deb9b8ab3cf39) Docs: fix typo in issue-templates/new-rule (#11149) (薛定谔的猫) +* [`5d451c5`](https://github.com/eslint/eslint/commit/5d451c510c15abc41b5bb14b4955a7db96aeb100) Chore: Remove dependency on is-resolvable (#11128) (Matt Grande) +* [`bc50dc7`](https://github.com/eslint/eslint/commit/bc50dc7737496712463220e662946eb516e36ae1) Chore: Move ignored-paths, report-translator to lib/util (refs #10559) (#11116) (Kevin Partington) +* [`c0a80d0`](https://github.com/eslint/eslint/commit/c0a80d0ca3c80ca27694fc8aedcf84b72bfd9465) Fix: Do not strip underscores in camelcase allow (fixes #11000) (#11001) (Luke Page) +* [`a675c89`](https://github.com/eslint/eslint/commit/a675c89573836adaf108a932696b061946abf1e6) Docs: (Grammar) "the setup" -> "to set up" (#11117) (MarvinJWendt) +* [`54dfa60`](https://github.com/eslint/eslint/commit/54dfa602f62e6d183d57d60d5fdd417a263f479e) Fix: Typo in function comment parameters (#11111) (Pierre Maoui) +* [`cf296bd`](https://github.com/eslint/eslint/commit/cf296bdabf0dbbfbae491419e38aee4ecd63ec71) Docs: switch incorrect example with correct one (#11107) (Romain Le Quellec) +* [`d2d500c`](https://github.com/eslint/eslint/commit/d2d500ca5dff307189b9d4161a5e7b8282557dd6) Docs: no-console#When-Not-To-Use provides incorrect rule snippet (#11093) (Lawrence Chou) +* [`f394a1d`](https://github.com/eslint/eslint/commit/f394a1dfc5eb4874f899b7bc19685896893af7b8) Chore: Extract config comment parsing (#11091) (Nicholas C. Zakas) +* [`709190f`](https://github.com/eslint/eslint/commit/709190f8c5d7559b1e0915e25af60b50a94ba1c7) Build: fix test failure on Node 11 (#11100) (Teddy Katz) +* [`3025cdd`](https://github.com/eslint/eslint/commit/3025cddf0a2ea8461ce05575098a5714fcf6278d) Update: don't indent leading semi in line after import (fixes #11082) (#11085) (Pig Fang) +* [`e18c827`](https://github.com/eslint/eslint/commit/e18c827cc12cb1c52e5d0aa993f572cb56238704) Chore: refactor linter#parseBooleanConfig to improve readability (#11074) (薛定谔的猫) +* [`5da378a`](https://github.com/eslint/eslint/commit/5da378ac922d732ca1765f08edee0face1b1b924) Upgrade: eslint-release@1.2.0 (#11073) (Teddy Katz) + +v5.9.0 - November 9, 2018 + +* 9436712 Fix: Unused recursive function expressions (fixes #10982) (#11032) (Sergei Startsev) +* c832cd5 Update: add `ignoreDestructuring` option to `id-match` rule (#10554) (一名宅。) +* 54687a8 Fix: prefer-const autofix multiline assignment (fixes #10582) (#10987) (Scott Stern) +* ae2b61d Update: "off" options for "space-before-blocks" (refs #10906) (#10907) (Sophie Kirschner) +* 57f357e Docs: Update require-await docs with exception (fixes #9540) (#11063) (Nicholas C. Zakas) +* 79a2797 Update: no-restricted-imports to check re-export (fixes #9678) (#11064) (Nicholas C. Zakas) +* 3dd7493 Docs: update ecmaVersion to include 2019/10 values (#11059) (Vse Mozhet Byt) +* 607635d Upgrade: eslint-plugin-node & eslint-plugin (#11067) (薛定谔的猫) +* dcc6233 Fix: Ignore empty statements in no-unreachable (fixes #9081) (#11058) (Nicholas C. Zakas) +* 7ad86de New: Add --fix-type option to CLI (fixes #10855) (#10912) (Nicholas C. Zakas) +* 0800b20 Chore: fix invalid super() calls in tests (#11054) (Teddy Katz) +* 4fe3287 Docs: Cross-reference two rules (refs #11041) (#11042) (Paul Melnikow) +* 5525eb6 Fix: rule deprecation warnings did not consider all rules (#11044) (Teddy Katz) +* 44d37ca Docs: Update steps for adding new TSC member (#11038) (Nicholas C. Zakas) +* 802e926 Update: Warn for deprecation in Node output (fixes #7443) (#10953) (Colin Chang) + +v5.8.0 - October 26, 2018 + +* 9152417 Fix: deprecation warning in RuleTester using Node v11 (#11009) (Teddy Katz) +* e349a03 Docs: Update issue templates to ask for PRs (#11012) (Nicholas C. Zakas) +* 3d88b38 Chore: avoid using legacy report API in no-irregular-whitespace (#11013) (Teddy Katz) +* 5a31a92 Build: compile espree's deps to ES5 when generating site (fixes #11014) (#11015) (Teddy Katz) +* 3943635 Update: Create Linter.version API (fixes #9271) (#11010) (Nicholas C. Zakas) +* a940cf4 Docs: Mention version for config glob patterns (fixes #8793) (Nicholas C. Zakas) +* 6e1c530 Build: run tests on Node 11 (#11008) (Teddy Katz) +* 58ff359 Docs: add instructions for npm 2FA (refs #10631) (#10992) (Teddy Katz) +* 2f87bb3 Upgrade: eslint-release@1.0.0 (refs #10631) (#10991) (Teddy Katz) +* 57ef0fd Fix: prefer-const when using destructuring assign (fixes #8308) (#10924) (Nicholas C. Zakas) +* 577cbf1 Chore: Add typescript-specific edge case tests to space-infix-ops (#10986) (Bence Dányi) +* d45b184 Chore: Using deconstruction assignment for shelljs (#10974) (ZYSzys) + +v5.7.0 - October 12, 2018 + +* 6cb63fd Update: Add iife to padding-line-between-statements (fixes #10853) (#10916) (Kevin Partington) +* 5fd1bda Update: no-tabs allowIndentationTabs option (fixes #10256) (#10925) (Kevin Partington) +* d12be69 Fix: no-extra-bind No autofix if arg may have side effect (fixes #10846) (#10918) (Kevin Partington) +* 847372f Fix: no-unused-vars false pos. with markVariableAsUsed (fixes #10952) (#10954) (Roy Sutton) +* 4132de7 Chore: Simplify space-infix-ops (#10935) (Bence Dányi) +* 543edfa Fix: Fix error with one-var (fixes #10937) (#10938) (Justin Krup) +* 95c4cb1 Docs: Fix typo for no-unsafe-finally (#10945) (Sergio Santoro) +* 5fe0e1a Fix: no-invalid-regexp disallows \ at end of pattern (fixes #10861) (#10920) (Toru Nagashima) +* f85547a Docs: Add 'When Not To Use' section to space-infix-ops (#10931) (Bence Dányi) +* 3dccac4 Docs: Update working-with-parsers link (#10929) (Azeem Bande-Ali) +* 557a8bb Docs: Remove old note about caching, add a new one (fixes #10739) (#10913) (Zac) +* fe8111a Chore: Add more test cases to space-infix-ops (#10936) (Bence Dányi) +* 066f7e0 Update: camelcase rule ignoreList added (#10783) (Julien Martin) +* 70bde69 Upgrade: table to version 5 (#10903) (Rouven Weßling) +* 2e52bca Chore: Update issue templates (#10900) (Nicholas C. Zakas) + +v5.6.1 - September 28, 2018 + +* 9b26bdb Fix: avoid exponential require-atomic-updates traversal (fixes #10893) (#10894) (Teddy Katz) +* 9432b10 Fix: make separateRequires work in consecutive mode (fixes #10784) (#10886) (Pig Fang) +* e51868d Upgrade: debug@4 (fixes #10854) (#10887) (薛定谔的猫) +* d3f3994 Docs: add information about reporting security issues (#10889) (Teddy Katz) +* cc458f4 Build: fix failing tests on master (#10890) (Teddy Katz) +* a6ebfd3 Docs: clarify defaultAssignment option, fix no-unneeded-ternary examples (#10874) (CoffeeTableEspresso) +* 9d52541 Fix: Remove duplicate error message on crash (fixes #8964) (#10865) (Nicholas C. Zakas) +* 4eb9a49 Docs: Update quotes.md (#10862) (The Jared Wilcurt) +* 9159e9b Docs: Update complexity.md (#10867) (Szymon Przybylski) +* 14f4e46 Docs: Use Linter instead of linter in Nodejs API page (#10864) (Nicholas C. Zakas) +* b3e3cb1 Chore: Update debug log name to match filename (#10863) (Nicholas C. Zakas) + +v5.6.0 - September 14, 2018 + +* c5b688e Update: Added generators option to func-names (fixes #9511) (#10697) (Oscar Barrett) +* 7da36d5 Fix: respect generator function expressions in no-constant-condition (#10827) (Julian Rosse) +* 0a65844 Chore: quote enable avoidEscape option in eslint-config-eslint (#10626) (薛定谔的猫) +* 32f41bd Chore: Add configuration wrapper markdown for the bug report template (#10669) (Iulian Onofrei) + +v5.5.0 - August 31, 2018 + +* 6e110e6 Fix: camelcase duplicate warning bug (fixes #10801) (#10802) (Julian Rosse) +* 5103ee7 Docs: Add Brackets integration (#10813) (Jan Pilzer) +* b61d2cd Update: max-params to only highlight function header (#10815) (Ian Obermiller) +* 2b2f11d Upgrade: babel-code-frame to version 7 (#10808) (Rouven Weßling) +* 2824d43 Docs: fix comment placement in a code example (#10799) (Vse Mozhet Byt) +* 10690b7 Upgrade: devdeps and deps to latest (#10622) (薛定谔的猫) +* 80c8598 Docs: gitignore syntax updates (fixes #8139) (#10776) (Gustavo Santana) +* cb946af Chore: use meta.messages in some rules (1/4) (#10764) (薛定谔的猫) + +v5.4.0 - August 17, 2018 + +* a70909f Docs: Add jscs-dev.github.io links (#10771) (Gustavo Santana) +* 034690f Fix: no-invalid-meta crashes for non Object values (fixes #10750) (#10753) (Sandeep Kumar Ranka) +* 11a462d Docs: Broken jscs.info URLs (fixes #10732) (#10770) (Gustavo Santana) +* 985567d Chore: rm unused dep string.prototype.matchall (#10756) (薛定谔的猫) +* f3d8454 Update: Improve no-extra-parens error message (#10748) (Timo Tijhof) +* 562a03f Fix: consistent-docs-url crashes if meta.docs is empty (fixes #10722) (#10749) (Sandeep Kumar Ranka) +* 6492233 Chore: enable no-prototype-builtins in codebase (fixes #10660) (#10664) (薛定谔的猫) +* 137140f Chore: use eslintrc overrides (#10677) (薛定谔的猫) + +v5.3.0 - August 3, 2018 + +* dd6cb19 Docs: Updated no-return-await Rule Documentation (fixes #9695) (#10699) (Marla Foreman) +* 6009239 Chore: rename utils for consistency (#10727) (薛定谔的猫) +* 6eb972c New: require-unicode-regexp rule (fixes #9961) (#10698) (Toru Nagashima) +* 5c5d64d Fix: ignored-paths for Windows path (fixes #10687) (#10691) (Toru Nagashima) +* 5f6a765 Build: ensure URL fragments remain in documentation links (fixes #10717) (#10720) (Teddy Katz) +* 863aa78 Docs: add another example for when not to use no-await-in-loop (#10714) (Valeri Karpov) +* 6e78b7d Docs: remove links to terminated jscs.info domain (#10706) (Piotr Kuczynski) +* d56c39d Fix: ESLint cache no longer stops autofix (fixes #10679) (#10694) (Kevin Partington) +* 2cc3240 New: add no-misleading-character-class (fixes #10049) (#10511) (Toru Nagashima) +* 877f4b8 Fix: The "../.." folder is always ignored (fixes #10675) (#10682) (Sridhar) +* 5984820 Chore: Move lib/file-finder.js to lib/util/ (refs #10559) (#10695) (Kevin Partington) +* e37a593 Update: Fix incorrect default value for position (#10670) (Iulian Onofrei) +* 8084bfc Docs: change when not to use object spread (#10621) (Benny Powers) +* 7f496e2 Chore: Update require path for ast-utils (#10693) (Kevin Partington) +* 648a33a Chore: reorganize code structure of utilities (refs #10599) (#10680) (薛定谔的猫) +* f026fe1 Update: Fix 'function' in padding-line-between-statements (fixes #10487) (#10676) (Kevin Partington) +* c2bb8bb Docs: Remove superfluous object option sample code (#10652) (Iulian Onofrei) +* d34a13b Docs: add subheader in configuring/configuring-rules (#10686) (薛定谔的猫) +* d8aea28 Chore: rm unnecessary plugin in eslint-config-eslint (#10685) (薛定谔的猫) +* 9e76be7 Update: indent comments w/ nearby code if no blank lines (fixes #9733) (#10640) (Kevin Partington) +* 9e93d46 New: add no-async-promise-executor rule (fixes #10217) (#10661) (Teddy Katz) +* 5a2538c New: require-atomic-updates rule (fixes #10405) (#10655) (Teddy Katz) +* 8b83d2b Fix: always resolve default ignore patterns from CWD (fixes #9227) (#10638) (Teddy Katz) +* acb6658 Fix: ESLint crash with prefer-object-spread (fixes #10646) (#10649) (薛定谔的猫) +* 99fb7d3 Docs: fix misleading no-prototype-builtins description (#10666) (薛定谔的猫) +* 005b849 Docs: fix outdated description of `baseConfig` option (#10657) (Teddy Katz) +* 15a77c4 Docs: fix broken links (fixes eslint/eslint-jp#6) (#10658) (Toru Nagashima) +* 87cd344 Docs: Make marking a default option consistent with other rules (#10650) (Iulian Onofrei) +* 0cb5e3e Chore: Replace some function application with spread operators (#10645) (Kevin Partington) +* b6daf0e Docs: Remove superfluous section from no-unsafe-negation (#10648) (Iulian Onofrei) +* e1a3cac Chore: rm deprecated experimentalObjectRestSpread option in tests (#10647) (薛定谔的猫) + +v5.2.0 - July 20, 2018 + +* 81283d0 Update: Cache files that failed linting (fixes #9948) (#10571) (Kevin Partington) +* 13cc63e Upgrade: ignore@4.0.2 (#10619) (Rouven Weßling) +* ac77a80 Chore: Fixing a call to Object.assign.apply in Linter (#10629) (Kevin Partington) +* 761f802 Upgrade: eslint-plugin-node to 7.0.1 (#10612) (Toru Nagashima) +* c517b2a Build: fix npm run perf failing(fixes #10577) (#10607) (薛定谔的猫) +* e596939 Chore: fix redundant equality check (#10617) (Toru Nagashima) +* 9f93d5f Docs: Updated Working with Custom Formatters (fixes #9950) (#10592) (Marla Foreman) +* 9aaf195 Chore: Extract lint result cache logic (refs #9948) (#10562) (Kevin Partington) +* 80b296e Build: package.json update for eslint-config-eslint release (ESLint Jenkins) +* e4e7ff2 Chore: fix error message in eslint-config-eslint (#10588) (薛定谔的猫) +* 1e88170 Chore: Move lib/logging and lib/timing to lib/util/ (refs #10559) (#10579) (Kevin Partington) +* 64dfa21 Build: Fix prerelease logic in blog post generation (fixes #10578) (#10581) (Kevin Partington) +* 0faf633 Chore: Simplify helper method in Linter tests (#10580) (Kevin Partington) + +v5.1.0 - July 8, 2018 + +* 7328f99 Build: package.json update for eslint-config-eslint release (ESLint Jenkins) +* b161f6b Build: Include prerelease install info in release blog post (#10463) (Kevin Partington) +* b2df738 Fix: prefer-object-spread duplicated comma (fixes #10512, fixes #10532) (#10524) (Toru Nagashima) +* d8c3a25 Fix: wrap-regex doesn't work in some expression(fixes #10573) (#10576) (薛定谔的猫) +* 114f42e Docs: Clarify option defaults in max-lines-per-function docs (#10569) (Chris Harwood) +* 63f36f7 Fix: sort-keys in an object that contains spread (fixes #10261) (#10495) (katerberg) +* 601a5c4 Fix: Prefer-const rule crashing on array destructuring (fixes #10520) (#10527) (Michael Mason) +* 143890a Update: Adjust grammar of error/warnings fixable (#10546) (Matt Mischuk) +* 8ee39c5 Chore: small refactor config-validator (#10565) (薛定谔的猫) +* 100f1be Docs: add note about release issues to readme (#10572) (Teddy Katz) +* 02efeac Fix: do not fail on nested unknown operators (#10561) (Rubén Norte) +* 92b19ca Chore: use eslintrc overrides(dogfooding) (#10566) (薛定谔的猫) +* 076a6b6 Docs: add actionable fix to no-irregular-whitespace (#10558) (Matteo Collina) +* de663ec Docs: Only successfully linted files are cached (fixes #9802) (#10557) (Kevin Partington) +* f0e22fc Upgrade: globals@11.7.0 (#10497) (薛定谔的猫) +* 8a2ff2c Docs: adding a section about disable rules for some files (#10536) (Wellington Soares) +* f22a3f8 Docs: fix a word in no-implied-eval (#10539) (Dan Homola) +* 20d8bbd Docs: add missing paragraph about "custom parsers" (#10547) (Pig Fang) +* b7addf6 Update: deprecate no-catch-shadow (fixes #10466) (#10526) (Toru Nagashima) +* e862dc3 Fix: Remove autofixer for no-debugger (fixes #10242) (#10509) (Teddy Katz) + +v5.0.1 - June 25, 2018 + +* 196c102 Fix: valid-jsdoc should allow optional returns for async (fixes #10386) (#10480) (Mark Banner) +* 4c823bd Docs: Fix max-lines-per-function correct code's max value (#10513) (Rhys Bower) + +v5.0.0 - June 22, 2018 + +* 0feedfd New: Added max-lines-per-function rule (fixes #9842) (#10188) (peteward44) +* daefbdb Upgrade: eslint-scope and espree to 4.0.0 (refs #10458) (#10500) (Brandon Mills) +* 077358b Docs: no-process-exit: recommend process.exitCode (#10478) (Andres Kalle) +* f93d6ff Fix: do not fail on unknown operators from custom parsers (fixes #10475) (#10476) (Rubén Norte) +* 05343fd Fix: add parens for yield statement (fixes #10432) (#10468) (Pig Fang) +* d477c5e Fix: check destructuring for "no-shadow-restricted-names" (fixes #10467) (#10470) (Pig Fang) +* 7a7580b Update: Add considerPropertyDescriptor option to func-name-matching (#9078) (Dieter Luypaert) +* e0a0418 Fix: crash on optional catch binding (#10429) (Toru Nagashima) +* de4dba9 Docs: styling team members (#10460) (薛定谔的猫) +* 5e453a3 Docs: display team members in tables. (#10433) (薛定谔的猫) +* b1895eb Docs: Restore intentional spelling mistake (#10459) (Wilfred Hughes) + +v5.0.0-rc.0 - June 9, 2018 + +* abf400d Update: Add ignoreDestructing option to camelcase rule (fixes #9807) (#10373) (Andrew Lunny) +* e2b394d Upgrade: espree and eslint-scope to rc versions (#10457) (Kevin Partington) +* a370da2 Chore: small opt to improve readability (#10241) (薛定谔的猫) +* 640bf07 Update: Fixes multiline no-warning-comments rule. (fixes #9884) (#10381) (Scott Stern) +* 831c39a Build: Adding rc release script to package.json (#10456) (Kevin Partington) +* dc4075e Update: fix false negative in no-use-before-define (fixes #10227) (#10396) (Toru Nagashima) +* 3721841 Docs: Add new experimental syntax policy to README (fixes #9804) (#10408) (Kevin Partington) +* d0aae3c Docs: Create docs landing page (#10453) (Kevin Partington) +* fe8bec3 Fix: fix writing config file when `source` is `prompt` (#10422) (Pig Fang) +* 917108d Update: Add requireParamType option to valid-jsdoc (fixes #6753) (#10220) (Tomasz Sterna) +* 1984c21 Docs: move custom parsers docs into a page (fixes #9919) (#10431) (Pig Fang) +* 400d4b5 Docs: Add rest and spread operator changes to migration guide (#10416) (Yannick Croissant) +* e7bdd02 Upgrade: Consume espree@4.0.0-alpha.1 (#10410) (Kevin Partington) +* 3e9f33a Fix: prevent crashing from JSON parsing error (fixes #10364) (#10376) (Pig Fang) +* 636457d Fix: parse later ES files in `eslint --init` (fixes #10003) (#10378) (Pig Fang) + +v5.0.0-alpha.4 - May 28, 2018 + +* ce3e62a Docs: remove test coverage badge (#10407) (薛定谔的猫) +* 240c1a4 Fix: prefer-const object destructuring false positive (fixes #9108) (#10368) (Pig Fang) +* 93c9a52 Update: config-validator should validate overrides (#10357) (Toru Nagashima) +* c2e0398 Update: Improves the prefer-object-spread rule by removing extraneous visitors (#10351) (Sharmila Jesupaul) +* d848949 Update: Support JSXFragment node (fixes #9662) (#9664) (Clement Hoang) +* f268128 Build: add Node v10 to travis (#10262) (alberto) +* 9c922ce Update: Add "consistent" option to array-element-newline (fixes #9457) (#10355) (Pig Fang) +* 65bce3a Fix: ensure --stdin flag works when stdin is piped asynchronously (#10393) (Teddy Katz) +* b9b23a9 Chore: rm unused argument (#10400) (薛定谔的猫) +* 8b7a70c Fix: handle one-var with no semicolon (fixes #10330) (#10371) (Malcolm Groves) +* 465e615 New: prompt users before installing dependencies (#10353) (Pig Fang) +* e25fc22 Chore: remove assert.doesNotThrow in tests (#10199) (Ruben Bridgewater) +* fb148aa Fix: allow no tokens after `return` keyword (fixes #10372) (#10379) (Pig Fang) +* 074bc1c Docs: polish for max-classes-per-file rule (#10377) (Pig Fang) +* a812845 Fix: allow array spread for prefer-object-spread rule (fixes #10344) (#10347) (Pig Fang) +* 448fc52 Docs: Update link to Integrations / Build tools / Start (#10354) (Kir Belevich) +* 4e5e9be Chore: avoid unnecessary filesystem accesses during config search (#10359) (Teddy Katz) +* 363da01 Chore: avoid code duplication in rule severity checking (#10358) (Teddy Katz) + +v5.0.0-alpha.3 - May 11, 2018 + +* 1a6b399 New: Adds prefer-object-spread rule (refs: #7230) (#9955) (Sharmila Jesupaul) +* c4109b2 New: add max-classes-per-file rule (#10163) (James Garbutt) +* 41f0f6e Breaking: report multiline eslint-disable-line directives (fixes #10334) (#10335) (Teddy Katz) +* 4ccd25a Chore: add eslint-plugin-node to eslint-config-eslint(fixes #10319) (#10320) (薛定谔的猫) +* 82757b2 Docs: Adding a little guidance to rule documentation (#10301) (Justin) +* 09dde26 Breaking: new object-curly-newline/no-self-assign default (fixes #10215) (#10337) (Teddy Katz) +* d65f11d Fix: correct comma fix in spare array (fixes #10273) (#10329) (Malcolm Groves) +* c343d86 Fix: do not autofix octal escape sequence (fixes #10031) (#10240) (Malcolm Groves) +* 514013c New: Add `globInputPaths` CLIEngine option (fixes #9972) (#10191) (Pierre Vanduynslager) +* 02e7b28 Chore: upgrade deps (#10339) (薛定谔的猫) +* 1397179 Chore: unskip test for scope analysis (#10336) (Teddy Katz) +* e5b33be Update: Add --fix for one-var rule (refs #9072) (#10040) (Sebastian Malton) +* 99b842d Chore: upgrade mock-fs@4.5.0 (#10325) (Tim Schaub) +* fe91859 Chore: Update issue templates with new format (#10309) (Ilya Volodin) +* 2f30aa5 Docs: add a better vim linting engine (#10292) (Jon Smithers) +* df2c1fb Docs: improve formatter guide (refs #9550) (#10294) (Dominic Lee) +* f7330c1 Chore: Add ESLint path to plugin-missing message (#10283) (Kevin Partington) +* bb6090f Fix: Throw error when --ignore-path not a file (fixes #10076) (#10205) (Malcolm Groves) +* 1b6b2b2 Build: remove trailing spaces in blogpost template (#10280) (Teddy Katz) +* a960d69 Docs: remove outdated notes from migration guide (#10279) (Teddy Katz) + +v5.0.0-alpha.2 - April 27, 2018 + +* 510ca8b Docs: make grammatical tweaks in migration guide (#10278) (Teddy Katz) +* 02e44a5 Breaking: remove TDZ scopes (fixes #10245) (#10270) (Toru Nagashima) +* c74933b Breaking: remove extra check in getScope (fixes #10246, fixes #10247) (#10252) (Toru Nagashima) +* 7c2e83a Chore: improve tests and checking for equality (#10182) (Ruben Bridgewater) +* 8799972 Docs: make template link wording more clear (#10219) (David Luzar) +* 8b7c6ea Breaking: report fatal error for linting nonexistent files (fixes #7390) (#10143) (Teddy Katz) +* 9100819 Breaking: fix plugin resolver in extends (fixes #9904) (#10236) (Toru Nagashima) +* c45f1d0 Breaking: add rules to recommended (fixes #8865) (#10158) (薛定谔的猫) +* 1d443a0 Fix: valid-jsdoc does not know async function returns (fixes #9881) (#10161) (Rachael Sim) +* a82cbea Update: re-enable experimentalObjectRestSpread (fixes #9990) (#10230) (Toru Nagashima) +* f9c7371 Fix: do not autofix object-shorthand with comments (fixes #10038) (#10238) (Malcolm Groves) +* 4672b56 Docs: Correct wording in the `smart-tabs` docs page (#10277) (Jed Fox) +* b32d1f4 Chore: upgrade eslump@1.6.2 (#10258) (薛定谔的猫) +* 7938bf1 Chore: update eslint-fuzzer ecmaVersion to 2018 (#10255) (薛定谔的猫) +* a2953ec Chore: small opt to improve readability (#10225) (薛定谔的猫) +* 85a5191 Docs: Update JSCS FAQ (#10221) (alberto) +* 8e89d5c Docs: Fix typo (#10223) (alberto) +* c0c331e Docs: Add Prettier to FAQ (#10222) (alberto) +* 2443627 Docs: add backticks in getter-return (#10218) (薛定谔的猫) +* 74bb5b5 Docs: Fix misspelling in changelog (#10216) (Kevin Partington) + +v5.0.0-alpha.1 - April 13, 2018 + +* b2a48a9 Breaking: stop using fake `context._linter` property (fixes #10140) (#10209) (Teddy Katz) +* a039956 Breaking: remove deprecated browser/jest/node globals (fixes #10141) (#10210) (Teddy Katz) +* 98f1cad Docs: update migration guide with latest changes (#10212) (Teddy Katz) +* 2e60017 Chore: remove concat-stream dependency (#10173) (Teddy Katz) +* 7f69f11 Chore: rearrange init options. (#10131) (薛定谔的猫) +* f595fd8 Upgrade: upgrade deps (#10184) (alberto) +* 71167be Docs: fix wrong config in id-length (#10196) (薛定谔的猫) +* 81629d2 Chore: enable rest/spread rules on ESLint codebase (#10211) (Teddy Katz) +* 2324570 Breaking: no-unused-vars reports all after-used params (fixes #9909) (#10119) (Kevin Partington) +* 7765fc4 Upgrade: ajv@^6.0.1, still using json schema draft 04 (#9856) (Kevin Partington) +* b77846d Breaking: drop supporting Node.js 4 (fixes #10052) (#10074) (薛定谔的猫) +* cd34d44 Chore: avoid modifying global state when tests fail (#10201) (Teddy Katz) +* 731da1e Docs: fix code in correct example. (#10195) (薛定谔的猫) +* 3780915 Docs: fix some small errors in examples (#10194) (薛定谔的猫) +* 869c9f5 Upgrade: babelify (#10185) (alberto) +* 218ee57 Fix: report no-case-declarations from declarations (fixes #10048) (#10167) (Carlo Abelli) +* b7ee1ed Upgrade: upgrade devdeps (#10178) (alberto) +* db1a582 Chore: Add debug logging for CLI args as they came in (#10174) (Kevin Partington) +* f3a0291 Upgrade: Update dependencies. (#10168) (alberto) +* 7d6e052 Upgrade: esquery@^1.0.1 (fixes #8733) (#10170) (Kevin Partington) +* 1e7252f Docs: Add more related rules for object-curly-spacing (#10175) (Saugat Acharya) +* e5cf9cc Docs: Reorder README sections (#10172) (alberto) +* c85578f Chore: Remove `esprima-fb` dependency. (#10171) (alberto) +* d0dc2e3 Docs: Add Missing Quotes (#10162) (Samarth Verma) +* 7a63bfa Upgrade: eslint-release to v0.11.1 (#10156) (Teddy Katz) +* b7a1a7a Build: Gensite creates prerelease dirs if needed (#10154) (Brandon Mills) + +v5.0.0-alpha.0 - March 30, 2018 + +* f4b3af5 Breaking: Upgrade to Espree v4 alpha (refs #9990) (#10152) (Brandon Mills) +* 3351129 Docs: add v5.0.0 migration guide (fixes #10142) (#10147) (Teddy Katz) +* f2f98dd Build: make prerelease script publish to GitHub/website (#10151) (Teddy Katz) +* d440e84 Breaking: support @scope shorthand in plugins (fixes #9903) (#9905) (Toru Nagashima) +* 462b058 Update: Include debugging information when rule throws error (#9742) (Patrick Hayes) +* 9a020dc Chore: refactor --no-ignore flag logic (#10146) (Teddy Katz) +* 4f61a0d Chore: add noopener/noreferrer (薛定谔的猫) +* 65cc834 Docs: Ensure CLI doc sections match command line help order (#10144) (Kevin Partington) +* 9c79174 Docs: Update capitalized-comments with missing letters (fixes #10135) (#10134) (jasonfry) +* 9e66bfb Docs: remove eslint vs jshint from faq (#10108) (alberto) +* 692e383 Docs: Add modified variable examples for no-loop-func (fixes #9527) (#10098) (Rachael Sim) +* a9ee9ae Breaking: require rules to provide report messages (fixes #10011) (#10057) (Teddy Katz) +* 837edc7 Chore: Uncommented test for empty program for no-invalid-meta (#10046) (Kevin Partington) +* c383bc5 Breaking: Make require('eslint').linter non-enumerable (fixes #9270) (#9692) (Jed Fox) +* 4eaebe5 Breaking: set `parent` of AST nodes before rules run (fixes #9122) (#10014) (Teddy Katz) +* 91ece32 Breaking: remove special exception for linting empty files (fixes #9534) (#10013) (Teddy Katz) +* 27e3f24 Breaking: remove `source` property from linting messages (fixes #7358) (#10012) (Teddy Katz) +* e4c3b3c Breaking: use an exit code of 2 for fatal config problems (fixes #9384) (#10009) (Teddy Katz) +* 2a7ecaa Breaking: Use strict equality in RuleTester comparisons (fixes #9417) (#10008) (Teddy Katz) +* 0bc4a38 Fix: Make rule-tester strictly check messageId. (ref #9890) (#9908) (Jacques Favreau) +* ea6fb17 Update: Make no-cond-assign work for ternaries (fixes #10091) (#10109) (Aaron Harper) + +v4.19.1 - March 21, 2018 + +* 3ff5d11 Fix: no-invalid-regexp not understand variable for flags (fixes #10112) (#10113) (薛定谔的猫) +* abc765c Fix: object-curly-newline minProperties w/default export (fixes #10101) (#10103) (Kevin Partington) +* 6f9e155 Docs: Update ambiguous for...in example for guard-for-in (#10114) (CJ R) +* 0360cc2 Chore: Adding debug logs on successful plugin loads (#10100) (Kevin Partington) +* a717c5d Chore: Adding log at beginning of unit tests in Makefile.js (#10102) (Kevin Partington) + +v4.19.0 - March 16, 2018 + +* 55a1593 Update: consecutive option for one-var (fixes #4680) (#9994) (薛定谔的猫) +* 8d3814e Fix: false positive about ES2018 RegExp enhancements (fixes #9893) (#10062) (Toru Nagashima) +* 935f4e4 Docs: Clarify default ignoring of node_modules (#10092) (Matijs Brinkhuis) +* 72ed3db Docs: Wrap `Buffer()` in backticks in `no-buffer-constructor` rule description (#10084) (Stephen Edgar) +* 3aded2f Docs: Fix lodash typos, make spacing consistent (#10073) (Josh Smith) +* e33bb64 Chore: enable no-param-reassign on ESLint codebase (#10065) (Teddy Katz) +* 66a1e9a Docs: fix possible typo (#10060) (Vse Mozhet Byt) +* 2e68be6 Update: give a node at least the indentation of its parent (fixes #9995) (#10054) (Teddy Katz) +* 72ca5b3 Update: Correctly indent JSXText with trailing linebreaks (fixes #9878) (#10055) (Teddy Katz) +* 2a4c838 Docs: Update ECMAScript versions in FAQ (#10047) (alberto) + +v4.18.2 - March 2, 2018 + +* 6b71fd0 Fix: table@4.0.2, because 4.0.3 needs "ajv": "^6.0.1" (#10022) (Mathieu Seiler) +* 3c697de Chore: fix incorrect comment about linter.verify return value (#10030) (Teddy Katz) +* 9df8653 Chore: refactor parser-loading out of linter.verify (#10028) (Teddy Katz) +* f6901d0 Fix: remove catastrophic backtracking vulnerability (fixes #10002) (#10019) (Jamie Davis) +* e4f52ce Chore: Simplify dataflow in linter.verify (#10020) (Teddy Katz) +* 33177cd Chore: make library files non-executable (#10021) (Teddy Katz) +* 558ccba Chore: refactor directive comment processing (#10007) (Teddy Katz) +* 18e15d9 Chore: avoid useless catch clauses that just rethrow errors (#10010) (Teddy Katz) +* a1c3759 Chore: refactor populating configs with defaults in linter (#10006) (Teddy Katz) +* aea07dc Fix: Make max-len ignoreStrings ignore JSXText (fixes #9954) (#9985) (Rachael Sim) + +v4.18.1 - February 20, 2018 + +* f417506 Fix: ensure no-await-in-loop reports the correct node (fixes #9992) (#9993) (Teddy Katz) +* 3e99363 Docs: Fixed typo in key-spacing rule doc (#9987) (Jaid) +* 7c2cd70 Docs: deprecate experimentalObjectRestSpread (#9986) (Toru Nagashima) + +v4.18.0 - February 16, 2018 + +* 70f22f3 Chore: Apply memoization to config creation within glob utils (#9944) (Kenton Jacobsen) +* 0e4ae22 Update: fix indent bug with binary operators/ignoredNodes (fixes #9882) (#9951) (Teddy Katz) +* 47ac478 Update: add named imports and exports for object-curly-newline (#9876) (Nicholas Chua) +* e8efdd0 Fix: support Rest/Spread Properties (fixes #9885) (#9943) (Toru Nagashima) +* f012b8c Fix: support Async iteration (fixes #9891) (#9957) (Toru Nagashima) +* 74fa253 Docs: Clarify no-mixed-operators options (fixes #9962) (#9964) (Ivan Hayes) +* 426868f Docs: clean up key-spacing docs (fixes #9900) (#9963) (Abid Uzair) +* 4a6f22e Update: support eslint-disable-* block comments (fixes #8781) (#9745) (Erin) +* 777283b Docs: Propose fix typo for function (#9965) (John Eismeier) +* bf3d494 Docs: Fix typo in max-len ignorePattern example. (#9956) (Tim Martin) +* d64fbb4 Docs: fix typo in prefer-destructuring.md example (#9930) (Vse Mozhet Byt) +* f8d343f Chore: Fix default issue template (#9946) (Kai Cataldo) + +v4.17.0 - February 2, 2018 + +* 1da1ada Update: Add "multiline" type to padding-line-between-statements (#8668) (Matthew Bennett) +* bb213dc Chore: Use messageIds in some of the core rules (#9648) (Jed Fox) +* 1aa1970 Docs: remove outdated rule naming convention (#9925) (Teddy Katz) +* 3afaff6 Docs: Add prefer-destructuring variable reassignment example (#9873) (LePirlouit) +* d20f6b4 Fix: Typo in error message when running npm (#9866) (Maciej Kasprzyk) +* 51ec6a7 Docs: Use GitHub Multiple PR/Issue templates (#9911) (Kai Cataldo) +* dc80487 Update: space-unary-ops uses astUtils.canTokensBeAdjacent (fixes #9907) (#9906) (Kevin Partington) +* 084351b Docs: Fix the messageId example (fixes #9889) (#9892) (Jed Fox) +* 9cbb487 Docs: Mention the `globals` key in the no-undef docs (#9867) (Dan Dascalescu) + +v4.16.0 - January 19, 2018 + +* e26a25f Update: allow continue instead of if wrap in guard-for-in (fixes #7567) (#9796) (Michael Ficarra) +* af043eb Update: Add NewExpression support to comma-style (#9591) (Frazer McLean) +* 4f898c7 Build: Fix JSDoc syntax errors (#9813) (Matija Marohnić) +* 13bcf3c Fix: Removing curly quotes in no-eq-null report message (#9852) (Kevin Partington) +* b96fb31 Docs: configuration hierarchy for CLIEngine options (fixes #9526) (#9855) (PiIsFour) +* 8ccbdda Docs: Clarify that -c configs merge with `.eslintrc.*` (fixes #9535) (#9847) (Kevin Partington) +* 978574f Docs: Fix examples for no-useless-escape (#9853) (Toru Kobayashi) +* cd5681d Chore: Deactivate consistent-docs-url in internal rules folder (#9815) (Kevin Partington) +* 2e87ddd Docs: Sync messageId examples' style with other examples (#9816) (Kevin Partington) +* 1d61930 Update: use doctrine range information in valid-jsdoc (#9831) (Teddy Katz) +* 133336e Update: fix indent behavior on template literal arguments (fixes #9061) (#9820) (Teddy Katz) +* ea1b15d Fix: avoid crashing on malformed configuration comments (fixes #9373) (#9819) (Teddy Katz) +* add1e70 Update: fix indent bug on comments in ternary expressions (fixes #9729) (#9818) (Teddy Katz) +* 6a5cd32 Fix: prefer-destructuring error with computed properties (fixes #9784) (#9817) (Teddy Katz) +* 601f851 Docs: Minor modification to code comments for clarity (#9821) (rgovind92) +* b9da067 Docs: fix misleading info about RuleTester column numbers (#9830) (Teddy Katz) +* 2cf4522 Update: Rename and deprecate object-property-newline option (#9570) (Jonathan Pool) +* acde640 Docs: Add ES 2018 to Configuring ESLint (#9829) (Kai Cataldo) +* ccfce15 Docs: Minor tweaks to working with rules page (#9824) (Kevin Partington) +* 54b329a Docs: fix substitution of {{ name }} (#9822) (Andres Kalle) + +v4.15.0 - January 6, 2018 + +* 6ab04b5 New: Add context.report({ messageId }) (fixes #6740) (#9165) (Jed Fox) +* fc7f404 Docs: add url to each of the rules (refs #6582) (#9788) (Patrick McElhaney) +* fc44da9 Docs: fix sort-imports rule block language (#9805) (ferhat elmas) +* 65f0176 New: CLIEngine#getRules() (refs #6582) (#9782) (Patrick McElhaney) +* c64195f Update: More detailed assert message for rule-tester (#9769) (Weijia Wang) +* 9fcfabf Fix: no-extra-parens false positive (fixes: #9755) (#9795) (Erin) +* 61e5fa0 Docs: Add table of contents to Node.js API docs (#9785) (Patrick McElhaney) +* 4c87f42 Fix: incorrect error messages of no-unused-vars (fixes #9774) (#9791) (akouryy) +* bbabf34 Update: add `ignoreComments` option to `indent` rule (fixes #9018) (#9752) (Kevin Partington) +* db431cb Docs: HTTP -> HTTPS (fixes #9768) (#9768) (Ronald Eddy Jr) +* cbf0fb9 Docs: describe how to feature-detect scopeManager/visitorKeys support (#9764) (Teddy Katz) +* f7dcb70 Docs: Add note about "patch release pending" label to maintainer guide (#9763) (Teddy Katz) + +v4.14.0 - December 23, 2017 + +* be2f57e Update: support separate requires in one-var. (fixes #6175) (#9441) (薛定谔的猫) +* 370d614 Docs: Fix typos (#9751) (Jed Fox) +* 8196c45 Chore: Reorganize CLI options and associated docs (#9758) (Kevin Partington) +* 75c7419 Update: Logical-and is counted in `complexity` rule (fixes #8535) (#9754) (Kevin Partington) +* eb4b1e0 Docs: reintroduce misspelling in `valid-typeof` example (#9753) (Teddy Katz) +* ae51eb2 New: Add allowImplicit option to array-callback-return (fixes #8539) (#9344) (James C. Davis) +* e9d5dfd Docs: improve no-extra-parens formatting (#9747) (Rich Trott) +* 37d066c Chore: Add unit tests for overrides glob matching. (#9744) (Robert Jackson) +* 805a94e Chore: Fix typo in CLIEngine test name (#9741) (@scriptdaemon) +* 1c2aafd Update: Improve parser integrations (fixes #8392) (#8755) (Toru Nagashima) +* 4ddc131 Upgrade: debug@^3.1.0 (#9731) (Kevin Partington) +* f252c19 Docs: Make the lint message `source` property a little more subtle (#9735) (Jed Fox) +* 5a5c23c Docs: fix the link to contributing page (#9727) (Victor Hom) +* f44ce11 Docs: change beginner to good first issue label text (#9726) (Victor Hom) +* 14baa2e Chore: improve arrow-body-style error message (refs #5498) (#9718) (Teddy Katz) +* f819920 Docs: fix typos (#9723) (Thomas Broadley) +* 43d4ba8 Fix: false positive on rule`lines-between-class-members` (fixes #9665) (#9680) (sakabar) + +v4.13.1 - December 11, 2017 + +* b72dc83 Fix: eol-last allow empty-string to always pass (refs #9534) (#9696) (Kevin Partington) +* d80aa7c Fix: camelcase destructure leading/trailing underscore (fixes #9700) (#9701) (Kevin Partington) +* d49d9d0 Docs: Add missing period to the README (#9702) (Kevin Partington) +* 4564fe0 Chore: no-invalid-meta crash if no export assignment (refs #9534) (#9698) (Kevin Partington) + +v4.13.0 - December 8, 2017 + +* 256481b Update: update handling of destructuring in camelcase (fixes #8511) (#9468) (Erin) +* d067ae1 Docs: Don’t use undocumented array-style configuration for max-len (#9690) (Jed Fox) +* 1ad3091 Chore: fix test-suite to work with node master (#9688) (Myles Borins) +* cdb1488 Docs: Adds an example with try/catch. (#9672) (Jaap Taal) + +v4.12.1 - November 30, 2017 + +* 1e362a0 Revert "Fix: Use XML 1.1 on XML formatters (fixes #9607) (#9608)" (#9667) (Kevin Partington) + +v4.12.0 - November 25, 2017 + +* 76dab18 Upgrade: doctrine@^2.0.2 (#9656) (Kevin Partington) +* 28c9c8e New: add a Linter#defineParser function (#9321) (Ives van Hoorne) +* 5619910 Update: Add autofix for `sort-vars` (#9496) (Trevin Hofmann) +* 71eedbf Update: add `beforeStatementContinuationChars` to semi (fixes #9521) (#9594) (Toru Nagashima) +* 4118f14 New: Adds implicit-arrow-linebreak rule (refs #9510) (#9629) (Sharmila Jesupaul) +* 208fb0f Fix: Use XML 1.1 on XML formatters (fixes #9607) (#9608) (Daniel Reigada) +* 6e04f14 Upgrade: `globals` to 11.0.1 (fixes #9614) (#9632) (Toru Nagashima) +* e13d439 Fix: space-in-parens crash (#9655) (Toru Nagashima) +* 92171cc Docs: Updating migration guide for single-line disable (#9385) (Justin Helmer) +* f39ffe7 Docs: remove extra punctuation from readme (#9640) (Teddy Katz) +* a015234 Fix: prefer-destructuring false positive on "super" (fixes #9625) (#9626) (Kei Ito) +* 0cf081e Update: add importNames option to no-restricted-imports (#9506) (Benjamin R Gibson) +* 332c214 Docs: Add @platinumazure to TSC (#9618) (Ilya Volodin) + +v4.11.0 - November 10, 2017 + +* d4557a6 Docs: disallow use of the comma operator using no-restricted-syntax (#9585) (薛定谔的猫) +* d602f9e Upgrade: espree v3.5.2 (#9611) (Kai Cataldo) +* 4def876 Chore: avoid handling rules instances in config-validator (#9364) (Teddy Katz) +* fe5ac7e Chore: fix incorrect comment in safe-emitter.js (#9605) (Teddy Katz) +* 6672fae Docs: Fixed a typo on lines-between-class-members doc (#9603) (Moinul Hossain) +* 980ecd3 Chore: Update copyright and license info (#9599) (薛定谔的猫) +* cc2c7c9 Build: use Node 8 in appveyor (#9595) (薛定谔的猫) +* 2542f04 Docs: Add missing options for `lines-around-comment` (#9589) (Clément Fiorio) +* b6a7490 Build: ensure fuzzer tests get run with `npm test` (#9590) (Teddy Katz) +* 1073bc5 Build: remove shelljs-nodecli (refs #9533) (#9588) (Teddy Katz) +* 7e3bf6a Fix: edge-cases of semi-style (#9560) (Toru Nagashima) +* e5a37ce Fix: object-curly-newline for flow code (#9458) (Tiddo Langerak) +* 9064b9c Chore: add equalTokens in ast-utils. (#9500) (薛定谔的猫) +* b7c5b19 Fix: Correct [object Object] output of error.data. (#9561) (Jonathan Pool) +* 51c8cf0 Docs: Disambiguate definition of Update tag (#9584) (Jonathan Pool) +* afc3c75 Docs: clarify what eslint-config-eslint is (#9582) (Teddy Katz) +* aedae9d Docs: fix spelling in valid-typeof example (#9574) (Maksim Degtyarev) +* 4c5aaf3 Docs: Fix typo in no-underscore-dangle rule (#9567) (Fabien Lucas) +* 3623600 Chore: upgrade ajv@5.3.0 (#9557) (薛定谔的猫) +* 1b606cd Chore: Remove an indirect dependency on jsonify (#9444) (Rouven Weßling) +* 4d7d7ab Update: Resolve npm installed formatters (#5900) (#9464) (Tom Erik Støwer) +* accc490 Fix: Files with no failures get "passing" testcase (#9547) (Samuel Levy) +* ab0f66d Docs: Add examples to better show rule coverage. (#9548) (Jonathan Pool) +* 88d2303 Chore: Add object-property-newline tests to increase coverage. (#9553) (Jonathan Pool) +* 7f37b1c Build: test Node 9 on Travis (#9556) (Teddy Katz) +* acccfbd Docs: Minor rephrase in `no-invalid-this`. (#9542) (Francisc) +* 8f9c0fe Docs: improve id-match usage advice (#9544) (Teddy Katz) +* a9606a3 Fix: invalid tests with super (fixes #9539) (#9545) (Teddy Katz) +* 8e1a095 Chore: enable a modified version of multiline-comment-style on codebase (#9452) (Teddy Katz) +* cb60285 Chore: remove commented test for HTML formatter (#9532) (Teddy Katz) +* 06b491e Docs: fix duplicate entries in changelog (#9530) (Teddy Katz) +* 2224733 Chore: use eslint-plugin-rulesdir instead of --rulesdir for self-linting (#9164) (Teddy Katz) +* 9cf4ebe Docs: add .md to link(for github users) (#9529) (薛定谔的猫) + +v4.10.0 - October 27, 2017 + +* bb6e60a Fix: Improve the doc for no-restricted-modules rule (fixes #9437) (#9495) (vibss2397) +* c529de9 Docs: Amend rule document to correct and complete it (refs #6251). (#9498) (Jonathan Pool) +* f9c6673 Chore: Add tests to cover array and object values and leading commas. (#9502) (Jonathan Pool) +* 9169258 Chore: remove `npm run check-commit` script (#9513) (Teddy Katz) +* 7d390b2 Docs: Revise contributor documentation on issue labels. (#9469) (Jonathan Pool) +* d80b9d0 Fix: no-var don't fix globals (fixes #9520) (#9525) (Toru Nagashima) +* b8aa071 Fix: allow linting the empty string from stdin (fixes #9515) (#9517) (Teddy Katz) +* 350a72c Chore: regex.test => string.startsWith (#9518) (薛定谔的猫) +* de0bef4 Chore: remove obsolete eslintbot templates (#9512) (Teddy Katz) +* 720b6d5 Docs: Update ISSUE_TEMPLATE.md (#9504) (薛定谔的猫) +* 2fa64b7 Fix: should not convert non-consecutive line comments to a single blo… (#9475) (薛定谔的猫) +* 9725146 Fix: multiline-comment-style fix produces invalid code (fixes #9461). (#9463) (薛定谔的猫) +* b12cff8 Fix: Expected order of jsdoc tags (fixes #9412) (#9451) (Orlando Wenzinger) +* f054ab5 Docs: add `.md` to link (for github users) (#9501) (薛定谔的猫) +* 5ed9cfc Docs: Correct violations of “Variable Declarations” in Code Conventions (#9447) (Jonathan Pool) +* 3171097 Docs: Clears confusion on usage of global and local plugins.(#9492) (Vasili Sviridov) +* 3204773 Chore: enable max-len. (#9414) (薛定谔的猫) +* 0f71fef Docs: Unquote booleans in lines-between-class-members docs (#9497) (Brandon Mills) +* b3d7532 Docs: use consistent terminology & fix link etc. (#9490) (薛定谔的猫) +* 87db8ae Docs: Fix broken links (#9488) (gpiress) +* 51bdb2f Docs: Incorrect link to related rule (#9477) (Gavin King) +* 1a962e8 Docs: Add FAQ for when ESLint cannot find plugin (#9467) (Kevin Partington) +* 8768b2d Fix: multiline-comment-style autofixer added trailing space (#9454) (Teddy Katz) +* e830aa1 Fix: multiline-comment-style reports block comments followed by code (#9450) (Teddy Katz) +* b12e5fe Docs: Repair broken links and add migration links. (#9473) (Jonathan Pool) +* eca01ed Docs: Add missing info about special status of home-dir config files. (#9472) (Jonathan Pool) +* eb8cfb1 Fix: change err report in constant condition (fixes #9398) (#9436) (Victor Hom) +* da77eb4 Chore: Revise no-config-file test to prevent false failure. (#9443) (Jonathan Pool) +* 47e5f6f Docs: ensure "good commit message" examples actually follow guidelines (#9466) (Teddy Katz) +* ebb530d Update: Don't ignore comments (no-trailing-spaces) (#9416) (Chris van Marle) +* 5012661 Build: fix `npm run profile` script (fixes #9397) (#9455) (Teddy Katz) +* ecac0fd Docs: Remove blockBindings references (#9446) (Jan Pilzer) +* 0b89865 Chore: ensure tests for internal rules get run (#9453) (Teddy Katz) +* 052c504 Docs: suggest deleting branches after merging PRs (#9449) (Teddy Katz) +* b31e55a Chore: move internal rules out of lib/ (#9448) (Teddy Katz) +* a7521e3 Docs: improve examples for multiline-comment-style (#9440) (Teddy Katz) + +v4.9.0 - October 14, 2017 + +* 85388fb Fix: Correct error and test messages to fit config search path (#9428) (Jonathan Pool) +* 62a323c Fix: Add class options for `lines-around-comment` (fixes #8564) (#8565) (Ed Lee) +* 8eb4aae New: multiline-comment-style rule (fixes #8320) (#9389) (薛定谔的猫) +* db41408 Chore: avoid applying eslint-env comments twice (#9278) (Teddy Katz) +* febb897 Chore: avoid loose equality assertions (#9415) (Teddy Katz) +* 2247efa Update: Add FunctionExpression to require-jsdoc (fixes #5867) (#9395) (Kai Cataldo) +* 6791d18 Docs: Corrected noun to verb. (#9438) (Jonathan Pool) +* b02fbb6 Update: custom messages for no-restricted-* (refs #8400) (Maja Wichrowska) +* 02732bd Docs: Reorganized to avoid misunderstandings. (#9434) (Jonathan Pool) +* d9466b8 Docs: Correct time forecast for tests. (#9432) (Jonathan Pool) +* f7ed84f Docs: Add instruction re home-directory config files (refs #7729) (#9426) (Jonathan Pool) +* 30d018b Chore: Add Aladdin-ADD & VictorHom to README (#9424) (Kai Cataldo) +* 2d8a303 Docs: fix examples for prefer-numeric-literals (#9155) (Lutz Lengemann) +* d7610f5 Docs: Add jquery warning to prefer-destructuring (#9409) (Thomas Grainger) +* e835dd1 Docs: clarify no-mixed-operators (fixes #8051) (Ruxandra Fediuc) +* 51360c8 Docs: update block-spacing details (fixes #8743) (#9375) (Victor Hom) +* 6767857 Update: fix ignored nodes in indent rule when using tabs (fixes #9392) (#9393) (Robin Houston) +* 37dde77 Chore: Refactor SourceCode#getJSDocComment (#9403) (Kai Cataldo) +* 9fedd51 Chore: Add missing space in blog post template (#9407) (Kevin Partington) +* 7654c99 Docs: add installing prerequisites in readme. (#9401) (薛定谔的猫) +* 786cc73 Update: Add "consistent" option to array-bracket-newline (fixes #9136) (#9206) (Ethan Rutherford) +* e171f6b Docs: add installing prerequisites. (#9394) (薛定谔的猫) +* 74dfc87 Docs: update doc for class-methods-use-this (fixes #8910) (#9374) (Victor Hom) +* b4a9dbf Docs: show console call with no-restricted-syntax (fixes #7806) (#9376) (Victor Hom) +* 8da525f Fix: recognise multiline comments as multiline arrays (fixes #9211) (#9369) (Phil Quinn) +* c581b77 Chore: Error => TypeError (#9390) (薛定谔的猫) +* ee99876 New: lines-between-class-members rule (fixes #5949) (#9141) (薛定谔的猫) +* 9d3f5ad Chore: report unused eslint-disable directives in ESLint codebase (#9371) (Teddy Katz) +* 1167638 Update: add allowElseIf option to no-else-return (fixes #9228) (#9229) (Thomas Grainger) +* 4567ab1 New: Add the fix-dry-run flag (fixes #9076) (#9073) (Rafał Ruciński) + +v4.8.0 - September 29, 2017 + +* 3f2b908 New: add option to report unused eslint-disable directives (fixes #9249) (#9250) (Teddy Katz) +* ff2be59 Fix: dot notation rule failing to catch string template (fixes #9350) (#9357) (Phil Quinn) +* b1372da Chore: remove sourceCode property from Linter (refs #9161) (#9363) (Teddy Katz) +* cef6f8c Docs: remove line about removing rules from semver policy (#9367) (Teddy Katz) +* 06efe87 Fix: Add meta element with charset attribute. (#9365) (H1Gdev) +* 458ca67 Docs: update architecture page (fixes #9337) (#9345) (Victor Hom) +* 1c6bc67 Fix: special EventEmitter keys leak information about other rules (#9328) (Teddy Katz) +* d593e61 Docs: update eslint.org links to use https (#9358) (Teddy Katz) +* 38d0cb2 Fix: fix wrong code-path about try-for-in (fixes #8848) (#9348) (Toru Nagashima) +* 434d9e2 Fix: Invalid font-size property value issue. (#9341) (H1Gdev) +* a7668c2 Chore: Remove unnecessary slice from logging utility (#9343) (Gyandeep Singh) +* 2ff6fb6 Chore: remove unused arguments in codebase (#9340) (Teddy Katz) + +v4.7.2 - September 21, 2017 + +* 4f87732 Fix: Revert setting node.parent early (fixes #9331) (#9336) (Teddy Katz) + +v4.7.1 - September 18, 2017 + +* 08656db Fix: Handle nested disable directive correctly (fixes #9318) (#9322) (Gyandeep Singh) +* 9226495 Revert "Chore: rewrite parseListConfig for a small perf gain." (#9325) (薛定谔的猫) + +v4.7.0 - September 15, 2017 + +* 787b78b Upgrade: Espree v3.5.1 (fixes #9153) (#9314) (Brandon Mills) +* 1488b51 Update: run rules after `node.parent` is already set (fixes #9122) (#9283) (Teddy Katz) +* 4431d68 Docs: fix wrong config in max-len example. (#9309) (薛定谔的猫) +* 7d24dde Docs: Fix code snippet to refer to the correct option (#9313) (Ruben Tytgat) +* 12388d4 Chore: rewrite parseListConfig for a small perf gain. (#9300) (薛定谔的猫) +* ce1f084 Update: fix MemberExpression handling in no-extra-parens (fixes #9156) (jackyho112) +* 0c720a3 Update: allow autofixing when using processors (fixes #7510) (#9090) (Teddy Katz) +* 838df76 Chore: upgrade deps. (#9289) (薛定谔的猫) +* f12def6 Update: indent flatTernary option to handle `return` (fixes #9285) (#9296) (Teddy Katz) +* e220687 Fix: remove autofix for var undef inits (fixes #9231) (#9288) (Victor Hom) +* 002e199 Docs: fix no-restricted-globals wrong config. (#9305) (薛定谔的猫) +* fcfe91a Docs: fix wrong config in id-length example. (#9303) (薛定谔的猫) +* 2731f94 Update: make newline-per-chained-call fixable (#9149) (João Granado) +* 61f1093 Chore: avoid monkeypatching Linter instances in RuleTester (#9276) (Teddy Katz) +* 28929cb Chore: remove Linter#reset (refs #9161) (#9268) (Teddy Katz) +* abc8634 Build: re-run browserify when generating site (#9275) (Teddy Katz) +* 7685fed Fix: IIFE and arrow functions in no-invalid-this (fixes #9126) (#9258) (Toru Nagashima) +* 2b1eba2 Chore: enable eslint-plugin/no-deprecated-context-methods (#9279) (Teddy Katz) +* 981f933 Fix: reuse the AST of source code object in verify (#9256) (Toru Nagashima) +* cd698ba Docs: move RuleTester documentation to Node.js API page (#9273) (Teddy Katz) +* 4ae7ad3 Docs: fix inaccuracy in `npm run perf` description (#9274) (Teddy Katz) +* cad45bd Docs: improve documentation for rule contexts (#9272) (Teddy Katz) +* 3b0c6fd Chore: remove extraneous linter properties (refs #9161) (#9267) (Teddy Katz) +* c3231b3 Docs: Fix typo in array-bracket-newline.md (#9269) (宋文强) +* 51132d6 Fix: Formatters keep trailing '.' if preceded by a space (fixes #9154) (#9247) (i-ron-y) +* 88d5d4d Chore: remove undocumented Linter#markVariableAsUsed method (refs #9161) (#9266) (Teddy Katz) +* 09414cf Chore: remove internal Linter#getDeclaredVariables method (refs #9161) (#9264) (Teddy Katz) +* f31f59d Chore: prefer smaller scope for variables in codebase (#9265) (Teddy Katz) +* 3693e4e Chore: remove undocumented Linter#getScope method (#9253) (Teddy Katz) +* 5d7eb81 Chore: refactor config hash caching in CLIEngine (#9260) (Teddy Katz) +* 1a76c4d Chore: remove SourceCode passthroughs from Linter.prototype (refs #9161) (#9263) (Teddy Katz) +* 40ae27b Chore: avoid relying on Linter#getScope/markVariableAsUsed in tests (#9252) (Teddy Katz) +* b383d81 Chore: make executeOnFile a pure function in CLIEngine (#9262) (Teddy Katz) +* 5e0e579 Chore: avoid internal SourceCode methods in Linter tests (refs #9161) (#9223) (Teddy Katz) +* adab827 Chore: remove unused eslint-disable comment (#9251) (Teddy Katz) +* 31e4ec8 Chore: use consistent names for apply-disable-directives in tests (#9246) (Teddy Katz) +* 7ba46e6 Fix: shebang error in eslint-disable-new-line; add tests (fixes #9238) (#9240) (i-ron-y) +* 8f6546c Chore: remove undocumented defaults() method (refs #9161) (#9237) (Teddy Katz) +* 82d8b73 Docs: Fix error in example code for sort-imports (fixes #8734) (#9245) (i-ron-y) +* a32ec36 Update: refactor eslint-disable comment processing (#9216) (Teddy Katz) +* 583f0b8 Chore: avoid using globals in CLIEngine tests (#9242) (Teddy Katz) +* c8bf687 Chore: upgrade eslint-plugin-eslint-plugin@1.0.0 (#9234) (薛定谔的猫) +* 3c41a05 Chore: always normalize rules to new API in rules.js (#9236) (Teddy Katz) +* c5f4227 Chore: move logic for handling missing rules to rules.js (#9235) (Teddy Katz) +* bf1e344 Chore: create report translators lazily (#9221) (Teddy Katz) +* 2eedc1f Chore: remove currentFilename prop from Linter instances (refs #9161) (#9219) (Teddy Katz) +* 5566e94 Docs: Replace misleading CLA links (#9133) (#9232) (i-ron-y) +* c991630 Chore: remove ConfigOps.normalize in favor of ConfigOps.getRuleSeverity (#9224) (Teddy Katz) +* 171962a Chore: remove internal Linter#getAncestors helper (refs #9161) (#9222) (Teddy Katz) +* a567499 Chore: avoid storing list of problems on Linter instance (refs #9161) (#9214) (Teddy Katz) +* ed6d088 Chore: avoid relying on undocumented Linter#getFilename API in tests (#9218) (Teddy Katz) + +v4.6.1 - September 3, 2017 + +* bdec46d Build: avoid process leak when generating website (#9217) (Teddy Katz) +* cb74b87 Fix: avoid adding globals when an env is used with `false` (fixes #9202) (#9203) (Teddy Katz) +* f9b7544 Docs: Correct a typo in generator-star-spacing documentation (#9205) (Ethan Rutherford) +* e5c5e83 Build: Fixing issue with docs generation (Fixes #9199) (#9200) (Ilya Volodin) + +v4.6.0 - September 1, 2017 + +* 56dd769 Docs: fix link format in prefer-arrow-callback.md (#9198) (Vse Mozhet Byt) +* 6becf91 Update: add eslint version to error output. (fixes #9037) (#9071) (薛定谔的猫) +* 0e09973 New: function-paren-newline rule (fixes #6074) (#8102) (Teddy Katz) +* 88a64cc Chore: Make parseJsonConfig() a pure function in Linter (#9186) (Teddy Katz) +* 1bbac51 Fix: avoid breaking eslint-plugin-eslint-comments (fixes #9193) (#9196) (Teddy Katz) +* 3e8b70a Fix: off-by-one error in eslint-disable comment checking (#9195) (Teddy Katz) +* 73815f6 Docs: rewrite prefer-arrow-callback documentation (fixes #8950) (#9077) (Charles E. Morgan) +* 0d3a854 Chore: avoid mutating report descriptors in report-translator (#9189) (Teddy Katz) +* 2db356b Update: no-unused-vars Improve message to include the allowed patterns (#9176) (Eli White) +* 8fbaf0a Update: Add configurability to generator-star-spacing (#8985) (Ethan Rutherford) +* 8ed779c Chore: remove currentScopes property from Linter instances (refs #9161) (#9187) (Teddy Katz) +* af4ad60 Fix: Handle error when running init without npm (#9169) (Gabriel Aumala) +* 4b94c6c Chore: make parse() a pure function in Linter (refs #9161) (#9183) (Teddy Katz) +* 1be5634 Chore: don't make Linter a subclass of EventEmitter (refs #9161) (#9177) (Teddy Katz) +* e95af9b Chore: don't include internal test helpers in npm package (#9160) (Teddy Katz) +* 6fb32e1 Chore: avoid using private Linter APIs in astUtils tests (refs #9161) (#9173) (Teddy Katz) +* de6dccd Docs: add documentation for Linter methods (refs #6525) (#9151) (Teddy Katz) +* 2d90030 Chore: remove unused assignment. (#9182) (薛定谔的猫) +* d672aef Chore: refactor reporting logic (refs #9161) (#9168) (Teddy Katz) +* 5ab0434 Fix: indent crash on sparse arrays with "off" option (fixes #9157) (#9166) (Teddy Katz) +* c147b97 Chore: Make SourceCodeFixer accept text instead of a SourceCode instance (#9178) (Teddy Katz) +* f127423 Chore: avoid using private Linter APIs in Linter tests (refs #9161) (#9175) (Teddy Katz) +* 2334335 Chore: avoid using private Linter APIs in SourceCode tests (refs #9161) (#9174) (Teddy Katz) +* 2dc243a Chore: avoid using internal Linter APIs in RuleTester (refs #9161) (#9172) (Teddy Katz) +* d6e436f Fix: no-extra-parens reported some parenthesized IIFEs (fixes #9140) (#9158) (Teddy Katz) +* e6b115c Build: Add an edit link to the rule docs’ metadata (#9049) (Jed Fox) +* fcb7bb4 Chore: avoid unnecessarily complex forEach calls in no-extra-parens (#9159) (Teddy Katz) +* ffa021e Docs: quotes rule - when does \n require backticks (#9135) (avimar) +* 60c5148 Chore: improve coverage in lib/*.js (#9130) (Teddy Katz) + +v4.5.0 - August 18, 2017 + +* decdd2c Update: allow arbitrary nodes to be ignored in `indent` (fixes #8594) (#9105) (Teddy Katz) +* 79062f3 Update: fix indentation of multiline `new.target` expressions (#9116) (Teddy Katz) +* d00e24f Upgrade: `chalk` to 2.x release (#9115) (Stephen Edgar) +* 6ef734a Docs: add missing word in processor documentation (#9106) (Teddy Katz) +* a4f53ba Fix: Include files with no messages in junit results (#9093) (#9094) (Sean DuBois) +* 1d6a9c0 Chore: enable eslint-plugin/test-case-shorthand-strings (#9067) (薛定谔的猫) +* f8add8f Fix: don't autofix with linter.verifyAndFix when `fix: false` is used (#9098) (Teddy Katz) +* 77bcee4 Docs: update instructions for adding TSC members (#9086) (Teddy Katz) +* bd09cd5 Update: avoid requiring NaN spaces of indentation (fixes #9083) (#9085) (Teddy Katz) +* c93a853 Chore: Remove extra space in blogpost template (#9088) (Kai Cataldo) + +v4.4.1 - August 7, 2017 + +* ec93614 Fix: no-multi-spaces to avoid reporting consecutive tabs (fixes #9079) (#9087) (Teddy Katz) + +v4.4.0 - August 5, 2017 + +* 89196fd Upgrade: Espree to 3.5.0 (#9074) (Gyandeep Singh) +* b3e4598 Fix: clarify AST and don't use `node.start`/`node.end` (fixes #8956) (#8984) (Toru Nagashima) +* 62911e4 Update: Add ImportDeclaration option to indent rule (#8955) (David Irvine) +* de75f9b Chore: enable object-curly-newline & object-property-newline.(fixes #9042) (#9068) (薛定谔的猫) +* 5ae8458 Docs: fix typo in object-shorthand.md (#9066) (Jon Berry) +* c3d5b39 Docs: clarify options descriptions (fixes #8875) (#9060) (Brandon Mailhiot) +* 37158c5 Docs: clarified behavior of globalReturn option (fixes #8953) (#9058) (Brandon Mailhiot) +* c2f3553 Docs: Update example for MemberExpression option of indent (fixes #9056) (#9057) (Jeff) +* 78a85e0 Fix: no-extra-parens incorrectly reports async function expressions (#9035) (薛定谔的猫) +* c794f86 Fix: getter-return reporting method named 'get' (fixes #8919) (#9004) (薛定谔的猫) +* d0f78ec Docs: update rule deprecation policy (fixes #8635) (#9033) (Teddy Katz) +* 5ab282f Fix: Print error message in bin/eslint.js (fixes #9011) (#9041) (Victor Hom) +* 50e3cf3 Docs: Update sort-keys doc to define natural ordering (fixes #9043) (#9045) (Karan Sharma) +* 7ecfe6a Chore: enable eslint-plugin/test-case-property-ordering (#9040) (薛定谔的猫) +* ad32697 Upgrade: js-yaml to 3.9.1 (refs #9011) (#9044) (Teddy Katz) +* 66c1d43 Docs: Create SUPPORT.md (#9031) (Teddy Katz) +* 7247b6c Update: handle indentation of custom destructuring syntax (fixes #8990) (#9027) (Teddy Katz) +* cdb82f2 Fix: padding-line-between-statements crash on semicolons after blocks (#8748) (Alexander Madyankin) +* 3141872 Chore: remove unnecessary eslint-disable comments in codebase (#9032) (Teddy Katz) +* 0f97279 Fix: refactor no-multi-spaces to avoid regex backtracking (fixes #9001) (#9008) (Teddy Katz) +* b74514d Fix: refactor RuleContext to not modify report locations (fixes #8980) (#8997) (Teddy Katz) +* 31d7fd2 Fix: inconsistent `indent` behavior on computed properties (fixes #8989) (#8999) (Teddy Katz) +* 3393894 Fix: avoid reporting the entire AST for missing rules (#8998) (Teddy Katz) +* b3b95b8 Chore: enable additional rules on ESLint codebase (#9013) (Teddy Katz) +* 9b6c552 Upgrade: eslint-plugin-eslint-plugin@0.8.0 (#9012) (薛定谔的猫) +* acbe86a Chore: disallow .substr and .substring in favor of .slice (#9010) (Teddy Katz) +* d0536d6 Chore: Optimizes adding Linter methods (fixes #9000) (#9007) (Sean C Denison) +* 0a0401f Chore: fix spelling error. (#9003) (薛定谔的猫) +* 3d020b9 Update: emit a warning for ecmaFeatures rather than throwing an error (#8974) (Teddy Katz) +* d2f8f9f Fix: include name of invalid config in validation messages (fixes #8963) (#8973) (Teddy Katz) +* c3ee46b Chore: fix misleading comment in RuleTester (#8995) (Teddy Katz) + +v4.3.0 - July 21, 2017 + +* 91dccdf Update: support more options in prefer-destructuring (#8796) (Victor Hom) +* 3bebcfd Update: Support generator yields in no constant condition (#8762) (Victor Hom) +* 96df8c9 Fix: Handle fixing objects containing comments (fixes #8484) (#8944) (Brian Schemp) +* e39d41d Docs: Make `peerDependencies` package.json snippet valid JSON (#8971) (Sam Adams) +* a5fd101 Fix: duplicated error message if a crash occurs (fixes #8964) (#8965) (Teddy Katz) +* f8d122c Docs: trailing commas not allowed in json (#8969) (Scott Fletcher) +* d09288a Chore: Use `output: null` to assert that a test case is not autofixed. (#8960) (薛定谔的猫) +* e639358 Update: add question to confirm downgrade (fixes #8870) (#8911) (Toru Nagashima) +* 601039d Docs: fix badge in eslint-config-eslint readme (#8954) (Teddy Katz) +* 3c231fa Update: add enforceInMethodNames to no-underscore-dangle (fixes #7065) (#7234) (Gabriele Petronella) +* 128591f Update: prefer-numeric-literals warns Number.parseInt (fixes #8913) (#8929) (Kevin Partington) +* 846f8b1 Docs: Clarified that core PRs require issue in maintainer guide (#8927) (Kevin Partington) +* 55bc35d Fix: Avoid shell mangling during eslint --init (#8936) (Anders Kaseorg) +* 10c3d78 Chore: fix misleading `indent` test (#8925) (Teddy Katz) +* fb8005d Update: no-restricted-globals custom error messages (fixes #8315) (#8932) (Kevin Partington) +* a747b6f Chore: make minor improvements to `indent` internals (#8947) (Teddy Katz) +* 1ea3723 Update: fix indentation of parenthesized MemberExpressions (fixes #8924) (#8928) (Teddy Katz) +* 9abc6f7 Update: fix BinaryExpression indentation edge case (fixes #8914) (#8930) (Teddy Katz) +* 0e90453 Docs: Fixing broken cyclomatic complexity link (fixes #8396) (#8937) (Chris Bargren) +* a8a8350 Chore: improve performance of `indent` rule (#8905) (Teddy Katz) +* 764b2a9 Chore: update header info in `indent` (#8926) (Teddy Katz) +* 597c217 Fix: confusing error if plugins from config is not an array (#8888) (Calvin Freitas) +* 3c1dd6d Docs: add description of no-sync `allowAtRootLevel` option (fixes #8902) (#8906) (Teddy Katz) +* 933a9cf Chore: add a fuzzer to detect bugs in core rules (#8422) (Teddy Katz) +* 45f8cd9 Docs: fix verifyAndFix result property name (#8903) (Tino Vyatkin) +* 1a89e1c Docs: Fix always-multiline example in multiline-ternary docs (#8904) (Nathan Woltman) + +v4.2.0 - July 8, 2017 + +* e0f0101 Update: fix indentation of nested function parameters (fixes #8892) (#8900) (Teddy Katz) +* 9f95a3e Chore: remove unused helper method from `indent` (#8901) (Teddy Katz) +* 11ffe6b Fix: no-regex-spaces rule incorrectly fixes quantified spaces (#8773) (Keri Warr) +* 975dacf Update: fix indentation of EmptyStatements (fixes #8882) (#8885) (Teddy Katz) +* 88ed041 Build: Turnoff CI branch build (fixes #8804) (#8873) (Gyandeep Singh) +* 72f22eb Chore: replace is-my-json-valid with Ajv (#8852) (Gajus Kuizinas) +* 7c8de92 Docs: Clarified PR guidelines in maintainer guide (#8876) (Kevin Partington) +* d1fc408 Docs: Update CLA link in Contributing docs (#8883) (Calvin Freitas) +* 931a9f1 Fix: indent false positive with multi-line await expression (#8837) (薛定谔的猫) +* 3767cda Update: add no-sync option to allow at root level (fixes #7985) (#8859) (Victor Hom) +* 1ce553d Docs: Fix wording of minProperties in object-curly-newline (fixes #8874) (#8878) (solmsted) +* f00854e Fix: --quiet no longer fixes warnings (fixes #8675) (#8858) (Kevin Partington) +* b678535 Chore: Add collapsible block for config in ISSUE_TEMPLATE (#8872) (Gyandeep Singh) +* 1f5bfc2 Update: Add always-multiline option to multiline-ternary (fixes #8770) (#8841) (Nathan Woltman) +* 22116f2 Fix: correct comma-dangle JSON schema (#8864) (Evgeny Poberezkin) +* 676af9e Update: fix indentation of JSXExpressionContainer contents (fixes #8832) (#8850) (Teddy Katz) +* 330dd58 Chore: fix title of linter test suite (#8861) (Teddy Katz) +* 60099ed Chore: enable for-direction rule on ESLint codebase (#8853) (薛定谔的猫) +* e0d1a84 Chore: upgrade eslint-plugin-eslint-plugin & eslint-plugin-node (#8856) (薛定谔的猫) +* 0780d86 Chore: remove identical tests (#8851) (Teddy Katz) +* 5c3ac8e Fix: arrow-parens fixer gets tripped up with trailing comma in args (#8838) (薛定谔的猫) +* c4f2e29 Build: fix race condition in demo (#8827) (Teddy Katz) +* c693be5 New: Allow passing a function as `fix` option (fixes #8039) (#8730) (Ian VanSchooten) +* 8796d55 Docs: add missing item to 4.0 migration guide table of contents (#8835) (薛定谔的猫) +* 742998c doc md update: false -> `false` (#8825) (Erik Vold) +* ce969f9 Docs: add guidelines for patch release communication (fixes #7277) (#8823) (Teddy Katz) +* 5c83c99 Docs: Clarify arrow function parens in no-extra-parens (fixes #8741) (#8822) (Kevin Partington) +* 84d921d Docs: Added note about Node/CJS scoping to no-redeclare (fixes #8814) (#8820) (Kevin Partington) +* 85c9327 Update: fix parenthesized CallExpression indentation (fixes #8790) (#8802) (Teddy Katz) +* be8d354 Update: simplify variable declarator indent handling (fixes #8785) (#8801) (Teddy Katz) +* 9417818 Fix: no-debugger autofixer produced invalid syntax (#8806) (Teddy Katz) +* 8698a92 New: getter-return rule (fixes #8449) (#8460) (薛定谔的猫) +* eac06f2 Fix: no-extra-parens false positives for variables called "let" (#8808) (Teddy Katz) +* 616587f Fix: dot-notation autofix produces syntax errors for object called "let" (#8807) (Teddy Katz) +* a53ef7e Fix: don't require a third argument in linter.verifyAndFix (fixes #8805) (#8809) (Teddy Katz) +* 5ad8b70 Docs: add minor formatting improvement to paragraph about parsers (#8816) (Teddy Katz) + +v4.1.1 - June 25, 2017 + +* f307aa0 Fix: ensure configs from a plugin are cached separately (fixes #8792) (#8798) (Teddy Katz) +* 8b48ae8 Docs: Add doc on parser services (fixes #8390) (#8795) (Victor Hom) +* 0d041e7 Fix: avoid crashing when using baseConfig with extends (fixes #8791) (#8797) (Teddy Katz) +* 03213bb Chore: improve comment explanation of `indent` internal functions (#8800) (Teddy Katz) +* d2e88ed Chore: Fix misleading comment in ConfigCache.js (#8799) (Teddy Katz) + +v4.1.0 - June 23, 2017 + +* e8f1362 Docs: Remove wrong descriptions in `padded-block` rule (#8783) (Plusb Preco) +* 291a783 Update: `enforceForArrowConditionals` to `no-extra-parens` (fixes #6196) (#8439) (Evilebot Tnawi) +* a21dd32 New: Add `overrides`/`files` options for glob-based config (fixes #3611) (#8081) (Sylvan Mably) +* 879688c Update: Add ignoreComments option to no-trailing-spaces (#8061) (Jake Roussel) +* b58ae2e Chore: Only instantiate fileEntryCache when cache flage set (perf) (#8763) (Gyandeep Singh) +* 9851288 Update: fix indent errors on multiline destructure (fixes #8729) (#8756) (Victor Hom) +* 3608f06 Docs: Increase visibility of code of conduct (fixes #8758) (#8764) (Kai Cataldo) +* 673a58b Update: support multiple fixes in a report (fixes #7348) (#8101) (Toru Nagashima) +* 7a1bc38 Fix: don't pass default parserOptions to custom parsers (fixes #8744) (#8745) (Teddy Katz) +* c5b4052 Chore: enable computed-property-spacing on ESLint codebase (#8760) (Teddy Katz) +* 3419f64 Docs: describe how to use formatters on the formatter demo page (#8754) (Teddy Katz) +* a3ff8f2 Chore: combine tests in tests/lib/eslint.js and tests/lib/linter.js (#8746) (Teddy Katz) +* b7cc1e6 Fix: Space-infix-ops should ignore type annotations in TypeScript (#8341) (Reyad Attiyat) +* 46e73ee Fix: eslint --init installs wrong dependencies of popular styles (fixes #7338) (#8713) (Toru Nagashima) +* a82361b Chore: Prevent package-lock.json files from being created (fixes #8742) (#8747) (Teddy Katz) +* 5f81a68 New: Add eslintIgnore support to package.json (fixes #8458) (#8690) (Victor Hom) +* b5a70b4 Update: fix multiline binary operator/parentheses indentation (#8719) (Teddy Katz) +* ab8b016 Update: fix MemberExpression indentation with "off" option (fixes #8721) (#8724) (Teddy Katz) +* eb5d12c Update: Add Fixer method to Linter API (#8631) (Gyandeep Singh) +* 26a2daa Chore: Cache fs reads in ignored-paths (fixes #8363) (#8706) (Victor Hom) + +v4.0.0 - June 11, 2017 + +* 4aefb49 Chore: avoid using deprecated rules on ESLint codebase (#8708) (Teddy Katz) +* 389feba Chore: upgrade deps. (#8684) (薛定谔的猫) +* 3da7b5e Fix: Semi-Style only check for comments when tokens exist (fixes #8696) (#8697) (Reyad Attiyat) +* 3cfe9ee Fix: Add space between async and param on fix (fixes #8682) (#8693) (Reyad Attiyat) +* c702858 Chore: enable no-multiple-empty-lines on ESLint codebase (#8694) (Teddy Katz) +* 34c4020 Update: Add support for parens on left side for-loops (fixes: #8393) (#8679) (Victor Hom) +* 735cd09 Docs: Correct the comment in an example for `no-mixed-requires` (#8686) (Fangzhou Li) +* 026f048 Chore: remove dead code from prefer-const (#8683) (Teddy Katz) + +v4.0.0-rc.0 - June 2, 2017 + +* 0058b0f8 Update: add --fix to no-debugger (#8660) (薛定谔的猫) +* b4daa225 Docs: Note to --fix option for strict rule (#8680) (Vitaliy Potapov) +* 4df33e7c Chore: check for root:true in project sooner (fixes #8561) (#8638) (Victor Hom) +* c9b980ce Build: Add Node 8 on travis (#8669) (Gyandeep Singh) +* 95248336 Fix: Don't check object destructing in integer property (fixes #8654) (#8657) (flowmemo) +* c4ac969c Update: fix parenthesized ternary expression indentation (fixes #8637) (#8649) (Teddy Katz) +* 4f2f9fcb Build: update license checker to allow LGPL (fixes #8647) (#8652) (Teddy Katz) +* b0c83bd1 Docs: suggest pushing new commits to a PR instead of amending (#8632) (Teddy Katz) +* d0e9fd2d Fix: Config merge to correctly account for extends (fixes #8193) (#8636) (Gyandeep Singh) +* 705d88f7 Docs: Update CLA link on Pull Requests page (#8642) (Teddy Katz) +* 794d4d6c Docs: missing paren on readme (#8640) (Dan Beam) +* 7ebd9d6f New: array-element-newline rule (fixes #6075) (#8375) (Jan Peer Stöcklmair) +* f62cff66 Chore: Remove dependency to user-home (fixes #8604) (#8629) (Pavol Madar) +* 936bc174 Docs: Add missing documentation for scoped modules in sharable config developer-guide (#8610) (Jonathan Samines) + +v4.0.0-beta.0 - May 19, 2017 + +* 2f7015b6 New: semi-style rule (fixes #8169) (#8542) (Toru Nagashima) +* 1eaef580 Revert "Breaking: Traverse into type annotations (fixes #7129) (#8365)" (#8584) (Kai Cataldo) +* eb14584a Fix: no-unneeded-ternary change code behavior after fix (fixes #8507) (#8624) (Jan Peer Stöcklmair) +* 3ec436ee Breaking: New Linter API (fixes #8454) (#8465) (Gyandeep Singh) +* 3fc9653a Fix: Call expression consistency in variable declaration (fixes #8607) (#8619) (Reyad Attiyat) +* 5b6093ef Docs: Remove .eslintignore reference to transpiled file filtering (#8622) (Alex Summer) +* 729bbcdb Chore: Fix lgtm alerts. (#8611) (Max Schaefer) +* 3418479a Update: improve indent of `flatTernaryExpressions` (fixes #8481) (#8587) (Toru Nagashima) +* 268d52ef Update: Use sane defaults for JSX indentation (fixes #8425) (#8593) (Teddy Katz) +* d21f5283 Chore: make shelljs a devDependency instead of a dependency (#8608) (Teddy Katz) +* 11493781 Docs: Rephrase in about section (#8609) (Sudarsan G P) +* 23401626 Chore: remove strip-bom dependency (refs #8603) (#8606) (Teddy Katz) +* a93a2f95 New: padding-line-between-statements rule (fixes #7356) (#8099) (Toru Nagashima) +* 0ef09ea0 New: for-direction rule (fixes #8387) (#8519) (薛定谔的猫) +* a73e6c09 Fix: Fix failing uknown node test since #8569 indents class bodies (#8588) (Reyad Attiyat) +* c6c639d6 Fix: Ignore unknown nodes for Indent rule (fixes #8440) (#8504) (Reyad Attiyat) +* df17bc87 Fix: object-shorthand crash on some computed keys (fixes #8576) (#8577) (Teddy Katz) +* 482d5720 New: switch-colon-spacing rule (fixes #7981) (#8540) (Toru Nagashima) +* afa35c68 Update: check allman-style classes correctly in indent (fixes #8493) (#8569) (Teddy Katz) +* de0b4ad7 Fix: Indent Ignore Variable Declaration init operator (fixes #8546) (#8563) (Reyad Attiyat) +* 927ca0dc Fix: invalid syntax from prefer-arrow-callback autofixer (fixes #8541) (#8555) (Teddy Katz) +* 25db3d22 Chore: avoid skipping test for env overrides (refs #8291) (#8556) (Teddy Katz) +* 456f519b Update: make indent MemberExpression handling more robust (fixes #8552) (#8554) (Teddy Katz) +* 873310e5 Fix: run no-unexpected-multiline only if needed (fixes #8550) (#8551) (Ruben Bridgewater) +* 833a0cad Fix: confusing RuleTester error message when options is not an array (#8557) (Teddy Katz) + +v4.0.0-alpha.2 - May 5, 2017 + +* 74ab344 Update: check allman-style blocks correctly in indent rule (fixes #8493) (#8499) (Teddy Katz) +* f6256d4 Update: no-extend-native checks global scope refs only (fixes #8461) (#8528) (Kevin Partington) +* b463045 Docs: add typescript-eslint-parser (#8388) (#8534) (薛定谔的猫) +* 99c56d5 Update: handle multiline parents consistently in indent (fixes #8455) (#8498) (Teddy Katz) +* cf940c6 Update: indent `from` tokens in import statements (fixes #8438) (#8466) (Teddy Katz) +* 0a9a90f Fix: max-len doesn't allow comments longer than code (#8532) (Ken Gregory) +* 734846b Breaking: validate eslintrc properties (fixes #8213) (#8295) (alberto) +* 025e97a Chore: delete duplicated test. (#8527) (薛定谔的猫) +* 6a333ff Upgrade: espree@^3.4.2 (#8526) (Kevin Partington) +* e52d998 Docs: Configuring Cascading and Hierarchy example correction (#8512) (Cheong Yip) +* e135aa5 Docs: Correct code of conduct link on Readme.md (#8517) (Zander Mackie) +* 37e3ba1 Chore: Add license report and scan status (#8503) (Kevin Wang) +* afbea78 Chore: don't pull default options from eslint:recommended (fixes #8374) (#8381) (Teddy Katz) +* d49acc3 Update: fix no-self-compare false negative on non-literals (fixes #7677) (#8492) (Teddy Katz) +* aaa1a81 Fix: avoid creating extra whitespace in brace-style fixer (fixes #7621) (#8491) (Teddy Katz) +* 9c3da77 Docs: list another related rule in no-undefined (#8467) (Ethan) +* f987814 Docs: Update CHANGELOG.md for v4.0.0-alpha.1 release (#8488) (Kai Cataldo) + +v4.0.0-alpha.1 - April 21, 2017 + +* b0dadfe3 Docs: Update comments section of Migrating to v4.0.0 (#8486) (Kai Cataldo) +* b337738f Update: Add `consistent` option to `object-curly-newline` (fixes #6488) (#7720) (Evilebot Tnawi) +* 53fefb3b Update: add fix for no-confusing-arrow (#8347) (Mordy Tikotzky) +* 735d02d5 Update: Deprecate sourceCode.getComments() (fixes #8408) (#8434) (Kai Cataldo) +* ac39e3b0 Update: no-unexpected-multiline to flag confusing division (fixes #8469) (#8475) (Teddy Katz) +* e35107f0 Fix: indent crash on arrow functions without parens at start of line (#8477) (Teddy Katz) +* 973adeb6 Docs: State that functions option only applies in ES2017 (fixes #7809) (#8468) (Thenaesh Elango) +* 7bc6fe0a New: array-bracket-newline rule (#8314) (Jan Peer Stöcklmair) +* 10a1a2d7 Chore: Do not use cache when testing (#8464) (Kai Cataldo) +* 9f540fd2 Update: no-unused-vars false negative about destructuring (fixes #8442) (#8459) (Toru Nagashima) +* 741ed393 Docs: Clarify how to run local ESLint installation (#8463) (Kai Cataldo) +* fac53890 Breaking: Remove array-callback-return from recommended (fixes #8428) (#8433) (Kai Cataldo) +* 288c96c1 Upgrade: dependencies (#8304) (alberto) +* 48700fc8 Docs: Remove extra header line from LICENSE (#8448) (Teddy Katz) +* 161ee4ea Chore: avoid cloning comments array in TokenStore (#8436) (Teddy Katz) +* 0c2a386e Docs: clarify new indent behavior with MemberExpressions (#8432) (Teddy Katz) +* 446b8876 Docs: update space-before-function-paren docs for 4.0 (fixes #8430) (#8431) (Teddy Katz) + +v4.0.0-alpha.0 - April 7, 2017 + +* 950874f Docs: add 4.0.0 migration guide (fixes #8306) (#8313) (Teddy Katz) +* 2754141 Fix: more autofix token-combining bugs (#8394) (Teddy Katz) +* f5a7e42 Breaking: log number of fixable problems (fixes #7364) (#8324) (alberto) +* 769b121 Chore: Fix indentation errors in indent-legacy (#8424) (Kai Cataldo) +* 8394e48 Update: add deprecated indent-legacy rule as v3.x indent rule snapshot (#8286) (Teddy Katz) +* 3c87e85 Fix: no-multi-spaces false positive with irregular indent whitespace (#8412) (Teddy Katz) +* cc53481 Breaking: rewrite indent (fixes #1801, #3737, #3845, #6007, ...16 more) (#7618) (Teddy Katz) +* 867dd2e Breaking: Calculate leading/trailing comments in core (#7516) (Kai Cataldo) +* de9f1a0 Docs: ES6 syntax vs globals configuration (fixes #7984) (#8350) (Zander Mackie) +* 66af53e Breaking: Traverse into type annotations (fixes #7129) (#8365) (Kai Cataldo) +* 86cf3e4 New: no-buffer-constructor rule (fixes #5614) (#8413) (Teddy Katz) +* f560c06 Update: fix space-unary-ops behavior with postfix UpdateExpressions (#8391) (Teddy Katz) +* 936af66 Fix: no-multiple-empty-lines crash on space after last \n (fixes #8401) (#8402) (Teddy Katz) +* e395919 Breaking: Resolve patterns from .eslintignore directory (fixes #6759) (#7678) (Ian VanSchooten) +* c778676 Breaking: convert RuleTester to ES6 class (refs #8231) (#8263) (Teddy Katz) +* 6f7757e Breaking: convert SourceCode to ES6 class (refs #8231) (#8264) (Teddy Katz) +* 8842d7e Chore: fix comment spacing in tests (#8405) (Teddy Katz) +* 9a9d916 Breaking: update eslint:recommended for 4.0.0 (fixes #8236) (#8372) (Teddy Katz) +* b0c63f0 Breaking: infer endLine and endColumn from a reported node (fixes #8004) (#8234) (Teddy Katz) +* 40b8c69 Breaking: no-multi-spaces check around inline comments (fixes #7693) (#7696) (Kai Cataldo) +* 034a575 Breaking: convert CLIEngine to ES6 class (refs #8231) (#8262) (Teddy Katz) +* 7dd890d Breaking: tweak space-before-function-paren default option (fixes #8267) (#8285) (Teddy Katz) +* 0e0dd27 Breaking: Remove `ecmaFeatures` from `eslint:recommended` (#8239) (alberto) +* 2fa7502 Breaking: disallow scoped plugin references without scope (fixes #6362) (#8233) (Teddy Katz) +* 4673f6e Chore: Switch to eslint-scope from escope (#8280) (Corbin Uselton) +* e232464 Breaking: change defaults for padded-blocks (fixes #7879) (#8134) (alberto) + +v3.19.0 - March 31, 2017 + +* e09132f Fix: no-extra-parens false positive with exports and object literals (#8359) (Teddy Katz) +* 91baed4 Update: allow custom messages in no-restricted-syntax (fixes #8298) (#8357) (Vitor Balocco) +* 35c93e6 Fix: prevent space-before-function-paren from checking type annotations (#8349) (Teddy Katz) +* 3342e9f Fix: don't modify operator precedence in operator-assignment autofixer (#8358) (Teddy Katz) +* f88375f Docs: clarify that no-unsafe-negation is in eslint:recommended (#8371) (Teddy Katz) +* 02f0d27 Docs: Add soda0289 to Development Team (#8367) (Kai Cataldo) +* 155424c Fix: ignore empty path in patterns (fixes #8362) (#8364) (alberto) +* 27616a8 Fix: prefer-const false positive with object spread (fixes #8187) (#8297) (Vitor Balocco) +* 8569a90 Docs: add note about git's linebreak handling to linebreak-style docs (#8361) (Teddy Katz) +* 5878593 Chore: fix invalid syntax in no-param-reassign test (#8360) (Teddy Katz) +* 1b1046b Fix: don't classify plugins that throw errors as "missing" (fixes #6874) (#8323) (Teddy Katz) +* 29f4ba5 Fix: no-useless-computed-key invalid autofix for getters and setters (#8335) (Teddy Katz) +* 0541eaf Fix: no-implicit-coercion invalid autofix with consecutive identifiers (#8340) (Teddy Katz) +* 41b9786 Fix: no-extra-parens false positive with objects following arrows (#8339) (Teddy Katz) +* 3146167 Fix: `eslint.verify` should not mutate config argument (fixes #8329) (#8334) (alberto) +* 927de90 Fix: dot-notation autofix produces invalid syntax for integer properties (#8332) (Teddy Katz) +* a9d1bea Fix: comma-style autofix produces errors on parenthesized elements (#8331) (Teddy Katz) +* d52173f Fix: don't generate invalid options in config-rule (#8326) (Teddy Katz) +* 6eda3b5 Fix: no-extra-parens invalid autofix in for-of statements (#8337) (Teddy Katz) +* 6c819d8 Fix: dot-notation autofix produces errors on parenthesized computed keys (#8330) (Teddy Katz) +* 2d883d7 Fix: object-shorthand autofix produces errors on parenthesized functions (#8328) (Teddy Katz) +* cd9b774 Fix: quotes false positive with backtick option in method names (#8327) (Teddy Katz) +* d064ba2 Fix: no-else-return false positive for ifs in single-statement position (#8338) (Teddy Katz) +* 6a718ba Chore: enable max-statements-per-line on ESLint codebase (#8321) (Teddy Katz) +* 614b62e Chore: update sinon calls to deprecated API. (#8310) (alberto) +* 0491572 Chore: use precalculated counts in codeframe formatter (#8296) (Vitor Balocco) +* 8733e6a Chore: Fix incorrect error location properties in tests (#8307) (alberto) +* c4ffb49 Chore: Fix typos in test option assertions (#8305) (Teddy Katz) +* 79a97cb Upgrade: devDependencies (#8303) (alberto) +* e4da200 Upgrade: Mocha to 3.2.0 (#8299) (Ilya Volodin) +* 2f144ca Fix: operator-assignment autofix errors with parentheses (fixes #8293) (#8294) (Teddy Katz) +* 7521cd5 Chore: update token logic in rules to use ast-utils (#8288) (Teddy Katz) +* 9b509ce Chore: refactor space-before-function-paren rule (#8284) (Teddy Katz) +* ddc6350 Fix: no-param-reassign false positive on destructuring (fixes #8279) (#8281) (Teddy Katz) +* f8176b3 Chore: improve test coverage for node-event-generator (#8287) (Teddy Katz) +* 602e9c2 Docs: fix incorrect selector examples (#8278) (Teddy Katz) + +v3.18.0 - March 17, 2017 + +* 85f74ca Fix: broken code path of direct nested loops (fixes #8248) (#8274) (Toru Nagashima) +* a61c359 Fix: Ignore hidden folders when resolving globs (fixes #8259) (#8270) (Ian VanSchooten) +* 6f05546 Chore: convert StubModuleResolver in config tests to ES6 class (#8265) (Teddy Katz) +* 0c0fc31 Fix: false positive of no-extra-parens about spread and sequense (#8275) (Toru Nagashima) +* e104973 Docs: remove self-reference in no-restricted-syntax docs (#8277) (Vitor Balocco) +* 23eca51 Update: Add allowTaggedTemplates to no-unused-expressions (fixes #7632) (#8253) (Kevin Partington) +* f9ede3f Upgrade: doctrine to 2.0.0 (#8269) (alberto) +* 1b678a6 New: allow rules to listen for AST selectors (fixes #5407) (#7833) (Teddy Katz) +* 63ca0c5 Chore: use precalculated counts in stylish formatter (#8251) (alberto) +* 47c3171 Fix: typo in console.error (#8258) (Jan Peer Stöcklmair) +* e74ed6d Chore: convert Traverser to ES6 class (refs #7849) (#8232) (Teddy Katz) +* 13eead9 Fix: sort-vars crash on mixed destructuring declarations (#8245) (Teddy Katz) +* 133f489 Fix: func-name-matching crash on destructuring assignment to functions (#8247) (Teddy Katz) +* a34b9c4 Fix: func-name-matching crash on non-string literal computed keys (#8246) (Teddy Katz) +* 7276e6d Docs: remove unneeded semicolons in arrow-parens.md (#8249) (Dmitry Gershun) +* 8c40a25 concat-stream known to be vulnerable prior 1.5.2 (#8228) (Samuel) +* 149c055 Upgrade: mock-fs to v4.2.0 (fixes #8194) (#8243) (Teddy Katz) +* a83bff9 Build: remove unneeded json config in demo (fixes #8237) (#8242) (alberto) +* df12137 Docs: fix typos (#8235) (Gyandeep Singh) +* b5e9788 Chore: rename no-extra-parens methods (#8225) (Vitor Balocco) +* 7f8afe6 Update: no-extra-parens overlooked spread and superClass (fixes #8175) (#8209) (Toru Nagashima) +* ce6ff56 Docs: set recommended true for no-global-assign (fixes #8215) (#8218) (BinYi LIU) +* 5b5c236 Fix: wrong comment when module not found in config (fixes #8192) (#8196) (alberto) + +v3.17.1 - March 6, 2017 + +* f8c8e6e Build: change mock-fs path without SSH (fixes #8207) (#8208) (Toru Nagashima) +* f713f11 Fix: nonblock-statement-body-position multiline error (fixes #8202) (#8203) (Teddy Katz) +* 41e3d9c Fix: `operator-assignment` with parenthesized expression (fixes #8190) (#8197) (alberto) +* 5e3bca7 Chore: add eslint-plugin-eslint-plugin (#8198) (Teddy Katz) +* 580da36 Chore: add missing `output` property to tests (#8195) (alberto) + +v3.17.0 - March 3, 2017 + +* 4fdf6d7 Update: deprecate `applyDefaultPatterns` in `line-comment-position` (#8183) (alberto) +* 25e5817 Fix: Don't autofix `+ +a` to `++a` in space-unary-ops (#8176) (Alan Pierce) +* a6ce8f9 Build: Sort rules before dumping them to doc files (#8154) (Danny Andrews) +* 0af9057 Chore: Upgrade to a patched version of mock-fs (fixes #8177) (#8188) (Teddy Katz) +* bf4d8cf Update: ignore eslint comments in lines-arount-comment (fixes #4345) (#8155) (alberto) +* dad20ad New: add SourceCode#getLocFromIndex and #getIndexFromLoc (fixes #8073) (#8158) (Teddy Katz) +* 18a519f Update: let RuleTester cases assert that no autofix occurs (fixes #8157) (#8163) (Teddy Katz) +* a30eb8d Docs: improve documentation for RuleTester cases (#8162) (Teddy Katz) +* a78ec9f Chore: upgrade `coveralls` to ^2.11.16 (#8161) (alberto) +* d02bd11 Fix: padded-blocks autofix problems with comments (#8149) (alberto) +* 9994889 Docs: Add missing space to `create` in `no-use-before-define` (#8166) (Justin Anastos) +* 4d542ba Docs: Remove unneeded statement about autofix (#8164) (alberto) +* 20daea5 New: no-compare-neg-zero rule (#8091) (薛定谔的猫) +* 4d35a81 Fix: Add a utility to avoid autofix conflicts (fixes #7928, fixes #8026) (#8067) (Alan Pierce) +* 287e882 New: nonblock-statement-body-position rule (fixes #6067) (#8108) (Teddy Katz) +* 7f1f4e5 Chore: remove unneeded devDeps `linefix` and `gh-got` (#8160) (alberto) +* ca1694b Update: ignore negative ranges in fixes (#8133) (alberto) +* 163d751 Docs: `lines-around-comment` doesn't disallow empty lines (#8151) (alberto) +* 1c84922 Chore: upgrade eslint-plugin-node (#8156) (alberto) +* 1ee5c27 Fix: Make RuleTester handle empty-string cases gracefully (fixes #8142) (#8143) (Teddy Katz) +* 044bc10 Docs: Add details about "--fix" option for "sort-imports" rule (#8077) (Olivier Audard) +* 3fec54a Add option to ignore property in no-param-reassign (#8087) (Christian Bundy) +* 4e52cfc Fix: Improve keyword-spacing typescript support (fixes #8110) (#8111) (Reyad Attiyat) +* 7ff42e8 New: Allow regexes in RuleTester (fixes #7837) (#8115) (Daniel Lo Nigro) +* cbd7ded Build: display rules’ meta data in their docs (fixes #5774) (#8127) (Wilson Kurniawan) +* da8e8af Update: include function name in report message if possible (fixes #7260) (#8058) (Dieter Luypaert) +* 8f91e32 Fix: `ignoreRestSiblings` option didn't cover arguments (fixes #8119) (#8120) (Toru Nagashima) + +v3.16.1 - February 22, 2017 + +* ff8a80c Fix: duplicated autofix output for inverted fix ranges (fixes #8116) (#8117) (Teddy Katz) +* a421897 Docs: fix typo in arrow-parens.md (#8132) (Will Chen) +* 22d7fbf Chore: fix invalid redeclared variables in tests (#8130) (Teddy Katz) +* 8d95598 Chore: fix output assertion typos in rule tests (#8129) (Teddy Katz) +* 9fa2559 Docs: Add missing quotes in key-spacing rule (#8121) (Glenn Reyes) +* f3a6ced Build: package.json update for eslint-config-eslint release (ESLint Jenkins) + +v3.16.0 - February 20, 2017 + +* d89d0b4 Update: fix quotes false negative for string literals as template tags (#8107) (Teddy Katz) +* 21be366 Chore: Ensuring eslint:recommended rules are sorted. (#8106) (Kevin Partington) +* 360dbe4 Update: Improve error message when extend config missing (fixes #6115) (#8100) (alberto) +* f62a724 Chore: use updated token iterator methods (#8103) (Kai Cataldo) +* daf6f26 Fix: check output in RuleTester when errors is a number (fixes #7640) (#8097) (alberto) +* cfb65c5 Update: make no-lone-blocks report blocks in switch cases (fixes #8047) (#8062) (Teddy Katz) +* 290fb1f Update: Add includeComments to getTokenByRangeStart (fixes #8068) (#8069) (Kai Cataldo) +* ff066dc Chore: Incorrect source code test text (#8096) (Jack Ford) +* 14d146d Docs: Clarify --ext only works with directories (fixes #7939) (#8095) (alberto) +* 013a454 Docs: Add TSC meeting quorum requirement (#8086) (Kevin Partington) +* 7516303 Fix: `sourceCode.getTokenAfter` shouldn't skip tokens after comments (#8055) (Toru Nagashima) +* c53e034 Fix: unicode-bom fixer insert BOM in appropriate location (fixes #8083) (#8084) (pantosha) +* 55ac302 Chore: fix the timing to define rules for tests (#8082) (Toru Nagashima) +* c7e64f3 Upgrade: mock-fs (#8070) (Toru Nagashima) +* acc3301 Update: handle uncommon linebreaks consistently in rules (fixes #7949) (#8049) (Teddy Katz) +* 591b74a Chore: enable operator-linebreak on ESLint codebase (#8064) (Teddy Katz) +* 6445d2a Docs: Add documentation for /* exported */ (fixes #7998) (#8065) (Lee Yi Min) +* fcc38db Chore: simplify and improve performance for autofix (#8035) (Toru Nagashima) +* b04fde7 Chore: improve performance of SourceCode constructor (#8054) (Teddy Katz) +* 90fd555 Update: improve null detection in eqeqeq for ES6 regexes (fixes #8020) (#8042) (Teddy Katz) +* 16248e2 Fix: no-extra-boolean-cast incorrect Boolean() autofixing (fixes #7977) (#8037) (Jonathan Wilsson) +* 834f45d Update: rewrite TokenStore (fixes #7810) (#7936) (Toru Nagashima) +* 329dcdc Chore: unify checks for statement list parents (#8048) (Teddy Katz) +* c596690 Docs: Clarify generator-star-spacing config example (fixes #8027) (#8034) (Hòa Trần) +* a11d4a6 Docs: fix a typo in shareable configs documentation (#8036) (Dan Homola) +* 1e3d4c6 Update: add fixer for no-unused-labels (#7841) (Teddy Katz) +* f47fb98 Update: ensure semi-spacing checks import/export declarations (#8033) (Teddy Katz) +* e228d56 Update: no-undefined handles properties/classes/modules (fixes #7964) (#7966) (Kevin Partington) +* 7bc92d9 Chore: fix invalid test cases (#8030) (Toru Nagashima) + +v3.15.0 - February 3, 2017 + +* f2a3580 Fix: `no-extra-parens` incorrect precedence (fixes #7978) (#7999) (alberto) +* d6b6ba1 Fix: no-var should fix ForStatement.init (#7993) (Toru Nagashima) +* 99d386d Upgrade: Espree v3.4.0 (#8019) (Kai Cataldo) +* 42390fd Docs: update README.md for team (#8016) (Toru Nagashima) +* d7ffd88 Chore: enable template-tag-spacing on ESLint codebase (#8005) (Teddy Katz) +* f2be7e3 Docs: Fix typo in object-curly-newline.md (#8002) (Danny Andrews) +* df2351a Docs: Fix misleading section in brace-style documentation (#7996) (Teddy Katz) +* 5ae6e00 Chore: avoid unnecessary feature detection for Symbol (#7992) (Teddy Katz) +* 5d57c57 Chore: fix no-else-return lint error (refs #7986) (#7994) (Vitor Balocco) +* 62fb054 Chore: enable no-else-return on ESLint codebase (#7986) (Teddy Katz) +* c59a0ba Update: add ignoreRestSiblings option to no-unused-vars (#7968) (Zack Argyle) +* 5cdfa99 Chore: enable no-unneeded-ternary on ESLint codebase (#7987) (Teddy Katz) +* fbd7c13 Update: ensure operator-assignment handles exponentiation operators (#7970) (Teddy Katz) +* c5066ce Update: add "variables" option to no-use-before-define (fixes #7111) (#7948) (Teddy Katz) +* 09546a4 New: `template-tag-spacing` rule (fixes #7631) (#7913) (Jonathan Wilsson) + +v3.14.1 - January 25, 2017 + +* 791f32b Fix: brace-style false positive for keyword method names (fixes #7974) (#7980) (Teddy Katz) +* d7a0add Docs: Add ESLint tutorial embed to getting started (#7971) (Jamis Charles) +* 72d41f0 Fix: no-var autofix syntax error in single-line statements (fixes #7961) (#7962) (Teddy Katz) +* b9e5b68 Fix: indent rule crash on sparse array with object (fixes #7959) (#7960) (Gyandeep Singh) +* a7bd66a Chore: Adding assign/redeclare tests to no-undefined (refs #7964) (#7965) (Kevin Partington) +* 8bcbf5d Docs: typo in prefer-promise-reject-errors (#7958) (Patrick McElhaney) + +v3.14.0 - January 20, 2017 + +* 506324a Fix: `no-var` does not fix if causes ReferenceError (fixes #7950) (#7953) (Toru Nagashima) +* 05e7432 New: no-chained-assignments rule (fixes #6424) (#7904) (Stewart Rand) +* 243e47d Update: Add fixer for no-else-return (fixes #7863) (#7864) (Xander Dumaine) +* f091d95 New: `prefer-promise-reject-errors` rule (fixes #7685) (#7689) (Teddy Katz) +* ca01e00 Fix: recognize all line terminators in func-call-spacing (fixes #7923) (#7924) (Francesco Trotta) +* a664e8a Update: add ignoreJSX option to no-extra-parens (Fixes #7444) (#7926) (Robert Rossmann) +* 8ac3518 Fix: no-useless-computed-key false positive with `__proto__` (#7934) (Teddy Katz) +* c835e19 Docs: remove reference to deleted rule (#7942) (Alejandro Oviedo) +* 3c1e63b Docs: Improve examples for no-case-declarations (fixes #6716) (#7920) (Kevin Rangel) +* 7e04b33 Fix: Ignore inline plugin rule config in autoconfig (fixes #7860) (#7919) (Ian VanSchooten) +* 6448ba0 Fix: add parentheses in no-extra-boolean-cast autofixer (fixes #7912) (#7914) (Szymon Przybylski) +* b3f2094 Fix: brace-style crash with lone block statements (fixes #7908) (#7909) (Teddy Katz) +* 5eb2e88 Docs: Correct typos in configuring.md (#7916) (Gabriel Delépine) +* bd5e219 Update: ensure brace-style validates class bodies (fixes #7608) (#7871) (Teddy Katz) +* 427543a Fix: catastrophic backtracking in astUtils linebreak regex (fixes #7893) (#7898) (Teddy Katz) +* 995554c Fix: Correct typos in no-alert.md and lib/ast-utils.js (#7905) (Stewart Rand) +* d6150e3 Chore: Enable comma-dangle on ESLint codebase (fixes #7725) (#7906) (Teddy Katz) +* 075ec25 Chore: update to use ES6 classes (refs #7849) (#7891) (Claire Dranginis) +* 55f0cb6 Update: refactor brace-style and fix inconsistencies (fixes #7869) (#7870) (Teddy Katz) + +v3.13.1 - January 9, 2017 + +* 3fc4e3f Fix: prefer-destructuring reporting compound assignments (fixes #7881) (#7882) (Teddy Katz) +* f90462e Fix: no-extra-label autofix should not remove labels used elsewhere (#7885) (Teddy Katz) + +v3.13.0 - January 6, 2017 + +* cd4c025 Update: add fixer for no-extra-label (#7840) (Teddy Katz) +* aa75c92 Fix: Ensure prefer-const fixes destructuring assignments (fixes #7852) (#7859) (Teddy Katz) +* 4008022 Chore: Refactor to use ES6 Classes (Part 3)(refs #7849) (#7865) (Gyandeep Singh) +* c9ba40a Update: add fixer for `no-unneeded-ternary` (#7540) (Teddy Katz) +* dd56d87 Update: add object-shorthand option for arrow functions (fixes #7564) (#7746) (Teddy Katz) +* fbafdc0 Docs: `padded-blocks` `never` case (fixes #7868) (#7878) (alberto) +* ca1f841 Fix: no-useless-return stack overflow on loops after throw (fixes #7855) (#7856) (Teddy Katz) +* d80d994 Update: add fixer for object-property-newline (fixes #7740) (#7808) (Teddy Katz) +* bf3ea3a Fix: capitalized-comments: Ignore consec. comments if first is invalid (#7835) (Kevin Partington) +* 616611a Chore: Refactor to use ES6 Classes (Part 2)(refs #7849) (#7847) (Gyandeep Singh) +* 856084b Chore: Refactor to use ES6 Classes (Part 1)(refs #7849) (#7846) (Gyandeep Singh) +* bf45893 Docs: Clarify that we only support Stage 4 proposals (#7845) (Kevin Partington) +* 0fc24f7 Fix: adapt new-paren rule so it handles TypeScript (fixes #7817) (#7820) (Philipp A) +* df0b06b Fix: no-multiple-empty-lines perf issue on large files (fixes #7803) (#7843) (Teddy Katz) +* 18fa521 Chore: use ast-utils helper functions in no-multiple-empty-lines (#7842) (Teddy Katz) +* 7122205 Docs: Array destructuring example for no-unused-vars (fixes #7838) (#7839) (Remco Haszing) +* e21b36b Chore: add integration tests for cache files (refs #7748) (#7794) (Teddy Katz) +* 2322733 Fix: Throw error if ruletester is missing required test scenarios (#7388) (Teddy Katz) +* 1beecec Update: add fixer for `operator-linebreak` (#7702) (Teddy Katz) +* c5c3b21 Fix: no-implied-eval false positive on 'setTimeoutFoo' (fixes #7821) (#7836) (Teddy Katz) +* 00dd96c Chore: enable array-bracket-spacing on ESLint codebase (#7830) (Teddy Katz) +* ebcae1f Update: no-return-await with with complex `return` argument (fixes #7594) (#7595) (Dalton Santos) +* fd4cd3b Fix: Disable no-var autofixer in some incorrect cases in loops (#7811) (Alan Pierce) +* 1f25834 Docs: update outdated info in Architecture page (#7816) (Teddy Katz) +* f20b9e9 Fix: Relax no-useless-escape's handling of ']' in regexes (fixes #7789) (#7793) (Teddy Katz) +* 3004c1e Fix: consistent-return shouldn't report class constructors (fixes #7790) (#7797) (Teddy Katz) +* b938f1f Docs: Add an example for the spread operator to prefer-spread.md (#7802) (#7804) (butlermd) +* b8ce2dc Docs: Remove .html extensions from links in developer-guide (#7805) (Kevin Partington) +* aafebb2 Docs: Wrap placeholder sample in {% raw %} (#7798) (Daniel Lo Nigro) +* bb6b73b Chore: replace unnecessary function callbacks with arrow functions (#7795) (Teddy Katz) +* 428fbdf Fix: func-call-spacing "never" doesn't fix w/ line breaks (fixes #7787) (#7788) (Kevin Partington) +* 6e61070 Fix: `semi` false positive before regex/template literals (fixes #7782) (#7783) (Teddy Katz) +* ff0c050 Fix: remove internal property from config generation (fixes #7758) (#7761) (alberto) +* 27424cb New: `prefer-destructuring` rule (fixes #6053) (#7741) (Alex LaFroscia) +* bb648ce Docs: fix unclear example for no-useless-escape (#7781) (Teddy Katz) +* 8c3a962 Fix: syntax errors from object-shorthand autofix (fixes #7744) (#7745) (Teddy Katz) +* 8b296a2 Docs: fix in semi.md: correct instead of incorrect (#7779) (German Prostakov) +* 3493241 Upgrade: strip-json-comments ~v2.0.1 (Janus Troelsen) +* 75b7ba4 Chore: enable object-curly-spacing on ESLint codebase (refs #7725) (#7770) (Teddy Katz) +* 7d1dc7e Update: Make default-case comment case-insensitive (fixes #7673) (#7742) (Robert Rossmann) +* f1bf5ec Chore: convert remaining old-style context.report() calls to the new API (#7763) (Teddy Katz) + +v3.12.2 - December 14, 2016 + +* dec3ec6 Fix: indent bug with AssignmentExpressions (fixes #7747) (#7750) (Teddy Katz) +* 5344751 Build: Don't create blogpost links from rule names within other words (#7754) (Teddy Katz) +* 639b798 Docs: Use `Object.prototype` in examples (#7755) (Alex Reardon) + +v3.12.1 - December 12, 2016 + +* 0ad4d33 Fix: `indent` regression with function calls (fixes #7732, fixes #7733) (#7734) (Teddy Katz) +* ab246dd Docs: Rules restricting globals/properties/syntax are linked together (#7743) (Kevin Partington) +* df2f115 Docs: Add eslint-config-mdcs to JSCS Migration Guide (#7737) (Joshua Koo) +* 4b77333 Build: avoid creating broken rule links in the changelog (#7731) (Teddy Katz) + +v3.12.0 - December 9, 2016 + +* e569225 Update: fix false positive/negative of yoda rule (fixes #7676) (#7695) (Toru Nagashima) +* e95a230 Fix: indent "first" option false positive on nested arrays (fixes #7727) (#7728) (Teddy Katz) +* 81f9e7d Fix: Allow duplicated let declarations in `prefer-const` (fixes #7712) (#7717) (Teddy Katz) +* 1d0d61d New: Add no-await-in-loop rule (#7563) (Nat Mote) +* 2cdfb4e New: Additional APIs (fixes #6256) (#7669) (Ilya Volodin) +* 4278c42 Update: make no-obj-calls report errors for Reflect (fixes #7700) (#7710) (Tomas Echeverri Valencia) +* 4742d82 Docs: clarify the default behavior of `operator-linebreak` (fixes #7459) (#7726) (Teddy Katz) +* a8489e2 Chore: Avoid parserOptions boilerplate in tests for ES6 rules (#7724) (Teddy Katz) +* b921d1f Update: add `indent` options for array and object literals (fixes #7473) (#7681) (Teddy Katz) +* 7079c89 Update: Add airbnb-base to init styleguides (fixes #6986) (#7699) (alberto) +* 63bb3f8 Docs: improve the documentation for the autofix API (#7716) (Teddy Katz) +* f8786fb Update: add fixer for `capitalized-comments` (#7701) (Teddy Katz) +* abfd24f Fix: don't validate schemas for disabled rules (fixes #7690) (#7692) (Teddy Katz) +* 2ac07d8 Upgrade: Update globals dependency to 9.14.0 (#7683) (Aleksandr Oleynikov) +* 90a5d29 Docs: Remove incorrect info about issue requirements from PR guide (#7691) (Teddy Katz) +* f80c278 Docs: Add sails-hook-lint to integrations list (#7679) (Anthony M) +* e96da3f Docs: link first instance of `package.json` (#7684) (Kent C. Dodds) +* bf20e20 Build: include links to rule pages in release blogpost (#7671) (Teddy Katz) +* b30116c Docs: Fix code-blocks in spaced-comment docs (#7524) (Michał Gołębiowski) +* 0a2a7fd Fix: Allow \u2028 and \u2029 as string escapes in no-useless-escape (#7672) (Teddy Katz) +* 76c33a9 Docs: Change Sails.js integration to active npm package (#7675) (Anthony M) + +v3.11.1 - November 28, 2016 + +* be739d0 Fix: capitalized-comments fatal error fixed (fixes #7663) (#7664) (Rich Trott) +* cc4cedc Docs: Fix a typo in array-bracket-spacing documentation (#7667) (Alex Guerrero) +* f8adadc Docs: fix a typo in capitalized-comments documentation (#7666) (Teddy Katz) + +v3.11.0 - November 25, 2016 + +* ad56694 New: capitalized-comments rule (fixes #6055) (#7415) (Kevin Partington) +* 7185567 Update: add fixer for `operator-assignment` (#7517) (Teddy Katz) +* faf5f56 Update: fix false negative of `quotes` with \n in template (fixes #7646) (#7647) (Teddy Katz) +* 474e444 Update: add fixer for `sort-imports` (#7535) (Teddy Katz) +* f9b70b3 Docs: Enable example highlighting in rules examples (ref #6444) (#7644) (Alex Guerrero) +* d50f6c1 Fix: incorrect location for `no-useless-escape` errors (fixes #7643) (#7645) (Teddy Katz) +* 54a993c Docs: Fix a typo in the require-yield.md (#7652) (Vse Mozhet Byt) +* eadd808 Chore: Fix prefer-arrow-callback lint errors (#7651) (Kevin Partington) +* 89bd8de New: `require-await` rule (fixes #6820) (#7435) (Toru Nagashima) +* b7432bd Chore: Ensure JS files are checked out with LF (#7624) (Kevin Partington) +* 32a3547 Docs: Add absent quotes in rules documentation (#7625) (Denis Sikuler) +* 5c9a4ad Fix: Prevent `quotes` from fixing templates to directives (fixes #7610) (#7617) (Teddy Katz) +* d90ca46 Upgrade: Update markdownlint dependency to 0.3.1 (fixes #7589) (#7592) (David Anson) +* 07124d1 Docs: add missing quote mark (+=" → "+=") (#7613) (Sean Juarez) +* 8998043 Docs: fix wording in docs for no-extra-parens config (Michael Ficarra) + +v3.10.2 - November 15, 2016 + +* 0643bfe Fix: correctly handle commented code in `indent` autofixer (fixes #7604) (#7606) (Teddy Katz) +* bd0514c Fix: syntax error after `key-spacing` autofix with comment (fixes #7603) (#7607) (Teddy Katz) +* f56c1ef Fix: `indent` crash on parenthesized global return values (fixes #7573) (#7596) (Teddy Katz) +* 100c6e1 Docs: Fix example for curly "multi-or-nest" option (#7597) (Will Chen) +* 6abb534 Docs: Update code of conduct link (#7599) (Nicholas C. Zakas) +* 8302cdb Docs: Update no-tabs to match existing standards & improve readbility (#7590) (Matt Stow) + +v3.10.1 - November 14, 2016 + +* 8a0e92a Fix: handle try/catch correctly in `no-return-await` (fixes #7581) (#7582) (Teddy Katz) +* c4dd015 Fix: no-useless-return stack overflow on unreachable loops (fixes #7583) (#7584) (Teddy Katz) + +v3.10.0 - November 11, 2016 + +* 7ee039b Update: Add comma-style options for calls, fns, imports (fixes #7470) (Max Englander) +* 670e060 Chore: make the `object-shorthand` tests more readable (#7580) (Teddy Katz) +* c3f4809 Update: Allow `func-names` to recognize inferred ES6 names (fixes #7235) (#7244) (Logan Smyth) +* b8d6e48 Fix: syntax errors created by `object-shorthand` autofix (fixes #7574) (#7575) (Teddy Katz) +* 1b3b65c Chore: ensure that files in tests/conf are linted (#7579) (Teddy Katz) +* 2bd1dd7 Update: avoid creating extra whitespace in `arrow-body-style` fixer (#7504) (Teddy Katz) +* 66fe9ff New: `no-return-await` rule. (fixes #7537) (#7547) (Jordan Harband) +* 759525e Chore: Use process.exitCode instead of process.exit() in bin/eslint.js (#7569) (Teddy Katz) +* 0d60db7 Fix: Curly rule doesn't account for leading comment (fixes #7538) (#7539) (Will Chen) +* 5003b1c Update: fix in/instanceof handling with `space-infix-ops` (fixes #7525) (#7552) (Teddy Katz) +* 3e6131e Docs: explain config option merging (#7499) (Danny Andrews) +* 1766524 Update: "Error type should be" assertion in rule-tester (fixes 6106) (#7550) (Frans Jaspers) +* 44eb274 Docs: Missing semicolon report was missing a comma (#7553) (James) +* 6dbda15 Docs: Document the optional defaults argument for RuleTester (#7548) (Teddy Katz) +* e117b80 Docs: typo fix (#7546) (oprogramador) +* 25e5613 Chore: Remove incorrect test from indent.js. (#7531) (Scott Stern) +* c0f4937 Fix: `arrow-parens` supports type annotations (fixes #7406) (#7436) (Toru Nagashima) +* a838b8e Docs: `func-name-matching`: update with “always”/“never” option (#7536) (Jordan Harband) +* 3c379ff Update: `no-restricted-{imports,modules}`: add “patterns” (fixes #6963) (#7433) (Jordan Harband) +* f5764ee Docs: Update example of results returned from `executeOnFiles` (#7362) (Simen Bekkhus) +* 4613ba0 Fix: Add support for escape char in JSX. (#7461) (Scott Stern) +* ea0970d Fix: `curly` false positive with no-semicolon style (#7509) (Teddy Katz) +* af1fde1 Update: fix `brace-style` false negative on multiline node (fixes #7493) (#7496) (Teddy Katz) +* 3798aea Update: max-statements to report function name (refs #7260) (#7399) (Nicholas C. Zakas) +* 0c215fa Update: Add `ArrowFunctionExpression` support to `require-jsdoc` rule (#7518) (Gyandeep Singh) +* 578c373 Build: handle deprecated rules with no 'replacedBy' (refs #7471) (#7494) (Vitor Balocco) +* a7f3976 Docs: Specify min ESLint version for new rule format (#7501) (cowchimp) +* 8a3e717 Update: Fix `lines-around-directive` semicolon handling (fixes #7450) (#7483) (Teddy Katz) +* e58cead Update: add a fixer for certain statically-verifiable `eqeqeq` cases (#7389) (Teddy Katz) +* 0dea0ac Chore: Add Node 7 to travis ci build (#7506) (Gyandeep Singh) +* 36338f0 Update: add fixer for `no-extra-boolean-cast` (#7387) (Teddy Katz) +* 183def6 Chore: enable `prefer-arrow-callback` on ESLint codebase (fixes #6407) (#7503) (Teddy Katz) +* 4f1fa67 Docs: Update copyright (#7497) (Nicholas C. Zakas) + +v3.9.1 - October 31, 2016 + +* 2012258 Fix: incorrect `indent` check for array property access (fixes #7484) (#7485) (Teddy Katz) +* 8a71d4a Fix: `no-useless-return` false positive on conditionals (fixes #7477) (#7482) (Teddy Katz) +* 56a662b Fix: allow escaped backreferences in `no-useless-escape` (fixes #7472) (#7474) (Teddy Katz) +* fffdf13 Build: Fix prefer-reflect rule to not crash site gen build (#7471) (Ilya Volodin) +* 8ba68a3 Docs: Update broken link (#7490) (Devinsuit) +* 65231d8 Docs: add the "fixable" icon for `no-useless-return` (#7480) (Teddy Katz) + +v3.9.0 - October 28, 2016 + +* d933516 New: `no-useless-return` rule (fixes #7309) (#7441) (Toru Nagashima) +* 5e7af30 Update: Add `CallExpression` option for `indent` (fixes #5946) (#7189) (Teddy Katz) +* b200086 Fix: Support type annotations in array-bracket-spacing (#7445) (Jimmy Jia) +* 5ed8b9b Update: Deprecate prefer-reflect (fixes #7226) (#7464) (Kai Cataldo) +* 92ad43b Chore: Update deprecated rules in conf/eslint.json (#7467) (Kai Cataldo) +* e46666b New: Codeframe formatter (fixes #5860) (#7437) (Vitor Balocco) +* fe0d903 Upgrade: Shelljs to ^0.7.5 (fixes #7316) (#7465) (Gyandeep Singh) +* 1d5146f Update: fix wrong indentation about `catch`,`finally` (#7371) (Toru Nagashima) +* 77e3a34 Chore: Pin mock-fs dev dependency (#7466) (Gyandeep Singh) +* c675d7d Update: Fix `no-useless-escape` false negative in regexes (fixes #7424) (#7425) (Teddy Katz) +* ee3bcea Update: add fixer for `newline-after-var` (fixes #5959) (#7375) (Teddy Katz) +* 6e9ff08 Fix: indent.js to support multiline array statements. (#7237) (Scott Stern) +* f8153ad Build: Ensure absolute links in docs retain .md extensions (fixes #7419) (#7438) (Teddy Katz) +* 16367a8 Fix: Return statement spacing. Fix for indent rule. (fixes #7164) (#7197) (Imad Elyafi) +* 3813988 Update: fix false negative of `no-extra-parens` (fixes #7122) (#7432) (Toru Nagashima) +* 23062e2 Docs: Fix typo in no-unexpected-multiline (fixes #7442) (#7447) (Denis Sikuler) +* d257428 Update: `func-name-matching`: add “always”/“never” option (fixes #7391) (#7428) (Jordan Harband) +* c710584 Fix: support for MemberExpression with function body. (#7400) (Scott Stern) +* 2c8ed2d Build: ensure that all files are linted on bash (fixes #7426) (#7427) (Teddy Katz) +* 18ff70f Chore: Enable `no-useless-escape` (#7403) (Vitor Balocco) +* 8dfd802 Fix: avoid `camelcase` false positive with NewExpressions (fixes #7363) (#7409) (Teddy Katz) +* e8159b4 Docs: Fix typo and explain static func calls for class-methods-use-this (#7421) (Scott O'Hara) +* 85d7e24 Docs: add additional examples for MemberExpressions in Indent rule. (#7408) (Scott Stern) +* 2aa1107 Docs: Include note on fatal: true in the node.js api section (#7376) (Simen Bekkhus) +* e064a25 Update: add fixer for `arrow-body-style` (#7240) (Teddy Katz) +* e0fe727 Update: add fixer for `brace-style` (fixes #7074) (#7347) (Teddy Katz) +* cbbe420 New: Support enhanced parsers (fixes #6974) (#6975) (Nicholas C. Zakas) +* 644d25b Update: Add an ignoreRegExpLiterals option to max-len (fixes #3229) (#7346) (Wilfred Hughes) +* 6875576 Docs: Remove broken links to jslinterrors.com (fixes #7368) (#7369) (Dannii Willis) + +v3.8.1 - October 17, 2016 + +* 681c78a Fix: `comma-dangle` was confused by type annotations (fixes #7370) (#7372) (Toru Nagashima) +* 7525042 Fix: Allow useless escapes in tagged template literals (fixes #7383) (#7384) (Teddy Katz) +* 9106964 Docs: Fix broken link for stylish formatter (#7386) (Vitor Balocco) +* 49d3c1b Docs: Document the deprecated meta property (#7367) (Randy Coulman) +* 19d2996 Docs: Relax permission for merging PRs (refs eslint/tsc-meetings#20) (#7360) (Brandon Mills) + +v3.8.0 - October 14, 2016 + +* ee60acf Chore: add integration tests for autofixing (fixes #5909) (#7349) (Teddy Katz) +* c8796e9 Update: `comma-dangle` supports trailing function commas (refs #7101) (#7181) (Toru Nagashima) +* c4abaf0 Update: `space-before-function-paren` supports async/await (refs #7101) (#7180) (Toru Nagashima) +* d0d3b28 Fix: id-length rule incorrectly firing on member access (fixes #6475) (#7365) (Burak Yiğit Kaya) +* 2729d94 Fix: Don't report setter params in class bodies as unused (fixes #7351) (#7352) (Teddy Katz) +* 0b85004 Chore: Enable prefer-template (fixes #6407) (#7357) (Kai Cataldo) +* ca1947b Chore: Update pull request template (refs eslint/tsc-meetings#20) (#7359) (Brandon Mills) +* d840afe Docs: remove broken link from no-loop-func doc (#7342) (Michael McDermott) +* 5266793 Update: no-useless-escape checks template literals (fixes #7331) (#7332) (Kai Cataldo) +* b08fb91 Update: add source property to LintResult object (fixes #7098) (#7304) (Vitor Balocco) +* 0db4164 Chore: run prefer-template autofixer on test files (refs #6407) (#7354) (Kai Cataldo) +* c1470b5 Update: Make the `prefer-template` fixer unescape quotes (fixes #7330) (#7334) (Teddy Katz) +* 5d08c33 Fix: Handle parentheses correctly in `yoda` fixer (fixes #7326) (#7327) (Teddy Katz) +* cd72bba New: `func-name-matching` rule (fixes #6065) (#7063) (Annie Zhang) +* 55b5146 Fix: `RuleTester` didn't support `mocha --watch` (#7287) (Toru Nagashima) +* f8387c1 Update: add fixer for `prefer-spread` (#7283) (Teddy Katz) +* 52da71e Fix: Don't require commas after rest properties (fixes #7297) (#7298) (Teddy Katz) +* 3b11d3f Chore: refactor `no-multiple-empty-lines` (#7314) (Teddy Katz) +* 16d495d Docs: Updating CLI overview with latest changes (#7335) (Kevin Partington) +* 52dfce5 Update: add fixer for `one-var-declaration-per-line` (#7295) (Teddy Katz) +* 0e994ae Update: Improve the error messages for `no-unused-vars` (fixes #7282) (#7315) (Teddy Katz) +* 93214aa Chore: Convert non-lib/test files to template literals (refs #6407) (#7329) (Kai Cataldo) +* 72f394d Update: Fix false negative of `no-multiple-empty-lines` (fixes #7312) (#7313) (Teddy Katz) +* 756bc5a Update: Use characters instead of code units for `max-len` (#7299) (Teddy Katz) +* c9a7ec5 Fix: Improving optionator configuration for --print-config (#7206) (Kevin Partington) +* 51bfade Fix: avoid `object-shorthand` crash with spread properties (fixes #7305) (#7306) (Teddy Katz) +* a12d1a9 Update: add fixer for `no-lonely-if` (#7202) (Teddy Katz) +* 1418384 Fix: Don't require semicolons before `++`/`--` (#7252) (Adrian Heine né Lang) +* 2ffe516 Update: add fixer for `curly` (#7105) (Teddy Katz) +* ac3504d Update: add functionPrototypeMethods to wrap-iife (fixes #7212) (#7284) (Eli White) +* 5e16fb4 Update: add fixer for `no-extra-bind` (#7236) (Teddy Katz) + +v3.7.1 - October 3, 2016 + +* 3dcae13 Fix: Use the correct location for `comma-dangle` errors (fixes #7291) (#7292) (Teddy Katz) +* cb7ba6d Fix: no-implicit-coercion should not fix ~. (fixes #7272) (#7289) (Eli White) +* ce590e2 Chore: Add additional tests for bin/eslint.js (#7290) (Teddy Katz) +* 8ec82ee Docs: change links of templates to raw data (#7288) (Toru Nagashima) + +v3.7.0 - September 30, 2016 + +* 2fee8ad Fix: object-shorthand's consistent-as-needed option (issue #7214) (#7215) (Naomi Jacobs) +* c05a19c Update: add fixer for `prefer-numeric-literals` (#7205) (Teddy Katz) +* 2f171f3 Update: add fixer for `no-undef-init` (#7210) (Teddy Katz) +* 876d747 Docs: Steps for adding new committers/TSCers (#7221) (Nicholas C. Zakas) +* dffb4fa Fix: `no-unused-vars` false positive (fixes #7250) (#7258) (Toru Nagashima) +* 4448cec Docs: Adding missing ES8 reference to configuring (#7271) (Kevin Partington) +* 332d213 Update: Ensure `indent` handles nested functions correctly (fixes #7249) (#7265) (Teddy Katz) +* c36d842 Update: add fixer for `no-useless-computed-key` (#7207) (Teddy Katz) +* 18376cf Update: add fixer for `lines-around-directive` (#7217) (Teddy Katz) +* f8e8fab Update: add fixer for `wrap-iife` (#7196) (Teddy Katz) +* 558b444 Docs: Add @not-an-aardvark to development team (#7279) (Ilya Volodin) +* cd1dc57 Update: Add a fixer for `dot-location` (#7186) (Teddy Katz) +* 89787b2 Update: for `yoda`, add a fixer (#7199) (Teddy Katz) +* 742ae67 Fix: avoid indent and no-mixed-spaces-and-tabs conflicts (fixes #7248) (#7266) (Teddy Katz) +* 85b8714 Fix: Use error templates even when reading from stdin (fixes #7213) (#7223) (Teddy Katz) +* 66adac1 Docs: correction in prefer-reflect docs (fixes #7069) (#7150) (Scott Stern) +* e3f95de Update: Fix `no-extra-parens` false negative (fixes #7229) (#7231) (Teddy Katz) +* 2909c19 Docs: Fix typo in object-shorthand docs (#7267) (Brian Donovan) +* 7bb800d Chore: add internal rule to enforce meta.docs conventions (fixes #6954) (#7155) (Vitor Balocco) +* 722c68c Docs: add code fences to the issue template (#7254) (Teddy Katz) + +v3.6.1 - September 26, 2016 + +* b467436 Upgrade: Upgrade Espree to 3.3.1 (#7253) (Ilya Volodin) +* 299a563 Build: Do not strip .md extension from absolute URLs (#7222) (Kai Cataldo) +* 27042d2 Chore: removed unused code related to scopeMap (#7218) (Yang Su) +* d154204 Chore: Lint bin/eslint.js (#7243) (Kevin Partington) +* 87625fa Docs: Improve eol-last examples in docs (#7227) (Chainarong Tangsurakit) +* de8eaa4 Docs: `class-methods-use-this`: fix option name (#7224) (Jordan Harband) +* 2355f8d Docs: Add Brunch plugin to integrations (#7225) (Aleksey Shvayka) +* a5817ae Docs: Default option from `operator-linebreak` is `after`and not always (#7228) (Konstantin Pschera) + +v3.6.0 - September 23, 2016 + +* 1b05d9c Update: add fixer for `strict` (fixes #6668) (#7198) (Teddy Katz) +* 0a36138 Docs: Update ecmaVersion instructions (#7195) (Nicholas C. Zakas) +* aaa3779 Update: Allow `space-unary-ops` to handle await expressions (#7174) (Teddy Katz) +* 91bf477 Update: add fixer for `prefer-template` (fixes #6978) (#7165) (Teddy Katz) +* 745343f Update: `no-extra-parens` supports async/await (refs #7101) (#7178) (Toru Nagashima) +* 8e1fee1 Fix: Handle number literals correctly in `no-whitespace-before-property` (#7185) (Teddy Katz) +* 462a3f7 Update: `keyword-spacing` supports async/await (refs #7101) (#7179) (Toru Nagashima) +* 709a734 Update: Allow template string in `valid-typeof` comparison (fixes #7166) (#7168) (Teddy Katz) +* f71937a Fix: Don't report async/generator callbacks in `array-callback-return` (#7172) (Teddy Katz) +* 461b015 Fix: Handle async functions correctly in `prefer-arrow-callback` fixer (#7173) (Teddy Katz) +* 7ea3e4b Fix: Handle await expressions correctly in `no-unused-expressions` (#7175) (Teddy Katz) +* 16bb802 Update: Ensure `arrow-parens` handles async arrow functions correctly (#7176) (Teddy Katz) +* 2d10657 Chore: add tests for `generator-star-spacing` and async (refs #7101) (#7182) (Toru Nagashima) +* c118d21 Update: Let `no-restricted-properties` check destructuring (fixes #7147) (#7151) (Teddy Katz) +* 9e0b068 Fix: valid-jsdoc does not throw on FieldType without value (fixes #7184) (#7187) (Kai Cataldo) +* 4b5d9b7 Docs: Update process for evaluating proposals (fixes #7156) (#7183) (Kai Cataldo) +* 95c777a Update: Make `no-restricted-properties` more flexible (fixes #7137) (#7139) (Teddy Katz) +* 0fdf23c Update: fix `quotes` rule's false negative (fixes #7084) (#7141) (Toru Nagashima) +* f2a789d Update: fix `no-unused-vars` false negative (fixes #7124) (#7143) (Toru Nagashima) +* 6148d85 Fix: Report columns for `eol-last` correctly (fixes #7136) (#7149) (kdex) +* e016384 Update: add fixer for quote-props (fixes #6996) (#7095) (Teddy Katz) +* 35f7be9 Upgrade: espree to 3.2.0, remove tests with SyntaxErrors (fixes #7169) (#7170) (Teddy Katz) +* 28ddcf8 Fix: `max-len`: `ignoreTemplateLiterals`: handle 3+ lines (fixes #7125) (#7138) (Jordan Harband) +* 660e091 Docs: Update rule descriptions (fixes #5912) (#7152) (Kenneth Williams) +* 8b3fc32 Update: Make `indent` report lines with mixed spaces/tabs (fixes #4274) (#7076) (Teddy Katz) +* b39ac2c Update: add fixer for `no-regex-spaces` (#7113) (Teddy Katz) +* cc80467 Docs: Update PR templates for formatting (#7128) (Nicholas C. Zakas) +* 76acbb5 Fix: include LogicalExpression in indent length calc (fixes #6731) (#7087) (Alec) +* a876673 Update: no-implicit-coercion checks TemplateLiterals (fixes #7062) (#7121) (Kai Cataldo) +* 8db4f0c Chore: Enable `typeof` check for `no-undef` rule in eslint-config-eslint (#7103) (Teddy Katz) +* 7e8316f Docs: Update release process (#7127) (Nicholas C. Zakas) +* 22edd8a Update: `class-methods-use-this`: `exceptMethods` option (fixes #7085) (#7120) (Jordan Harband) +* afd132a Fix: line-comment-position "above" string option now works (fixes #7100) (#7102) (Kevin Partington) +* 1738b2e Chore: fix name of internal-no-invalid-meta test file (#7142) (Vitor Balocco) +* ac0bb62 Docs: Fixes examples for allowTemplateLiterals (fixes #7115) (#7135) (Zoe Ingram) +* bcfa3e5 Update: Add `always`/`never` option to `eol-last` (fixes #6938) (#6952) (kdex) +* 0ca26d9 Docs: Distinguish examples for space-before-blocks (#7132) (Timo Tijhof) +* 9a2aefb Chore: Don't require an issue reference in check-commit npm script (#7104) (Teddy Katz) +* c85fd84 Fix: max-statements-per-line rule to force minimum to be 1 (fixes #7051) (#7092) (Scott Stern) +* e462e47 Docs: updates category of no-restricted-properties (fixes #7112) (#7118) (Alec) +* 6ae660b Fix: Don't report comparisons of two typeof expressions (fixes #7078) (#7082) (Teddy Katz) +* 710f205 Docs: Fix typos in Issues section of Maintainer's Guide (#7114) (Kai Cataldo) +* 546a3ca Docs: Clarify that linter does not process configuration (fixes #7108) (#7110) (Kevin Partington) +* 0d50943 Docs: Elaborate on `guard-for-in` best practice (fixes #7071) (#7094) (Dallon Feldner) +* 58e6d76 Docs: Fix examples for no-restricted-properties (#7099) (not-an-aardvark) +* 6cfe519 Docs: Corrected typo in line-comment-position rule doc (#7097) (Alex Mercier) +* f02e52a Docs: Add fixable note to no-implicit-coercion docs (#7096) (Brandon Mills) + +v3.5.0 - September 9, 2016 + +* 08fa538 Update: fix false negative of `arrow-spacing` (fixes #7079) (#7080) (Toru Nagashima) +* cec65e3 Update: add fixer for no-floating-decimal (fixes #7070) (#7081) (not-an-aardvark) +* 2a3f699 Fix: Column number for no-multiple-empty-lines (fixes #7086) (#7088) (Ian VanSchooten) +* 6947299 Docs: Add info about closing accepted issues to docs (fixes #6979) (#7089) (Kai Cataldo) +* d30157a Docs: Add link to awesome-eslint in integrations page (#7090) (Vitor Balocco) +* 457be1b Docs: Update so issues are not required (fixes #7015) (#7072) (Nicholas C. Zakas) +* d9513b7 Fix: Allow linting of .hidden files/folders (fixes #4828) (#6844) (Ian VanSchooten) +* 6d97c18 New: `max-len`: `ignoreStrings`+`ignoreTemplateLiterals` (fixes #5805) (#7049) (Jordan Harband) +* 538d258 Update: make no-implicit-coercion support autofixing. (fixes #7056) (#7061) (Eli White) +* 883316d Update: add fixer for prefer-arrow-callback (fixes #7002) (#7004) (not-an-aardvark) +* 7502eed Update: auto-fix for `comma-style` (fixes #6941) (#6957) (Gyandeep Singh) +* 645dda5 Update: add fixer for dot-notation (fixes #7014) (#7054) (not-an-aardvark) +* 2657846 Fix: `no-console` ignores user-defined console (fixes #7010) (#7058) (Toru Nagashima) +* 656bb6e Update: add fixer for newline-before-return (fixes #5958) (#7050) (Vitor Balocco) +* 1f995c3 Fix: no-implicit-coercion string concat false positive (fixes #7057) (#7060) (Kai Cataldo) +* 6718749 Docs: Clarify that `es6` env also sets `ecmaVersion` to 6 (#7067) (Jérémie Astori) +* e118728 Update: add fixer for wrap-regex (fixes #7013) (#7048) (not-an-aardvark) +* f4fcd1e Update: add more `indent` options for functions (fixes #6052) (#7043) (not-an-aardvark) +* 657eee5 Update: add fixer for new-parens (fixes #6994) (#7047) (not-an-aardvark) +* ff19aa9 Update: improve `max-statements-per-line` message (fixes #6287) (#7044) (Jordan Harband) +* 3960617 New: `prefer-numeric-literals` rule (fixes #6068) (#7029) (Annie Zhang) +* fa760f9 Chore: no-regex-spaces uses internal rule message format (fixes #7052) (#7053) (Kevin Partington) +* 22c7e09 Update: no-magic-numbers false negative on reassigned vars (fixes #4616) (#7028) (not-an-aardvark) +* be29599 Update: Throw error if whitespace found in plugin name (fixes #6854) (#6960) (Jesse Ostrander) +* 4063a79 Fix: Rule message placeholders can be inside braces (fixes #6988) (#7041) (Kevin Partington) +* 52e8d9c Docs: Clean up sort-vars (#7045) (Matthew Dunsdon) +* 4126f12 Chore: Rule messages use internal rule message format (fixes #6977) (#6989) (Kevin Partington) +* 46cb690 New: `no-restricted-properties` rule (fixes #3218) (#7017) (Eli White) +* 00b3042 Update: Pass file path to parse function (fixes #5344) (#7024) (Annie Zhang) +* 3f13325 Docs: Add kaicataldo and JamesHenry to our teams (#7039) (alberto) +* 8e77f16 Update: `new-parens` false negative (fixes #6997) (#6999) (Toru Nagashima) +* 326f457 Docs: Add missing 'to' in no-restricted-modules (#7022) (Oskar Risberg) +* 8277357 New: `line-comment-position` rule (fixes #6077) (#6953) (alberto) +* c1f0d76 New: `lines-around-directive` rule (fixes #6069) (#6998) (Kai Cataldo) +* 61f1de0 Docs: Fix typo in no-debugger (#7019) (Denis Ciccale) +* 256c4a2 Fix: Allow separate mode option for multiline and align (fixes #6691) (#6991) (Annie Zhang) +* a989a7c Docs: Declaring dependency on eslint in shared config (fixes #6617) (#6985) (alberto) +* 6869c60 Docs: Fix minor typo in no-extra-parens doc (#6992) (Jérémie Astori) +* 28f1619 Docs: Update the example of SwitchCase (#6981) (fish) + +v3.4.0 - August 26, 2016 + +* c210510 Update: add fixer for no-extra-parens (fixes #6944) (#6950) (not-an-aardvark) +* ca3d448 Fix: `prefer-const` false negative about `eslintUsed` (fixes #5837) (#6971) (Toru Nagashima) +* 1153955 Docs: Draft of JSCS migration guide (refs #5859) (#6942) (Nicholas C. Zakas) +* 3e522be Fix: false negative of `indent` with `else if` statements (fixes #6956) (#6965) (not-an-aardvark) +* 2dfb290 Docs: Distinguish examples in rules under Stylistic Issues part 7 (#6760) (Kenneth Williams) +* 3c710c9 Fix: rename "AirBnB" => "Airbnb" init choice (fixes #6969) (Harrison Shoff) +* 7660b39 Fix: `object-curly-spacing` for type annotations (fixes #6940) (#6945) (Toru Nagashima) +* 21ab784 New: do not remove non visited files from cache. (fixes #6780) (#6921) (Roy Riojas) +* 3a1763c Fix: enable `@scope/plugin/ruleId`-style specifier (refs #6362) (#6939) (Toru Nagashima) +* d6fd064 Update: Add never option to multiline-ternary (fixes #6751) (#6905) (Kai Cataldo) +* 0d268f1 New: `symbol-description` rule (fixes #6778) (#6825) (Jarek Rencz) +* a063d4e Fix: no-cond-assign within a function expression (fixes #6908) (#6909) (Patrick McElhaney) +* 16db93a Build: Tag docs, publish release notes (fixes #6892) (#6934) (Nicholas C. Zakas) +* 0cf1d55 Chore: Fix object-shorthand errors (fixes #6958) (#6959) (Kai Cataldo) +* 8851ddd Fix: Improve pref of globbing by inheriting glob.GlobSync (fixes #6710) (#6783) (Kael Zhang) +* cf2242c Update: `requireStringLiterals` option for `valid-typeof` (fixes #6698) (#6923) (not-an-aardvark) +* 8561389 Fix: `no-trailing-spaces` wrong fixing (fixes #6933) (#6937) (Toru Nagashima) +* 6a92be5 Docs: Update semantic versioning policy (#6935) (alberto) +* a5189a6 New: `class-methods-use-this` rule (fixes #5139) (#6881) (Gyandeep Singh) +* 1563808 Update: add support for ecmaVersion 20xx (fixes #6750) (#6907) (Kai Cataldo) +* d8b770c Docs: Change rule descriptions for consistent casing (#6915) (Brandon Mills) +* c676322 Chore: Use object-shorthand batch 3 (refs #6407) (#6914) (Kai Cataldo) + +v3.3.1 - August 15, 2016 + +* a2f06be Build: optimize rule page title for small browser tabs (fixes #6888) (#6904) (Vitor Balocco) +* 02a00d6 Docs: clarify rule details for no-template-curly-in-string (#6900) (not-an-aardvark) +* b9b3446 Fix: sort-keys ignores destructuring patterns (fixes #6896) (#6899) (Kai Cataldo) +* 3fe3a4f Docs: Update options in `object-shorthand` (#6898) (Grant Snodgrass) +* cd09c96 Chore: Use object-shorthand batch 2 (refs #6407) (#6897) (Kai Cataldo) +* 2841008 Chore: Use object-shorthand batch 1 (refs #6407) (#6893) (Kai Cataldo) + +v3.3.0 - August 12, 2016 + +* 683ac56 Build: Add CI release scripts (fixes #6884) (#6885) (Nicholas C. Zakas) +* ebf8441 Update: `prefer-rest-params` relax for member accesses (fixes #5990) (#6871) (Toru Nagashima) +* df01c4f Update: Add regex support for exceptions (fixes #5187) (#6883) (Annie Zhang) +* 055742c Fix: `no-dupe-keys` type errors (fixes #6886) (#6889) (Toru Nagashima) +* e456fd3 New: `sort-keys` rule (fixes #6076) (#6800) (Toru Nagashima) +* 3e879fc Update: Rule "eqeqeq" to have more specific null handling (fixes #6543) (#6849) (Simon Sturmer) +* e8cb7f9 Chore: use eslint-plugin-node (refs #6407) (#6862) (Toru Nagashima) +* e37bbd8 Docs: Remove duplicate statement (#6878) (Richard Käll) +* 11395ca Fix: `no-dupe-keys` false negative (fixes #6801) (#6863) (Toru Nagashima) +* 1ecd2a3 Update: improve error message in `no-control-regex` (#6839) (Jordan Harband) +* d610d6c Update: make `max-lines` report the actual number of lines (fixes #6766) (#6764) (Jarek Rencz) +* b256c50 Chore: Fix glob for core js files for lint (fixes #6870) (#6872) (Gyandeep Singh) +* f8ab8f1 New: func-call-spacing rule (fixes #6080) (#6749) (Brandon Mills) +* be68f0b New: no-template-curly-in-string rule (fixes #6186) (#6767) (Jeroen Engels) +* 80789ab Chore: don't throw if rule is in old format (fixes #6848) (#6850) (Vitor Balocco) +* d47c505 Fix: `newline-after-var` false positive (fixes #6834) (#6847) (Toru Nagashima) +* bf0afcb Update: validate void operator in no-constant-condition (fixes #5726) (#6837) (Vitor Balocco) +* 5ef839e New: Add consistent and ..-as-needed to object-shorthand (fixes #5438) (#5439) (Martijn de Haan) +* 7e1bf01 Fix: update peerDependencies of airbnb option for `--init` (fixes #6843) (#6846) (Vitor Balocco) +* 8581f4f Fix: `no-invalid-this` false positive (fixes #6824) (#6827) (Toru Nagashima) +* 90f78f4 Update: add `props` option to `no-self-assign` rule (fixes #6718) (#6721) (Toru Nagashima) +* 30d71d6 Update: 'requireForBlockBody' modifier for 'arrow-parens' (fixes #6557) (#6558) (Nicolas Froidure) +* cdded07 Chore: use native `Object.assign` (refs #6407) (#6832) (Gyandeep Singh) +* 579ec49 Chore: Add link to rule change guidelines in "needs info" template (fixes #6829) (#6831) (Kevin Partington) +* 117e7aa Docs: Remove incorrect "constructor" statement from `no-new-symbol` docs (#6830) (Jarek Rencz) +* aef18b4 New: `no-unsafe-negation` rule (fixes #2716) (#6789) (Toru Nagashima) +* d94e945 Docs: Update Getting Started w/ Readme installation instructions (#6823) (Kai Cataldo) +* dfbc112 Upgrade: proxyquire to 1.7.10 (fixes #6821) (#6822) (alberto) +* 4c5e911 Chore: enable `prefer-const` and apply it to our codebase (refs #6407) (#6805) (Toru Nagashima) +* e524d16 Update: camelcase rule fix for import declarations (fixes #6755) (#6784) (Lorenzo Zottar) +* 8f3509d Update: make `eslint:all` excluding deprecated rules (fixes #6734) (#6756) (Toru Nagashima) +* 2b17459 New: `no-global-assign` rule (fixes #6586) (#6746) (alberto) + +v3.2.2 - August 1, 2016 + +* 510ce4b Upgrade: file-entry-cache@^1.3.1 (fixes #6816, refs #6780) (#6819) (alberto) +* 46b14cd Fix: ignore MemberExpression in VariableDeclarators (fixes #6795) (#6815) (Nicholas C. Zakas) + +v3.2.1 - August 1, 2016 + +* 584577a Build: Pin file-entry-cache to avoid licence issue (refs #6816) (#6818) (alberto) +* 38d0d23 Docs: clarify minor releases and suggest using `~ to version (#6804) (Henry Zhu) +* 4ca809e Fix: Normalizes messages so all end with a period (fixes #6762) (#6807) (Patrick McElhaney) +* c7488ac Fix: Make MemberExpression option opt-in (fixes #6797) (#6798) (Rich Trott) +* 715e8fa Docs: Update issue closing policy (fixes #6765) (#6808) (Nicholas C. Zakas) +* 288f7bf Build: Fix site generation (fixes #6791) (#6793) (Nicholas C. Zakas) +* 261a9f3 Docs: Update JSCS status in README (#6802) (alberto) +* 5ae0887 Docs: Update no-void.md (#6799) (Daniel Hritzkiv) + +v3.2.0 - July 29, 2016 + +* 2438ee2 Upgrade: Update markdownlint dependency to 0.2.0 (fixes #6781) (#6782) (David Anson) +* 4fc0018 Chore: dogfooding `no-var` rule and remove `var`s (refs #6407) (#6757) (Toru Nagashima) +* b22eb5c New: `no-tabs` rule (fixes #6079) (#6772) (Gyandeep Singh) +* ddea63a Chore: Updated no-control-regex tests to cover all cases (fixes #6438) (#6752) (Efe Gürkan YALAMAN) +* 1025772 Docs: Add plugin example to disabling with comments guide (fixes #6742) (#6747) (Brandon Mills) +* 628aae4 Docs: fix inconsistent spacing inside block comment (#6768) (Brian Jacobel) +* 2983c32 Docs: Add options to func-names config comments (#6748) (Brandon Mills) +* 2f94443 Docs: fix wrong path (#6763) (molee1905) +* 6f3faa4 Revert "Build: Remove support for Node v5 (fixes #6743)" (#6758) (Nicholas C. Zakas) +* 99dfd1c Docs: fix grammar issue in rule-changes page (#6761) (Vitor Balocco) +* e825458 Fix: Rule no-unused-vars had missing period (fixes #6738) (#6739) (Brian Mock) +* 71ae64c Docs: Clarify cache file deletion (fixes #4943) (#6712) (Nicholas C. Zakas) +* 26c85dd Update: merge warnings of consecutive unreachable nodes (fixes #6583) (#6729) (Toru Nagashima) +* 106e40b Fix: Correct grammar in object-curly-newline reports (fixes #6725) (#6728) (Vitor Balocco) +* e00754c Chore: Dogfooding ES6 rules (refs #6407) (#6735) (alberto) +* 181b26a Build: Remove support for Node v5 (fixes #6743) (#6744) (alberto) +* 5320a6c Update: `no-use-before-define` false negative on for-in/of (fixes #6699) (#6719) (Toru Nagashima) +* a2090cb Fix: space-infix-ops doesn't fail for type annotations(fixes #5211) (#6723) (Nicholas C. Zakas) +* 9c36ecf Docs: Add @vitorbal and @platinumazure to development team (Ilya Volodin) +* e09d1b8 Docs: describe all RuleTester options (fixes #4810, fixes #6709) (#6711) (Nicholas C. Zakas) +* a157f47 Chore: Update CLIEngine option desc (fixes #5179) (#6713) (Nicholas C. Zakas) +* a0727f9 Chore: fix `.gitignore` for vscode (refs #6383) (#6720) (Toru Nagashima) +* 75d2d43 Docs: Clarify Closure type hint expectation (fixes #5231) (#6714) (Nicholas C. Zakas) +* 95ea25a Update: Check indentation of multi-line chained properties (refs #1801) (#5940) (Rich Trott) +* e7b1e1c Docs: Edit issue/PR waiting period docs (fixes #6009) (#6715) (Nicholas C. Zakas) +* 053aa0c Update: Added 'allowSuper' option to `no-underscore-dangle` (fixes #6355) (#6662) (peteward44) +* 8929045 Build: Automatically generate rule index (refs #2860) (#6658) (Ilya Volodin) +* f916ae5 Docs: Fix multiline-ternary typos (#6704) (Cédric Malard) +* c64b0c2 Chore: First ES6 refactoring (refs #6407) (#6570) (Nicholas C. Zakas) + +v3.1.1 - July 18, 2016 + +* 565e584 Fix: `eslint:all` causes regression in 3.1.0 (fixes #6687) (#6696) (alberto) +* cb90359 Fix: Allow named recursive functions (fixes #6616) (#6667) (alberto) +* 3f206dd Fix: `balanced` false positive in `spaced-comment` (fixes #6689) (#6692) (Grant Snodgrass) +* 57f1676 Docs: Add missing brackets from code examples (#6700) (Plusb Preco) +* 124f066 Chore: Remove fixable key from multiline-ternary metadata (fixes #6683) (#6688) (Kai Cataldo) +* 9f96086 Fix: Escape control characters in XML. (fixes #6673) (#6672) (George Chung) + +v3.1.0 - July 15, 2016 + +* e8f8c6c Fix: incorrect exitCode when eslint is called with --stdin (fixes #6677) (#6682) (Steven Humphrey) +* 38639bf Update: make `no-var` fixable (fixes #6639) (#6644) (Toru Nagashima) +* dfc20e9 Fix: `no-unused-vars` false positive in loop (fixes #6646) (#6649) (Toru Nagashima) +* 2ba75d5 Update: relax outerIIFEBody definition (fixes #6613) (#6653) (Stephen E. Baker) +* 421e4bf Chore: combine multiple RegEx replaces with one (fixes #6669) (#6661) (Sakthipriyan Vairamani) +* 089ee2c Docs: fix typos,wrong path,backticks (#6663) (molee1905) +* ef827d2 Docs: Add another pre-commit hook to integrations (#6666) (David Alan Hjelle) +* a343b3c Docs: Fix option typo in no-underscore-dangle (Fixes #6674) (#6675) (Luke Page) +* 5985eb2 Chore: add internal rule that validates meta property (fixes #6383) (#6608) (Vitor Balocco) +* 4adb15f Update: Add `balanced` option to `spaced-comment` (fixes #4133) (#6575) (Annie Zhang) +* 1b13c25 Docs: fix incorrect example being mark as correct (#6660) (David Björklund) +* a8b4e40 Fix: Install required eslint plugin for "standard" guide (fixes #6656) (#6657) (Feross Aboukhadijeh) +* 720686b New: `endLine` and `endColumn` of the lint result. (refs #3307) (#6640) (Toru Nagashima) +* 54faa46 Docs: Small tweaks to CLI documentation (fixes #6627) (#6642) (Kevin Partington) +* e108850 Docs: Added examples and structure to `padded-blocks` (fixes #6628) (#6643) (alberto) +* 350e1c0 Docs: Typo (#6650) (Peter Rood) +* b837c92 Docs: Correct a term in max-len.md (fixes #6637) (#6641) (Vse Mozhet Byt) +* baeb313 Fix: Warning behavior for executeOnText (fixes #6611) (#6632) (Nicholas C. Zakas) +* e6004be Chore: Enable preferType in valid-jsdoc (refs #5188) (#6634) (Nicholas C. Zakas) +* ca323cf Fix: Use default assertion messages (fixes #6532) (#6615) (Dmitrii Abramov) +* 2bdf22c Fix: Do not throw exception if baseConfig is provided (fixes #6605) (#6625) (Kevin Partington) +* e42cacb Upgrade: mock-fs to 3.10, fixes for Node 6.3 (fixes #6621) (#6624) (Tim Schaub) +* 8a263ae New: multiline-ternary rule (fixes #6066) (#6590) (Kai Cataldo) +* e951303 Update: Adding new `key-spacing` option (fixes #5613) (#5907) (Kyle Mendes) +* 10c3e91 Docs: Remove reference from 3.0.0 migration guide (refs #6605) (#6618) (Kevin Partington) +* 5010694 Docs: Removed non-existing resource (#6609) (Moritz Kröger) +* 6d40d85 Docs: Note that PR requires ACCEPTED issue (refs #6568) (#6604) (Patrick McElhaney) + +v3.0.1 - July 5, 2016 + +* 27700cf Fix: `no-unused-vars` false positive around callback (fixes #6576) (#6579) (Toru Nagashima) +* 124d8a3 Docs: Pull request template (#6568) (Nicholas C. Zakas) +* e9a2ed9 Docs: Fix rules\id-length exceptions typos (fixes #6397) (#6593) (GramParallelo) +* a2cfa1b Fix: Make outerIIFEBody work correctly (fixes #6585) (#6596) (Nicholas C. Zakas) +* 9c451a2 Docs: Use string severity in example (#6601) (Kenneth Williams) +* 8308c0b Chore: remove path-is-absolute in favor of the built-in (fixes #6598) (#6600) (shinnn) +* 7a63717 Docs: Add missing pull request step (fixes #6595) (#6597) (Nicholas C. Zakas) +* de3ed84 Fix: make `no-unused-vars` ignore for-in (fixes #2342) (#6126) (Oleg Gaidarenko) +* 6ef2cbe Fix: strip Unicode BOM of config files (fixes #6556) (#6580) (Toru Nagashima) +* ee7fcfa Docs: Correct type of `outerIIFEBody` in `indent` (fixes #6581) (#6584) (alberto) +* 25fc7b7 Fix: false negative of `max-len` (fixes #6564) (#6565) (not-an-aardvark) +* f6b8452 Docs: Distinguish examples in rules under Stylistic Issues part 6 (#6567) (Kenneth Williams) + +v3.0.0 - July 1, 2016 + +* 66de9d8 Docs: Update installation instructions on README (#6569) (Nicholas C. Zakas) +* dc5b78b Breaking: Add `require-yield` rule to `eslint:recommended` (fixes #6550) (#6554) (Gyandeep Singh) +* 7988427 Fix: lib/config.js tests pass if personal config exists (fixes #6559) (#6566) (Kevin Partington) +* 4c05967 Docs: Update rule docs for new format (fixes #5417) (#6551) (Nicholas C. Zakas) +* 70da5a8 Docs: Correct link to rules page (#fixes 6553) (#6561) (alberto) +* e2b2030 Update: Check RegExp strings for `no-regex-spaces` (fixes #3586) (#6379) (Jackson Ray Hamilton) +* 397e51b Update: Implement outerIIFEBody for indent rule (fixes #6259) (#6382) (David Shepherd) +* 666da7c Docs: 3.0.0 migration guide (#6521) (Nicholas C. Zakas) +* b9bf8fb Docs: Update Governance Policy (fixes #6452) (#6522) (Nicholas C. Zakas) +* 1290657 Update: `no-unused-vars` ignores read it modifies itself (fixes #6348) (#6535) (Toru Nagashima) +* d601f6b Fix: Delete cache only when executing on files (fixes #6459) (#6540) (Kai Cataldo) +* e0d4b19 Breaking: Error thrown/printed if no config found (fixes #5987) (#6538) (Kevin Partington) +* 18663d4 Fix: false negative of `no-useless-rename` (fixes #6502) (#6506) (Toru Nagashima) +* 0a7936d Update: Add fixer for prefer-const (fixes #6448) (#6486) (Nick Heiner) +* c60341f Chore: Update index and `meta` for `"eslint:recommended"` (refs #6403) (#6539) (Mark Pedrotti) +* 73da28d Better wording for the error reported by the rule "no-else-return" #6411 (#6413) (Olivier Thomann) +* e06a5b5 Update: Add fixer for arrow-parens (fixes #4766) (#6501) (madmed88) +* 5f8f3e8 Docs: Remove Box as a sponsor (#6529) (Nicholas C. Zakas) +* 7dfe0ad Docs: fix max-lines samples (fixes #6516) (#6515) (Dmitriy Shekhovtsov) +* fa05119 Breaking: Update eslint:recommended (fixes #6403) (#6509) (Nicholas C. Zakas) +* e96177b Docs: Add "Proposing a Rule Change" link to CONTRIBUTING.md (#6511) (Kevin Partington) +* bea9096 Docs: Update pull request steps (fixes #6474) (#6510) (Nicholas C. Zakas) +* 7bcf6e0 Docs: Consistent example headings & text pt3 (refs #5446) (#6492) (Guy Fraser) +* 1a328d9 Docs: Consistent example headings & text pt4 (refs #5446) (#6493) (Guy Fraser) +* ff5765e Docs: Consistent example headings & text pt2 (refs #5446)(#6491) (Guy Fraser) +* 01384fa Docs: Fixing typos (refs #5446)(#6494) (Guy Fraser) +* 4343ae8 Fix: false negative of `object-shorthand` (fixes #6429) (#6434) (Toru Nagashima) +* b7d8c7d Docs: more accurate yoda-speak (#6497) (Tony Lukasavage) +* 3b0ab0d Fix: add warnIgnored flag to CLIEngine.executeOnText (fixes #6302) (#6305) (Robert Levy) +* c2c6cec Docs: Mark object-shorthand as fixable. (#6485) (Nick Heiner) +* 5668236 Fix: Allow objectsInObjects exception when destructuring (fixes #6469) (#6470) (Adam Renklint) +* 17ac0ae Fix: `strict` rule reports a syntax error for ES2016 (fixes #6405) (#6464) (Toru Nagashima) +* 4545123 Docs: Rephrase documentation for `no-duplicate-imports` (#6463) (Simen Bekkhus) +* 1b133e3 Docs: improve `no-native-reassign` and specifying globals (fixes #5358) (#6462) (Toru Nagashima) +* b179373 Chore: Remove dead code in excuteOnFiles (fixes #6467) (#6466) (Andrew Hutchings) +* 18fbc4b Chore: Simplify eslint process exit code (fixes #6368) (#6371) (alberto) +* 58542e4 Breaking: Drop support for node < 4 (fixes #4483) (#6401) (alberto) +* f50657e Breaking: use default for complexity in eslint:recommended (fixes #6021) (#6410) (alberto) +* 3e690fb Fix: Exit init early if guide is chosen w/ no package.json (fixes #6476) (#6478) (Kai Cataldo) + +v2.13.1 - June 20, 2016 + +* 434de7f Fix: wrong baseDir (fixes #6450) (#6457) (Toru Nagashima) +* 3c9ce09 Fix: Keep indentation when fixing `padded-blocks` "never" (fixes #6454) (#6456) (Ed Lee) +* a9d4cb2 Docs: Fix typo in max-params examples (#6471) (J. William Ashton) +* 1e185b9 Fix: no-multiple-empty-lines errors when no line breaks (fixes #6449) (#6451) (strawbrary) + +v2.13.0 - June 17, 2016 + +* cf223dd Fix: add test for a syntax error (fixes #6013) (#6378) (Toru Nagashima) +* da30cf9 Update: Add fixer for object-shorthand (fixes #6412) (#6418) (Nick Heiner) +* 2cd90eb Chore: Fix rule meta description inconsistencies (refs #5417) (#6422) (Mark Pedrotti) +* d798b2c Added quotes around "classes" option key (#6441) (Guy Fraser) +* 852b6df Docs: Delete empty table of links from Code Path Analysis (#6423) (Mark Pedrotti) +* 5e9117e Chore: sort rules in eslint.json (fixes #6425) (#6426) (alberto) +* c2b5277 Docs: Add gitter chat link to Reporting Bugs (#6430) (Mark Pedrotti) +* 1316db0 Update: Add `never` option for `func-names` (fixes #6059) (#6392) (alberto) +* 1c123e2 Update: Add autofix for `padded-blocks` (fixes #6320) (#6393) (alberto) +* 8ec89c8 Fix: `--print-config` return config inside subdir (fixes #6329) (#6385) (alberto) +* 4f73240 Fix: `object-curly-newline` multiline with comments (fixes #6381) (#6396) (Toru Nagashima) +* 77697a7 Chore: Fake config hierarchy fixtures (fixes #6206) (#6402) (Gyandeep Singh) +* 73a9a6d Docs: Fix links in Configuring ESLint (#6421) (Mark Pedrotti) +* ed84c4c Fix: improve `newline-per-chained-call` message (fixes #6340) (#6360) (Toru Nagashima) +* 9ea4e44 Docs: Update parser reference to `espree` instead of `esprima` (#6404) (alberto) +* 7f57467 Docs: Make `fix` param clearer (fixes #6366) (#6367) (Nick Heiner) +* fb49c7f Fix: nested `extends` with relative path (fixes #6358) (#6359) (Toru Nagashima) +* 5122f73 Update: no-multiple-empty-lines fixer (fixes #6225) (#6226) (Ruurd Moelker) +* 0e7ce72 Docs: Fix rest-spread-spacing's name (#6365) (cody) +* cfdd524 Fix: allow semi as braceless body of statements (fixes #6386) (#6391) (alberto) +* 6b08cfc Docs: key-spacing fixable documenation notes (fixes #6375) (#6376) (Ruurd Moelker) +* 4b4be3b Docs: `max-lines` option: fix `skipComments` typo (#6374) (Jordan Harband) +* 20ab4f6 Docs: Fix wrong link in object-curly-newline (#6373) (Grant Snodgrass) +* 412ce8d Docs: Fix broken links in no-mixed-operators (#6372) (Grant Snodgrass) + +v2.12.0 - June 10, 2016 + +* 54c30fb Update: Add explicit default option `always` for `eqeqeq` (refs #6144) (#6342) (alberto) +* 2d63370 Update: max-len will warn indented comment lines (fixes #6322) (#6324) (Kai Cataldo) +* dcd4ad7 Docs: clarify usage of inline disable comments (fixes #6335) (#6347) (Kai Cataldo) +* c03300b Docs: Clarified how plugin rules look in plugin configs (fixes #6346) (#6351) (Kevin Partington) +* 9c87709 Docs: Add semantic versioning policy (fixes #6244) (#6343) (Nicholas C. Zakas) +* 5affab1 Docs: Describe values under Extending Configuration Files (refs #6240) (#6336) (Mark Pedrotti) +* 2520f5a New: `max-lines` rule (fixes #6078) (#6321) (alberto) +* 9bfbc64 Update: Option for object literals in `arrow-body-style` (fixes #5936) (#6216) (alberto) +* 977cdd5 Chore: remove unused method from FileFinder (fixes #6344) (#6345) (alberto) +* 477fbc1 Docs: Add section about customizing RuleTester (fixes #6227) (#6331) (Jeroen Engels) +* 0e14016 New: `no-mixed-operators` rule (fixes #6023) (#6241) (Toru Nagashima) +* 6e03c4b Update: Add never option to arrow-body-style (fixes #6317) (#6318) (Andrew Hyndman) +* f804397 New: Add `eslint:all` option (fixes #6240) (#6248) (Robert Fletcher) +* dfe05bf Docs: Link JSCS rules to their corresponding page. (#6334) (alberto) +* 1cc4356 Docs: Remove reference to numeric config (fixes #6309) (#6327) (Kevin Partington) +* 2d4efbe Docs: Describe options in rule under Strict Mode (#6312) (Mark Pedrotti) +* c1953fa Docs: Typo fix 'and' -> 'any' (#6326) (Stephen Edgar) +* d49ab4b Docs: Code conventions improvements (#6313) (Kevin Partington) +* 316a507 Fix: one-var allows uninitialized vars in ForIn/ForOf (fixes #5744) (#6272) (Kai Cataldo) +* 6cbee31 Docs: Typo fix 'colum' -> 'column' (#6306) (Andrew Cobby) +* 2663569 New: `object-curly-newline` (fixes #6072) (#6223) (Toru Nagashima) +* 72c2ea5 Update: callback-return allows for object methods (fixes #4711) (#6277) (Kai Cataldo) +* 89580a4 Docs: Distinguish examples in rules under Stylistic Issues part 5 (#6291) (Kenneth Williams) +* 1313804 New: rest-spread-spacing rule (fixes #5391) (#6278) (Kai Cataldo) +* 61dfe68 Fix: `no-useless-rename` false positive in babel-eslint (fixes #6266) (#6290) (alberto) +* c78c8cb Build: Remove commit check from appveyor (fixes #6292) (#6294) (alberto) +* 3e38fc1 Chore: more tests for comments at the end of blocks (refs #6090) (#6273) (Kai Cataldo) +* 38dccdd Docs: `--no-ignore` disables all forms of ignore (fixes #6260) (#6304) (alberto) +* bb69380 Fix: no-useless-rename handles ExperimentalRestProperty (fixes #6284) (#6288) (Kevin Partington) +* fca0679 Update: Improve perf not traversing default ignored dirs (fixes #5679) (#6276) (alberto) +* 320e8b0 Docs: Describe options in rules under Possible Errors part 4 (#6270) (Mark Pedrotti) +* 3e052c1 Docs: Mark no-useless-rename as fixable in rules index (#6297) (Dalton Santos) + +v2.11.1 - May 30, 2016 + +* 64b0d0c Fix: failed to parse `/*eslint` comments by colon (fixes #6224) (#6258) (Toru Nagashima) +* c8936eb Build: Don't check commit count (fixes #5935) (#6263) (Nicholas C. Zakas) +* 113c1a8 Fix: `max-statements-per-line` false positive at exports (fixes #6264) (#6268) (Toru Nagashima) +* 03beb27 Fix: `no-useless-rename` false positives (fixes #6266) (#6267) (alberto) +* fe89037 Docs: Fix rule name in example (#6279) (Kenneth Williams) + +v2.11.0 - May 27, 2016 + +* 77dd2b4 Fix: On --init, print message when package.json is invalid (fixes #6257) (#6261) (Kai Cataldo) +* 7f60186 Fix: `--ignore-pattern` can't uningnore files (fixes #6127) (#6253) (alberto) +* fea8fe6 New: no-useless-rename (fixes #6058) (#6249) (Kai Cataldo) +* b4cff9d Fix: Incorrect object-property-newline behavior (fixes #6207) (#6213) (Rafał Ruciński) +* 35b4656 Docs: Edit arrow-parens.md to show correct output value (#6245) (Adam Terlson) +* ee0cd58 Fix: `newline-before-return` shouldn't disallow newlines (fixes #6176) (#6217) (alberto) +* d4f5526 Fix: `vars-on-top` crashs at export declarations (fixes #6210) (#6220) (Toru Nagashima) +* 088bda9 New: `unicode-bom` rule to allow or disallow BOM (fixes #5502) (#6230) (Andrew Johnston) +* 14bfc03 Fix: `comma-dangle` wrong autofix (fixes #6233) (#6235) (Toru Nagashima) +* cdd65d7 Docs: added examples for arrow-body-style (refs #5498) (#6242) (Tieme van Veen) +* c10c07f Fix: lost code in autofixing (refs #6233) (#6234) (Toru Nagashima) +* e6d5b1f Docs: Add rule deprecation section to user guide (fixes #5845) (#6201) (Kai Cataldo) +* 777941e Upgrade: doctrine to 1.2.2 (fixes #6121) (#6231) (alberto) +* 74c458d Update: key-spacing rule whitespace fixer (fixes #6167) (#6169) (Ruurd Moelker) +* 04bd586 New: Disallow use of Object.prototype methods on objects (fixes #2693) (#6107) (Andrew Levine) +* 53754ec Update: max in `max-statements-per-line` should be >=0 (fixes #6171) (#6172) (alberto) +* 54d1201 Update: Add treatUndefinedAsUnspecified option (fixes #6026) (#6194) (Kenneth Williams) +* 18152dd Update: Add checkLoops option to no-constant-condition (fixes #5477) (#6202) (Kai Cataldo) +* 7644908 Fix: no-multiple-empty-lines BOF and EOF defaults (fixes #6179) (#6180) (Ruurd Moelker) +* 72335eb Fix: `max-statements-per-line` false positive (fixes #6173, fixes #6153) (#6192) (Toru Nagashima) +* 9fce04e Fix: `generator-star-spacing` false positive (fixes #6135) (#6168) (Toru Nagashima) + +v2.10.2 - May 16, 2016 + +* bda5de5 Fix: Remove default parser from CLIEngine options (fixes #6182) (#6183) (alberto) +* e59e5a0 Docs: Describe options in rules under Possible Errors part 3 (#6105) (Mark Pedrotti) +* 842ab2e Build: Run phantomjs tests using karma (fixes #6128) (#6178) (alberto) + +v2.10.1 - May 14, 2016 + +* 9397135 Fix: `valid-jsdoc` false positive at default parameters (fixes #6097) (#6170) (Toru Nagashima) +* 2166ad4 Fix: warning & error count in `CLIEngine.getErrorResults` (fixes #6155) (#6157) (alberto) +* 1e0a652 Fix: ignore empty statements in max-statements-per-line (fixes #6153) (#6156) (alberto) +* f9ca0d6 Fix: `no-extra-parens` to check for nulls (fixes #6161) (#6164) (Gyandeep Singh) +* d095ee3 Fix: Parser merge sequence in config (fixes #6158) (#6160) (Gyandeep Singh) +* f33e49f Fix: `no-return-assign` to check for null tokens (fixes #6159) (#6162) (Gyandeep Singh) + +v2.10.0 - May 13, 2016 + +* 098cd9c Docs: Distinguish examples in rules under Stylistic Issues part 4 (#6136) (Kenneth Williams) +* 805742c Docs: Clarify JSX option usage (#6132) (Richard Collins) +* 10b0933 Fix: Optimize no-irregular-whitespace for the common case (fixes #6116) (#6117) (Andres Suarez) +* 36bec90 Docs: linkify URLs in development-environment.md (#6150) (chrisjshull) +* 29c401a Docs: Convert rules in index under Removed from list to table (#6091) (Mark Pedrotti) +* e13e696 Fix: `_` and `$` in isES5Constructor (fixes #6085) (#6094) (Kevin Locke) +* 67916b9 Fix: `no-loop-func` crashed (fixes #6130) (#6138) (Toru Nagashima) +* d311a62 Fix: Sort fixes consistently even if they overlap (fixes #6124) (#6133) (alberto) +* 6294459 Docs: Correct syntax for default ignores and `.eslintignore` example (#6118) (alberto) +* 067db14 Fix: Replace `assert.deepEqual` by `lodash.isEqual` (fixes #6111) (#6112) (alberto) +* 52fdf04 Fix: `no-multiple-empty-lines` duplicate errors at BOF (fixes #6113) (#6114) (alberto) +* e6f56da Docs: Document `--ignore-pattern` (#6120) (alberto) +* ef739cd Fix: Merge various command line configs at the same time (fixes #6104) (#6108) (Ed Lee) +* 767da6f Update: add returnAssign option to no-extra-parens (fixes #6036) (#6095) (Kai Cataldo) +* 06f6252 Build: Use split instead of slice/indexOf for commit check (fixes #6109) (#6110) (Ed Lee) +* c4fc39b Docs: Update headings of rules under Removed (refs #5774) (#6102) (Mark Pedrotti) +* 716345f Build: Match rule id at beginning of heading (refs #5774) (#6089) (Mark Pedrotti) +* 0734967 Update: Add an option to `prefer-const` (fixes #5692) (#6040) (Toru Nagashima) +* 7941d5e Update: Add autofix for `lines-around-comment` (fixes #5956) (#6062) (alberto) +* dc538aa Build: Pin proxyquire to ">=1.0.0 <1.7.5" (fixes #6096) (#6100) (alberto) +* 04563ca Docs: Describe options in rules under Possible Errors part 2 (#6063) (Mark Pedrotti) +* 5d390b2 Chore: Replace deprecated calls to context - batch 4 (fixes #6029) (#6087) (alberto) +* 6df4b23 Fix: `no-return-assign` warning nested expressions (fixes #5913) (#6041) (Toru Nagashima) +* 16fad58 Merge pull request #6088 from eslint/docs-one-var-per-line (alberto) +* 0b67170 Docs: Correct default for `one-var-declaration-per-line` (fixes #6017) (#6022) (Ed Lee) +* d40017f Fix: comma-style accounts for parens in array (fixes #6006) (#6038) (Kai Cataldo) +* 992d9cf Docs: Fix typography/teriminology in indent doc (fixes #6045) (#6044) (Rich Trott) +* 4ae39d2 Chore: Replace deprecated calls to context - batch 3 (refs #6029) (#6056) (alberto) +* 8633e4d Update: multipass should not exit prematurely (fixes #5995) (#6048) (alberto) +* 3c44c2c Update: Adds an avoidQuotes option for object-shorthand (fixes #3366) (#5870) (Chris Sauvé) +* a9a4652 Fix: throw when rule uses `fix` but `meta.fixable` not set (fixes #5970) (#6043) (Vitor Balocco) +* ad10106 Docs: Update comma-style docs (#6039) (Kai Cataldo) +* 388d6f8 Fix: `no-sequences` false negative at arrow expressions (fixes #6082) (#6083) (Toru Nagashima) +* 8e96064 Docs: Clarify rule example in README since we allow string error levels (#6061) (Kevin Partington) +* a66bf19 Fix: `lines-around-comment` multiple errors on same line (fixes #5965) (#5994) (alberto) +* a2cc54e Docs: Organize meta and describe visitor in Working with Rules (#5967) (Mark Pedrotti) +* ef8cbff Fix: object-shorthand should only lint computed methods (fixes #6015) (#6024) (Kai Cataldo) +* cd1b057 Chore: Replace deprecated calls to context - batch 2 (refs #6029) (#6049) (alberto) +* a3a6e06 Update: no-irregal-whitespace in a regular expression (fixes #5840) (#6018) (Linda_pp) +* 9b9d76c Chore: Replace deprecated calls to context - batch 1 (refs #6029) (#6034) (alberto) +* dd8bf93 Fix: blockless else in max-statements-per-line (fixes #5926) (#5993) (Glen Mailer) +* f84eb80 New: Add new rule `object-property-newline` (fixes #5667) (#5933) (Vitor Balocco) +* d5f4104 Docs: mention parsing errors in strict mode (fixes #5485) (#5991) (Mark Pedrotti) +* 249732e Docs: Move docs from eslint.github.io (fixes #5964) (#6012) (Nicholas C. Zakas) +* 4c2de6c Docs: Add example of diff clarity to comma-dangle rule docs (#6035) (Vitor Balocco) +* 3db2e89 Fix: Do not swallow exceptions in CLIEngine.getFormatter (fixes #5977) (#5978) (Gustav Nikolaj) +* eb2fb44 Fix: Always ignore defaults unless explicitly passed (fixes #5547) (#5820) (Ian VanSchooten) +* ab57e94 Docs: Add example of diff clarity to newline-per-chained-call (#5986) (Vitor Balocco) +* 88bc014 Docs: Update readme info about jshint (#6027) (alberto) +* a2c15cc Docs: put config example in code block (#6005) (Amos Wenger) +* a5011cb Docs: Fix a wrong examples' header of `prefer-arrow-callback`. (#6020) (Toru Nagashima) +* 1484ede Docs: Typo in nodejs-api (#6025) (alberto) +* ade6a9b Docs: typo: "eslint-disable-line" not "eslint disable-line" (#6019) (Will Day) +* 2f15354 Fix: Removed false positives of break and continue (fixes #5972) (#6000) (Onur Temizkan) + +v2.9.0 - April 29, 2016 + +* a8a2cd8 Fix: Avoid autoconfig crashes from inline comments (fixes #5992) (#5999) (Ian VanSchooten) +* 23b00e0 Upgrade: npm-license to 0.3.2 (fixes #5996) (#5998) (alberto) +* 377167d Upgrade: ignore to 3.1.2 (fixes #5979) (#5988) (alberto) +* 141b778 Fix: no-control-regex literal handling fixed. (fixes #5737) (#5943) (Efe Gürkan YALAMAN) +* 577757d Fix: Clarify color option (fixes #5928) (#5974) (Grant Snodgrass) +* e7e6581 Docs: Update CLA link (#5980) (Gustav Nikolaj) +* 0be26bc Build: Add nodejs 6 to travis (fixes #5971) (#5973) (Gyandeep Singh) +* e606523 New: Rule `no-unsafe-finally` (fixes #5808) (#5932) (Onur Temizkan) +* 42d1ecc Chore: Add metadata to existing rules - Batch 7 (refs #5417) (#5969) (Vitor Balocco) +* e2ad1ec Update: object-shorthand lints computed methods (fixes #5871) (#5963) (Chris Sauvé) +* d24516a Chore: Add metadata to existing rules - Batch 6 (refs #5417) (#5966) (Vitor Balocco) +* 1e7a3ef Fix: `id-match` false positive in property values (fixes #5885) (#5960) (Mike Sherov) +* 51ddd4b Update: Use process @abstract when processing @return (fixes #5941) (#5945) (Simon Schick) +* 52a4bea Update: Add autofix for `no-whitespace-before-property` (fixes #5927) (#5951) (alberto) +* 46e058d Docs: Correct typo in configuring.md (#5957) (Nick S. Plekhanov) +* 5f8abab Chore: Add metadata to existing rules - Batch 5 (refs #5417) (#5944) (Vitor Balocco) +* 0562f77 Chore: Add missing newlines to test cases (fixes #5947) (Rich Trott) +* fc78e78 Chore: Enable quote-props rule in eslint-config-eslint (refs #5188) (#5938) (Gyandeep Singh) +* 43f6d05 Docs: Update docs to refer to column (#5937) (Sashko Stubailo) +* 586478e Update: Add autofix for `comma-dangle` (fixes #3805) (#5925) (alberto) +* a4f9c5a Docs: Distinguish examples in rules under Stylistic Issues part 3 (Kenneth Williams) +* e7c0737 Chore: Enable no-console rule in eslint-config-eslint (refs #5188) (Kevin Partington) +* 0023fe6 Build: Add “chore” to commit tags (fixes #5880) (#5929) (Mike Sherov) +* 25d626a Upgrade: espree 3.1.4 (fixes #5923, fixes #5756) (Kai Cataldo) +* a01b412 New: Add `no-useless-computed-key` rule (fixes #5402) (Burak Yigit Kaya) +* 9afb9cb Chore: Remove workaround for espree and escope bugs (fixes #5852) (alberto) +* 3ffc582 Chore: Update copyright and license info (alberto) +* 249eb40 Docs: Clarify init sets up local installation (fixes #5874) (Kai Cataldo) +* 6cd8c86 Docs: Describe options in rules under Possible Errors part 1 (Mark Pedrotti) +* f842d18 Fix: `no-this-before-super` crash on unreachable paths (fixes #5894) (Toru Nagashima) +* a02960b Docs: Fix missing delimiter in README links (Kevin Partington) +* 3a9e72c Docs: Update developer guide with new standards (Nicholas C. Zakas) +* cb78585 Update: Add `allowUnboundThis` to `prefer-arrow-callback` (fixes #4668) (Burak Yigit Kaya) +* 02be29f Chore: Remove CLA check from bot (Nicholas C. Zakas) +* 220713e Chore: Add metadata to existing rules - Batch 4 (refs #5417) (Vitor Balocco) +* df53414 Chore: Include jQuery Foundation info (Nicholas C. Zakas) +* f1b2992 Fix: `no-useless-escape` false positive in JSXAttribute (fixes #5882) (Toru Nagashima) +* 74674ad Docs: Move `sort-imports` to 'ECMAScript 6' (Kenneth Williams) +* ae69ddb Docs: Fix severity type in example (Kenneth Williams) +* 19f6fff Update: Autofixing does multiple passes (refs #5329) (Nicholas C. Zakas) +* 1e4b0ca Docs: Reduce length of paragraphs in rules index (Mark Pedrotti) +* 8cfe1eb Docs: Fix a wrong option (Zach Orlovsky) +* 8f6739f Docs: Add alberto as reviewer (alberto) +* 2ae4938 Docs: Fix message for `inline-config` option (alberto) +* 089900b Docs: Fix a wrong rule name in an example (Toru Nagashima) +* c032b41 Docs: Fix emphasis (Toru Nagashima) +* ae606f0 Docs: Update JSCS info in README (alberto) +* a9c5323 Fix: Install ESLint on init if not installed (fixes #5833) (Kai Cataldo) +* ed38358 Docs: Removed incorrect example (James M. Greene) +* af3113c Docs: Fix config comments in indent docs (Brandon Mills) +* 2b39461 Update: `commentPattern` option for `default-case` rule (fixes #5803) (Artyom Lvov) + +v2.8.0 - April 15, 2016 + +* a8821a5 Docs: Distinguish examples in rules under Stylistic Issues part 2 (Kenneth Williams) +* 76913b6 Update: Add metadata to existing rules - Batch 3 (refs #5417) (Vitor Balocco) +* 34ad8d2 Fix: Check that module.paths exists (fixes #5791) (Nicholas C. Zakas) +* 37239b1 Docs: Add new members of the team (Ilya Volodin) +* fb3c2eb Update: allow template literals (fixes #5234) (Jonathan Haines) +* 5a4a935 Update: Add metadata to existing rules - Batch 2 (refs #5417) (Vitor Balocco) +* ea2e625 Fix: newline-before-return handles return as first token (fixes #5816) (Kevin Partington) +* f8db9c9 Update: add nestedBinaryExpressions to no-extra-parens (fixes #3065) (Ilya Volodin) +* 0045d57 Update: `allowNamedFunctions` in `prefer-arrow-callback` (fixes #5675) (alberto) +* 19da72a Update: Add metadata to existing rules - Batch 1 (refs #5417) (Vitor Balocco) +* cc14e43 Fix: `no-fallthrough` empty case with comment (fixes #5799) (alberto) +* 13c8b14 Fix: LogicalExpression checks for short circuit (fixes #5693) (Vamshi krishna) +* 73b225e Fix: Document and fix metadata (refs #5417) (Ilya Volodin) +* 882d199 Docs: Improve options description in `no-redeclare` (alberto) +* 6a71ceb Docs: Improve options description in `no-params-reassign` (alberto) +* 24b6215 Update: Include 'typeof' in rule 'no-constant-condition' (fixes #5228) (Vamshi krishna) +* a959063 Docs: Remove link to deprecated ESLintTester project (refs #3110) (Trey Thomas) +* 6fd7d82 Update: Change order in `eslint --init` env options (fixes #5742) (alberto) +* c59d909 Fix: Extra paren check around object arrow bodies (fixes #5789) (Brandon Mills) +* 6f88546 Docs: Use double quotes for better Win compatibility (fixes #5796) (alberto) +* 02743d5 Fix: catch self-assignment operators in `no-magic-number` (fixes #4400) (alberto) +* c94e74e Docs: Make rule descriptions more consistent (Kenneth Williams) +* 6028252 Docs: Distinguish examples in rules under Stylistic Issues part 1 (Mark Pedrotti) +* ccd8ca9 Fix: Added property onlyDeclaration to id-match rule (fixes #3488) (Gajus Kuizinas) +* 6703c02 Update: no-useless-escape / exact locations of errors (fixes #5751) (Onur Temizkan) +* 3d84b91 Fix: ignore trailing whitespace in template literal (fixes #5786) (Kai Cataldo) +* b0e6bc4 Update: add allowEmptyCatch option to no-empty (fixes #5800) (Kai Cataldo) +* f1f1dd7 Docs: Add @pedrottimark as a committer (Brandon Mills) +* 228f201 Update: `commentPattern` option for `no-fallthrough` rule (fixes #5757) (Artyom Lvov) +* 41db670 Docs: Clarify disable inline comments (Kai Cataldo) +* 9c9a295 Docs: Add note about shell vs node glob parameters in cli (alberto) +* 5308ff9 Docs: Add code backticks to sentence in fixable rules (Mark Pedrotti) +* 965ec06 Docs: fix the examples for space-before-function-paren. (Craig Silverstein) +* 2b202fc Update: Add ignore option to space-before-function-parens (fixes #4127) (Craig Silverstein) +* 24c12ba Fix: improve `constructor-super` errors for literals (fixes #5449) (Toru Nagashima) + +v2.7.0 - April 4, 2016 + +* 134cb1f Revert "Update: adds nestedBinaryExpressions for no-extra-parens rule (fixes #3065)" (Ilya Volodin) +* 7e80867 Docs: Update sentence in fixable rules (Mark Pedrotti) +* 1b6d5a3 Update: adds nestedBinaryExpressions for no-extra-parens (fixes #3065) (Nick Fisher) +* 4f93c32 Docs: Clarify `array-bracket-spacing` with newlines (fixes #5768) (alberto) +* 161ddac Fix: remove `console.dir` (fixes #5770) (Toru Nagashima) +* 0c33f6a Fix: indent rule uses wrong node for class indent level (fixes #5764) (Paul O’Shannessy) + +v2.6.0 - April 1, 2016 + +* ce2accd Fix: vars-on-top now accepts exported variables (fixes #5711) (Olmo Kramer) +* 7aacba7 Update: Deprecate option `maximum` in favor of `max` (fixes #5685) (Vitor Balocco) +* 5fe6fca Fix: no-useless-escape \B regex escape (fixes #5750) (Onur Temizkan) +* 9b73ffd Update: `destructuring` option of `prefer-const` rule (fixes #5594) (Toru Nagashima) +* 8ac9206 Docs: Typo in `sort-imports` (alberto) +* 12902c5 Fix: valid-jsdoc crash w/ Field & Array Type (fixes #5745) (fixes #5746) (Burak Yigit Kaya) +* 2c8b65a Docs: Edit examples for a few rules (Mark Pedrotti) +* d736bc2 Fix: Treat SwitchCase like a block in lines-around-comment (fixes #5718) (Scott O'Hara) +* 24a61a4 Update: make `no-useless-escape` allowing line breaks (fixes #5689) (Toru Nagashima) +* 4ecd45e Fix: Ensure proper lookup of config files (fixes #5175, fixes #5468) (Nicholas C. Zakas) +* 088e26b Fix: Update doctrine to allow hyphens in JSDoc names (fixes #5612) (Kai Cataldo) +* 692fd5d Upgrade: Old Chalk.JS deprecated method (fixes #5716) (Morris Singer) +* f59d91d Update: no-param-reassign error msgs (fixes #5705) (Isaac Levy) +* c1b16cd Fix: Object spread throws error in key-spacing rule. (fixes #5724) (Ziad El Khoury Hanna) +* 3091613 Docs: Correct explanation about properties (James Monger) +* cb0f0be Fix: Lint issue with `valid-jsdoc` rule (refs #5188) (Gyandeep Singh) +* aba1954 Build: Ignore jsdoc folder internally (fixes #5714) (alberto) +* a35f127 Fix: Lint for eslint project in regards to vars (refs #5188) (Gyandeep Singh) +* d9ab4f0 Fix: Windows scoped package configs (fixes #5644) (Nicholas C. Zakas) +* 8d0cd0d Update: Basic valid-jsdoc default parameter support (fixes #5658) (Tom Andrews) + +v2.5.3 - March 28, 2016 + +* 8749ac5 Build: Disable bundling dependencies (fixes #5687) (Nicholas C. Zakas) + +v2.5.2 - March 28, 2016 + +* 1cc7f8e Docs: Remove mention of minimatch for .eslintignore (Ian VanSchooten) +* 5bd69a9 Docs: Reorder FAQ in README (alberto) +* 98e6bd9 Fix: Correct default for indentation in `eslint --init` (fixes #5698) (alberto) +* 679095e Fix: make the default of `options.cwd` in runtime (fixes #5694) (Toru Nagashima) +* 4f06f2f Docs: Distinguish examples in rules under Best Practices part 2 (Mark Pedrotti) +* 013a18e Build: Fix bundling script (fixes #5680) (Nicholas C. Zakas) +* 8c5d954 Docs: Typo fix (István Donkó) +* 09659d6 Docs: Use string severity (Kenneth Williams) +* a4ae769 Docs: Manual changelog update for v2.5.1 (Nicholas C. Zakas) +* c41fab9 Fix: don't use path.extname with undefined value (fixes #5678) (Myles Borins) + +v2.5.1 - March 25, 2016 + +* Build: No functional changes, just republished with a working package. + +v2.5.0 - March 25, 2016 + +* 7021aa9 Fix: lines-around-comment in ESLint repo, part 2 (refs #5188) (Kevin Partington) +* 095c435 Docs: Remove ES2016 from experimental section of README (Kevin Partington) +* 646f863 Build: Bundle dependencies in package.json (fixes #5013) (Nicholas C. Zakas) +* ea06868 Docs: Clarify --ext does not apply to globs (fixes #5452) (Ian VanSchooten) +* 569c478 Build: Fix phantomjs CI problems (fixes #5666) (alberto) +* 6022426 Docs: Add link to chat room in README primary links (alberto) +* 2fbb530 Docs: Add link to "Proposing a Rule Change" in README (alberto) +* 25bf491 Upgrade: globals 9.x (fixes #5668) (Toru Nagashima) +* d6f8409 New: Rule - No useless escape (fixes #5460) (Onur Temizkan) +* 12a43f1 Docs: remove brace expansion from configuring.md (refs #5314) (Jonathan Haines) +* 92d1749 New: max-statements-per-line (fixes #5424) (Kenneth Williams) +* aaf324a Fix: missing support for json sub configs (fixes #5413) (Noam Okman) +* 48ad5fe Update: Add 'caughtErrors' to rule no-unused-vars (fixes #3837) (vamshi) +* ad90c2b Fix: incorrect config message (fixes #5653) (s0ph1e) +* a551831 Docs: Distinguish examples in rules under Node.js and CommonJS (Mark Pedrotti) +* 83cd651 Upgrade: chai to 3.5.0 (fixes #5647) (alberto) +* 32748dc Fix: `radix` rule false positive at shadowed variables (fixes #5639) (Toru Nagashima) +* 66db38d Fix: `--no-ignore` should not un-ignore default ignores (fixes #5547) (alberto) +* e3e06f3 Docs: Distinguish examples in rules under Best Practices part 4 (Mark Pedrotti) +* a9f0865 Docs: Update no-sequences rule docs for clarity (fixes #5536) (Kai Cataldo) +* bae7b30 Docs: Add michaelficarra as committer (alberto) +* e2990e7 Docs: Consistent wording in rules README (alberto) +* 49b4d2a Docs: Update team list with new members (Ilya Volodin) +* d0ae66c Update: Allow autoconfiguration for JSX code (fixes #5511) (Ian VanSchooten) +* 38a0a64 Docs: Clarify `linebreak-style` docs (fixes #5628) (alberto) +* 4b7305e Fix: Allow default ignored files to be unignored (fixes #5410) (Ian VanSchooten) +* 4b05ce6 Update: Enforce repo coding conventions via ESLint (refs #5188) (Kevin Partington) +* 051b255 Docs: Remove or rewrite references to former ecmaFeatures (Mark Pedrotti) +* 9a22625 Fix: `prefer-const` false positive at non-blocked if (fixes #5610) (Toru Nagashima) +* b1fd482 Fix: leading comments added from previous node (fixes #5531) (Kai Cataldo) +* c335650 Docs: correct the no-confusing-arrow docs (Daniel Norman) +* e94b77d Fix: Respect 'ignoreTrailingComments' in max-len rule (fixes #5563) (Vamshi Krishna) +* 9289ef8 Fix: handle personal package.json without config (fixes #5496) (Denny Christochowitz) +* 87d74b2 Fix: `prefer-const` got to not change scopes (refs #5284) (Toru Nagashima) +* 5a881e7 Docs: Fix typo in code snippet for no-unmodified-loop-condition rule (Chris Rebert) +* 03037c2 Update: Overrides for space-unary-ops (fixes #5060) (Afnan Fahim) +* 24d986a Update: replace MD5 hashing of cache files with MurmurHash (fixes #5522) (Michael Ficarra) +* f405030 Fix: Ensure allowing `await` as a property name (fixes #5564) (Toru Nagashima) +* aefc90c Fix: `no-useless-constructor` clash (fixes #5573) (Toru Nagashima) +* 9eaa20d Docs: Fix typo in CLI help message (ryym) +* a7c3e67 Docs: Invalid json in `configuring.md` (alberto) +* 4e50332 Docs: Make `prefer-template` examples consistent. (alberto) +* cfc14a9 Fix: valid-jsdoc correctly checks type union (fixes #5260) (Kai Cataldo) +* 689cb7d Fix: `quote-props` false positive on certain keys (fixes #5532) (Burak Yigit Kaya) +* 167a03a Fix: `brace-style` erroneously ignoring certain errors (fixes #5197) (Burak Yigit Kaya) +* 3133f28 Fix: object-curly-spacing doesn't know types (fixes #5537) (fixes #5538) (Burak Yigit Kaya) +* d0ca171 Docs: Separate parser and config questions in issue template (Kevin Partington) +* bc769ca Fix: Improve file path resolution (fixes #5314) (Ian VanSchooten) +* 9ca8567 Docs: Distinguish examples in rules under Best Practices part 3 (Mark Pedrotti) +* b9c69f1 Docs: Distinguish examples in rules under Variables part 2 (Mark Pedrotti) +* c289414 New: `no-duplicate-imports` rule (fixes #3478) (Simen Bekkhus) + +v2.4.0 - March 11, 2016 + +* 97b2466 Fix: estraverse/escope to work with unknowns (fixes #5476) (Nicholas C. Zakas) +* 641b3f7 Fix: validate the type of severity level (fixes #5499) (Shinnosuke Watanabe) +* 9ee8869 Docs: no-unused-expressions - add more edge unusable and usable examples (Brett Zamir) +* 56bf864 Docs: Create parity between no-sequences examples (Brett Zamir) +* 13ef1c7 New: add `--parser-options` to CLI (fixes #5495) (Jordan Harband) +* ae1ee54 Docs: fix func-style arrow exception option (Craig Martin) +* 91852fd Docs: no-lone-blocks - show non-problematic (and problematic) label (Brett Zamir) +* b34458f Docs: Rearrange rules for better categories (and improve rule summaries) (Brett Zamir) +* 1198b26 Docs: Minor README clarifications (Brett Zamir) +* 03e6869 Fix: newline-before-return: bug with comment (fixes #5480) (mustafa) +* ad100fd Fix: overindent in VariableDeclarator parens or brackets (fixes #5492) (David Greenspan) +* 9b8e04b Docs: Replace all node references to Node.js which is the official name (Brett Zamir) +* cc1f2f0 Docs: Minor fixes in no-new-func (Brett Zamir) +* 6ab81d4 Docs: Distinguish examples in rules under Best Practices part 1 (Mark Pedrotti) +* 9c6c70c Update: add `allowParens` option to `no-confusing-arrow` (fixes #5332) (Burak Yigit Kaya) +* 979c096 Docs: Document linebreak-style as fixable. (Afnan Fahim) +* 9f18a81 Fix: Ignore destructuring assignment in `object-shorthand` (fixes #5488) (alberto) +* 5d9a798 Docs: README.md, prefer-const; change modified to reassigned (Michiel de Bruijne) +* 38eb7f1 Fix: key-spacing checks ObjectExpression is multiline (fixes #5479) (Kevin Partington) +* 9592c45 Fix: `no-unmodified-loop-condition` false positive (fixes #5445) (Toru Nagashima) + +v2.3.0 - March 4, 2016 + +* 1b2c6e0 Update: Proposed no-magic-numbers option: ignoreJSXNumbers (fixes #5348) (Brandon Beeks) +* 63c0b7d Docs: Fix incorrect environment ref. in Rules in Plugins. (fixes #5421) (Jesse McCarthy) +* 124c447 Build: Add additional linebreak to docs (fixes #5464) (Ilya Volodin) +* 0d3831b Docs: Add RuleTester parserOptions migration steps (Kevin Partington) +* 50f4d5a Fix: extends chain (fixes #5411) (Toru Nagashima) +* 0547072 Update: Replace getLast() with lodash.last() (fixes #5456) (Jordan Eldredge) +* 8c29946 Docs: Distinguish examples in rules under Possible Errors part 1 (Mark Pedrotti) +* 5319b4a Docs: Distinguish examples in rules under Possible Errors part 2 (Mark Pedrotti) +* 1da2420 Fix: crash when SourceCode object was reused (fixes #5007) (Toru Nagashima) +* 9e9daab New: newline-before-return rule (fixes #5009) (Kai Cataldo) +* e1bbe45 Fix: Check space after anonymous generator star (fixes #5435) (alberto) +* 119e0ed Docs: Distinguish examples in rules under Variables (Mark Pedrotti) +* 905c049 Fix: `no-undef` false positive at new.target (fixes #5420) (Toru Nagashima) +* 4a67b9a Update: Add ES7 support (fixes #5401) (Brandon Mills) +* 89c757d Docs: Replace ecmaFeatures with parserOptions in working-with-rules (Kevin Partington) +* 804c08e Docs: Add parserOptions to RuleTester section of working-with-rules (Kevin Partington) +* 1982c50 Docs: Document string option for `no-unused-vars`. (alberto) +* 4f82b2b Update: Support classes in `padded-blocks` (fixes #5092) (alberto) +* ed5564f Docs: Specify results of `no-unused-var` with `args` (fixes #5334) (chinesedfan) +* de0a4ef Fix: `getFormatter` throws an error when called as static (fixes #5378) (cowchimp) +* 78f7ca9 Fix: Prevent crash from swallowing console.log (fixes #5381) (Ian VanSchooten) +* 34b648d Fix: remove tests which have invalid syntax (fixes #5405) (Toru Nagashima) +* 7de5ae4 Docs: Missing allow option in docs (Scott O'Hara) +* cf14c71 Fix: `no-useless-constructor` rule crashes sometimes (fixes #5290) (Burak Yigit Kaya) +* 70e3a02 Update: Allow string severity in config (fixes #3626) (Nicholas C. Zakas) +* 13c7c19 Update: Exclude ES5 constructors from consistent-return (fixes #5379) (Kevin Locke) +* 784d3bf Fix: Location info in `dot-notation` rule (fixes #5397) (Gyandeep Singh) +* 6280b2d Update: Support switch statements in padded-blocks (fixes #5056) (alberto) +* 25a5b2c Fix: Allow irregular whitespace in comments (fixes #5368) (Christophe Porteneuve) +* 560c0d9 New: no-restricted-globals rule implementation (fixes #3966) (Benoît Zugmeyer) +* c5bb478 Fix: `constructor-super` false positive after a loop (fixes #5394) (Toru Nagashima) +* 6c0c4aa Docs: Add Issue template (fixes #5313) (Kai Cataldo) +* 1170e67 Fix: indent rule doesn't handle constructor instantiation (fixes #5384) (Nate Cavanaugh) +* 6bc9932 Fix: Avoid magic numbers in rule options (fixes #4182) (Brandon Beeks) +* 694e1c1 Fix: Add tests to cover default magic number tests (fixes #5385) (Brandon Beeks) +* 0b5349d Fix: .eslintignore paths should be absolute (fixes #5362) (alberto) +* 8f6c2e7 Update: Better error message for plugins (refs #5221) (Nicholas C. Zakas) +* 972d41b Update: Improve error message for rule-tester (fixes #5369) (Jeroen Engels) +* fe3f6bd Fix: `no-self-assign` false positive at shorthand (fixes #5371) (Toru Nagashima) +* 2376291 Docs: Missing space in `no-fallthrough` doc. (alberto) +* 5aedb87 Docs: Add mysticatea as reviewer (Nicholas C. Zakas) +* 1f9fd10 Update: no-invalid-regexp allows custom flags (fixes #5249) (Afnan Fahim) +* f1eab9b Fix: Support for dash and slash in `valid-jsdoc` (fixes #1598) (Gyandeep Singh) +* cd12a4b Fix:`newline-per-chained-call` should only warn on methods (fixes #5289) (Burak Yigit Kaya) +* 0d1377d Docs: Add missing `symbol` type into valid list (Plusb Preco) +* 6aa2380 Update: prefer-const; change modified to reassigned (fixes #5350) (Michiel de Bruijne) +* d1d62c6 Fix: indent check for else keyword with Stroustrup style (fixes #5218) (Gyandeep Singh) +* 7932f78 Build: Fix commit message validation (fixes #5340) (Nicholas C. Zakas) +* 1c347f5 Fix: Cleanup temp files from tests (fixes #5338) (Nick) +* 2f3e1ae Build: Change rules to warnings in perf test (fixes #5330) (Brandon Mills) +* 36f40c2 Docs: Achieve consistent order of h2 in rule pages (Mark Pedrotti) + +v2.2.0 - February 19, 2016 + +* 45a22b5 Docs: remove esprima-fb from suggested parsers (Henry Zhu) +* a4d9cd3 Docs: Fix semi rule typo (Brandon Mills) +* 9d005c0 Docs: Correct option name in `no-implicit-coercion` rule (Neil Kistner) +* 2977248 Fix: Do not cache `.eslintrc.js` (fixes #5067) (Nick) +* 211eb8f Fix: no-multi-spaces conflicts with smart tabs (fixes #2077) (Afnan Fahim) +* 6dc9483 Fix: Crash in `constructor-super` (fixes #5319) (Burak Yigit Kaya) +* 3f48875 Docs: Fix yield star spacing examples (Dmitriy Lazarev) +* 4dab76e Docs: Update `preferType` heading to keep code format (fixes #5307) (chinesedfan) +* 7020b82 Fix: `sort-imports` warned between default and members (fixes #5305) (Toru Nagashima) +* 2f4cd1c Fix: `constructor-super` and `no-this-before-super` false (fixes #5261) (Toru Nagashima) +* 59e9c5b New: eslint-disable-next-line (fixes #5206) (Kai Cataldo) +* afb6708 Fix: `indent` rule forgot about some CallExpressions (fixes #5295) (Burak Yigit Kaya) +* d18d406 Docs: Update PR creation bot message (fixes #5268) (Nicholas C. Zakas) +* 0b1cd19 Fix: Ignore parser option if set to default parser (fixes #5241) (Kai Cataldo) + +v2.1.0 - February 15, 2016 + +* 7981ef5 Build: Fix release script (Nicholas C. Zakas) +* c9c34ea Fix: Skip computed members in `newline-per-chained-call` (fixes #5245) (Burak Yigit Kaya) +* b32ddad Build: `npm run perf` command should check the exit code (fixes #5279) (Burak Yigit Kaya) +* 6580d1c Docs: Fix incorrect `api.verify` JSDoc for `config` param (refs #5104) (Burak Yigit Kaya) +* 1f47868 Docs: Update yield-star-spacing documentation for 2.0.0 (fixes #5272) (Burak Yigit Kaya) +* 29da8aa Fix: `newline-after-var` crash on a switch statement (fixes #5277) (Toru Nagashima) +* 86c5a20 Fix: `func-style` should ignore ExportDefaultDeclarations (fixes #5183) (Burak Yigit Kaya) +* ba287aa Fix: Consolidate try/catches to top levels (fixes #5243) (Ian VanSchooten) +* 3ef5da1 Docs: Update no-magic-numbers#ignorearrayindexes. (KazuakiM) +* 0d6850e Update: Allow var declaration at end of block (fixes #5246) (alberto) +* c1e3a73 Fix: Popular style init handles missing package.json keys (refs #5243) (Brandon Mills) +* 68c6e22 Docs: fix default value of `keyword-spacing`'s overrides option. (Toru Nagashima) +* 00fe46f Upgrade: inquirer (fixes #5265) (Bogdan Chadkin) +* ef729d7 Docs: Remove option that is not being used in max-len rule (Thanos Lefteris) +* 4a5ddd5 Docs: Fix rule config above examples for require-jsdoc (Thanos Lefteris) +* c5cbc1b Docs: Add rule config above each example in jsx-quotes (Thanos Lefteris) +* f0aceba Docs: Correct alphabetical ordering in rule list (Randy Coulman) +* 1651ffa Docs: update migrating to 2.0.0 (fixes #5232) (Toru Nagashima) +* 9078537 Fix: `indent` on variable declaration with separate array (fixes #5237) (Burak Yigit Kaya) +* f8868b2 Docs: Typo fix in consistent-this rule doc fixes #5240 (Nicolas Froidure) +* 44f6915 Fix: ESLint Bot mentions the wrong person for extra info (fixes #5229) (Burak Yigit Kaya) +* c612a8e Fix: `no-empty-function` crash (fixes #5227) (Toru Nagashima) +* ae663b6 Docs: Add links for issue documentation (Nicholas C. Zakas) +* 717bede Build: Switch to using eslint-release (fixes #5223) (Nicholas C. Zakas) +* 980e139 Fix: Combine all answers for processAnswers (fixes #5220) (Ian VanSchooten) +* 1f2a1d5 Docs: Remove inline errors from doc examples (fixes #4104) (Burak Yigit Kaya) + +v2.0.0 - February 12, 2016 + +* cc3a66b Docs: Issue message when more info is needed (Nicholas C. Zakas) +* 2bc40fa Docs: Simplify hierarchy of headings in rule pages (Mark Pedrotti) +* 1666254 Docs: Add note about only-whitespace rule for `--fix` (fixes #4774) (Burak Yigit Kaya) +* 2fa09d2 Docs: Add `quotes` to related section of `prefer-template` (fixes #5192) (Burak Yigit Kaya) +* 7b12995 Fix: `key-spacing` not enforcing no-space in minimum mode (fixes #5008) (Burak Yigit Kaya) +* c1c4f4d Breaking: new `no-empty-function` rule (fixes #5161) (Toru Nagashima) + +v2.0.0-rc.1 - February 9, 2016 + +* 4dad82a Update: Adding shared environment for node and browser (refs #5196) (Eli White) +* b46c893 Fix: Config file relative paths (fixes #5164, fixes #5160) (Nicholas C. Zakas) +* aa5b2ac Fix: no-whitespace-before-property fixes (fixes #5167) (Kai Cataldo) +* 4e99924 Update: Replace several dependencies with lodash (fixes #5012) (Gajus Kuizinas) +* 718dc68 Docs: Remove periods in rules' README for consistency. (alberto) +* 7a47085 Docs: Correct `arrow-spacing` overview. (alberto) +* a4cde1b Docs: Clarify global-require inside try/catch (fixes #3834) (Brandon Mills) +* fd07925 Docs: Clarify docs for api.verify (fixes #5101, fixes #5104) (Burak Yigit Kaya) +* 413247f New: Add a --print-config flag (fixes #5099) (Christopher Crouzet) +* efeef42 Update: Implement auto fix for space-in-parens (fixes #5050) (alberto) +* e07fdd4 Fix: code path analysis and labels (fixes #5171) (Toru Nagashima) +* 2417bb2 Fix: `no-unmodified-loop-condition` false positive (fixes #5166) (Toru Nagashima) +* fae1884 Fix: Allow same-line comments in padded-blocks (fixes #5055) (Brandon Mills) +* a24d8ad Fix: Improve autoconfig logging (fixes #5119) (Ian VanSchooten) +* e525923 Docs: Correct obvious inconsistencies in rules h2 elements (Mark Pedrotti) +* 9675b5e Docs: `avoid-escape` does not allow backticks (fixes #5147) (alberto) +* a03919a Fix: `no-unexpected-multiline` false positive (fixes #5148) (Feross Aboukhadijeh) +* 74360d6 Docs: Note no-empty applies to empty block statements (fixes #5105) (alberto) +* 6eeaa3f Build: Remove pending tests (fixes #5126) (Ian VanSchooten) +* 02c83df Docs: Update docs/rules/no-plusplus.md (Sheldon Griffin) +* 0c4de5c New: Added "table" formatter (fixes #4037) (Gajus Kuizinas) +* 0a59926 Update: 'implied strict mode' ecmaFeature (fixes #4832) (Nick Evans) +* 53a6eb3 Fix: Handle singular case in rule-tester error message (fixes #5141) (Bryan Smith) +* 97ac91c Build: Increment eslint-config-eslint (Nicholas C. Zakas) + +v2.0.0-rc.0 - February 2, 2016 + +* 973c499 Fix: `sort-imports` crash (fixes #5130) (Toru Nagashima) +* e64b2c2 Breaking: remove `no-empty-label` (fixes #5042) (Toru Nagashima) +* 79ebbc9 Breaking: update `eslint:recommended` (fixes #5103) (Toru Nagashima) +* e1d7368 New: `no-extra-label` rule (fixes #5059) (Toru Nagashima) +* c83b48c Fix: find ignore file only in cwd (fixes #5087) (Nicholas C. Zakas) +* 3a24240 Docs: Fix jsdoc param names to match function param names (Thanos Lefteris) +* 1d79746 Docs: Replace ecmaFeatures setting with link to config page (Thanos Lefteris) +* e96ffd2 New: `template-curly-spacing` rule (fixes #5049) (Toru Nagashima) +* 4b02902 Update: Extended no-console rule (fixes #5095) (EricHenry) +* 757651e Docs: Remove reference to rules enabled by default (fixes #5100) (Brandon Mills) +* 0d87f5d Docs: Clarify eslint-disable comments only affect rules (fixes #5005) (Brandon Mills) +* 1e791a2 New: `no-self-assign` rule (fixes #4729) (Toru Nagashima) +* c706eb9 Fix: reduced `no-loop-func` false positive (fixes #5044) (Toru Nagashima) +* 3275e86 Update: Add extra aliases to consistent-this rule (fixes #4492) (Zachary Alexander Belford) +* a227360 Docs: Replace joyent org with nodejs (Thanos Lefteris) +* b2aedfe New: Rule to enforce newline after each call in the chain (fixes #4538) (Rajendra Patil) +* d67bfdd New: `no-unused-labels` rule (fixes #5052) (Toru Nagashima) + +v2.0.0-beta.3 - January 29, 2016 + +* 86a3e3d Update: Remove blank lines at beginning of files (fixes #5045) (Jared Sohn) +* 4fea752 New: Autoconfiguration from source inspection (fixes #3567) (Ian VanSchooten) +* 519f39f Breaking: Remove deprecated rules (fixes #5032) (Gyandeep Singh) +* c75ee4a New: Add support for configs in plugins (fixes #3659) (Ilya Volodin) +* 361377f Fix: `prefer-const` false positive reading before writing (fixes #5074) (Toru Nagashima) +* ff2551d Build: Improve `npm run perf` command (fixes #5028) (Toru Nagashima) +* bcca69b Update: add int32Hint option to `no-bitwise` rule (fixes #4873) (Maga D. Zandaqo) +* e3f2683 Update: config extends dependency lookup (fixes #5023) (Nicholas C. Zakas) +* a327a06 Fix: Indent rule for allman brace style scenario (fixes #5064) (Gyandeep Singh) +* afdff6d Fix: `no-extra-bind` false positive (fixes #5058) (Toru Nagashima) +* c1fad4f Update: add autofix support for spaced-comment (fixes #4969, fixes #5030) (Maga D. Zandaqo) +* 889b942 Revert "Docs: Update readme for legend describing rules icons (refs #4355)" (Nicholas C. Zakas) +* b0f21a0 Fix: `keyword-spacing` false positive in template strings (fixes #5043) (Toru Nagashima) +* 53fa5d1 Fix: `prefer-const` false positive in a loop condition (fixes #5024) (Toru Nagashima) +* 385d399 Docs: Update readme for legend describing rules icons (Kai Cataldo) +* 505f1a6 Update: Allow parser to be relative to config (fixes #4985) (Nicholas C. Zakas) +* 79e8a0b New: `one-var-declaration-per-line` rule (fixes #1622) (alberto) +* 654e6e1 Update: Check extra Boolean calls in no-extra-boolean-cast (fixes #3650) (Andrew Sutton) + +v2.0.0-beta.2 - January 22, 2016 + +* 3fa834f Docs: Fix formatter links (fixes #5006) (Gyandeep Singh) +* 54b1bc8 Docs: Fix link in strict.md (fixes #5026) (Nick Evans) +* e0c5cf7 Upgrade: Espree to 3.0.0 (fixes #5018) (Ilya Volodin) +* 69f149d Docs: language tweaks (Andres Kalle) +* 2b33c74 Update: valid-jsdoc to not require @return in constructors (fixes #4976) (Maga D. Zandaqo) +* 6ac2e01 Docs: Fix description of exported comment (Mickael Jeanroy) +* 29392f8 New: allow-multiline option on comma-dangle (fixes #4967) (Alberto Gimeno) +* 05b8cb3 Update: Module overrides all 'strict' rule options (fixes #4936) (Nick Evans) +* 8470474 New: Add metadata to few test rules (fixes #4494) (Ilya Volodin) +* ba11c1b Docs: Add Algolia as sponsor to README (Nicholas C. Zakas) +* b28a19d Breaking: Plugins envs and config removal (fixes #4782, fixes #4952) (Nicholas C. Zakas) +* a456077 Docs: newline-after-var doesn't allow invalid options. (alberto) +* 3e6a24e Breaking: Change `strict` default mode to "safe" (fixes #4961) (alberto) +* 5b96265 Breaking: Update eslint:recommended (fixes #4953) (alberto) +* 7457a4e Upgrade: glob to 6.x (fixes #4991) (Gyandeep Singh) +* d3f4bdd Build: Cleanup for code coverage (fixes #4983) (Gyandeep Singh) +* b8fbaa0 Fix: multiple message in TAP formatter (fixes #4975) (Simon Degraeve) +* 990f8da Fix: `getNodeByRangeIndex` performance issue (fixes #4989) (Toru Nagashima) +* 8ac1dac Build: Update markdownlint dependency to 0.1.0 (fixes #4988) (David Anson) +* 5cd5429 Fix: function expression doc in call expression (fixes #4964) (Tim Schaub) +* 4173baa Fix: `no-dupe-class-members` false positive (fixes #4981) (Toru Nagashima) +* 12fe803 Breaking: Supports Unicode BOM (fixes #4878) (Toru Nagashima) +* 1fc80e9 Build: Increment eslint-config-eslint (Nicholas C. Zakas) +* e0a9024 Update: Report newline between template tag and literal (fixes #4210) (Rajendra Patil) +* da3336c Update: Rules should get `sourceType` from Program node (fixes #4960) (Nick Evans) +* a2ac359 Update: Make jsx-quotes fixable (refs #4377) (Gabriele Petronella) +* ee1014d Fix: Incorrect error location for object-curly-spacing (fixes #4957) (alberto) +* b52ed17 Fix: Incorrect error location for space-in-parens (fixes #4956) (alberto) +* 9c1bafb Fix: Columns of parse errors are off by 1 (fixes #4896) (alberto) +* 5e4841e New: 'id-blacklist' rule (fixes #3358) (Keith Cirkel) +* 700b8bc Update: Add "allow" option to allow specific operators (fixes #3308) (Rajendra Patil) +* d82eeb1 Update: Add describe around rule tester blocks (fixes #4907) (Ilya Volodin) +* 2967402 Update: Add minimum value to integer values in schema (fixes #4941) (Ilya Volodin) +* 7b632f8 Upgrade: Globals to ^8.18.0 (fixes #4728) (Gyandeep Singh) +* 86e6e57 Fix: Incorrect error at EOF for no-multiple-empty-lines (fixes #4917) (alberto) +* 7f058f3 Fix: Incorrect location for padded-blocks (fixes #4913) (alberto) +* b3de8f7 Fix: Do not show ignore messages for default ignored files (fixes #4931) (Gyandeep Singh) +* b1360da Update: Support multiLine and singleLine options (fixes #4697) (Rajendra Patil) +* 82fbe09 Docs: Small semantic issue in documentation example (fixes #4937) (Marcelo Zarate) +* 13a4e30 Docs: Formatting inconsistencies (fixes #4912) (alberto) +* d487013 Update: Option to allow extra parens for cond assign (fixes #3317) (alberto) +* 0f469b4 Fix: JSDoc for function expression on object property (fixes #4900) (Tim Schaub) +* c2dee27 Update: Add module tests to no-extra-semi (fixes #4915) (Nicholas C. Zakas) +* 5a633bf Update: Add `preferType` option to `valid-jsdoc` rule (fixes #3056) (Gyandeep Singh) +* ebd01b7 Build: Fix version number on release (fixes #4921) (Nicholas C. Zakas) +* 2d626a3 Docs: Fix typo in changelog (Nicholas C. Zakas) +* c4c4139 Fix: global-require no longer warns if require is shadowed (fixes #4812) (Kevin Partington) +* bbf7f27 New: provide config.parser via `parserName` on RuleContext (fixes #3670) (Ben Mosher) + +v2.0.0-beta.1 - January 11, 2016 + +* 6c70d84 Build: Fix prerelease script (fixes #4919) (Nicholas C. Zakas) +* d5c9435 New: 'sort-imports' rule (refs #3143) (Christian Schuller) +* a8cfd56 Fix: remove duplicate of eslint-config-eslint (fixes #4909) (Toru Nagashima) +* 19a9fbb Breaking: `space-before-blocks` ignores after keywords (fixes #1338) (Toru Nagashima) +* c275b41 Fix: no-extra-parens ExpressionStatement restricted prods (fixes #4902) (Michael Ficarra) +* b795850 Breaking: don't load ~/.eslintrc when using --config flag (fixes #4881) (alberto) +* 3906481 Build: Add AppVeyor CI (fixes #4894) (Gyandeep Singh) +* 6390862 Docs: Fix missing footnote (Yoshiya Hinosawa) +* e5e06f8 Fix: Jsdoc comment for multi-line function expressions (fixes #4889) (Gyandeep Singh) +* 7c9be60 Fix: Fix path errors in windows (fixes #4888) (Gyandeep Singh) +* a1840e7 Fix: gray text was invisible on Solarized Dark theme (fixes #4886) (Jack Leigh) +* fc9f528 Docs: Modify unnecessary flag docs in quote-props (Matija Marohnić) +* 186e8f0 Update: Ignore camelcase in object destructuring (fixes #3185) (alberto) +* 7c97201 Upgrade: doctrine version to 1.1.0 (fixes #4854) (Tim Schaub) +* ceaf324 New: Add no-new-symbol rule (fixes #4862) (alberto) +* e2f2b66 Breaking: Remove defaults from `eslint:recommended` (fixes #4809) (Ian VanSchooten) +* 0b3c01e Docs: Specify default for func-style (fixes #4834) (Ian VanSchooten) +* 008ea39 Docs: Document default for operator assignment (fixes #4835) (alberto) +* b566f56 Docs: no-new-func typo (alberto) +* 1569695 Update: Adds default 'that' for consistent-this (fixes #4833) (alberto) +* f7b28b7 Docs: clarify `requireReturn` option for valid-jsdoc rule (fixes #4859) (Tim Schaub) +* 407f329 Build: Fix prerelease script (Nicholas C. Zakas) +* 688f277 Fix: Set proper exit code for Node > 0.10 (fixes #4691) (Nicholas C. Zakas) +* 58715e9 Fix: Use single quotes in context.report messages (fixes #4845) (Joe Lencioni) +* 5b7586b Fix: do not require a @return tag for @interface (fixes #4860) (Tim Schaub) +* d43f26c Breaking: migrate from minimatch to node-ignore (fixes #2365) (Stefan Grönke) +* c07ca39 Breaking: merges keyword spacing rules (fixes #3869) (Toru Nagashima) +* 871f534 Upgrade: Optionator version to 0.8.1 (fixes #4851) (Eric Johnson) +* 82d4cd9 Update: Add atomtest env (fixes #4848) (Andres Suarez) +* 9c9beb5 Update: Add "ignore" override for operator-linebreak (fixes #4294) (Rajendra Patil) +* 9c03abc Update: Add "allowCall" option (fixes #4011) (Rajendra Patil) +* 29516f1 Docs: fix migration guide for no-arrow-condition rule (Peter Newnham) +* 2ef7549 Docs: clarify remedy to some prefer-const errors (Turadg Aleahmad) +* 1288ba4 Update: Add default limit to `complexity` (fixes #4808) (Ian VanSchooten) +* d3e8179 Fix: env is rewritten by modules (fixes #4814) (Toru Nagashima) +* fd72aba Docs: Example fix for `no-extra-parens` rule (fixes #3527) (Gyandeep Singh) +* 315f272 Fix: Change max-warnings type to Int (fixes #4660) (George Zahariev) +* 5050768 Update: Ask for `commonjs` under config init (fixes #3553) (Gyandeep Singh) +* 4665256 New: Add no-whitespace-before-property rule (fixes #1086) (Kai Cataldo) +* f500d7d Fix: allow extending @scope/eslint/file (fixes #4800) (André Cruz) +* 5ab564e New: 'ignoreArrayIndexes' option for 'no-magic-numbers' (fixes #4370) (Christian Schuller) +* 97cdb95 New: Add no-useless-constructor rule (fixes #4785) (alberto) +* b9bcbaf Fix: Bug in no-extra-bind (fixes #4806) (Andres Kalle) +* 246a6d2 Docs: Documentation fix (Andres Kalle) +* 9ea6b36 Update: Ignore case in jsdoc tags (fixes #4576) (alberto) +* acdda24 Fix: ignore argument parens in no-unexpected-multiline (fixes #4658) (alberto) +* 4931f56 Update: optionally allow bitwise operators (fixes #4742) (Swaagie) + +v2.0.0-alpha-2 - December 23, 2015 + +* Build: Add prerelease script (Nicholas C. Zakas) +* Update: Allow to omit semi for one-line blocks (fixes #4385) (alberto) +* Fix: Handle getters and setters in key-spacing (fixes #4792) (Brandon Mills) +* Fix: ObjectRestSpread throws error in key-spacing rule (fixes #4763) (Ziad El Khoury Hanna) +* Docs: Typo in generator-star (alberto) +* Fix: Backtick behavior in quotes rule (fixes #3090) (Nicholas C. Zakas) +* Fix: Empty schemas forbid any options (fixes #4789) (Brandon Mills) +* Fix: Remove `isMarkedAsUsed` function name (fixes #4783) (Gyandeep Singh) +* Fix: support arrow functions in no-return-assign (fixes #4743) (alberto) +* Docs: Add license header to Working with Rules guide (Brandon Mills) +* Fix: RuleTester to show parsing errors (fixes #4779) (Nicholas C. Zakas) +* Docs: Escape underscores in no-path-concat (alberto) +* Update: configuration for classes in space-before-blocks (fixes #4089) (alberto) +* Docs: Typo in no-useless-concat (alberto) +* Docs: fix typos, suggests (molee1905) +* Docs: Typos in space-before-keywords and space-unary-ops (fixes #4771) (alberto) +* Upgrade: beefy to ^2.0.0, fixes installation errors (fixes #4760) (Kai Cataldo) +* Docs: Typo in no-unexpected-multiline (fixes #4756) (alberto) +* Update: option to ignore top-level max statements (fixes #4309) (alberto) +* Update: Implement auto fix for semi-spacing rule (fixes #3829) (alberto) +* Fix: small typos in code examples (Plusb Preco) +* Docs: Add section on file extensions to user-guide/configuring (adam) +* Fix: Comma first issue in `indent` (fixes #4739, fixes #3456) (Gyandeep Singh) +* Fix: no-constant-condition false positive (fixes #4737) (alberto) +* Fix: Add source property for fatal errors (fixes #3325) (Gyandeep Singh) +* New: Add a comment length option to the max-len rule (fixes #4665) (Ian) +* Docs: RuleTester doesn't require any tests (fixes #4681) (alberto) +* Fix: Remove path analysis from debug log (fixes #4631) (Ilya Volodin) +* Fix: Set null to property ruleId when fatal is true (fixes #4722) (Sébastien Règne) +* New: Visual Studio compatible formatter (fixes #4708) (rhpijnacker) +* New: Add greasemonkey environment (fixes #4715) (silverwind) +* Fix: always-multiline for comma-dangle import (fixes #4704) (Nicholas C. Zakas) +* Fix: Check 1tbs non-block else (fixes #4692) (Nicholas C. Zakas) +* Fix: Apply environment configs last (fixes #3915) (Nicholas C. Zakas) +* New: `no-unmodified-loop-condition` rule (fixes #4523) (Toru Nagashima) +* Breaking: deprecate `no-arrow-condition` rule (fixes #4417) (Luke Karrys) +* Update: Add cwd option for cli-engine (fixes #4472) (Ilya Volodin) +* New: Add no-confusing-arrow rule (refs #4417) (Luke Karrys) +* Fix: ensure `ConfigOps.merge` do a deep copy (fixes #4682) (Toru Nagashima) +* Fix: `no-invalid-this` allows this in static method (fixes #4669) (Toru Nagashima) +* Fix: Export class syntax for `require-jsdoc` rule (fixes #4667) (Gyandeep Singh) +* Update: Add "safe" mode to strict (fixes #3306) (Brandon Mills) + +v2.0.0-alpha-1 - December 11, 2015 + +* Breaking: Correct links between variables and references (fixes #4615) (Toru Nagashima) +* Fix: Update rule tests for parser options (fixes #4673) (Nicholas C. Zakas) +* Breaking: Implement parserOptions (fixes #4641) (Nicholas C. Zakas) +* Fix: max-len rule overestimates the width of some tabs (fixes #4661) (Nick Evans) +* New: Add no-implicit-globals rule (fixes #4542) (Joshua Peek) +* Update: `no-use-before-define` checks invalid initializer (fixes #4280) (Toru Nagashima) +* Fix: Use oneValuePerFlag for --ignore-pattern option (fixes #4507) (George Zahariev) +* New: `array-callback-return` rule (fixes #1128) (Toru Nagashima) +* Upgrade: Handlebars to >= 4.0.5 for security reasons (fixes #4642) (Jacques Favreau) +* Update: Add class body support to `indent` rule (fixes #4372) (Gyandeep Singh) +* Breaking: Remove space-after-keyword newline check (fixes #4149) (Nicholas C. Zakas) +* Breaking: Treat package.json like the rest of configs (fixes #4451) (Ilya Volodin) +* Docs: writing mistake (molee1905) +* Update: Add 'method' option to no-empty (fixes #4605) (Kai Cataldo) +* Breaking: Remove autofix from eqeqeq (fixes #4578) (Ilya Volodin) +* Breaking: Remove ES6 global variables from builtins (fixes #4085) (Brandon Mills) +* Fix: Handle forbidden LineTerminators in no-extra-parens (fixes #4229) (Brandon Mills) +* Update: Option to ignore constructor Fns object-shorthand (fixes #4487) (Kai Cataldo) +* Fix: Check YieldExpression argument in no-extra-parens (fixes #4608) (Brandon Mills) +* Fix: Do not cache `package.json` (fixes #4611) (Spain) +* Build: Consume no-underscore-dangle allowAfterThis option (fixes #4599) (Kevin Partington) +* New: Add no-restricted-imports rule (fixes #3196) (Guy Ellis) +* Docs: no-extra-semi no longer refers to deprecated rule (fixes #4598) (Kevin Partington) +* Fix: `consistent-return` checks the last (refs #3530, fixes #3373) (Toru Nagashima) +* Update: add class option to `no-use-before-define` (fixes #3944) (Toru Nagashima) +* Breaking: Simplify rule schemas (fixes #3625) (Nicholas C. Zakas) +* Docs: Update docs/rules/no-plusplus.md (Xiangyun Chi) +* Breaking: added bower_components to default ignore (fixes #3550) (Julian Laval) +* Fix: `no-unreachable` with the code path (refs #3530, fixes #3939) (Toru Nagashima) +* Fix: `no-this-before-super` with the code path analysis (refs #3530) (Toru Nagashima) +* Fix: `no-fallthrough` with the code path analysis (refs #3530) (Toru Nagashima) +* Fix: `constructor-super` with the code path analysis (refs #3530) (Toru Nagashima) +* Breaking: Switch to Espree 3.0.0 (fixes #4334) (Nicholas C. Zakas) +* Breaking: Freeze context object (fixes #4495) (Nicholas C. Zakas) +* Docs: Add Code of Conduct (fixes #3095) (Nicholas C. Zakas) +* Breaking: Remove warnings of readonly from `no-undef` (fixes #4504) (Toru Nagashima) +* Update: allowAfterThis option in no-underscore-dangle (fixes #3435) (just-boris) +* Fix: Adding options unit tests for --ignore-pattern (refs #4507) (Kevin Partington) +* Breaking: Implement yield-star-spacing rule (fixes #4115) (Bryan Smith) +* New: `prefer-rest-params` rule (fixes #4108) (Toru Nagashima) +* Update: `prefer-const` begins to cover separating init (fixes #4474) (Toru Nagashima) +* Fix: `no-eval` come to catch indirect eval (fixes #4399, fixes #4441) (Toru Nagashima) +* Breaking: Default no-magic-numbers to none. (fixes #4193) (alberto) +* Breaking: Allow empty arrow body (fixes #4411) (alberto) +* New: Code Path Analysis (fixes #3530) (Toru Nagashima) + +v1.10.3 - December 1, 2015 + +* Docs: Update strict rule docs (fixes #4583) (Nicholas C. Zakas) +* Docs: Reference .eslintrc.* in contributing docs (fixes #4532) (Kai Cataldo) +* Fix: Add for-of to `curly` rule (fixes #4571) (Kai Cataldo) +* Fix: Ignore space before function in array start (fixes #4569) (alberto) + +v1.10.2 - November 27, 2015 + +* Upgrade: escope@3.3.0 (refs #4485) (Nicholas C. Zakas) +* Upgrade: Pinned down js-yaml to avoid breaking dep (fixes #4553) (alberto) +* Fix: lines-around-comment with multiple comments (fixes #3509) (alberto) +* Upgrade: doctrine@0.7.1 (fixes #4545) (Kevin Partington) +* Fix: Bugfix for eqeqeq autofix (fixes #4540) (Kevin Partington) +* Fix: Add for-in to `curly` rule (fixes #4436) (Kai Cataldo) +* Fix: `valid-jsdoc` unneeded require check fix (fixes #4527) (Gyandeep Singh) +* Fix: `brace-style` ASI fix for if-else condition (fixes #4520) (Gyandeep Singh) +* Build: Add branch update during release process (fixes #4491) (Gyandeep Singh) +* Build: Allow revert commits in commit messages (fixes #4452) (alberto) +* Fix: Incorrect location in no-fallthrough (fixes #4516) (alberto) +* Fix: `no-spaced-func` had been crashed (fixes #4508) (Toru Nagashima) +* Fix: Add a RestProperty test of `no-undef` (fixes #3271) (Toru Nagashima) +* Docs: Load badge from HTTPS (Brian J Brennan) +* Build: Update eslint bot messages (fixes #4497) (Nicholas C. Zakas) + +v1.10.1 - November 20, 2015 + +* Fix: Revert freezing context object (refs #4495) (Nicholas C. Zakas) +* 1.10.0 (Nicholas C. Zakas) + +v1.10.0 - November 20, 2015 + +* Docs: Remove dupes from changelog (Nicholas C. Zakas) +* Update: --init to create extensioned files (fixes #4476) (Nicholas C. Zakas) +* Docs: Update description of exported comment (fixes #3916) (Nicholas C. Zakas) +* Docs: Move legacy rules to stylistic (files #4111) (Nicholas C. Zakas) +* Docs: Clean up description of recommended rules (fixes #4365) (Nicholas C. Zakas) +* Docs: Fix home directory config description (fixes #4398) (Nicholas C. Zakas) +* Update: Add class support to `require-jsdoc` rule (fixes #4268) (Gyandeep Singh) +* Update: return type error in `valid-jsdoc` rule (fixes #4443) (Gyandeep Singh) +* Update: Display errors at the place where fix should go (fixes #4470) (nightwing) +* Docs: Fix typo in default `cacheLocation` value (Andrew Hutchings) +* Fix: Handle comments in block-spacing (fixes #4387) (alberto) +* Update: Accept array for `ignorePattern` (fixes #3982) (Jesse McCarthy) +* Update: replace label and break with IIFE and return (fixes #4459) (Ilya Panasenko) +* Fix: space-before-keywords false positive (fixes #4449) (alberto) +* Fix: Improves performance (refs #3530) (Toru Nagashima) +* Fix: Autofix quotes produces invalid javascript (fixes #4380) (nightwing) +* Docs: Update indent.md (Nathan Brown) +* New: Disable comment config option (fixes #3901) (Matthew Riley MacPherson) +* New: Config files with extensions (fixes #4045, fixes #4263) (Nicholas C. Zakas) +* Revert "Update: Add JSX exceptions to no-extra-parens (fixes #4229)" (Brandon Mills) +* Update: Add JSX exceptions to no-extra-parens (fixes #4229) (Brandon Mills) +* Docs: Replace link to deprecated rule with newer rule (Andrew Marshall) +* Fix: `no-extend-native` crashed at empty defineProperty (fixes #4438) (Toru Nagashima) +* Fix: Support empty if blocks in lines-around-comment (fixes #4339) (alberto) +* Fix: `curly` warns wrong location for `else` (fixes #4362) (Toru Nagashima) +* Fix: `id-length` properties never option (fixes #4347) (Toru Nagashima) +* Docs: missing close rbracket in example (@storkme) +* Revert "Update: Allow empty arrow body (fixes #4411)" (Nicholas C. Zakas) +* Fix: eqeqeq autofix avoids clashes with space-infix-ops (fixes #4423) (Kevin Partington) +* Docs: Document semi-spacing behaviour (fixes #4404) (alberto) +* Update: Allow empty arrow body (fixes #4411) (alberto) +* Fix: Handle comments in comma-spacing (fixes #4389) (alberto) +* Update: Refactor eslint.verify args (fixes #4395) (Nicholas C. Zakas) +* Fix: no-undef-init should ignore const (fixes #4284) (Nicholas C. Zakas) +* Fix: Add the missing "as-needed" docs to the radix rule (fixes #4364) (Michał Gołębiowski) +* Fix: Display singular/plural version of "line" in message (fixes #4359) (Marius Schulz) +* Update: Add Popular Style Guides (fixes #4320) (Jamund Ferguson) +* Fix: eslint.report can be called w/o node if loc provided (fixes #4220) (Kevin Partington) +* Update: no-implicit-coercion validate AssignmentExpression (fixes #4348) (Ilya Panasenko) + +v1.9.0 - November 6, 2015 + +* Update: Make radix accept a "as-needed" option (fixes #4048) (Michał Gołębiowski) +* Fix: Update the message to include number of lines (fixes #4342) (Brian Delahunty) +* Docs: ASI causes problem whether semicolons are used or not (Thai Pangsakulyanont) +* Fix: Fixer to not overlap ranges among fix objects (fixes #4321) (Gyandeep Singh) +* Update: Add default to `max-nested-callbacks` (fixes #4297) (alberto) +* Fix: Check comments in space-in-parens (fixes #4302) (alberto) +* Update: Add quotes to error messages to improve clarity (fixes #4313) (alberto) +* Fix: tests failing due to differences in temporary paths (fixes #4324) (alberto) +* Fix: Make tests compatible with Windows (fixes #4315) (Ian VanSchooten) +* Update: Extract glob and filesystem logic from cli-engine (fixes #4305) (Ian VanSchooten) +* Build: Clarify commit-check messages (fixes #4256) (Ian VanSchooten) +* Upgrade: Upgrade various dependencies (fixes #4303) (Gyandeep Singh) +* Build: Add node 5 to travis build (fixes #4310) (Gyandeep Singh) +* Fix: ensure using correct estraverse (fixes #3951) (Toru Nagashima) +* Docs: update docs about using gitignore (Mateusz Derks) +* Update: Detect and fix wrong linebreaks (fixes #3981) (alberto) +* New: Add no-case-declarations rule (fixes #4278) (Erik Arvidsson) + +v1.8.0 - October 30, 2015 + +* Fix: Check for node property before testing type (fixes #4298) (Ian VanSchooten) +* Docs: Specify 'double' as default for quotes (fixes #4270) (Ian VanSchooten) +* Fix: Missing errors in space-in-parens (fixes #4257, fixes #3996) (alberto) +* Docs: fixed typo (Mathieu M-Gosselin) +* Fix: `cacheLocation` handles paths in windows style. (fixes #4285) (royriojas) +* Docs: fixed typo (mpal9000) +* Update: Add support for class in `valid-jsdoc` rule (fixes #4279) (Gyandeep Singh) +* Update: cache-file accepts a directory. (fixes #4241) (royriojas) +* Update: Add `maxEOF` to no-multiple-empty-lines (fixes #4235) (Adrien Vergé) +* Update: fix option for comma-spacing (fixes #4232) (HIPP Edgar (PRESTA EXT)) +* Docs: Fix use of wrong word in configuration doc (Jérémie Astori) +* Fix: Prepare config before verifying SourceCode (fixes #4230) (Ian VanSchooten) +* Update: RuleTester come to check AST was not modified (fixes #4156) (Toru Nagashima) +* Fix: wrong count for 'no-multiple-empty-lines' on last line (fixes #4228) (alberto) +* Update: Add `allow` option to `no-shadow` rule (fixes #3035) (Gyandeep Singh) +* Doc: Correct the spelling of Alberto's surname (alberto) +* Docs: Add alberto as a committer (Gyandeep Singh) +* Build: Do not stub console in testing (fixes #1328) (Gyandeep Singh) +* Fix: Check node exists before checking type (fixes #4231) (Ian VanSchooten) +* Update: Option to exclude afterthoughts from no-plusplus (fixes #4093) (Brody McKee) +* New: Add rule no-arrow-condition (fixes #3280) (Luke Karrys) +* Update: Add linebreak style option to eol-last (fixes #4148) (alberto) +* New: arrow-body-style rule (fixes #4109) (alberto) + +v1.7.3 - October 21, 2015 + +* Fix: Support comma-first style in key-spacing (fixes #3877) (Brandon Mills) +* Fix: no-magic-numbers: variable declarations (fixes #4192) (Ilya Panasenko) +* Fix: Support ES6 shorthand in key-spacing (fixes #3678) (Brandon Mills) +* Fix: `indent` array with memberExpression (fixes #4203) (Gyandeep Singh) +* Fix: `indent` param function on sameline (fixes #4174) (Gyandeep Singh) +* Fix: no-multiple-empty-lines fails when empty line at EOF (fixes #4214) (alberto) +* Fix: `comma-dangle` false positive (fixes #4200) (Nicholas C. Zakas) +* Fix: `valid-jsdoc` prefer problem (fixes #4205) (Nicholas C. Zakas) +* Docs: Add missing single-quote (Kevin Lamping) +* Fix: correct no-multiple-empty-lines at EOF (fixes #4140) (alberto) + +v1.7.2 - October 19, 2015 + +* Fix: comma-dangle confused by parens (fixes #4195) (Nicholas C. Zakas) +* Fix: no-mixed-spaces-and-tabs (fixes #4189, fixes #4190) (alberto) +* Fix: no-extend-native disallow using Object.properties (fixes #4180) (Nathan Woltman) +* Fix: no-magic-numbers should ignore Number.parseInt (fixes #4167) (Henry Zhu) + +v1.7.1 - October 16, 2015 + +* Fix: id-match schema (fixes #4155) (Nicholas C. Zakas) +* Fix: no-magic-numbers should ignore parseInt (fixes #4167) (Nicholas C. Zakas) +* Fix: `indent` param function fix (fixes #4165, fixes #4164) (Gyandeep Singh) + +v1.7.0 - October 16, 2015 + +* Fix: array-bracket-spacing for empty array (fixes #4141) (alberto) +* Fix: `indent` arrow function check fix (fixes #4142) (Gyandeep Singh) +* Update: Support .js files for config (fixes #3102) (Gyandeep Singh) +* Fix: Make eslint-config-eslint work (fixes #4145) (Nicholas C. Zakas) +* Fix: `prefer-arrow-callback` had been wrong at arguments (fixes #4095) (Toru Nagashima) +* Docs: Update various rules docs (Nicholas C. Zakas) +* New: Create eslint-config-eslint (fixes #3525) (Nicholas C. Zakas) +* Update: RuleTester allows string errors in invalid cases (fixes #4117) (Kevin Partington) +* Docs: Reference no-unexpected-multiline in semi (fixes #4114) (alberto) +* Update: added exceptions to `lines-around-comment` rule. (fixes #2965) (Mathieu M-Gosselin) +* Update: Add `matchDescription` option to `valid-jsdoc` (fixes #2449) (Gyandeep Singh) +* Fix: check for objects or arrays in array-bracket-spacing (fixes #4083) (alberto) +* Docs: Alphabetize Rules lists (Kenneth Chung) +* Fix: message templates fail when no parameters are passed (fixes #4080) (Ilya Volodin) +* Fix: `indent` multi-line function call (fixes #4073, fixes #4075) (Gyandeep Singh) +* Docs: Improve comma-dangle documentation (Gilad Peleg) +* Fix: no-mixed-tabs-and-spaces fails with some comments (fixes #4086) (alberto) +* Fix: `semi` to check for do-while loops (fixes #4090) (Gyandeep Singh) +* Build: Fix path related failures on Windows in tests (fixes #4061) (Burak Yigit Kaya) +* Fix: `no-unused-vars` had been missing some parameters (fixes #4047) (Toru Nagashima) +* Fix: no-mixed-spaces-and-tabs with comments and templates (fixes #4077) (alberto) +* Update: Add `allow` option for `no-underscore-dangle` rule (fixes #2135) (Gyandeep Singh) +* Update: `allowArrowFunctions` option for `func-style` rule (fixes #1897) (Gyandeep Singh) +* Fix: Ignore template literals in no-mixed-tabs-and-spaces (fixes #4054) (Nicholas C. Zakas) +* Build: Enable CodeClimate (fixes #4068) (Nicholas C. Zakas) +* Fix: `no-cond-assign` had needed double parens in `for` (fixes #4023) (Toru Nagashima) +* Update: Ignore end of function in newline-after-var (fixes #3682) (alberto) +* Build: Performance perf to not ignore jshint file (refs #3765) (Gyandeep Singh) +* Fix: id-match bug incorrectly errors on `NewExpression` (fixes #4042) (Burak Yigit Kaya) +* Fix: `no-trailing-spaces` autofix to handle linebreaks (fixes #4050) (Gyandeep Singh) +* Fix: renamed no-magic-number to no-magic-numbers (fixes #4053) (Vincent Lemeunier) +* New: add "consistent" option to the "curly" rule (fixes #2390) (Benoît Zugmeyer) +* Update: Option to ignore for loops in init-declarations (fixes #3641) (alberto) +* Update: Add webextensions environment (fixes #4051) (Blake Winton) +* Fix: no-cond-assign should report assignment location (fixes #4040) (alberto) +* New: no-empty-pattern rule (fixes #3668) (alberto) +* Upgrade: Upgrade globals to 8.11.0 (fixes #3599) (Burak Yigit Kaya) +* Docs: Re-tag JSX code fences (fixes #4020) (Brandon Mills) +* New: no-magic-number rule (fixes #4027) (Vincent Lemeunier) +* Docs: Remove list of users from README (fixes #3881) (Brandon Mills) +* Fix: `no-redeclare` and `no-sahadow` for builtin globals (fixes #3971) (Toru Nagashima) +* Build: Add `.eslintignore` file for the project (fixes #3765) (Gyandeep Singh) + +v1.6.0 - October 2, 2015 + +* Fix: cache is basically not working (fixes #4008) (Richard Hansen) +* Fix: a test failure on Windows (fixes #3968) (Toru Nagashima) +* Fix: `no-invalid-this` had been missing globals in node (fixes #3961) (Toru Nagashima) +* Fix: `curly` with `multi` had false positive (fixes #3856) (Toru Nagashima) +* Build: Add load performance check inside perf function (fixes #3994) (Gyandeep Singh) +* Fix: space-before-keywords fails with super keyword (fixes #3946) (alberto) +* Fix: CLI should not fail on account of ignored files (fixes #3978) (Dominic Barnes) +* Fix: brace-style rule incorrectly flagging switch (fixes #4002) (Aparajita Fishman) +* Update: Implement auto fix for space-unary-ops rule (fixes #3976) (alberto) +* Update: Implement auto fix for computed-property-spacing (fixes #3975) (alberto) +* Update: Implement auto fix for no-multi-spaces rule (fixes #3979) (alberto) +* Fix: Report shorthand method names in complexity rule (fixes #3955) (Tijn Kersjes) +* Docs: Add note about typeof check for isNaN (fixes #3985) (Daniel Lo Nigro) +* Update: ESLint reports parsing errors with clear prefix. (fixes #3555) (Kevin Partington) +* Build: Update markdownlint dependency (fixes #3954) (David Anson) +* Update: `no-mixed-require` to have non boolean option (fixes #3922) (Gyandeep Singh) +* Fix: trailing spaces auto fix to check for line breaks (fixes #3940) (Gyandeep Singh) +* Update: Add `typeof` option to `no-undef` rule (fixes #3684) (Gyandeep Singh) +* Docs: Fix explanation and typos for accessor-pairs (alberto) +* Docs: Fix typos for camelcase (alberto) +* Docs: Fix typos for max-statements (Danny Guo) +* Update: Implement auto fix for object-curly-spacing (fixes #3857) (alberto) +* Update: Implement auto fix for array-bracket-spacing rule (fixes #3858) (alberto) +* Fix: Add schema to `global-require` rule (fixes #3923) (Gyandeep Singh) +* Update: Apply lazy loading for rules (fixes #3930) (Gyandeep Singh) +* Docs: Fix typo for arrow-spacing (Danny Guo) +* Docs: Fix typos for wrap-regex (Danny Guo) +* Docs: Fix explanation for space-before-keywords (Danny Guo) +* Docs: Fix typos for operator-linebreak (Danny Guo) +* Docs: Fix typos for callback-return (Danny Guo) +* Fix: no-trailing-spaces autofix to account for blank lines (fixes #3912) (Gyandeep Singh) +* Docs: Fix example in no-negated-condition.md (fixes #3908) (alberto) +* Update:warn message use @return when prefer.returns=return (fixes #3889) (闲耘™) +* Update: Implement auto fix for generator-star-spacing rule (fixes #3873) (alberto) +* Update: Implement auto fix for arrow-spacing rule (fixes #3860) (alberto) +* Update: Implement auto fix for block-spacing rule (fixes #3859) (alberto) +* Fix: Support allman style for switch statement (fixes #3903) (Gyandeep Singh) +* New: no-negated-condition rule (fixes #3740) (alberto) +* Docs: Fix typo in blog post template (Nicholas C. Zakas) +* Update: Add env 'nashorn' to support Java 8 Nashorn Engine (fixes #3874) (Benjamin Winterberg) +* Docs: Prepare for rule doc linting (refs #2271) (Ian VanSchooten) + +v1.5.1 - September 22, 2015 + +* Fix: valid-jsdoc fix for param with properties (fixes #3476) (Gyandeep Singh) +* Fix: valid-jsdoc error with square braces (fixes #2270) (Gyandeep Singh) +* Upgrade: `doctrine` to 0.7.0 (fixes #3891) (Gyandeep Singh) +* Fix: `space-before-keywords` had been wrong on getters (fixes #3854) (Toru Nagashima) +* Fix: `no-dupe-args` had been wrong for nested destructure (fixes #3867) (Toru Nagashima) +* Docs: io.js is the new Node.js (thefourtheye) +* Docs: Fix method signature on working-with-rules docs (fixes #3862) (alberto) +* Docs: Add related ternary links (refs #3835) (Ian VanSchooten) +* Fix: don’t ignore config if cwd is the home dir (fixes #3846) (Mathias Schreck) +* Fix: `func-style` had been warning arrows with `this` (fixes #3819) (Toru Nagashima) +* Fix: `space-before-keywords`; allow opening curly braces (fixes #3789) (Marko Raatikka) +* Build: Fix broken .gitattributes generation (fixes #3566) (Nicholas C. Zakas) +* Build: Fix formatter docs generation (fixes #3847) (Nicholas C. Zakas) + +v1.5.0 - September 18, 2015 + +* Fix: invalidate cache when config changes. (fixes #3770) (royriojas) +* Fix: function body indent issues (fixes #3614, fixes #3799) (Gyandeep Singh) +* Update: Add configuration option to `space-before-blocks` (fixes #3758) (Phil Vargas) +* Fix: space checking between tokens (fixes #2211) (Nicholas C. Zakas) +* Fix: env-specified ecmaFeatures had been wrong (fixes #3735) (Toru Nagashima) +* Docs: Change example wording from warnings to problems (fixes #3676) (Ian VanSchooten) +* Build: Generate formatter example docs (fixes #3560) (Ian VanSchooten) +* New: Add --debug flag to CLI (fixes #2692) (Nicholas C. Zakas) +* Docs: Update no-undef-init docs (fixes #3170) (Nicholas C. Zakas) +* Docs: Update no-unused-expressions docs (fixes #3685) (Nicholas C. Zakas) +* Docs: Clarify node types in no-multi-spaces (fixes #3781) (Nicholas C. Zakas) +* Docs: Update new-cap docs (fixes #3798) (Nicholas C. Zakas) +* Fix: `space-before-blocks` had conflicted `arrow-spacing` (fixes #3769) (Toru Nagashima) +* Fix: `comma-dangle` had not been checking imports/exports (fixes #3794) (Toru Nagashima) +* Fix: tests fail due to differences in temporary paths. (fixes #3778) (royriojas) +* Fix: Directory ignoring should work (fixes #3812) (Nicholas C. Zakas) +* Fix: Ensure **/node_modules works in ignore files (fixes #3788) (Nicholas C. Zakas) +* Update: Implement auto fix for `space-infix-ops` rule (fixes #3801) (Gyandeep Singh) +* Fix: `no-warning-comments` can't be set via config comment (fixes #3619) (Burak Yigit Kaya) +* Update: `key-spacing` should allow 1+ around colon (fixes #3363) (Burak Yigit Kaya) +* Fix: false alarm of semi-spacing with semi set to never (fixes #1983) (Chen Yicai) +* Fix: Ensure ./ works correctly with CLI (fixes #3792) (Nicholas C. Zakas) +* Docs: add more examples + tests for block-scoped-var (fixes #3791) (JT) +* Update: Implement auto fix for `indent` rule (fixes #3734) (Gyandeep Singh) +* Fix: `space-before-keywords` fails to handle some cases (fixes #3756) (Marko Raatikka) +* Docs: Add if-else example (fixes #3722) (Ian VanSchooten) +* Fix: jsx-quotes exception for attributes without value (fixes #3793) (Mathias Schreck) +* Docs: Fix closing code fence on cli docs (Ian VanSchooten) +* Update: Implement auto fix for `space-before-blocks` rule (fixes #3776) (Gyandeep Singh) +* Update: Implement auto fix for `space-after-keywords` rule (fixes #3773) (Gyandeep Singh) +* Fix: `semi-spacing` had conflicted with `block-spacing` (fixes #3721) (Toru Nagashima) +* Update: Implement auto fix for `space-before-keywords` rule (fixes #3771) (Gyandeep Singh) +* Update: auto fix for space-before-function-paren rule (fixes #3766) (alberto) +* Update: Implement auto fix for `no-extra-semi` rule (fixes #3745) (Gyandeep Singh) +* Update: Refactors the traversing logic (refs #3530) (Toru Nagashima) +* Update: Implement auto fix for `space-return-throw-case` (fixes #3732) (Gyandeep Singh) +* Update: Implement auto fix for `no-spaced-func` rule (fixes #3728) (Gyandeep Singh) +* Update: Implement auto fix for `eol-last` rule (fixes #3725) (Gyandeep Singh) +* Update: Implement auto fix for `no-trailing-spaces` rule (fixes #3723) (Gyandeep Singh) + +v1.4.3 - September 15, 2015 + +* Fix: Directory ignoring should work (fixes #3812) (Nicholas C. Zakas) +* Fix: jsx-quotes exception for attributes without value (fixes #3793) (Mathias Schreck) + +v1.4.2 - September 15, 2015 + +* Fix: Ensure **/node_modules works in ignore files (fixes #3788) (Nicholas C. Zakas) +* Fix: Ensure ./ works correctly with CLI (fixes #3792) (Nicholas C. Zakas) + +v1.4.1 - September 11, 2015 + +* Fix: CLIEngine default cache parameter name (fixes #3755) (Daniel G. Taylor) +* Fix: Glob pattern from .eslintignore not applied (fixes #3750) (Burak Yigit Kaya) +* Fix: Skip JSDoc from NewExpression (fixes #3744) (Nicholas C. Zakas) +* Docs: Shorten and simplify autocomment for new issues (Nicholas C. Zakas) + +v1.4.0 - September 11, 2015 + +* Docs: Add new formatters to API docs (Ian VanSchooten) +* New: Implement autofixing (fixes #3134) (Nicholas C. Zakas) +* Fix: Remove temporary `"allow-null"` (fixes #3705) (Toru Nagashima) +* Fix: `no-unused-vars` had been crashed at `/*global $foo*/` (fixes #3714) (Toru Nagashima) +* Build: check-commit now checks commit message length. (fixes #3706) (Kevin Partington) +* Fix: make getScope acquire innermost scope (fixes #3700) (voideanvalue) +* Docs: Fix spelling mistake (domharrington) +* Fix: Allow whitespace in rule message parameters. (fixes #3690) (Kevin Partington) +* Fix: Eqeqeq rule with no option does not warn on 'a == null' (fixes #3699) (fediev) +* Fix: `no-unused-expressions` with `allowShortCircuit` false positive if left has no effect (fixes #3675) (Toru Nagashima) +* Update: Add Node 4 to travis builds (fixes #3697) (Ian VanSchooten) +* Fix: Not check for punctuator if on same line as last var (fixes #3694) (Gyandeep Singh) +* Docs: Make `quotes` docs clearer (fixes #3646) (Nicholas C. Zakas) +* Build: Increase mocha timeout (fixes #3692) (Nicholas C. Zakas) +* Fix: `no-extra-bind` to flag all arrow funcs (fixes #3672) (Nicholas C. Zakas) +* Docs: Update README with release and sponsor info (Nicholas C. Zakas) +* Fix: `object-curly-spacing` had been crashing on an empty object pattern (fixes #3658) (Toru Nagashima) +* Fix: `no-extra-parens` false positive at IIFE with member accessing (fixes #3653) (Toru Nagashima) +* Fix: `comma-dangle` with `"always"`/`"always-multiline"` false positive after a rest element (fixes #3627) (Toru Nagashima) +* New: `jsx-quotes` rule (fixes #2011) (Mathias Schreck) +* Docs: Add linting for second half of rule docs (refs #2271) (Ian VanSchooten) +* Fix: `no-unused-vars` had not shown correct locations for `/*global` (fixes #3617) (Toru Nagashima) +* Fix: `space-after-keywords` not working for `catch` (fixes #3654) (Burak Yigit Kaya) +* Fix: Incorrectly warning about ignored files (fixes #3649) (Burak Yigit Kaya) +* Fix: Indent rule VariableDeclarator doesn't apply to arrow functions (fixes #3661) (Burak Yigit Kaya) +* Upgrade: Consuming handlebars@^4.0.0 (fixes #3632) (Kevin Partington) +* Docs: Fixing typos in plugin processor section. (fixes #3648) (Kevin Partington) +* Fix: Invalid env keys would cause an unhandled exception.(fixes #3265) (Ray Booysen) +* Docs: Fixing broken link in documentation (Ilya Volodin) +* Update: Check for default assignment in no-unneeded-ternary (fixes #3232) (cjihrig) +* Fix: `consistent-as-needed` mode with `keyword: true` (fixes #3636) (Alex Guerrero) +* New: Implement cache in order to only operate on changed files since previous run. (fixes #2998) (Roy Riojas) +* Update: Grouping related CLI options. (fixes #3612) (Kevin Partington) +* Update: Using @override does not require @param or @returns (fixes #3629) (Whitney Young) +* Docs: Use eslint-env in no-undef (fixes #3616) (Ian VanSchooten) +* New: `require-jsdoc` rule (fixes #1842) (Gyandeep Singh) +* New: Support glob path on command line (fixes #3402) (Burak Yigit Kaya) +* Update: Short circuit and ternary support in no-unused-expressions (fixes #2733) (David Warkentin) +* Docs: Replace to npmjs.com (Ryuichi Okumura) +* Fix: `indent` should only indent chain calls if the first call is single line (fixes #3591) (Burak Yigit Kaya) +* Fix: `quote-props` should not crash for object rest spread syntax (fixes #3595) (Joakim Carlstein) +* Update: Use `globals` module for the `commonjs` globals (fixes #3606) (Sindre Sorhus) +* New: `no-restricted-syntax` rule to forbid certain syntax (fixes #2422) (Burak Yigit Kaya) +* Fix: `no-useless-concat` false positive at numbers (fixes #3575, fixes #3589) (Toru Nagashima) +* New: Add --max-warnings flag to CLI (fixes #2769) (Kevin Partington) +* New: Add `parser` as an option (fixes #3127) (Gyandeep Singh) +* New: `space-before-keywords` rule (fixes #1631) (Marko Raatikka) +* Update: Allowing inline comments to disable eslint rules (fixes #3472) (Whitney Young) +* Docs: Including for(;;) as valid case in no-constant-condition (Kevin Partington) +* Update: Add quotes around the label in `no-redeclare` error messages (fixes #3583) (Ian VanSchooten) +* Docs: correct contributing URL (Dieter Luypaert) +* Fix: line number for duplicate object keys error (fixes #3573) (Elliot Lynde) +* New: global-require rule (fixes #2318) (Jamund Ferguson) + +v1.3.1 - August 29, 2015 + +* Fix: `indent` to not crash on empty files (fixes #3570) (Gyandeep Singh) +* Fix: Remove unused config file (fixes #2227) (Gyandeep Singh) + +v1.3.0 - August 28, 2015 + +* Build: Autogenerate release blog post (fixes #3562) (Nicholas C. Zakas) +* New: `no-useless-concat` rule (fixes #3506) (Henry Zhu) +* Update: Add `keywords` flag to `consistent-as-needed` mode in `quote-props` (fixes #3532) (Burak Yigit Kaya) +* Update: adds `numbers` option to quote-props (fixes #2914) (Jose Roberto Vidal) +* Fix: `quote-props` rule should ignore computed and shorthand properties (fixes #3557) (fixes #3544) (Burak Yigit Kaya) +* Docs: Add config comments for rule examples 'accessor-pairs' to 'no-extra-semi' (refs #2271) (Ian VanSchooten) +* Update: Return to accept `undefined` type (fixes #3382) (Gyandeep Singh) +* New: Added HTML formatter (fixes #3505) (Julian Laval) +* Fix: check space after yield keyword in space-unary-ops (fixes #2707) (Mathias Schreck) +* Docs: (curly) Fix broken code in example (Kent C. Dodds) +* Update: Quote var name in `no-unused-vars` error messages (refs #3526) (Burak Yigit Kaya) +* Update: Move methods to SourceCode (fixes #3516) (Nicholas C. Zakas) +* Fix: Don't try too hard to find fault in `no-implicit-coercion` (refs #3402) (Burak Yigit Kaya) +* Fix: Detect ternary operator in operator-linebreak rule (fixes #3274) (Burak Yigit Kaya) +* Docs: Clearer plugin rule configuration (fixes #2022) (Nicholas C. Zakas) +* Update: Add quotes around the label in `no-empty-label` error reports (fixes #3526) (Burak Yigit Kaya) +* Docs: Turn off Liquid in example (Nicholas C. Zakas) +* Docs: Mention CommonJS along with Node.js (fixes #3388) (Nicholas C. Zakas) +* Docs: Make it clear which rules are recommended (fixes #3398) (Nicholas C. Zakas) +* Docs: Add links to JSON Schema resources (fixes #3411) (Nicholas C. Zakas) +* Docs: Add more info to migration guide (fixes #3439) (Nicholas C. Zakas) +* Fix: ASI indentation issue (fixes #3514) (Burak Yigit Kaya) +* Fix: Make `no-implicit-coercion` smarter about numerical expressions (fixes #3510) (Burak Yigit Kaya) +* Fix: `prefer-template` had not been handling TemplateLiteral as literal node (fixes #3507) (Toru Nagashima) +* Update: `newline-after-var` Allow comment + blank after var (fixes #2852) (Ian VanSchooten) +* Update: Add `unnecessary` option to `quote-props` (fixes #3381) (Burak Yigit Kaya) +* Fix: `indent` shouldn't check the last line unless it is a punctuator (fixes #3498) (Burak Yigit Kaya) +* Fix: `indent` rule does not indent when doing multi-line chain calls (fixes #3279) (Burak Yigit Kaya) +* Fix: sort-vars rule fails when memo is undefined (fixes #3474) (Burak Yigit Kaya) +* Fix: `brace-style` doesn't report some closing brace errors (fixes #3486) (Burak Yigit Kaya) +* Update: separate options for block and line comments in `spaced-comment` rule (fixes #2897) (Burak Yigit Kaya) +* Fix: `indent` does not check FunctionDeclaration nodes properly (fixes #3173) (Burak Yigit Kaya) +* Update: Added "properties" option to `id-length` rule to ignore property names. (fixes #3450) (Mathieu M-Gosselin) +* Update: add new ignore pattern options to no-unused-vars (fixes #2321) (Mathias Schreck) +* New: Protractor environment (fixes #3457) (James Whitney) +* Docs: Added section to shareable config (Gregory Waxman) +* Update: Allow pre-parsed code (fixes #1025, fixes #948) (Nicholas C. Zakas) + +v1.2.1 - August 20, 2015 + +* Fix: "key-spacing" crashes eslint on object literal shorthand properties (fixes #3463) (Burak Yigit Kaya) +* Fix: ignore leading space check for `null` elements in comma-spacing (fixes #3392) (Mathias Schreck) +* Fix: `prefer-arrow-callback` false positive at recursive functions (fixes #3454) (Toru Nagashima) +* Fix: one-var rule doesn’t have default options (fixes #3449) (Burak Yigit Kaya) +* Fix: Refactor `no-duplicate-case` to be simpler and more efficient (fixes #3440) (Burak Yigit Kaya) +* Docs: Fix trailing spaces in README (Nicholas C. Zakas) +* Docs: Update gyandeeps and add byk (Nicholas C. Zakas) +* Docs: Update plugins documentation for 1.0.0 (Nicholas C. Zakas) +* Docs: `object-curly-spacing` doc is inaccurate about exceptions (Burak Yigit Kaya) +* Fix: `object-curly-spacing` shows the incorrect column for opening brace (fixes #3438) (Burak Yigit Kaya) + +v1.2.0 - August 18, 2015 + +* Update: add support for semicolon in comma-first setup in indent rule (fixes #3423) (Burak Yigit Kaya) +* Docs: better JSDoc for indent rule (Burak Yigit Kaya) +* Docs: Document the second argument of `CLIEngine.executeOnText()` (Sindre Sorhus) +* New: `no-dupe-class-members` rule (fixes #3294) (Toru Nagashima) +* Fix: exclude `AssignmentExpression` and `Property` nodes from extra indentation on first line (fixes #3391) (Burak Yigit Kaya) +* Update: Separate indent options for var, let and const (fixes #3339) (Burak Yigit Kaya) +* Fix: Add AssignmentPattern to space-infix-ops (fixes #3380) (Burak Yigit Kaya) +* Docs: Fix typo: exception label (tienslebien) +* Update: Clean up tests for CLI config support (refs #2543) (Gyandeep Singh) +* New: `block-spacing` rule (fixes #3303) (Toru Nagashima) +* Docs: Update docs for no-iterator (fixes #3405) (Nicholas C. Zakas) +* Upgrade: bump `espree` dependency to `2.2.4` (fixes #3403) (Burak Yigit Kaya) +* Fix: false positive on switch 'no duplicate case', (fixes #3408) (Cristian Carlesso) +* Fix: `valid-jsdoc` test does not recognize aliases for `@param` (fixes #3399) (Burak Yigit Kaya) +* New: enable `-c` flag to accept a shareable config (fixes #2543) (Shinnosuke Watanabe) +* Fix: Apply plugin given in CLI (fixes #3383) (Ian VanSchooten) +* New: Add commonjs environment (fixes #3377) (Nicholas C. Zakas) +* Docs: Update no-unused-var docs (Nicholas C. Zakas) +* Fix: trailing commas in object-curly-spacing for import/export (fixes #3324) (Henry Zhu) +* Update: Make `baseConfig` to behave as other config options (fixes #3371) (Gyandeep Singh) +* Docs: Add "Compatibility" section to linebreak-style (Vitor Balocco) +* New: `prefer-arrow-callback` rule (fixes #3140) (Toru Nagashima) +* Docs: Clarify what an unused var is (fixes #2342) (Nicholas C. Zakas) +* Docs: Mention double-byte character limitation in max-len (fixes #2370) (Nicholas C. Zakas) +* Fix: object curly spacing incorrectly warning for import with default and multiple named specifiers (fixes #3370) (Luke Karrys) +* Fix: Indent rule errors with array of objects (fixes #3329) (Burak Yigit Kaya) +* Update: Make it clear that `space-infix-ops` support `const` (fixes #3299) (Burak Yigit Kaya) +* New: `prefer-template` rule (fixes #3014) (Toru Nagashima) +* Docs: Clarify `no-process-env` docs (fixes #3318) (Nicholas C. Zakas) +* Docs: Fix arrow name typo (fixes #3309) (Nicholas C. Zakas) +* Update: Improve error message for `indent` rule violation (fixes #3340) (Burak Yigit Kaya) +* Fix: radix rule does not apply for Number.parseInt (ES6) (fixes #3364) (Burak Yigit Kaya) +* Fix: `key-spacing.align` doesn't pay attention to non-whitespace before key (fixes #3267) (Burak Yigit Kaya) +* Fix: arrow-parens & destructuring/default params (fixes #3353) (Jamund Ferguson) +* Update: Add support for Allman to brace-style rule, brackets on newline (fixes #3347) (Burak Yigit Kaya) +* Fix: Regression no-catch-shadow (1.1.0) (fixes #3322) (Burak Yigit Kaya) +* Docs: remove note outdated in 1.0.0 (Denis Sokolov) +* Build: automatically convert line endings in release script (fixes #2642) (Burak Yigit Kaya) +* Update: allow disabling new-cap on object methods (fixes #3172) (Burak Yigit Kaya) +* Update: Improve checkstyle format (fixes #3183) (Burak Yigit Kaya) +* Fix: Indent rule errors if an array literal starts a new statement (fixes #3328) (Burak Yigit Kaya) +* Update: Improve validation error messages (fixes #3193) (Burak Yigit Kaya) +* Docs: fix syntax error in space-before-function-paren (Fabrício Matté) +* Fix: `indent` rule to check for last line correctly (fixes #3327) (Gyandeep Singh) +* Fix: Inconsistent off-by-one errors with column numbers (fixes #3231) (Burak Yigit Kaya) +* Fix: Keyword "else" must not be followed by a newline (fixes #3226) (Burak Yigit Kaya) +* Fix: `id-length` does not work for most of the new ES6 patterns (fixes #3286) (Burak Yigit Kaya) +* Fix: Spaced Comment Exceptions Not Working (fixes #3276) (Jamund Ferguson) + +v1.1.0 - August 7, 2015 + +* Update: Added as-needed option to arrow-parens (fixes #3277) (Jamund Ferguson) +* Fix: curly-spacing missing import case (fixes #3302) (Jamund Ferguson) +* Fix: `eslint-env` in comments had not been setting `ecmaFeatures` (fixes #2134) (Toru Nagashima) +* Fix: `es6` env had been missing `spread` and `newTarget` (fixes #3281) (Toru Nagashima) +* Fix: Report no-spaced-func on last token before paren (fixes #3289) (Benjamin Woodruff) +* Fix: Check for null elements in indent rule (fixes #3272) (Gyandeep Singh) +* Docs: Use backticks for option heading (Gyandeep Singh) +* Fix: `no-invalid-this` had been missing jsdoc comment (fixes #3287) (Toru Nagashima) +* Fix: `indent` rule for multi-line objects and arrays (fixes #3236) (Gyandeep Singh) +* Update: add new `multi-or-nest` option for the `curly` rule (fixes #1806) (Ivan Nikulin) +* Fix: `no-cond-assign` had been missing simplest pattern (fixes #3249) (Toru Nagashima) +* Fix: id-length rule doesn't catch violations in arrow function parameters (fixes #3275) (Burak Yigit Kaya) +* New: Added grep-style formatter (fixes #2991) (Nobody Really) +* Update: Split out generic AST methods into utility (fixes #962) (Gyandeep Singh) +* Fix: `accessor-pairs` false positive (fixes #3262) (Toru Nagashima) +* Fix: `context.getScope()` returns correct scope in blockBindings (fixes #3254) (Toru Nagashima) +* Update: Expose `getErrorResults` as a static method on `CLIEngine` (fixes #3242) (Gyandeep Singh) +* Update: Expose `getFormatter` as a static method on `CLIEngine` (fixes #3239) (Gyandeep Singh) +* Docs: use correct encoding for id-match.md (fixes #3246) (Matthieu Larcher) +* Docs: place id-match rule at correct place in README.md (fixes #3245) (Matthieu Larcher) +* Docs: Update no-proto.md (Joe Zimmerman) +* Docs: Fix typo in object-shorthand docs (Gunnar Lium) +* Upgrade: inquirer dependency (fixes #3241) (Gyandeep Singh) +* Fix: `indent` rule for objects and nested one line blocks (fixes #3238, fixes #3237) (Gyandeep Singh) +* Docs: Fix wrong options in examples of key-spacing (keik) +* Docs: Adds missing "not" to semi.md (Marius Schulz) +* Docs: Update no-multi-spaces.md (Kenneth Powers) +* Fix: `indent` to not error on same line nodes (fixes #3228) (Gyandeep Singh) +* New: Jest environment (fixes #3212) (Darshak Parikh) + +v1.0.0 - July 31, 2015 + +* Update: merge `no-reserved-keys` into `quote-props` (fixes #1539) (Jose Roberto Vidal) +* Fix: `indent` error message (fixes #3220) (Gyandeep Singh) +* Update: Add embertest env (fixes #3205) (ismay) +* Docs: Correct documentation errors for `id-length` rule. (Jess Telford) +* Breaking: `indent` rule to have node specific options (fixes #3210) (Gyandeep Singh) +* Fix: space-after-keyword shouldn't allow newlines (fixes #3198) (Brandon Mills) +* New: Add JSON formatter (fixes #3036) (Burak Yigit Kaya) +* Breaking: Switch to RuleTester (fixes #3186) (Nicholas C. Zakas) +* Breaking: remove duplicate warnings of `no-undef` from `block-scoped-var` (fixes #3201) (Toru Nagashima) +* Fix: `init-declarations` ignores in for-in/of (fixes #3202) (Toru Nagashima) +* Fix: `quotes` with `"backtick"` ignores ModuleSpecifier and LiteralPropertyName (fixes #3181) (Toru Nagashima) +* Fix: space-in-parens in Template Strings (fixes #3182) (Ian VanSchooten) +* Fix: Check for concatenation in no-throw-literal (fixes #3099, fixes #3101) (Ian VanSchooten) +* Build: Remove `eslint-tester` from devDependencies (fixes #3189) (Gyandeep Singh) +* Fix: Use new ESLintTester (fixes #3187) (Nicholas C. Zakas) +* Update: `new-cap` supports fullnames (fixes #2584) (Toru Nagashima) +* Fix: Non object rule options merge (fixes #3179) (Gyandeep Singh) +* New: add id-match rule (fixes #2829) (Matthieu Larcher) +* Fix: Rule options merge (fixes #3175) (Gyandeep Singh) +* Fix: `spaced-comment` allows a mix of markers and exceptions (fixes #2895) (Toru Nagashima) +* Fix: `block-scoped-var` issues (fixes #2253, fixes #2747, fixes #2967) (Toru Nagashima) +* New: Add id-length rule (fixes #2784) (Burak Yigit Kaya) +* Update: New parameters for quote-props rule (fixes #1283, fixes #1658) (Tomasz Olędzki) + +v1.0.0-rc-3 - July 24, 2015 + +* Fix: Make Chai and Mocha as a dependency (fixes #3156) (Gyandeep Singh) +* Fix: traverse `ExperimentalSpread/RestProperty.argument` (fixes #3157) (Toru Nagashima) +* Fix: Check shareable config package prefix correctly (fixes #3146) (Gyandeep Singh) +* Update: move redeclaration checking for builtins (fixes #3070) (Toru Nagashima) +* Fix: `quotes` with `"backtick"` allows directive prologues (fixes #3132) (Toru Nagashima) +* Fix: `ESLintTester` path in exposed API (fixes #3149) (Gyandeep Singh) +* Docs: Remove AppVeyor badge (Gyandeep Singh) +* Fix: Check no-new-func on CallExpressions (fixes #3145) (Benjamin Woodruff) + +v1.0.0-rc-2 - July 23, 2015 + +* Docs: Mention eslint-tester in migration guide (Nicholas C. Zakas) +* Docs: Mention variables defined in a global comment (fixes #3137) (William Becker) +* Docs: add documentation about custom-formatters. (fixes #1260) (royriojas) +* Fix: Multi-line variable declarations indent (fixes #3139) (Gyandeep Singh) +* Fix: handles blocks in no-use-before-define (fixes #2960) (Jose Roberto Vidal) +* Update: `props` option of `no-param-reassign` (fixes #1600) (Toru Nagashima) +* New: Support shared configs named `@scope/eslint-config`, with shortcuts of `@scope` and `@scope/` (fixes #3123) (Jordan Harband) +* New: Add ignorePattern, ignoreComments, and ignoreUrls options to max-len (fixes #2934, fixes #2221, fixes #1661) (Benjamin Woodruff) +* Build: Increase Windows Mocha timeout (fixes #3133) (Ian VanSchooten) +* Docs: incorrect syntax in the example for rule «one-var» (Alexander Burtsev) +* Build: Check commit message format at end of tests (fixes #3058) (Ian VanSchooten) +* Update: Move eslint-tester into repo (fixes #3110) (Nicholas C. Zakas) +* Fix: Not load configs outside config with `root: true` (fixes #3109) (Gyandeep Singh) +* Docs: Add config information to README (fixes #3074) (Nicholas C. Zakas) +* Docs: Add mysticatea as committer (Nicholas C. Zakas) +* Docs: Grammar fixes in rule descriptions (refs #3038) (Greg Cochard) +* Fix: Update sort-vars to ignore Array and ObjectPattern (fixes #2954) (Harry Ho) +* Fix: block-scoped-var rule incorrectly flagging break/continue with label (fixes #3082) (Aparajita Fishman) +* Fix: spaces trigger wrong in `no-useless-call` and `prefer-spread` (fixes #3054) (Toru Nagashima) +* Fix: `arrow-spacing` allow multi-spaces and line-endings (fixes #3079) (Toru Nagashima) +* Fix: add missing loop scopes to one-var (fixes #3073) (Jose Roberto Vidal) +* New: the `no-invalid-this` rule (fixes #2815) (Toru Nagashima) +* Fix: allow empty loop body in no-extra-semi (fixes #3075) (Mathias Schreck) +* Update: Add qunit to environments (fixes #2870) (Nicholas C. Zakas) +* Fix: `space-before-blocks` to consider classes (fixes #3062) (Gyandeep Singh) +* Fix: Include phantomjs globals (fixes #3064) (Linus Unnebäck) +* Fix: no-else-return handles multiple else-if blocks (fixes #3015) (Jose Roberto Vidal) +* Fix: `no-*-assgin` rules support destructuring (fixes #3029) (Toru Nagashima) +* New: the `no-implicit-coercion` rule (fixes #1621) (Toru Nagashima) +* Fix: Make no-implied-eval match more types of strings (fixes #2898) (Benjamin Woodruff) +* Docs: Clarify that bot message is automatic (Ian VanSchooten) +* Fix: Skip rest properties in no-dupe-keys (fixes 3042) (Nicholas C. Zakas) +* Docs: New issue template (fixes #3048) (Nicholas C. Zakas) +* Fix: strict rule supports classes (fixes #2977) (Toru Nagashima) +* New: the `prefer-reflect` rule (fixes #2939) (Keith Cirkel) +* Docs: make grammar consistent in rules index (Greg Cochard) +* Docs: Fix unmatched paren in rule description (Greg Cochard) +* Docs: Small typo fix in no-useless-call documentation (Paul O’Shannessy) +* Build: readd phantomjs dependency with locked down version (fixes #3026) (Mathias Schreck) +* Docs: Add IanVS as committer (Nicholas C. Zakas) +* docs: additional computed-property-spacing documentation (fixes #2941) (Jamund Ferguson) +* Docs: Add let and const examples for newline-after-var (fixes #3020) (James Whitney) +* Build: Remove unnecessary phantomjs devDependency (fixes #3021) (Gyandeep Singh) +* Update: added shared builtins list (fixes #2972) (Jose Roberto Vidal) + +v1.0.0-rc-1 - July 15, 2015 + +* Upgrade: Espree to 2.2.0 (fixes #3011) (Nicholas C. Zakas) +* Docs: fix a typo (bartmichu) +* Fix: indent rule should recognize single line statements with ASI (fixes #3001, fixes #3000) (Mathias Schreck) +* Update: Handle CRLF line endings in spaced-comment rule - 2 (fixes #3005) (Burak Yigit Kaya) +* Fix: Indent rule error on empty block body (fixes #2999) (Gyandeep Singh) +* New: the `no-class-assign` rule (fixes #2718) (Toru Nagashima) +* New: the `no-const-assign` rule (fixes #2719) (Toru Nagashima) +* Docs: Add 1.0.0 migration guide (fixes #2994) (Nicholas C. Zakas) +* Docs: Update changelog for 0.24.1 (fixes #2976) (Nicholas C. Zakas) +* Breaking: Remove deprecated rules (fixes #1898) (Ian VanSchooten) +* Fix: multi-line + fat arrow indent (fixes #2239) (Gyandeep Singh) +* Breaking: Create eslint:recommended and add to --init (fixes #2713) (Greg Cochard) +* Fix: Indent rule (fixes #1797, fixes #1799, fixes #2248, fixes #2343, fixes #2278, fixes #1800) (Gyandeep Singh) +* New: `context.getDeclaredVariables(node)` (fixes #2801) (Toru Nagashima) +* New: the `no-useless-call` rule (fixes #1925) (Toru Nagashima) +* New: the `prefer-spread` rule (fixes #2946) (Toru Nagashima) +* Fix: `valid-jsdoc` counts `return` for arrow expressions (fixes #2952) (Toru Nagashima) +* New: Add exported comment option (fixes #1200) (Jamund Ferguson) +* Breaking: Default to --reset behavior (fixes #2100) (Brandon Mills) +* New: Add arrow-parens and arrow-spacing rule (fixes #2628) (Jxck) +* Fix: Shallow cloning issues in eslint config (fixes #2961) (Gyandeep Singh) +* Add: Warn on missing rule definition or deprecation (fixes #1549) (Ian VanSchooten) +* Update: adding some tests for no-redeclare to test named functions (fixes #2953) (Dominic Barnes) +* New: Add support for root: true in config files (fixes #2736) (Ian VanSchooten) +* Fix: workaround for leading and trailing comments in padded-block (fixes #2336 and fixes #2788) (Mathias Schreck) +* Fix: object-shorthand computed props (fixes #2937) (Jamund Ferguson) +* Fix: Remove invalid check inside `getJSDocComment` function (fixes #2938) (Gyandeep Singh) +* Docs: Clarify when not to use space-before-blocks (Ian VanSchooten) +* Update: `no-loop-func` allows block-scoped variables (fixes #2517) (Toru Nagashima) +* Docs: remove mistaken "off by default" (Jan Schär) +* Build: Add appveyor CI system (fixes #2923) (Gyandeep Singh) +* Docs: Fix typo in the shareable configs doc (Siddharth Kannan) +* Fix: max-len to report correct column number (fixes #2926) (Mathias Schreck) +* Fix: add destructuring support to comma-dangle rule (fixes #2911) (Mathias Schreck) +* Docs: clarification in no-unused-vars (Jan Schär) +* Fix: `no-redeclare` checks module scopes (fixes #2903) (Toru Nagashima) +* Docs: missing quotes in JSON (Jan Schär) +* Breaking: Switch to 1-based columns (fixes #2284) (Nicholas C. Zakas) +* Docs: array-bracket-spacing examples used space-in-brackets (Brandon Mills) +* Docs: Add spaced-line-comment deprecation notice (Brandon Mills) +* Docs: Add space-in-brackets deprecation notice (Brandon Mills) +* Fix: Include execScript in no-implied-eval rule (fixes #2873) (Frederik Braun) +* Fix: Support class syntax for line-around-comment rule (fixes #2894) (Gyandeep Singh) +* Fix: lines-around-comment was crashing in some cases due to a missing check (fixes #2892) (Mathieu M-Gosselin) +* New: Add init-declarations rule (fixes #2606) (cjihrig) +* Docs: Fix typo in array-bracket-spacing rule (zallek) +* Fix: Added missing export syntax support to the block-scoped-var rule. (fixes #2887) (Mathieu M-Gosselin) +* Build: gensite target supports rule removal (refs #1898) (Brandon Mills) +* Update: Handle CRLF line endings in spaced-comment rule (fixes #2884) (David Anson) +* Update: Attach parent in getNodeByRangeIndex (fixes #2863) (Brandon Mills) +* Docs: Fix typo (Bryan Smith) +* New: Add serviceworker environment (fixes #2557) (Gyandeep Singh) +* Fix: Yoda should ignore comparisons where both sides are constants (fixes #2867) (cjihrig) +* Update: Loosens regex rules around intentional fall through comments (Fixes #2811) (greg5green) +* Update: Add missing schema to rules (fixes #2858) (Ilya Volodin) +* New: `require-yield` rule (fixes #2822) (Toru Nagashima) +* New: add callback-return rule (fixes #994) (Jamund Ferguson) + +v0.24.1 - July 10, 2015 + +* Docs: Clarify when not to use space-before-blocks (Ian VanSchooten) +* Docs: remove mistaken "off by default" (Jan Schär) +* Docs: remove mistaken "off by default" (Jan Schär) +* Docs: Fix typo in the shareable configs doc (Siddharth Kannan) +* Docs: clarification in no-unused-vars (Jan Schär) +* Docs: missing quotes in JSON (Jan Schär) +* Fix: Revert 1-based column changes in tests for patch (refs #2284) (Nicholas C. Zakas) +* Fix: Shallow cloning issues in eslint config (fixes #2961) (Gyandeep Singh) +* Fix: object-shorthand computed props (fixes #2937) (Jamund Ferguson) +* Fix: Remove invalid check inside `getJSDocComment` function (fixes #2938) (Gyandeep Singh) +* Fix: max-len to report correct column number (fixes #2926) (Mathias Schreck) +* Fix: add destructuring support to comma-dangle rule (fixes #2911) (Mathias Schreck) +* Fix: `no-redeclare` checks module scopes (fixes #2903) (Toru Nagashima) +* Fix: Include execScript in no-implied-eval rule (fixes #2873) (Frederik Braun) +* Fix: Support class syntax for line-around-comment rule (fixes #2894) (Gyandeep Singh) +* Fix: lines-around-comment was crashing in some cases due to a missing check (fixes #2892) (Mathieu M-Gosselin) +* Fix: Added missing export syntax support to the block-scoped-var rule. (fixes #2887) (Mathieu M-Gosselin) +* Fix: Yoda should ignore comparisons where both sides are constants (fixes #2867) (cjihrig) +* Docs: array-bracket-spacing examples used space-in-brackets (Brandon Mills) +* Docs: Add spaced-line-comment deprecation notice (Brandon Mills) +* Docs: Add space-in-brackets deprecation notice (Brandon Mills) + +v0.24.0 - June 26, 2015 + +* Upgrade: eslint-tester to 0.8.1 (Nicholas C. Zakas) +* Fix: no-dupe-args sparse array crash (fixes #2848) (Chris Walker) +* Fix: space-after-keywords should ignore extra parens (fixes #2847) (Mathias Schreck) +* New: add no-unexpected-multiline rule (fixes #746) (Glen Mailer) +* Update: refactor handle-callback-err to improve performance (fixes #2841) (Mathias Schreck) +* Fix: Add --init to the CLI options (fixes #2817) (Gyandeep Singh) +* Update: Add `except-parens` option to `no-return-assign` rule (fixes #2809) (Toru Nagashima) +* Fix: handle-callback-err missing arrow functions (fixes #2823) (Jamund Ferguson) +* Fix: `no-extra-semi` in class bodies (fixes #2794) (Toru Nagashima) +* Fix: Check type to be file when looking for config files (fixes #2790) (Gyandeep Singh) +* Fix: valid-jsdoc to work for object getters (fixes #2407) (Gyandeep Singh) +* Update: Add an option as an object to `generator-star-spacing` rule (fixes #2787) (Toru Nagashima) +* Build: Update markdownlint dependency (David Anson) +* Fix: context report message to handle more scenarios (fixes #2746) (Gyandeep Singh) +* Update: Ignore JsDoc comments by default for `spaced-comment` (fixes #2766) (Gyandeep Singh) +* Fix: one-var 'never' option for mixed initialization (Fixes #2786) (Ian VanSchooten) +* Docs: Fix a minor typo in a prefer-const example (jviide) +* Fix: comma-dangle always-multiline: no comma right before the last brace (fixes #2091) (Benoît Zugmeyer) +* Fix: Allow blocked comments with markers and new-line (fixes #2777) (Gyandeep Singh) +* Docs: small fix in quote-props examples (Jose Roberto Vidal) +* Fix: object-shorthand rule should not warn for NFEs (fixes #2748) (Michael Ficarra) +* Fix: arraysInObjects for object-curly-spacing (fixes #2752) (Jamund Ferguson) +* Docs: Clarify --rule description (fixes #2773) (Nicholas C. Zakas) +* Fix: object literals in arrow function bodies (fixes #2702) (Jose Roberto Vidal) +* New: `constructor-super` rule (fixes #2720) (Toru Nagashima) +* New: `no-this-before-super` rule (fixes #2721) (Toru Nagashima) +* Fix: space-unary-ops flags expressions starting w/ keyword (fixes #2764) (Michael Ficarra) +* Update: Add block options to `lines-around-comment` rule (fixes #2667) (Gyandeep Singh) +* New: array-bracket-spacing (fixes #2226) (Jamund Ferguson) +* Fix: No-shadow rule duplicating error messages (fixes #2706) (Aliaksei Shytkin) + +v0.23.0 - June 14, 2015 + +* Build: Comment out auto publishing of release notes (refs #2640) (Ilya Volodin) +* Fix: "extends" within package.json (fixes #2754) (Gyandeep Singh) +* Upgrade: globals@8.0.0 (fixes #2759) (silverwind) +* Docs: eol-last docs fix (fixes #2755) (Gyandeep Singh) +* Docs: btmills is a reviewer (Nicholas C. Zakas) +* Build: Revert lock io.js to v2.1.0 (refs #2745) (Brandon Mills) +* New: computed-property-spacing (refs #2226) (Jamund Ferguson) +* Build: Pin Sinon version (fixes #2742) (Ilya Volodin) +* Fix: `prefer-const` treats `for-in`/`for-of` with the same way (Fixes #2739) (Toru Nagashima) +* Docs: Add links to team members profile (Gyandeep Singh) +* Docs: add team and ES7 info to readme (Nicholas C. Zakas) +* Fix: don't try to strip "line:" prefix from parser errors with no such prefix (fixes #2698) (Tim Cuthbertson) +* Fix: never ignore config comment options (fixes #2725) (Brandon Mills) +* Update: Add clarification to spaced-comment (refs #2588) (Greg Cochard) +* Update: Add markers to spaced-comment (fixes #2588) (Greg Cochard) +* Fix: no-trailing-spaces now handles skipBlankLines (fixes #2575) (Greg Cochard) +* Docs: Mark global-strict on by default (fixes #2629) (Ilya Volodin) +* New: Allow extends to be an array (fixes #2699) (Justin Morris) +* New: globals@7.1.0 (fixes #2682) (silverwind) +* New: `prefer-const` rule (fixes #2333) (Toru Nagashima) +* Fix: remove hard-coded list of unary keywords in space-unary-ops rule (fixes #2696) (Tim Cuthbertson) +* Breaking: Automatically validate rule options (fixes #2595) (Brandon Mills) +* Update: no-lone-blocks does not report block-level scopes (fixes #2119) (Jose Roberto Vidal) +* Update: yoda onlyEquality option (fixes #2638) (Denis Sokolov) +* Docs: update comment to align with source code it's referencing (Michael Ficarra) +* Fix: Misconfigured default option for lines-around-comment rule (fixes #2677) (Gyandeep Singh) +* Fix: `no-shadow` allows shadowing in the TDZ (fixes #2568) (Toru Nagashima) +* New: spaced-comment rule (fixes #1088) (Gyandeep Singh) +* Fix: Check unused vars in exported functions (fixes #2678) (Gyandeep Singh) +* Build: Stringify payload of release notes (fixes #2640) (Greg Cochard) +* Fix: Allowing u flag in regex to properly lint no-empty-character-class (fixes #2679) (Dominic Barnes) +* Docs: deprecate no-wrap-func (fixes #2644) (Jose Roberto Vidal) +* Docs: Fixing grammar: then -> than (E) +* Fix: trailing commas in object-curly-spacing (fixes #2647) (Jamund Ferguson) +* Docs: be consistent about deprecation status (Matthew Dapena-Tretter) +* Docs: Fix mistakes in object-curly-spacing docs (Matthew Dapena-Tretter) +* New: run processors when calling executeOnText (fixes #2331) (Mordy Tikotzky) +* Update: move executeOnText() tests to the correct describe block (fixes #2648) (Mordy Tikotzky) +* Update: add tests to assert that the preprocessor is running (fixes #2651) (Mordy Tikotzky) +* Build: Lock io.js to v2.1.0 (fixes #2653) (Ilya Volodin) + +v0.22.1 - May 30, 2015 + +* Build: Remove release notes auto-publish (refs #2640) (Ilya Volodin) + +v0.22.0 - May 30, 2015 + +* Upgrade: escope 3.1.0 (fixes #2310, #2405) (Toru Nagashima) +* Fix: “consistent-this” incorrectly flagging destructuring of `this` (fixes #2633) (David Aurelio) +* Upgrade: eslint-tester to 0.7.0 (Ilya Volodin) +* Update: allow shadowed references in no-alert (fixes #1105) (Mathias Schreck) +* Fix: no-multiple-empty-lines and template strings (fixes #2605) (Jamund Ferguson) +* New: object-curly-spacing (fixes #2225) (Jamund Ferguson) +* Docs: minor fix for one-var rule (Jamund Ferguson) +* Fix: Shared config being clobbered by other config (fixes #2592) (Dominic Barnes) +* Update: adds "functions" option to no-extra-parens (fixes #2477) (Jose Roberto Vidal) +* Docs: Fix json formatting for lines-around-comments rule (Gyandeep Singh) +* Fix: Improve around function/class names of `no-shadow` (fixes #2556, #2552) (Toru Nagashima) +* Fix: Improve code coverage (fixes #2590) (Ilya Volodin) +* Fix: Allow scoped configs to have sub-configs (fixes #2594) (Greg Cochard) +* Build: Add auto-update of release tag on github (fixes #2566) (Greg Cochard) +* New: lines-around-comment (fixes #1344) (Jamund Ferguson) +* Build: Unblock build by increasing code coverage (Ilya Volodin) +* New: accessor-pairs rule to object initializations (fixes #1638) (Gyandeep Singh) +* Fix: counting of variables statements in one-var (fixes #2570) (Mathias Schreck) +* Build: Add sudo:false for Travis (fixes #2582) (Ilya Volodin) +* New: Add rule schemas (refs #2179) (Brandon Mills) +* Docs: Fix typo in shareable-configs example (fixes #2571) (Ted Piotrowski) +* Build: Relax markdownlint rules by disabling style-only items (David Anson) +* Fix: Object shorthand rule incorrectly flagging getters/setters (fixes #2563) (Brad Dougherty) +* New: Add config validator (refs #2179) (Brandon Mills) +* New: Add worker environment (fixes #2442) (Ilya Volodin) +* New no-empty-character class (fixes #2508) (Jamund Ferguson) +* New: Adds --ignore-pattern option. (fixes #1742) (Patrick McElhaney) + +v0.21.2 - May 18, 2015 + +* 0.21.2 (Nicholas C. Zakas) +* Fix: one-var exception for ForStatement.init (fixes #2505) (Brandon Mills) +* Fix: Don't throw spurious shadow errors for classes (fixes #2545) (Jimmy Jia) +* Fix: valid-jsdoc rule to support exported functions (fixes #2522) (Gyandeep Singh) +* Fix: Allow scoped packages in configuration extends (fixes #2544) (Eric Isakson) +* Docs: Add chatroom to FAQ (Nicholas C. Zakas) +* Docs: Move Gitter badge (Nicholas C. Zakas) + +v0.21.1 - May 15, 2015 + +* 0.21.1 (Nicholas C. Zakas) +* Fix: loc obj in report fn expects column (fixes #2481) (Varun Verma) +* Build: Make sure that all md files end with empty line (fixes #2520) (Ilya Volodin) +* Added Gitter badge (The Gitter Badger) +* Fix: forced no-shadow to check all scopes (fixes #2294) (Jose Roberto Vidal) +* Fix: --init indent setting (fixes #2493) (Nicholas C. Zakas) +* Docs: Mention bundling multiple shareable configs (Nicholas C. Zakas) +* Fix: Not to override the required extended config object directly (fixes #2487) (Gyandeep Singh) +* Build: Update markdownlint dependency (David Anson) +* Docs: added recursive function example to no-unused-vars (Jose Roberto Vidal) +* Docs: Fix typo (then -> than) (Vladimir Agafonkin) +* Revert "Fix: sanitise Jekyll interpolation during site generation (fixes #2297)" (Nicholas C. Zakas) +* Fix: dot-location should use correct dot token (fixes #2504) (Mathias Schreck) +* Fix: Stop linebreak-style from crashing (fixes #2490) (James Whitney) +* Fix: rule no-duplicate-case problem with CallExpressions. (fixes #2499) (Matthias Osswald) +* Fix: Enable full support for eslint-env comments (refs #2134) (Ilya Volodin) +* Build: Speed up site generation (fixes #2475) (Ilya Volodin) +* Docs: Fixing trailing spaces (Fixes #2478) (Ilya Volodin) +* Docs: Update README FAQs (Nicholas C. Zakas) +* Fix: Allow comment before comma for comma-spacing rule (fixes #2408) (Gyandeep Singh) + +v0.21.0 - May 9, 2015 + +* 0.21.0 (Nicholas C. Zakas) +* New: Shareable configs (fixes #2415) (Nicholas C. Zakas) +* Fix: Edge cases for no-wrap-func (fixes #2466) (Nicholas C. Zakas) +* Docs: Update ecmaFeatures description (Nicholas C. Zakas) +* New: Add dot-location rule. (fixes #1884) (Greg Cochard) +* New: Add addPlugin method to CLI-engine (Fixes #1971) (Ilya Volodin) +* Breaking: Do not check unset declaration types (Fixes #2448) (Ilya Volodin) +* Fix: no-redeclare switch scoping (fixes #2337) (Nicholas C. Zakas) +* Fix: Check extra scope in no-use-before-define (fixes #2372) (Nicholas C. Zakas) +* Fix: Ensure baseConfig isn't changed (fixes #2380) (Nicholas C. Zakas) +* Fix: Don't warn for member expression functions (fixes #2402) (Nicholas C. Zakas) +* New: Adds skipBlankLines option to the no-trailing-spaces rule (fixes #2303) (Andrew Vaughan) +* Fix: Adding exception for last line (Refs #2423) (Greg Cochard) +* Fix: crash on 0 max (fixes #2423) (gcochard) +* Fix object-shorthand arrow functions (fixes #2414) (Jamund Ferguson) +* Fix: Improves detection of self-referential functions (fixes #2363) (Jose Roberto Vidal) +* Update: key-spacing groups must be consecutive lines (fixes #1728) (Brandon Mills) +* Docs: grammar fix in no-sync (Tony Lukasavage) +* Docs: Update configuring.md to fix incorrect link. (Ans) +* New: Check --stdin-filename by ignore settings (fixes #2432) (Aliaksei Shytkin) +* Fix: `no-loop-func` rule allows functions at init part (fixes #2427) (Toru Nagashima) +* New: Add init command (fixes #2302) (Ilya Volodin) +* Fix: no-irregular-whitespace should work with irregular line breaks (fixes #2316) (Mathias Schreck) +* Fix: generator-star-spacing with class methods (fixes #2351) (Brandon Mills) +* New: no-unneeded-ternary rule to disallow boolean literals in conditional expressions (fixes #2391) (Gyandeep Singh) +* Docs: Add `restParams` to `ecmaFeatures` options list (refs: #2346) (Bogdan Savluk) +* Fix: space-in-brackets Cannot read property 'range' (fixes #2392) (Gyandeep Singh) +* Docs: Sort the rules (Lukas Böcker) +* Add: Exception option for `no-extend-native` and `no-native-reassign` (fixes #2355) (Gyandeep Singh) +* Fix: space-in-brackets import declaration (fixes #2378) (Gyandeep Singh) +* Update: Add uninitialized and initialized options (fixes #2206) (Ian VanSchooten) +* Fix: brace-style to not warn about curly mix ifStatements (fixes #1739) (Gyandeep Singh) +* Fix: npm run profile script should use espree (fixes #2150) (Mathias Schreck) +* New: Add support for extending configurations (fixes #1637) (Espen Hovlandsdal) +* Fix: Include string literal keys in object-shorthand (Fixes #2374) (Jamund Ferguson) +* Docs: Specify language for all code fences, enable corresponding markdownlint rule. (David Anson) +* New: linebreak-style rule (fixes #1255) (Erik Müller) +* Update: Add "none" option to operator-linebreak rule (fixes #2295) (Casey Visco) +* Fix: sanitise Jekyll interpolation during site generation (fixes #2297) (Michael Ficarra) + +v0.20.0 - April 24, 2015 + +* 0.20.0 (Nicholas C. Zakas) +* Fix: support arrow functions in no-extra-parens (fixes #2367) (Michael Ficarra) +* Fix: Column position in space-infix-ops rule (fixes #2354) (Gyandeep Singh) +* Fix: allow plugins to be namespaced (fixes #2360) (Seth Pollack) +* Update: one-var: enable let & const (fixes #2301) (Joey Baker) +* Docs: Add meteor to avaiable environments list (bartmichu) +* Update: Use `Object.assign()` polyfill for all object merging (fixes #2348) (Sindre Sorhus) +* Docs: Update markdownlint dependency, resolve/suppress new issues. (David Anson) +* Fix: newline-after-var declare and export (fixes #2325) (Gyandeep Singh) +* Docs: Some typos and grammar. (AlexKVal) +* Fix: newline-after-var to ignore declare in for specifiers (fixes #2317) (Gyandeep Singh) +* New: add --stdin-filename option (fixes #1950) (Mordy Tikotzky) +* Fix: Load .eslintrc in $HOME only if no other .eslintrc is found (fixes #2279) (Jasper Woudenberg) +* Fix: Add `v8` module to no-mixed-requires rule (fixes #2320) (Gyandeep Singh) +* Fix: key-spacing with single properties (fixes #2311) (Brandon Mills) +* Docs: `no-invalid-regexp`: add `ecmaFeatures` flags for `u`/`y` (Jordan Harband) +* New: object-shorthand rule (refs: #1617) (Jamund Ferguson) +* Update: backticks support for quotes rule (fixes #2153) (borislavjivkov) +* Fix: space-in-brackets to work with modules (fixes #2216) (Nicholas C. Zakas) + +v0.19.0 - April 11, 2015 + +* 0.19.0 (Nicholas C. Zakas) +* Upgrade: Espree to 2.0.1 (Nicholas C. Zakas) +* Docs: Update one-var documentation (fixes #2210) (Nicholas C. Zakas) +* Update: Add test for no-undef (fixes #2214) (Nicholas C. Zakas) +* Fix: Report better location for padded-blocks error (fixes #2224) (Nicholas C. Zakas) +* Fix: Don't check concise methods in quote-props (fixes #2251) (Nicholas C. Zakas) +* Fix: Consider tabs for space-in-parens rule (fixes #2191) (Josh Quintana) +* Fix: block-scoped-var to work with classes (fixes #2280) (Nicholas C. Zakas) +* Docs: Remove trailing spaces, enable corresponding markdownlint rule. (David Anson) +* Fix: padded-blocks with ASI (fixes #2273) (Brandon Mills) +* Fix: Handle comment lines in newline-after-var (fixed #2237) (Casey Visco) +* Docs: Standardize on '*' for unordered lists, enable corresponding markdownlint rule. (David Anson) +* Fix: no-undef and no-underscore-dangle to use double quotes (fixes #2258) (Gyandeep Singh) +* Docs: Improve grammar and style in comma-dangle.md (Nate Eagleson) +* Docs: Improve grammar and style in padded-blocks.md (Nate Eagleson) +* Docs: Update URL in no-wrap-func.md to resolve 404 (Nate Eagleson) +* Docs: Fix typo in command-line-interface.md (Nate Eagleson) +* Docs: Fix typo in working-with-rules.md (Nate Eagleson) +* Docs: Remove hard tabs from *.md, enable corresponding markdownlint rule. (David Anson) +* Fix: Function id missing in parent scope when using ecmaFeature `modules` for rule block-scoped-var (fixes #2242) (Michael Ferris) +* Fix: Ignore single lines for vertical alignment (fixes #2018) (Ian VanSchooten) +* Fix: Allow inline comments in newline-after-var rule (fixes #2229) (Casey Visco) +* Upgrade: Espree 2.0.0 and escope 3.0.0 (fixes #2234, fixes #2201, fixes (Nicholas C. Zakas) +* Docs: Update --no-ignore warning (Brandon Mills) +* Build: Remove jshint files (fixes #2222) (Jeff Tan) +* Docs: no-empty fix comment change (refs #2188) (Gyandeep Singh) +* Fix: duplicate semi and no-extra-semi errors (fixes #2207) (Brandon Mills) +* Docs: Update processors description (Nicholas C. Zakas) +* Fix: semi error on export declaration (fixes #2194) (Brandon Mills) +* New: operator-linebreak rule (fixes #1405) (Benoît Zugmeyer) +* Docs: Fixing broken links in documentation (Ilya Volodin) +* Upgrade: Espree to 0.12.3 (fixes #2195) (Gyandeep Singh) +* Fix: camelcase rule with {properties: never} shouldn't check assignment (fixes #2189) (Gyandeep Singh) +* New: Allow modifying base config (fixes #2143) (Meo) +* New: no-continue rule (fixes #1945) (borislavjivkov) +* Fix: `no-empty` rule should allow any comments (fixes #2188) (Gyandeep Singh) +* Docs: Fix spell in camelcase doc (fixes #2190) (Gyandeep Singh) +* Fix: Require semicolon after import/export statements (fixes #2174) (Gyandeep Singh) +* Build: Add linting of Markdown files to "npm test" script (fixes #2182) (David Anson) +* Build: Fixing site generation (Ilya Volodin) +* Build: Fix gensite task to work even if files are missing (Nicholas C. Zakas) + +v0.18.0 - March 28, 2015 + +* 0.18.0 (Nicholas C. Zakas) +* Fix: Mark variables as used in module scope (fixes #2137) (Nicholas C. Zakas) +* Fix: arrow functions need wrapping (fixes #2113) (Nicholas C. Zakas) +* Fix: Don't crash on empty array pattern item (fixes #2111) (Nicholas C. Zakas) +* Fix: Don't error on destructured params (fixes #2051) (Nicholas C. Zakas) +* Docs: Fixing broken links (Ilya Volodin) +* Fix: no-constant-condition should not flag += (fixes #2155) (Nicholas C. Zakas) +* Fix: Ensure piped in code will trigger correct errors (fixes #2154) (Nicholas C. Zakas) +* Fix: block-scoped-var to handle imports (fixes #2087) (Nicholas C. Zakas) +* Fix: no-dupe-args to work with destructuring (fixes #2148) (Nicholas C. Zakas) +* Fix: key-spacing crash on computed properties (fixes #2120) (Brandon Mills) +* Fix: indent crash on caseless switch (fixes #2144) (Brandon Mills) +* Fix: Don't warn about destructured catch params (fixes #2125) (Nicholas C. Zakas) +* Update: Omit setter param from no-unused-vars (fixes #2133) (Nicholas C. Zakas) +* Docs: Cleaning dead links (Ilya Volodin) +* Docs: Moving documentation out of the repository and modifying build scripts (Ilya Volodin) +* Docs: Update link to Documentation (Kate Lizogubova) +* Docs: Adding back deprecated space-unary-word-ops documentation (Ilya Volodin) +* Fix: Unused recursive functions should be flagged (issue2095) (Nicholas C. Zakas) +* Breaking: Remove JSX support from no-undef (fixes #2093) (Nicholas C. Zakas) +* Fix: markVariableAsUsed() should work in Node.js env (fixes #2089) (Nicholas C. Zakas) +* New: Add "always" and "never" options to "one-var" rule. (fixes #1619) (Danny Fritz) +* New: newline-after-var rule (fixes #2057) (Gopal Venkatesan) +* Fix: func-names with ES6 classes (fixes #2103) (Marsup) +* Fix: Add "Error" to the "new-cap" rule exceptions (fixes #2098) (Mickaël Tricot) +* Fix: vars-on-top conflict with ES6 import (fixes #2099) (Gyandeep Singh) +* Docs: Fixed JSON syntax (Sajin) +* New: space-before-function-paren rule (fixes #2028) (Brandon Mills) +* Breaking: rule no-empty also checking for empty catch blocks. (fixes #1841) (Dieter Oberkofler) +* Update: rule camelcase to allow snake_case in object literals. (fixes #1919) (Dieter Oberkofler) +* New: Added option int32Hint for space-infix-ops (fixes #1295) (Kirill Efimov) +* New: no-param-reassign rule (fixes #1599) (Nat Burns) + +v0.17.1 - March 17, 2015 + +* 0.17.1 (Nicholas C. Zakas) +* Fix: no-func-assign should not fail on import declarations (fixes #2060) (Igor Zalutsky) +* Fix: block-scoped-var to work with destructuring (fixes #2059) (Nicholas C. Zakas) +* Fix: no-redeclare should check Node.js scope (fixes #2064) (Nicholas C. Zakas) +* Fix: space-before-function-parentheses generator methods (fixes #2082) (Brandon Mills) +* Fix: Method name resolution in complexity rule (fixes #2049) (Nicholas C. Zakas) +* Fix: no-unused-vars crash from escope workaround (fixes #2042) (Brandon Mills) +* Fix: restrict dot-notation keywords to actual ES3 keywords (fixes #2075) (Michael Ficarra) +* Fix: block-scoped-var to work with classes (fixes #2048) (Nicholas C. Zakas) +* Docs: Update no-new documentation (fixes #2044) (Nicholas C. Zakas) +* Fix: yoda range exceptions with this (fixes #2063) (Brandon Mills) +* Docs: Fix documentation on configuring eslint with comments (Miguel Ping) +* Fix: rule no-duplicate-case problem with MemberExpressions. (fixes #2038) (Dieter Oberkofler) +* Fix: Exempt \0 from no-octal-escape (fixes #1923) (Michael Ficarra) + +v0.17.0 - March 14, 2015 + +* 0.17.0 (Nicholas C. Zakas) +* Fix: module import specifiers should be defined (refs #1978) (Nicholas C. Zakas) +* Fix: Ignore super in no-undef (refs #1968) (Nicholas C. Zakas) +* Upgrade: Espree to v0.12.0 (refs #1968) (Nicholas C. Zakas) +* Fix: destructured arguments should work in block-scoped-var (fixes #1996) (Nicholas C. Zakas) +* Fix: Line breaking with just carriage return (fixes #2005) (Nicholas C. Zakas) +* Fix: location of new-cap error messages (fixes #2025) (Mathias Schreck) +* Breaking: Stop checking JSX variable use, expose API instead (fixes #1911) (Glen Mailer) +* Fix: Check spacing of class methods (fixes #1989) (Nicholas C. Zakas) +* New: no-duplicate-case rule to disallow a duplicate case label (fixes #2015) (Dieter Oberkofler) +* Clarify issue requirement for doc pull requests (Ian) +* Add quotes around object key (Ian) +* Fix: Add comma-dangle allow-multiline (fixes #1984) (Keith Cirkel) +* Fix: Don't explode on default export function (fixes #1985) (Nicholas C. Zakas) +* Update: Add AST node exceptions to comma-style. (fixes #1932) (Evan Simmons) +* Docs: Add spread operator to available language options (Nicholas C. Zakas) +* New: generator-star-spacing rule (fixes #1680, fixes #1949) (Brandon Mills) + +v0.16.2 - March 10, 2015 + +* 0.16.2 (Nicholas C. Zakas) +* Fix: Ensure globalReturn isn't on when node:false (fixes #1995) (Nicholas C. Zakas) +* Downgrade: escope pegged to 2.0.6 (refs #2001) (Nicholas C. Zakas) +* Upgrade: escope to 2.0.7 (fixes #1978) (Nicholas C. Zakas) +* Docs: Update descriptive text for --no-ignore option. (David Anson) +* Upgrade: estraverse to latest for ESTree support (fixes #1986) (Nicholas C. Zakas) +* Fix: Global block-scope-var check should work (fixes #1980) (Nicholas C. Zakas) +* Fix: Don't warn about parens around yield (fixes #1981) (Nicholas C. Zakas) + +v0.16.1 - March 8, 2015 + +* 0.16.1 (Nicholas C. Zakas) +* Fix: Node.js scoping in block-scoped-var (fixes #1969) (Nicholas C. Zakas) +* Update: Enable ES6 scoping for more options (Nicholas C. Zakas) +* Fix: Ensure all export nodes are traversable (fixes #1965) (Nicholas C. Zakas) +* Fix: Ensure class names are marked as used (fixes #1967) (Nicholas C. Zakas) +* Fix: remove typo that caused a crash (fixes #1963) (Fabricio C Zuardi) +* Docs: Added missing "are" (Sean Wilkinson) + +v0.16.0 - March 7, 2015 + +* 0.16.0 (Nicholas C. Zakas) +* Fix: Pass correct sourceType to escope (fixes #1959) (Nicholas C. Zakas) +* Fix: Scoping for Node.js (fixes #892) (Nicholas C. Zakas) +* Fix: strict rule should honor module code (fixes #1956) (Nicholas C. Zakas) +* New: Add es6 environment (fixes #1864, fixes #1944) (Nicholas C. Zakas) +* Docs: Update ecmaFeatures list (fixes #1942) (Nicholas C. Zakas) +* Fix: Make no-unused-vars ignore exports (fixes #1903) (Nicholas C. Zakas) +* Upgrade: Espree to v1.11.0 (Nicholas C. Zakas) +* Fix: Comment configuration of rule doesn't work (fixes #1792) (Jary) +* Fix: Rest args should work in no-undef and block-scoped-var (fixes #1543) (Nicholas C. Zakas) +* Breaking: change no-comma-dangle to comma-dangle (fixes #1350) (Mathias Schreck) +* Update: space-before-function-parentheses to support generators (fixes #1929) (Brandon Mills) +* New: Adding support for "// eslint-disable-line rule" style comments (Billy Matthews) +* Fix: Use unversioned sinon file in browser test (fixes #1947) (Nicholas C. Zakas) +* Docs: Add mention of compatible parsers (Nicholas C. Zakas) +* Fix: Better error when given null as rule config (fixes #1760) (Glen Mailer) +* Update: no-empty to check TryStatement.handler (fixes #1930) (Brandon Mills) +* Fix: space-before-function-parentheses and object methods (fixes #1920) (Brandon Mills) +* New: no-dupe-args rule (fixes #1880) (Jamund Ferguson) +* Fix: comma-spacing should ignore JSX text (fixes #1916) (Brandon Mills) +* Breaking: made eol-last less strict (fixes #1460) (Glen Mailer) +* New: generator-star middle option (fixes #1808) (Jamund Ferguson) +* Upgrade: Espree to 1.10.0 for classes support (Nicholas C. Zakas) +* Docs: no-plusplus.md - auto semicolon insertion (Miroslav Obradović) +* Docs: Use union types in TokenStore JSDoc (refs #1878) (Brandon Mills) +* Fix: block-scoped-var to work with destructuring (fixes #1863) (Nicholas C. Zakas) +* Docs: Update docs for token-related methods (fixes #1878) (Nicholas C. Zakas) +* Update: Remove preferGlobal from package.json (fixes #1877) (Nicholas C. Zakas) +* Fix: allow block bindings in no-inner-declarations (fixes #1893) (Roberto Vidal) +* Fix: getScope and no-use-before-define for arrow functions (fixes #1895) (Brandon Mills) +* Fix: Make no-inner-declarations look for arrow functions (fixes #1892) (Brandon Mills) +* Breaking: Change no-space-before-semi to semi-spacing and add "after" option (fixes #1671) (Mathias Schreck) +* Update: Add support for custom preprocessors (fixes #1817) (Ilya Volodin) + +v0.15.1 - February 26, 2015 + +* 0.15.1 (Nicholas C. Zakas) +* Build: Fix release task (Nicholas C. Zakas) +* Fix: check all semicolons in no-space-before-semi (fixes #1885) (Mathias Schreck) +* Fix: Refactor comma-spacing (fixes #1587, fixes #1845) (Roberto Vidal) +* Fix: Allow globalReturn in consistent-return (fixes #1868) (Brandon Mills) +* Fix: semi rule should check throw statements (fixes #1873) (Mathias Schreck) +* Docs: Added HolidayCheck AG as user (0xPIT) +* Upgrade: `chalk` to 1.0.0 (Sindre Sorhus) +* Docs: Add CustomInk to the list of companies (Derek Lindahl) +* Docs: Alphabetize project & company usage list (Derek Lindahl) +* Docs: fix typo (Henry Zhu) +* Docs: Fix typo (Brenard Cubacub) + +v0.15.0 - February 21, 2015 + +* 0.15.0 (Nicholas C. Zakas) +* Upgrade: Espree to 1.9.1 (fixes #1816, fixes #1805) (Nicholas C. Zakas) +* Fix: make rules work with for-of statements (fixes #1859) (Mathias Schreck) +* Fix: Enable globalReturn for Node.js environment (fixes #1158) (Nicholas C. Zakas) +* Fix: Location of extra paren message (fixes #1814) (Nicholas C. Zakas) +* Fix: Remove unnecessary file exists check (fixes #1831) (Nicholas C. Zakas) +* Fix: Don't count else-if in max-depth (fixes #1835) (Nicholas C. Zakas) +* Fix: Don't flag for-of statement (fixes #1852) (Nicholas C. Zakas) +* Build: Test using io.js as well (Nicholas C. Zakas) +* Change customformat value to path (suisho) +* Docs: Add a missing word in the Contributing doc (Ben Linskey) +* Docs: Fix typo in wrap-iife rule doc title (Ben Linskey) +* Docs: Update pages to fix rendering of lists (David Anson) +* Fix: new-cap should allow defining exceptions (fixes #1424) (Brian Di Palma) +* Update: Add requireReturnDescription for valid-jsdoc (fixes #1833) (Brian Di Palma) +* New: rule no-throw-literal added (fixes #1791) (Dieter Oberkofler) +* New: multi-line option for the curly rule (fixes #1812) (Hugo Wood) +* Docs: fix typo in configuring docs (mendenhallmagic) +* Update: Backslashes in path (fixes #1818) (Jan Schär) +* Docs: Update pages to fix rendering of lists and fenced code blocks (David Anson) +* Docs: add webpack loader to the docs/integrations page (Maxime Thirouin) +* Breaking: space-before-function-parentheses replaces space-after-function-name and checkFunctionKeyword (fixes #1618) (Mathias Schreck) + +v0.14.1 - February 8, 2015 + +* 0.14.1 (Nicholas C. Zakas) +* Fix: Exit code should be 1 for any number of errors (fixes #1795) (Nicholas C. Zakas) +* Fix: Check indentation of first line (fixes #1796) (Nicholas C. Zakas) +* Fix: strict rules shouldn't throw on arrow functions (fixes #1789) (Nicholas C. Zakas) + +v0.14.0 - February 7, 2015 + +* 0.14.0 (Nicholas C. Zakas) +* Update: Fix indentation of comment (Nicholas C. Zakas) +* Fix: comma-spacing for template literals (fixes #1736) (Nicholas C. Zakas) +* Build: Add Node.js 0.12 testing (Nicholas C. Zakas) +* Breaking: Remove node from results (fixes #957) (Nicholas C. Zakas) +* Breaking: Exit code is now error count (Nicholas C. Zakas) +* Docs: Correct getFormatter() documentation (refs #1723) (Nicholas C. Zakas) +* Update: Make rules work with arrow functions (fixes #1508, fixes #1509, fixes #1493) (Nicholas C. Zakas) +* Fix: Ensure template string references count (fixes #1542) (Nicholas C. Zakas) +* Fix: no-undef to work with arrow functions (fixes #1604) (Nicholas C. Zakas) +* Upgrade: Espree to version 1.8.0 (Nicholas C. Zakas) +* Fix: Don't throw error for arguments (fixes #1759) (Nicholas C. Zakas) +* Fix: Don't warn on computed nonliteral properties (fixes #1762) (Nicholas C. Zakas) +* New: Allow parser to be configured (fixes #1624) (Nicholas C. Zakas) +* Docs: Added double quotes for JSON keys for comma-spacing and key-spacing rule (Dmitry Polovka) +* New: Rule indent (fixes #1022) (Dmitriy Shekhovtsov) +* Revert "New: Rule indent (fixes #1022)" (Nicholas C. Zakas) +* Update: fix eslint indentations (fixes #1770) (Dmitriy Shekhovtsov) +* Fix: Scoping issues for no-unused-vars (fixes #1741) (Nicholas C. Zakas) +* Docs: Added `eslint-enable` inline (Ivan Fraixedes) +* New: Add predefined Meteor globals (fixes #1763) (Johan Brook) +* New: Rule indent (fixes #1022) (Dmitriy Shekhovtsov) +* Update: Check all assignments for consistent-this (fixes #1513) (Timothy Jones) +* Fix: Support exceptions in no-multi-spaces (fixes #1755) (Brandon Mills) +* Docs: Forgotten parentheses in code snippet (Ivan Fraixedes) +* Update: CLIEngine results include warning and error count (fixes #1732) (gyandeeps) +* Fix: Scoping issues for no-unused-vars (fixes #1733) (Nicholas C. Zakas) +* Update: Add getNodeByRangeIndex method (refs #1755) (Brandon Mills) +* Update: Replace getTokenByRange(Index->Start) (refs #1721) (Brandon Mills) +* Update: Fast-path for empty input (fixes #546) (Nicholas C. Zakas) +* Fix: Allow single line else-if (fixes #1739) (Nicholas C. Zakas) +* Fix: Don't crash when $HOME isn't set (fixes #1465) (Nicholas C. Zakas) +* Fix: Make no-multi-spaces work for every case (fixes #1603, fixes #1659) (Nicholas C. Zakas) +* Breaking: Show error and warning counts in stylish summary (fixes #1746) (Brandon Mills) +* Docs: fixed typo in no-lone-blocks docs (Vitor Balocco) +* Docs: fixed typo in consistent-return docs (Vitor Balocco) +* Breaking: remove implied eval check from no-eval (fixes #1202) (Mathias Schreck) +* Update: Improve CLIEngine.getFormatter() (refs #1723) (Nicholas C. Zakas) +* Docs: Add Backbone plugin link (Ilya Volodin) +* Docs: use npm's keyword route (Tom Vincent) +* Build: Update sitegen script (Closes #1725) (Ilya Volodin) + +v0.13.0 - January 24, 2015 + +* 0.13.0 (Nicholas C. Zakas) +* Update: The rule spaced-line-comment now also allows tabs and not only spaces as whitespace. (fixes #1713) (Dieter Oberkofler) +* Docs: add Jasmine rules and eslintplugin npm links (Tom Vincent) +* Fix: Make no-redeclare work with let (fixes #917) (Nicholas C. Zakas) +* Update: Add CLIEngine.getFormatter() (fixes #1653) (Nicholas C. Zakas) +* Breaking: Update escope (fixes #1642) (Nicholas C. Zakas) +* Update: Switch to using estraverse-fb (fixes #1712) (Nicholas C. Zakas) +* Docs: Update README FAQ (Nicholas C. Zakas) +* Update: no-warning-comments matches on whole word only (fixes #1709) (Nick Fisher) +* Build: Add JSDoc generation (fixes #1363) (Nicholas C. Zakas) +* Docs: Add more info about context (fixes #1330) (Nicholas C. Zakas) +* Upgrade: Espree to 1.7.1 (fixes #1706) (Nicholas C. Zakas) +* Docs: Make CLA notice more prominent (Nicholas C. Zakas) +* Update: Added globals for: phantom,jquery, prototypejs, shelljs (fixes #1704) (Dmitriy Shekhovtsov) +* Docs: Fixed example for the space-return-throw-case rule (mpal9000) +* Fix: Except object literal methods from func-names (fixes #1699) (Brandon Mills) +* Update: use global strict mode everywhere (fixes #1691) (Brandon Mills) +* Update: Add allowPattern option for dot-notation rule (fixes #1679) (Tim Schaub) +* Fix: Missing undeclared variables in JSX (fixes #1676) (Yannick Croissant) +* Fix: no-unused-expressions rule incorrectly flagging yield (fixes #1672) (Rémi Gérard-Marchant) +* Update: Combine strict mode rules (fixes #1246) (Brandon Mills) +* Fix: disregards leading './' in ignore pattern or file name (fixes #1685) (Chris Montrois) +* Upgrade: globals module to latest (fixes #1670) (Nicholas C. Zakas) +* Fix: generator-star should allow params (fixes #1677) (Brandon Mills) +* Fix: no-unused-vars for JSX (fixes #1673 and fixes #1534) (Yannick Croissant) +* Docs: Add angularjs-eslint link into the integration doc (Emmanuel DEMEY) + +v0.12.0 - January 17, 2015 + +* 0.12.0 (Nicholas C. Zakas) +* Fix: Track JSX global variable correctly (fixes #1534) (Nicholas C. Zakas) +* Fix: Property regex flag checking (fixes #1537) (Nicholas C. Zakas) +* Docs: Add angularjs-eslint link into the integration doc (Emmanuel DEMEY) +* Update: Expose ecmaFeatures on context (fixes #1648) (Nicholas C. Zakas) +* Docs: Added Fitbit to the list of companies (Igor Zalutsky) +* New: gen-star rule (refs #1617) (Jamund Ferguson) +* New: no-var rule (refs #1617) (Jamund Ferguson) +* Fix: Support JSX spread operator (fixes #1634) (Nicholas C. Zakas) +* Docs: Document ecmaFeatures (Nicholas C. Zakas) +* Upgrade: several dependencies (fixes #1377) (Nicholas C. Zakas) +* Fix: Broken JSX test (Nicholas C. Zakas) +* Fix: no-bitwise reports on bitwise assignment expressions (fixes #1643) (Mathias Schreck) +* Fix: Find JSXIdentifier refs in no-unused-vars (fixes #1534) (Nicholas C. Zakas) +* Update: Add a couple JSX tests (Nicholas C. Zakas) +* Fix: quotes rule ignores JSX literals (fixes #1477) (Nicholas C. Zakas) +* Fix: Don't warn on JSX literals with newlines (fixes #1533) (Nicholas C. Zakas) +* Update: Fully enable JSX support (fixes #1640) (Nicholas C. Zakas) +* Breaking: Allow parser feature flips (fixes #1602) (Nicholas C. Zakas) +* Fix: Allow comments in key-spacing groups (fixes #1632) (Brandon Mills) +* Fix: block-scoped-var reports labels (fixes #1630) (Michael Ficarra) +* Docs: add newline to no-process-env (fixes #1627) (Tom Vincent) +* Fix: Update optionator, --no in help (fixes #1134) (George Zahariev) +* Fix: Allow individual newlines in space-in-brackets (fixes #1614) (Brandon Mills) +* Docs: Correct alignment in example project tree (Tim Schaub) +* Docs: Remove references to Esprima (Nicholas C. Zakas) +* Docs: Remove illegal code fence (Nicholas C. Zakas) + +v0.11.0 - December 30, 2014 + +* 0.11.0 (Nicholas C. Zakas) +* Fix: Adding regexp literal exception (fixes #1589) (Greg Cochard) +* Fix: padded-blocks incorrectly complained on comments (fixes #1416) (Mathias Schreck) +* Fix: column location of key-spacing with additional tokens (fixes #1458) (Mathias Schreck) +* Build: tag correct commit (refs #1606) (Mathias Schreck) +* Upgrade: Updat Espree to 1.3.1 (Nicholas C. Zakas) +* Fix: add es3 config option to dot-notation rule (fixes #1484) (Michael Ficarra) +* Fix: valid-jsdoc should recognize @class (fixes #1585) (Nicholas C. Zakas) +* Update: Switch to use Espree (fixes #1595) (Nicholas C. Zakas) +* Fix: brace-style stroustrup should report on cuddled elseif (fixes #1583) (Ian Christian Myers) +* New: Configuration via package.json (fixes #698) (Michael Mclaughlin) +* Update: Set environments w/ globals (fixes #1577) (Elan Shanker) +* Fix: yoda treats negative numbers as literals (fixes #1571) (Brandon Mills) +* Fix: function arguments now count towards no-shadow check (fixes #1584) (Glen Mailer) +* Fix: check if next statement is on newline when warning against extra semicolons. (fixes #1580) (Evan You) +* Update: add yoda exception for range tests (fixes #1561) (Brandon Mills) +* New: space-after-function-name (fixes #1340) (Roberto Vidal) + +v0.10.2 - December 12, 2014 + +* 0.10.2 (Nicholas C. Zakas) +* Fix: detect for...in in no-loop-func (fixes #1573) (Greg Cochard) +* Update: simplify comma-spacing logic (fixes #1562) (Brandon Mills) +* Fix: operator-assignment addition is non-commutative (fixes#1556) (Brandon Mills) +* 0.10.1 (Nicholas C. Zakas) +* Update: Add new-cap exception configurations. (Fixes #1487) - `newCapsAllowed` - `nonNewCapsAllowed` (Jordan Harband) + +v0.10.1 - December 6, 2014 + +* 0.10.1 (Nicholas C. Zakas) +* Docs: Fix v0.10.0 changelog (Nicholas C. Zakas) +* Build: Ensure changelog works with large semver versions (Nicholas C. Zakas) +* Fix: comma-spacing and comma-style to work with array literals (fixes #1492) (Nicholas C. Zakas) +* Update: better operator regex in use-isnan rule (fixes #1551) (Michael Ficarra) +* Fix: wrong op index in no-multi-spaces (fixes #1547) (Brandon Mills) +* Fix: Restrict use-isnan violations to comparison operators. (Fixes #1535) (Jordan Harband) +* Fix: comma-spacing has false positives when parenthesis are used (fixes #1457) (Jamund Ferguson) +* Docs: alphabetize the "Stylistic Issues" section (Jeff Williams) +* Build: make the "gensite" target work when DOCS_DIR does not exist (fixes #1530) (Jeff Williams) +* Docs: badges should only refer to master branch (Mathias Schreck) +* Fix: prevent crash on empty blocks in no-else-return (fixes #1527) (Mathias Schreck) +* Build: Fix md to html conversion regex (fixes #1525) (Brandon Mills) +* 0.10.0 (Nicholas C. Zakas) + +v0.10.0 - November 27, 2014 + +* 0.10.0 (Nicholas C. Zakas) +* Fix: Add Object and Function as exceptions in new-cap (refs #1487) (Nicholas C. Zakas) +* Breaking: Allow extensionless files to be passed on CLI (fixes #1131) (Nicholas C. Zakas) +* Fix: typo: iffe to iife, none to non (Michael Ficarra) +* Update: refactor tokens API (refs #1212) (Brandon Mills) +* New: Allow other file extensions (fixes #801) (Nicholas C. Zakas) +* Update: Add Event to browser globals (fixes #1474) (Nicholas C. Zakas) +* Fix: check function call arguments in comma-spacing (fixes #1515) (Mathias Schreck) +* Update: Add no-cond-assign option to disallow nested assignments in conditionals (fixes #1444) (Jeff Williams) +* Fix: crash in no-multi-spaces on empty array elements (fixes #1418) (Brandon Mills) +* Fix: Don't explode on directory traversal (fixes #1452) (Nicholas C. Zakas) +* Fix: no-fallthrough should work when semis are missing (fixes #1447) (Nicholas C. Zakas) +* Fix: JSDoc parsing by updating doctrine (fixes #1442) (Nicholas C. Zakas) +* Update: restore the "runs" global present in Jasmine 1.3 (fixes #1498) (Michał Gołębiowski) +* Fix: ignore undefined identifiers in typeof (fixes #1482) (Mathias Schreck) +* Fix: Ignoring empty comments. (fixes #1488) (Greg Cochard) +* New: Add space-unary-ops rules (#1346) (Marcin Kumorek) +* Update: Remove shebang workaround in spaced-line-comment (fixes #1433) (Michael Ficarra) +* Docs: change 'and' to 'an' in docs/rules/valid-jsdoc.md (fixes #1441) (Michael Ficarra) +* Update: Add `beforeAll` and `afterAll` to the Jasmine globals (fixes #1478) (Gyandeep Singh) +* Update: Add exception options to space-in-parens (fixes #1368) (David Clark) +* Build: Add check for license issues (fixes #782) (Brandon Mills) +* Docs: update badges (Yoshua Wuyts) +* Docs: Update pages to fix rendering of lists and fenced code blocks (David Anson) +* Fix: env rules merging for command line config (fixes #1271) (Roberto Vidal) +* Fix: Collect variables declare in switch-case.(fixes #1453) (chris) +* Fix: remove extra capture group (Nate-Wilkins) +* Update: allow distinct alignment groups in key-spacing (fixes #1439) (Brandon Mills) +* Fix: message for numeric property names in quote-props (fixes #1459) (Brandon Mills) +* Docs: Remove assumption about the rule config (Alexander Schmidt) +* New: Add ability to time individual rules (fixes #1437) (Brandon Mills) +* Fix: single quotes (Nate-Wilkins) +* Docs: Fix broken code fences in key-spacing docs (Brandon Mills) +* Docs: Explain .eslintignore features (fixes #1094) (Brandon Mills) +* Breaking: ignore node_modules by default (fixes #1163) (Brandon Mills) +* Fix: Adds clamping to getSource beforeCount (fixes #1427) (Greg Gianforcaro) +* New: add no-inline-comment rule (fixes #1366) (Greg Cochard) +* Fix: '.md' to '.html' with anchors (fixes #1415) (Nate-Wilkins) +* Build: Filter and sort versions in gensite (fixes #1430) (Brandon Mills) +* Build: Escape period in regex (fixes #1428) (Brandon Mills) +* Revert "Fix: '.md' to '.html' with anchors (fixes #1415)" (Nicholas C. Zakas) +* 0.9.2 (Nicholas C. Zakas) +* New: Add operator-assignment rule (fixes #1420) (Brandon Mills) + +v0.9.2 - November 1, 2014 + +* 0.9.2 (Nicholas C. Zakas) +* Fix: '.md' to '.html' with anchors (fixes #1415) (Nate-Wilkins) +* Fix: Allow line breaks in key-spacing rule (fixes #1407) (Brandon Mills) +* Build: add coveralls integration (fixes #1411) (Mathias Schreck) +* Fix: add severity flag for ignored file warning (fixes #1401) (Mathias Schreck) +* Fix: Keep sinon at ~1.10.3 (fixes #1406) (Brandon Mills) +* Fix: ! negates .eslintignore patterns (fixes #1093) (Brandon Mills) +* Fix: let fs.stat throw if a file does not exist (fixes #1296) (Mathias Schreck) +* Fix: check switch statements in space-before-blocks (fixes #1397) (Mathias Schreck) +* Docs: fix rule name in example configuration (Mathias Schreck) +* Fix: disable colors during test run (fixes #1395) (Mathias Schreck) +* New: add isPathIgnored method to CLIEngine (fixes #1392) (Mathias Schreck) +* Docs: changing eslint to ESLint and add missing backtick (Mathias Schreck) +* Docs: Documents the functionality to load a custom formatter from a file (Adam Baldwin) +* 0.9.1 (Nicholas C. Zakas) +* Update: Option type for mixed tabs and spaces (fixes #1374) (Max Nordlund) +* Fix: Nested occurrences of no-else-return now show multiple reports (fixes #1369) (Jordan Hawker) + +v0.9.1 - October 25, 2014 + +* 0.9.1 (Nicholas C. Zakas) +* Docs: fix link on governance model (azu) +* Fix: plugins without rulesConfig causes crash (fixes #1388) (Mathias Schreck) +* 0.9.0 (Nicholas C. Zakas) + +v0.9.0 - October 24, 2014 + +* 0.9.0 (Nicholas C. Zakas) +* New: Allow reading from STDIN (fixes #368) (Nicholas C. Zakas) +* New: add --quiet option (fixes #905) (Mathias Schreck) +* Update: Add support for plugin default configuration (fixes #1358) (Ilya Volodin) +* Fix: Make sure shebang comment node is removed (fixes #1352) (Nicholas C. Zakas) +* New: Adding in rule for irregular whitespace checking. (fixes #1024) (Jonathan Kingston) +* Fix: space-in-parens should not throw for multiline statements (fixes #1351) (Jary) +* Docs: Explain global vs. local plugins (fixes #1238) (Nicholas C. Zakas) +* Docs: Add docs on Node.js API (fixes #1247) (Nicholas C. Zakas) +* Docs: Add recommended keywords for plugins (fixes #1248) (Nicholas C. Zakas) +* Update: Add CLIEngine#getConfigForFile (fixes #1309) (Nicholas C. Zakas) +* Update: turn on comma-style for project (fixes #1316) (Nicholas C. Zakas) +* Fix: Ensure messages are sorted by line (fixes #1343) (Nicholas C. Zakas) +* Update: Added arraysInObjects and objectsInObjects options to space-in-brackets rule (fixes #1265, fixes #1302) (vegetableman) +* Breaking: Removed comma spacing check from space-infix-ops (fixes #1361) (vegetableman) +* Fix: addressed linting errors (Nicholas C. Zakas) +* Docs: Add Contributor Model (fixes #1341) (Nicholas C. Zakas) +* Docs: Add reference to CLA (Nicholas C. Zakas) +* Build: add version numbers to docs (fixes #1170) (Mathias Schreck) +* Fix: no-fallthrough incorrectly flagged falls through annotations (fixes #1353) (Mathias Schreck) +* Build: separate site publishing form generation (fixes #1356) (Mathias Schreck) +* New: Add key-spacing rule (fixes #1280) (Brandon Mills) +* New: add spaced-line-comment rule (fixes #1345) (Greg Cochard) +* Docs: added more Related Rules sections (fixes #1347) (Delapouite) +* Fix: resolve linting issue in (fixes #1339) (Nicholas C. Zakas) +* New: add space-before-blocks rule (fixes #1277) (Mathias Schreck) +* Docs: Remove moot integration plugins (Sindre Sorhus) +* New: add rule for multiple empty lines (fixes #1254) (Greg Cochard) +* Fix: no-shadow rule should consider function expressions (fixes #1322) (Mathias Schreck) +* Update: remove globals present only in Jasmine plugins (fixes #1326) (Michał Gołębiowski) +* New: added no-multi-spaces rule (fixes #630) (vegetableman) +* New: Added comma-spacing rule (Fixes #628, Fixes #1319) (vegetableman) +* New: add rule for padded blocks (fixes #1278) (Mathias Schreck) +* Docs: fix eqeqeq isNullCheck comment (Denis Sokolov) +* Fix: no-comma-dangle violation in unit test and Makefile.js/lint not checking return codes (fixes #1306) (David Anson) +* Fix: allow comma-last with object properties having line breaks (fixes #1314) (vegetableman) +* New: Added comma-style rule (fixes #1282) (vegetableman) +* Update: add space after function keyword check (fixes #1276) (Mathias Schreck) +* Update: Add missing environments and fix sorting/grouping of rules (fixes #1307, fixes #1308) (David Anson) +* Docs: Fix sorting of rules within each section (David Anson) +* Docs: Correct a few misspelled words (David Anson) +* Docs: Update multiple pages to fix rendering of fenced code blocks (David Anson) +* New: Added no-process-env rule (fixes #657) (vegetableman) +* Fix: add rule ensuring #1258 is fixed by recent rewrite (fixes #1258) (Michael Ficarra) +* Update: split propertyName from singleValue in space-in-brackets (fixes #1253) (Michael Ficarra) +* Update: add "as-needed" option to quote-props rule (fixes #1279) (Michael Ficarra) +* Docs: fixed broken link and changed warning level to error level (vegetableman) +* Docs: Added "the native web" to the list of companies that use ESLint. (Golo Roden) +* Docs: Add BountySource badge to README (Nicholas C. Zakas) +* 0.8.2 (Nicholas C. Zakas) + +v0.8.2 - September 20, 2014 + +* 0.8.2 (Nicholas C. Zakas) +* Docs: Updated contribution guidelines to add accepted/bounty issues descriptions (Nicholas C. Zakas) +* Docs: Update README with links and FAQs (Nicholas C. Zakas) +* Docs: add finally to space-after-keywords documentation (Mathias Schreck) +* New: add ignoreCase option to sort-vars (fixes #1272) (Mathias Schreck) +* Docs: fix typo (Barry Handelman) +* Docs: Fix broken Markdown on configuration page (Nicholas C. Zakas) +* Docs: Fix reference to wrong rule name (Harry Wolff) +* Upgrade: Most dev dependencies (Nicholas C. Zakas) +* Upgrade: shelljs to 0.3.0 (Nicholas C. Zakas) +* Upgrade: doctrine to 0.5.2 (Nicholas C. Zakas) +* Upgrade: esprima to 1.2.2 (Nicholas C. Zakas) +* Upgrade: eslint-tester to latest (Nicholas C. Zakas) +* Fix: Load .eslintrc in directory with $HOME as an ancestor (fixes #1266) (Beau Gunderson) +* Fix: load .eslintrc from HOME (fixes #1262) (Beau Gunderson) +* New: Add sharable rule settings (fixes #1233) (Ilya Volodin) +* Upgrade: upgrade outdated dependencies (fixes #1251) (Mathias Schreck) +* Docs: fix typo in no-ex-assign documentation (Michael Ficarra) +* Docs: add intellij plugin to integrations (ido) +* Docs: Changing NPM to npm (Peter deHaan) +* Fix: strict should check function expressions (fixes #1244) (Brandon Mills) +* Docs: fix vars-on-top documentation (fixes #1234) (Mathias Schreck) +* 0.8.1 (Nicholas C. Zakas) +* Docs: Fixed a typo in brace-style.md (Anton Antonov) + +v0.8.1 - September 9, 2014 + +* 0.8.1 (Nicholas C. Zakas) +* Fix: Ensure exit code is 1 when there's a syntax error (fixes #1239) (Nicholas C. Zakas) +* Docs: fix up vars-on-top documentation (fixes #1234) (Michael Ficarra) +* Fix: vars-on-top directive support (fixes #1235) (Michael Ficarra) +* Fix: Avoid mutating node.range in max-len (fixes #1224) (Brandon Mills) +* Docs: Typo, add missing quotation mark (Ádám Lippai) +* Update: space-in-brackets to allow exceptions (fixes #1142) (Brandyn Bennett) +* 0.8.0 (Nicholas C. Zakas) + +v0.8.0 - September 5, 2014 + +* 0.8.0 (Nicholas C. Zakas) +* Perf-related revert "Fix: Speed up tokens API (refs #1212)" (Nicholas C. Zakas) +* Fix: no-fallthrough: continue affects control flow, too (fixes #1220) (Michael Ficarra) +* Fix: rewrite no-unused-vars rule (refs #1212) (Michael Ficarra) +* Fix: Error when there's a \r in .eslintrc (#1172) (Gyandeep Singh) +* Added rule disallowing reserved words being used as keys (fixes #1144) (Emil Bay) +* Fix: rewrite no-spaced-func rule (refs #1212) (Michael Ficarra) +* Fix: Speed up getScope() (refs #1212) (Brandon Mills) +* Fix: no-extra-strict behavior for named function expressions (fixes #1209) (Mathias Schreck) +* Add Date.UTC to allowed capitalized functions (David Brockman Smoliansky) +* New: Adding 'vars-on-top' rule (fixes #1148) (Gyandeep Singh) +* Fix: Speed up tokens API (refs #1212) (Brandon Mills) +* Docs: document plugin usage (fixes #1117) (Mathias Schreck) +* New: accept plugins from cli (fixes #1113) (Mathias Schreck) +* Docs: fix some typos. (Mathias Schreck) +* New: Load plugins from configs (fixes #1115). (Mathias Schreck) +* Fix: no-unused-expressions better directive detection (fixes #1195) (Michael Ficarra) +* Fix: no-unused-expressions directive support (fixes #1185) (Michael Ficarra) +* Update: Add 'allowSingleLine' option to brace-style (fixes #1089) (John Gozde) +* Docs: Spell checking and one extra closing curly in code example (Juga Paazmaya) +* Fix: mergeConfigs ensures the plugins property exists (fixes #1191). (Mathias Schreck) +* Update: Declare ES6 collections (Map, Set, WeakMap, WeakSet) as built-in globals (fixes #1189) (Michał Gołębiowski) +* New: Adding 'plugin' CLI option (fixes #1112) (Greg) +* Fix: Correct a typo in the error message in tests (Michał Gołębiowski) +* New: Add no-extra-bind rule to flag unnecessary bind calls (fixes #982) (Bence Dányi) +* Fix: Useless bind call in cli-engine (fixes #1181) (Bence Dányi) +* Docs: Updates `amd` description (fixes #1175) (James Whitney) +* New: Adds support for the `jasmine` env (fixes #1176) (James Whitney) +* Fix: for-in support to no-empty-label rule (fixes #1161) (Marc Harter) +* docs: Update link (Mathias Bynens) +* Fix: crash when loading empty eslintrc file (fixes #1164) (Michael Ficarra) +* Fix: no-unused-var should respect compound assignments (fixes #1166) (Michael Ficarra) +* Update: ES3 `ReservedWord`s (fixes #1151) Adds ES3 `ReservedWord`s to the list of keywords in the `dot-notation` rule (fixes #1151) (Emil Bay) +* Update: Update comment parser to read rule slashes (fixes #1116) (Jary) +* New: add no-void rule (fixes #1017). (Mike Sidorov) +* New: Add rules.import() (fixes #1114) (Mathias Schreck) +* New: Make mergeConfigs() merge plugin entries (fixes #1111) (Mathias Schreck) +* Breaking: Change no-global-strict to global-strict and add "always" option (fixes #989) (Brandon Mills) +* Fix: no-unreachable should check top-level statements (fixes #1138) (Brandon Mills) +* Fix: Speed up no-unreachable (fixes #1135) (Brandon Mills) +* New: advanced handle-callback-err configuration (fixes #1124) (Mathias Schreck) +* New: Expose CLIEngine (fixes #1083) (Gyandeep Singh) +* Docs: Add link to new Atom linter (fixes #1125) (Gil Pedersen) +* Fix: space-after-keywords checks finally of TryStatement (fixes #1122) (Michael Ficarra) +* Fix: space-after-keywords checks while of DoWhileStatement (fixes #1120) (Michael Ficarra) +* Fix: space-after-keywords w/ "never" should allow else-if (fixes #1118) (Michael Ficarra) +* Fix: dot-notation rule flags non-keyword reserved words (fixes #1102) (Michael Ficarra) +* Update: Use xml-escape instead of inline helper (Ref #848) (jrajav) +* Update: Added comments support to .eslintignore (fixes #1084) (Vitaly Puzrin) +* Update: enabled 'no-trailing-spaces' rule by default (fixes #1051) (Vitaly Puzrin) +* Breaking: Ignore children of all patterns by adding "/**" (Fixes #1069) (jrajav) +* Fix: skip dot files and ignored dirs on traverse (fixes #1077, related to #814) (Vitaly Puzrin) +* Docs: Added Gruntjs plugin on integrations page (Gyandeep Singh) +* Fix: don't break node offsets if hasbang present (fixes #1078) (Vitaly Puzrin) +* Build: Exclude readme/index from rules Resources generation (Fixes #1072) (jrajav) +* Docs: Change eol-last examples to `
` (Fixes #1068) (jrajav)
+* 0.7.4 (Nicholas C. Zakas)
+* New: space-in-parens rule (Closes #627) (jrajav)
+
+v0.7.4 - July 10, 2014
+
+* 0.7.4 (Nicholas C. Zakas)
+* Docs: Fix 'lintinging' typo and ref links (Tom Vincent)
+* Fix: Transform envs option to object in Config (Fixes #1064) (jrajav)
+* 0.7.3 (Nicholas C. Zakas)
+
+v0.7.3 - July 9, 2014
+
+* 0.7.3 (Nicholas C. Zakas)
+* Update: Address code review comment for strict rule (refs #1011) (Nicholas C. Zakas)
+* Docs: Update copyright policy (Nicholas C. Zakas)
+* Docs: Update documentation for max-len to include description of second option (fixes #1006) (Nicholas C. Zakas)
+* Fix: Avoid double warnings for strict rule (fixes #1011) (Nicholas C. Zakas)
+* Fix: Check envs for true/false (Fixes #1059) (jrajav)
+* 0.7.2 (Nicholas C. Zakas)
+
+v0.7.2 - July 8, 2014
+
+* 0.7.2 (Nicholas C. Zakas)
+* Fix: no-mixed-spaces-and-tabs incorrectly flagging multiline comments (fixes #1055) (Nicholas C. Zakas)
+* Fix: new-cap error that throws on non-string member (fixes #1056) (Nicholas C. Zakas)
+* Fix: Always make globals an object (Fixes #1049) (jrajav)
+* 0.7.1 (Nicholas C. Zakas)
+
+v0.7.1 - July 7, 2014
+
+* 0.7.1 (Nicholas C. Zakas)
+* Docs: Add Related Rules sections (Fixes #990) (jrajav)
+* Fix: Check output file isn't dir, fix tests (Fixes #1034) (jrajav)
+* Docs: Updated documentation for several rules (Nicholas C. Zakas)
+* Docs: Updated contributor guide and dev env setup guide (Nicholas C. Zakas)
+* Breaking: Implement configuration hierarchy (fixes #963) (Nicholas C. Zakas)
+* Update: greatly simplify eqeqeq's operator finding logic (fixes #1037) (Michael Ficarra)
+* New: Add getSourceLines() to core and rule context (fixed #1005) (Jary)
+* Build + Docs: Adding generated resource links to rule docs (Fixes #1021) (jrajav)
+* Fix: Ignore unused params for args: 'none' (Fixes #1026) (jrajav)
+* Fix: Point eqeqeq error at operator (Fixes #1029) (jrajav)
+* New: report output to a file (fixes #1027) (Gyandeep Singh)
+* Breaking: CLIEngine abstraction for CLI operations; formatters no longer are passed configs (fixes #935) (Nicholas C. Zakas)
+* Fix: Allow stdout to drain before exiting (fixes #317) (Nicholas C. Zakas)
+* New: add no-undefined rule (fixes #1020) (Michael Ficarra)
+* New: Added no-mixed-spaces-and-tabs rule (fixes #1003) (Jary)
+* New: Added no-trailing-spaces rule (fixes #995) (Vitaly Puzrin)
+* Update: Factor ignores out of Config (fixes #958) (jrajav)
+* Fix: rewrite eol-last rule (fixes #1007) (fixes #1008) (Michael Ficarra)
+* Fix: add additional IIFE exception in no-extra-parens (fixes #1004) (Michael Ficarra)
+* Docs: Removed reference to brace-style Stroustrup default (fixes #1000) (Caleb Troughton)
+* New: Added eol-last rule (Fixes #996) (Vitaly Puzrin)
+* Fix: Put rule severity in messages (Fixes #984); deprecates passing full config to Formatters (jrajav)
+* Fix: no-unused-vars to check only file globals (fixes #975) (Aliaksei Shytkin)
+* Build: Makefile - Check for rule ids in docs titles (Fixes #969) (Delapouite)
+* Docs: guard-for-in - added missing id in title (Fixes #969) (Delapouite)
+* Breaking: Change 'no-yoda' rule to 'yoda' and add "always" option (Fixes #959) (jrajav)
+* Fix: Fixes no-unused-vars to check /*globals*/ (Fixes #955) (jrajav)
+* Update: no-eval to also warn on setTimeout and setInterval (fixes #721) (Nicholas C. Zakas)
+* Remove: experimental match() method (Nicholas C. Zakas)
+* Update: space-in-brackets now always allows empty object and array literals to have no spaces (fixes #797) (Nicholas C. Zakas)
+* New: Allow the cli parameter "color" and "no-color" (fixes #954) (Tom Gallacher)
+* Fix: valid-jsdoc no more warning for multi-level params (Fixes #925) (Delapouite)
+* Update: Search parent directories for .eslintignore (Fixes #933) (jrajav)
+* Fix: Correct order of arguments passed to assert.equal (fixes #945) (Michał Gołębiowski)
+* Update: Write the summary in stylish formatter in yellow if no errors (fixes #906); test coloring of messages (Michał Gołębiowski)
+* Fix: Corrects configs merging into base config (Fixes #838) (jrajav)
+* Fix: Adding check if char is non-alphabetic to new-cap (Fixes #940) (jrajav)
+* Docs: Update about page description (fixes #936) (Nicholas C. Zakas)
+* Docs: Add '/', forgotten in first commit (Fixes #931) (jrajav)
+* Update: Rule `new-cap` checks capitalized functions (fixes #904) (Aliaksei Shytkin)
+* Docs: Mention allowed semicolons in "never" mode for 'semi' rule (fixes #931) (jrajav)
+* Docs: Mention Yeoman generator in dev setup (fixes #914) (Nicholas C. Zakas)
+* Build: Remove flaky perf test from Travis (Nicholas C. Zakas)
+* Breaking: Refactor .eslintignore functionality (refs #928, fixes #901, fixes #837, fixes #853) (Nicholas C. Zakas)
+* 0.6.2 (Nicholas C. Zakas)
+* Breaking: Remove JSON support for .eslintignore (fixes #883) (icebox)
+
+v0.6.2 - May 23, 2014
+
+* 0.6.2 (Nicholas C. Zakas)
+* Fix: Adding per-environment rule configs to docs and doc validation (Fixes #918) (jrajav)
+* Docs: Updated contribution guidelines (Nicholas C. Zakas)
+* Docs: Update description of eqeqeq to mention special cases (fixes #924) (Nicholas C. Zakas)
+* Fix: block-scoped-var CatchClause handling (fixes #922) (Michael Ficarra)
+* Fix: block-scoped-var respects decls in for and for-in (fixes #919) (Michael Ficarra)
+* Update: Implement eqeqeq option "allow-null" (fixes #910) (Michał Gołębiowski)
+* Fix: new-cap should allow non-alpha characters (fixes #897) (Michael Ficarra)
+* Update: Refactor ESLintTester to fix dependency hell (fixes #602) (Nicholas C. Zakas)
+* Fix: Merge configs with ancestors (Fixes #820) (jrajav)
+* Fix: no-fallthrough should respect block statements in case statements (fixes #893) (Nicholas C. Zakas)
+* Docs: Fix layout issue in configuration docs (fixes #889) (Nicholas C. Zakas)
+* Build: Enable default-case rule (fixes #881) (icebox)
+* Build: Enable space-after-keywords (fixes #884) (icebox)
+* Fix api double emit on comment nodes (fixes #876) (Aliaksei Shytkin)
+* 0.6.1 (Nicholas C. Zakas)
+
+v0.6.1 - May 17, 2014
+
+* 0.6.1 (Nicholas C. Zakas)
+* Upgrade: Optionator to 0.4.0 (fixes #885) (Nicholas C. Zakas)
+* 0.6.0 (Nicholas C. Zakas)
+
+v0.6.0 - May 17, 2014
+
+* 0.6.0 (Nicholas C. Zakas)
+* Fix: Remove -r alias for --rule (fixes #882) (Nicholas C. Zakas)
+* Docs: Update dev setup, contributing, default-case descriptions (Nicholas C. Zakas)
+* Update: valid-jsdoc now allows you to optionally turn off parameter description checks (fixes #822) (Nicholas C. Zakas)
+* Breaking: brace-style now disallows block statements where curlies are on the same line (fixes #758) (Nicholas C. Zakas)
+* Add linting Makefile.js (fixes #870) (icebox)
+* add rule flag, closes #692 (George Zahariev)
+* Add check between rules doc and index (fixes #865) (icebox)
+* Add Build Next mention in integrations README. (icebox)
+* document new IIFE exception for no-extra parens added as part of #655 (Michael Ficarra)
+* (fixes #622) Add rule ID on documentation pages (Delapouite)
+* fixes #655: add IIFE exception to no-extra-parens (Michael Ficarra)
+* add new rule "no-new-require" (Wil Moore III)
+* exit with non-zero status when tests fail (fixes #858) (Márton Salomváry)
+* removed unicode zero width space character from messages (fixes #857) (Márton Salomváry)
+* Change: --rulesdir now can be specified multiple times (fixes #830) (Nicholas C. Zakas)
+* Update: Node 0.8 no longer supported (fixes #734) (Nicholas C. Zakas)
+* Update: Add typed arrays into builtin environment globals (fixes #846) (Nicholas C. Zakas)
+* Fix: Add prototype methods to global scope (fixes #700) (Nicholas C. Zakas)
+* Rule: no-restricted-modules (fixes #791) (Christian)
+* Upgrade: Esprima to 1.2 (fixes #842) (Nicholas C. Zakas)
+* Docs: reporting level 2 is an error (fixes #843) (Brandon Mills)
+* Upgrade: Esprima to 1.2, switch to using Esprima comment attachment (fixes #730) (Nicholas C. Zakas)
+* Fix: Semi rule incorrectly flagging extra semicolon (fixes #840) (Nicholas C. Zakas)
+* Build: Update Travis to only test Node 0.10 (refs #734) (Nicholas C. Zakas)
+* Add "nofunc" option (fixes #829) (Conrad Zimmerman)
+* Rule: no-inner-declarations (fixes #587) (Brandon Mills)
+* Rule 'block-scoped-var': correct scope for functions, arguments (fixes #832) (Aliaksei Shytkin)
+* Rule: default-case (fixes #787) (Aliaksei Shytkin)
+* Ignored files are excluded unless --force is passed on the CLI (Nick Fisher)
+* Fixes a typo and a broken link in the documentation (Nick Fisher)
+* Replaces .some() with .indexOf() where appropriate (Nick Fisher)
+* Fix correct config merge for array values (fixes #819) (Aliaksei Shytkin)
+* Remove warning about ESLint being in Alpha (Nick Fisher)
+* Adds `space-after-keywords` rule (fixes #807) (Nick Fisher)
+* Rule: no-lonely-if (fixes #790) (Brandon Mills)
+* Add ignore comments in file (fixes #305) (Aliaksei Shytkin)
+* 0.5.1 (Nicholas C. Zakas)
+* Change: no-unused-vars default to 'all' (fixes #760) (Nicholas C. Zakas)
+
+v0.5.1 - April 17, 2014
+
+* 0.5.1 (Nicholas C. Zakas)
+* Fix general config not to be modified by comment config in files (fixes #806) (Aliaksei Shytkin)
+* SVG badges (Ryuichi Okumura)
+* fixes #804: clean up implementation of #803 (which fixed #781) (Michael Ficarra)
+* Build: Fix perf test to take median of three runs (fixes #781) (Nicholas C. Zakas)
+* Fix: --reset will now properly ignore default rules in environments.json (fixes #800) (Nicholas C. Zakas)
+* Docs: Updated contributor guidelines (Nicholas C. Zakas)
+* Added Mocha global variables for TDD style. Fixes #793. (Golo Roden)
+* Rule: no-sequences (fixes #561) (Brandon Mills)
+* Change .eslintignore to plain text (fixes #761) (Brandon Mills)
+* Change 'no-spaced-func' message (fixes #762) (Aliaksei Shytkin)
+* Rule 'block-scoped-var' works correct when object inits (fixes #783) (Aliaksei Shytkin)
+* Build: Always build docs site on top of origin/master (Nicholas C. Zakas)
+* 0.5.0 (Nicholas C. Zakas)
+
+v0.5.0 - April 10, 2014
+
+* 0.5.0 (Nicholas C. Zakas)
+* Build: Bump perf limit so Travis won't fail every time (fixes #780) (Nicholas C. Zakas)
+* Add tests to cover 100% of eslint.js (Aliaksei Shytkin)
+* Fix: Make sure no-path-concat doesn't flag non-concat operations (fixes #776) (Nicholas C. Zakas)
+* Rule 'no-unused-var' in functional expression with identifier (fixes #775) (Aliaksei Shytkin)
+* Rule: valid-typeof (Ian Christian Myers)
+* Add global cli flag (ref #692) (Brandon Mills)
+* update to latest Optionator (George Zahariev)
+* Add options for rule 'no-unused-vars' to check all arguments in functions (fixes #728) (Aliaksei Shytkin)
+* Fix: Cleanup package.json (Nicholas C. Zakas)
+* New: Experimental support for CSS Auron (fixes #765) (Nicholas C. Zakas)
+* Lint tests on build (fixes #764) (Aliaksei Shytkin)
+* Rule block-scoped-var works correct with object properties (fixes #755) (Aliaksei Shytkin)
+* Breaking: implement eslint-env and remove jshint/jslint environment comment support (fixes #759) (Aliaksei Shytkin)
+* readme: npm i -> npm install (Linus Unnebäck)
+* Add env flag to cli options summary (fixes #752) (Brandon Mills)
+* Fix: Give the perf test a better calculated budget (fixes #749) (Nicholas C. Zakas)
+* give the `env` flag type `[String]`, improve code (fixes #748) (George Zahariev)
+* fixes #735: add new, more efficient getTokens interfaces (Michael Ficarra)
+* Add --env cli flag (ref #692) (Brandon Mills)
+* Fixes #740 - Make sure callbacks exist before marking them as 'handled'. (mstuart)
+* fixes #743: wrap-regex rule warns on regex used in dynamic member access (Michael Ficarra)
+* replace tab indents with 4 spaces in lib/rules/handle-callback-err.js (Michael Ficarra)
+* Adding homepage and bugs links to package.json (Peter deHaan)
+* JSDoc for rules (Anton Rudeshko)
+* 0.4.5 (Nicholas C. Zakas)
+
+v0.4.5 - March 29, 2014
+
+* 0.4.5 (Nicholas C. Zakas)
+* Build: Add perf check into Travis build to better monitor performance regressions (fixes #732) (Nicholas C. Zakas)
+* Fix: Make sure semi reports correct location of missing semicolon (fixes #726) (Nicholas C. Zakas)
+* Add --no-eslintrc cli flag (ref #717) (Brandon Mills)
+* Fix #716 crash with reset flag (Brandon Mills)
+* Fixed JSON formatting and highlighting (Anton Rudeshko (Tesla))
+* fixes #723: block-scoped-var throws on unnamed function expression (Michael Ficarra)
+* Fix: Make stroustrup brace-style closing message make sense (fixes #719) (Nicholas C. Zakas)
+* no-comma-dangle reports correct line number (Andrey Popp)
+* Upgrade: Esprima to 1.1.1 and EScope to 1.0.1 (fixes #718) (Nicholas C. Zakas)
+* Add reset cli flag (refs #692) (Brandon Mills)
+* Relax eqeqeq null check (fixes #669) (Brandon Mills)
+* 0.4.4 (Nicholas C. Zakas)
+* New Rule: handle-callback-err (fixes #567) (Jamund Ferguson)
+
+v0.4.4 - March 25, 2014
+
+* 0.4.4 (Nicholas C. Zakas)
+* Fix no-used-vars to report FunctionExpression params (fixes #697). (Andrey Popp)
+* fixes #711: eslint reports wrong line number for files with shebang (Michael Ficarra)
+* Fix for no-unused-vars and MemberExpression (Andrey Popp)
+* added no-warning-comments rule (Alexander Schmidt)
+* fixes #699: brace-style does not check function expressions (Michael Ficarra)
+* rewrite block-scoped-var (Michael Ficarra)
+* recommend using hasOwnProperty from Object.prototype in guard-for-in docs (Michael Ficarra)
+* change conf/environments.json spacing to be simpler and more consistent (Michael Ficarra)
+* Update API to use context.getFilename() instead of .filename. (Loren Segal)
+* Small changes, JSDoc is clarified (Aliaksei Shytkin)
+* Move FileFinder to separate file (Aliaksei Shytkin)
+* Cache if file is not found (Aliaksei Shytkin)
+* Use cache on config files seach (Aliaksei Shytkin)
+* Added .eslintignore to load from parents folders (fixes #681) (Aliaksei Shytkin)
+* fix 'node-modules' typo in docs (Fred K. Schott)
+* Upgrade to the latest version of doctrine. (Brian Di Palma)
+* Document optional filename and default it to `input`. (Loren Segal)
+* Fix: Compatibility for Node 0.8 (Nicholas C. Zakas)
+* Update: Makefile.js now uses shelljs-nodecli (Nicholas C. Zakas)
+* #681 apply all .eslintignore exclusions (Aliaksei Shytkin)
+* Add RuleContext.filename property (for eslint/eslint#468). (Loren Segal)
+* 0.4.3 (Nicholas C. Zakas)
+
+v0.4.3 - March 18, 2014
+
+* 0.4.3 (Nicholas C. Zakas)
+* fixes #682: rewrite no-constant-condition rule (Michael Ficarra)
+* Fixes #673 allow configuration of @return errors via requireReturn - (fixes #673) (Brian Di Palma)
+* Tweaking inline code formatting for "if, while, dowhile" (Peter deHaan)
+* Fixes #677 getJSDocComment() should not search beyond FunctionExpression or FunctionDeclaration parent nodes. (Brian Di Palma)
+* Relaxed enforcement of camelcase rule (Ian Christian Myers)
+* Fixing issue #675. Incorrect triggering of no-else-return rule. (Brian Di Palma)
+* Added style option for wrap-iife (Mathias Schreck)
+* Fix: Issues with named function expressions in no-unused-vars and no-shadow (fixes #662) (Nicholas C. Zakas)
+* Update: camelcase rule now doesn't flag function calls (fixes #656) (Nicholas C. Zakas)
+* Updating documentation description for: no-space-before-semi rule, changing rules to exempt strings with semicolons and test for that condition. Fixes #629. (Jonathan Kingston)
+* Adding in rule no-space-before-semi to prevent spaces before semicolons. fixes #629 (Jonathan Kingston)
+* show NPM version (Paul Verest)
+* adapt code formatting (Mathias Schreck)
+* Added a TextMate 2 integration to the docs (Nate Silva)
+* 0.4.2 (Nicholas C. Zakas)
+
+v0.4.2 - March 3, 2014
+
+* 0.4.2 (Nicholas C. Zakas)
+* fixes #651: disable no-catch-shadow rule in node environment (Michael Ficarra)
+* Fixed context.report message parsing (Ian Christian Myers)
+* fixe #648: wrap-iife rule should actually check that IIFEs are wrapped (Michael Ficarra)
+* Added "stroustrup" option for brace-style (Ian Christian Myers)
+* 0.4.1 (Nicholas C. Zakas)
+
+v0.4.1 - February 27, 2014
+
+* 0.4.1 (Nicholas C. Zakas)
+* Created space-in-brackets rule (Ian Christian Myers)
+* Update: Allow valid-jsdoc to specify replacement tags (fixes #637) (Nicholas C. Zakas)
+* Fix: Ensure getJSDocComment() works for all function declarations (fixes #638) (Nicholas C. Zakas)
+* Added broccoli-eslint to integration docs (Christian)
+* fixes #634: getters/setters shouldn't trigger no-dupe-keys (Michael Ficarra)
+* Update: semi to also enforce not using semicolons (fixes #618) (Nicholas C. Zakas)
+* New Rule: no-constant-condition  - removed SwitchStatement discriminant check  - removed AssignmentExpression with right Identifier  - fixed copy paste error  - added DoWhileStatement, ForStatement based on discussion: https://github.com/eslint/eslint/pull/624 (fixes #621) (Christian)
+* New Rule: no-constant-condition (fixes #621) (Christian)
+* Adding mimosa-eslint to Build System list (dbashford)
+* Fix: Make sure semi flags return statements without a semicolon (fixes #616) (Nicholas C. Zakas)
+* Fix: stylish formatter blue text -> white text (fixes #607) (Nicholas C. Zakas)
+* Fix: radix rule should warn (not throw error) when parseInt() is called without arguments (fixes #611) (Nicholas C. Zakas)
+* Update README.md (Dmitry)
+* Adding JSDoc comments for TAP format helper functions (Jonathan Kingston)
+* Updating documentation to include TAP format option (Jonathan Kingston)
+* Fixing validation issues to TAP formatter (Jonathan Kingston)
+* Adding TAP formatter and basic tests (Jonathan Kingston)
+* Docs: Updated integrations page (Nicholas C. Zakas)
+* 0.4.0 (Nicholas C. Zakas)
+
+v0.4.0 - February 12, 2014
+
+* 0.4.0 (Nicholas C. Zakas)
+* Change: Switch :after to :exit (fixes #605) (Nicholas C. Zakas)
+* Fix: Make sure no-unused-vars doesn't get confused by nested functions (fixes #584) (Nicholas C. Zakas)
+* Update: .eslintrc to check more things (Nicholas C. Zakas)
+* Fix: Make sure JSDoc parser accepts JSDoc3-style optional parameters (Nicholas C. Zakas)
+* Docs: Update documentation with linking instructions for ESLintTester (Nicholas C. Zakas)
+* New Rule: valid-jsdoc (fixes #536) (Nicholas C. Zakas)
+* #595 improved func-names documentation (Kyle Nunery)
+* #595 added more func-names tests (Kyle Nunery)
+* #595 fix rule message and add more tests (Kyle Nunery)
+* use optionator for option parsing, not optimist (George Zahariev)
+* Include instructions for working with ESLintTester (Nicholas C. Zakas)
+* #595 remove needless 'function Foo() {}' in tests (Kyle Nunery)
+* #595 fix whitespace (Kyle Nunery)
+* #595 fix markdown for js code blocks (Kyle Nunery)
+* Adding information about Yeomen generator (Ilya Volodin)
+* #595 add docs for rule func-names (Kyle Nunery)
+* #595 add func-names rule (Kyle Nunery)
+* migrate variables array to map (Brandon Mills)
+* Perf: Move try-catch out of verify() function to allow V8 optimization (refs #574) (Nicholas C. Zakas)
+* Docs: Added instructions for running npm run profile (Nicholas C. Zakas)
+* refactor variable name lookup into a separate function (Brandon Mills)
+* optimize findVariable() in no-unused-vars (Brandon Mills)
+* move to tests/bench (Chris Dickinson)
+* add `npm run profile`. (Chris Dickinson)
+* #586 refactor based on https://github.com/eslint/eslint/pull/590#discussion_r9476367 (Christian)
+* #586 added no-unreachable jsdoc, documentation note on hoisting case (Christian)
+* #586 add hoisting check to no-unreachable (Christian)
+* readme: Remove stray asterisk (Timo Tijhof)
+* #580 Remove eslint.getAllComments(), related docs, related tests (Christian)
+* Added test for bug fix #582. Test Passes (Shmueli Englard)
+* Added curly braces to if statment (Shmueli Englard)
+* Added new test for fix to #582 (fixes 582) (Shmueli Englard)
+* Bug #582: Added check if node.value isn't a string just exit (Shmueli Englard)
+* Update Rule: implement curly options for single-statement bodies (fixes #511) (Nicholas C. Zakas)
+* New Rule: no-extra-boolean-cast (fixes #557) (Brandon Mills)
+* New Rule: no-sparse-arrays (fixes #499) (Nicholas C. Zakas)
+* Fix: no-spaced-func is now an error (Nicholas C. Zakas)
+* New Rule: no-process-exit (fixes #568) (Nicholas C. Zakas)
+* New Rule: no-labels (fixes #550) (Nicholas C. Zakas)
+* New Rule: no-lone-blocks (fixes #512) (Brandon Mills)
+* Added Emacs/Flycheck integration (Nikolai Prokoschenko)
+* Build: Add perf test (Nicholas C. Zakas)
+* Fix: no-cond-assign shouldn't throw error when there's a for loop with an empty conditional (fixes #53) (Nicholas C. Zakas)
+* Docs: Add docs for no-regex-spaces and all doc errors now break build (closes #562) (Nicholas C. Zakas)
+* Rename: regex-spaces to no-regex-spaces (Nicholas C. Zakas)
+* Docs: Add docs for no-underscore-dangle (refs #562) (Nicholas C. Zakas)
+* Docs: Add docs for no-undef-init (refs #562) (Nicholas C. Zakas)
+* Docs: Add docs for no-return-assign (refs #562) (Nicholas C. Zakas)
+* Fix: Misspelling in no-return-assign message (Nicholas C. Zakas)
+* Docs: Add docs for no-new-wrappers (refs #562) (Nicholas C. Zakas)
+* Docs: Add docs for no-new-object (refs #562) (Nicholas C. Zakas)
+* Docs: Add docs for no-implied-eval (refs #562) (Nicholas C. Zakas)
+* Docs: Updated documentation for developing rules (Nicholas C. Zakas)
+* Testing: Move ESLintTester to be external dependency (fixes #480) (Nicholas C. Zakas)
+* Docs: Add list of known integrations (Nicholas C. Zakas)
+* Fix #570 (dmp42)
+* document no-array-constructor rule (Michael Ficarra)
+* fixes #500: no-array-constructor should not flag 1-argument construction (Michael Ficarra)
+* fixes #501: no-array-constructor recognises CallExpression form (Michael Ficarra)
+* rename no-new-array rule to no-array-constructor; ref #501 (Michael Ficarra)
+* Fix: Make radix rule warn on invalid second parameter (fixes #563) (Nicholas C. Zakas)
+* Docs: Added no-floating-decimal docs (refs #562) (Nicholas C. Zakas)
+* New Rule: no-path-concat (fixes #540) (Nicholas C. Zakas)
+* Docs: Add some missing rule docs (refs #562) (Nicholas C. Zakas)
+* Fix: CLI should not output anything when there are no warnings (fixes #558) (Nicholas C. Zakas)
+* New Rule: no-yoda (fixes #504) (Nicholas C. Zakas)
+* New Rule: consistent-return (fixes #481) (Nicholas C. Zakas)
+* Rewrite configuration documentation to include information about globals (fixes #555) (Nicholas C. Zakas)
+* Allow YAML configuration files (fixes #491) (Nicholas C. Zakas)
+* 0.3.0 (Nicholas C. Zakas)
+
+v0.3.0 - January 20, 2014
+
+* 0.3.0 (Nicholas C. Zakas)
+* Config: Allow comments in JSON configuration files (fixes #492) (Nicholas C. Zakas)
+* Bug: max-len fix to report correct line number (fixes #552) (Nicholas C. Zakas)
+* Build: Use browserify to create browser-ready ESLint (fixes #119) (Nicholas C. Zakas)
+* Docs: Ensure all rules have entry on top-level rules index page (Nicholas C. Zakas)
+* Docs: Add docs for no-fallthrough rule (Nicholas C. Zakas)
+* Update README.md (Peter deHaan)
+* Update README.md (Peter deHaan)
+* Update package.json (Peter deHaan)
+* Docs: Added documentation for semi rule (Nicholas C. Zakas)
+* Build: Reset branch coverage target (Nicholas C. Zakas)
+* Update build system to generate eslint.org during release (Nicholas C. Zakas)
+* Updated setup doc (Nicholas C. Zakas)
+* Fix #525 & #528 (Mangled Deutz)
+* Improve no-negated-in-lhs description (David Bruant)
+* Fixing typo (David Bruant)
+* Update no-new.md (Tamas Fodor)
+* Update no-extra-semi.md (Tamas Fodor)
+* Fixing broken links in documentation (Ilya Volodin)
+* Update about page (Nicholas C. Zakas)
+* Site generation build step and documentation updates to support it (fixes #478) (Nicholas C. Zakas)
+* Change message for brace-style rule (fixes #490) (Nicholas C. Zakas)
+* Add question about ES6 support to FAQ (fixes #530) (Nicholas C. Zakas)
+* Set unlimited number of listeners for event emitter (fixes #524) (Nicholas C. Zakas)
+* Add support for comment events (fixes #531) Add :after events for comments (Nicholas C. Zakas)
+* Add :after events for comments (Nicholas C. Zakas)
+* Allow config files to have any name (fixes #486). (Aparajita Fishman)
+* List available formatters (fixes #533). (Aparajita Fishman)
+* Add support for comment events (fixes #531) (Nicholas C. Zakas)
+* Add Stylish formatter and make it default. Fixes #517 (Sindre Sorhus)
+* Fix missing code exit (Mangled Deutz)
+* Added unit test for calling Config.getConfig with no arguments. (Aparajita Fishman)
+* Typo (Mangled Deutz)
+* Fixed docs typo (Nicholas C. Zakas)
+* Mark functions as used when any method is called on them (Nicholas C. Zakas)
+* Fixed: Config.getConfig is called either with a file path or with no args (fixes #520) (Aparajita Fishman)
+* Fix minor bug in no-empty rule (Nicholas C. Zakas)
+* add more info for failure messages (Nicholas C. Zakas)
+* Add ruleId to all formatters output (fixes #472) (Nicholas C. Zakas)
+* Remove unused code (Nicholas C. Zakas)
+* Correctly handle case with both finally and catch in no-empty (Nicholas C. Zakas)
+* Update documentation for no-unused-vars (Nicholas C. Zakas)
+* Ensure that bound function expressions are reported as being used (fixes #510) (Nicholas C. Zakas)
+* Allow empty catch/finally blocks (fixes #514) and update documentation (fixes #513) (Nicholas C. Zakas)
+* Updated contribution guidelines (Nicholas C. Zakas)
+* Add default setting for no-cond-assign (Nicholas C. Zakas)
+* Add build step to check rule consistency (Nicholas C. Zakas)
+* update docs: explicit cli args are exempt from eslintignore exclusions (Michael Ficarra)
+* fixes #505: no-cond-assign should ignore doubly parenthesised tests (Michael Ficarra)
+* Renamed unnecessary-strict to no-extra-strict (Nicholas C. Zakas)
+* Fixed missing documentation links (Nicholas C. Zakas)
+* Add build task to check for missing docs and tests for rules (Nicholas C. Zakas)
+* Slight reorganization of rule groups (Nicholas C. Zakas)
+* Added one-var and sorted some rules (Nicholas C. Zakas)
+* Updated Travis badge for new location (Nicholas C. Zakas)
+* fixes #494: allow shebangs in processed JS files (Michael Ficarra)
+* fixes #496: lint ignored files when explicitly specified via the CLI (Michael Ficarra)
+* More tests (Ilya Volodin)
+* Upgrade Istanbul (Ilya Volodin)
+* fixes #495: holey arrays cause no-comma-dangle rule to throw (Michael Ficarra)
+* Documentation and minor changes (Ilya Volodin)
+* Adding missing package registration (Ilya Volodin)
+* Adding support for .eslintignore and .jshintignore (Closes #484) (Ilya Volodin)
+* fixes #482: brace-style bug with multiline conditions (Michael Ficarra)
+* Switching Travis to use ESLint (Closes #462) (Ilya Volodin)
+* 0.2.0 (Nicholas C. Zakas)
+
+v0.2.0 - January 1, 2014
+
+* 0.2.0 (Nicholas C. Zakas)
+* Bump code coverage checks (Nicholas C. Zakas)
+* Take care of unreachable code in case statement (Nicholas C. Zakas)
+* Updated rule messaging and added extra tests (Nicholas C. Zakas)
+* Fixing eslint errors and unittests (Ilya Volodin)
+* Rule: max-nested-callbacks (Ian Christian Myers)
+* Fix fall-through rule with nested switch statements (fixes #430) (Nicholas C. Zakas)
+* Fixed trailing comma (Nicholas C. Zakas)
+* Added more tests for func-style (Nicholas C. Zakas)
+* Fixed documentation for func-style (Nicholas C. Zakas)
+* Fixed linting error (Nicholas C. Zakas)
+* Rule to enforce function style (fixes #460) (Nicholas C. Zakas)
+* Rule is off by default. Updated documentation (Ilya Volodin)
+* Rule: sort variables. Closes #457 (Ilya Volodin)
+* Update architecture.md (Nicholas C. Zakas)
+* Change quotes option to avoid-escapes and update docs (fixes #199) (Brandon Payton)
+* Add allow-avoiding-escaped-quotes option to quotes rule (fixes #199) (Brandon Payton)
+* Update no-empty-class.md (Nicholas C. Zakas)
+* Updated titles on all rule documentation (fixes #348) (Nicholas C. Zakas)
+* Fixing eslint errors in codebase (Ilya Volodin)
+* fixes #464: space-infix-ops checks for VariableDeclarator init spacing (Michael Ficarra)
+* Add options to no-unused-vars. Fixes #367 (Ilya Volodin)
+* rename escape function to xmlEscape in checkstyle formatter (Michael Ficarra)
+* The semi rule now reports correct line number (Ian Christian Myers)
+* context.report now takes optional location (Ian Christian Myers)
+* fixes #454: escape values for XML in checkstyle formatter (Michael Ficarra)
+* Add color to Mocha test reporting (Ian Christian Myers)
+* Rule no-nested-ternary (Ian Christian Myers)
+* Fixing no-unused-var and no-redeclare (Ilya Volodin)
+* fixes #449: no-mixed-requires throws TypeError when grouping is enabled (Michael Ficarra)
+* Fixed reported line number for trailing comma error (Ian Christian Myers)
+* Update doc title for quote (Matthew DuVall)
+* fixes #446: join paths without additional delimiters (Michael Ficarra)
+* docs: add documentation for quotes rule (Matthew DuVall)
+* minor style changes to lib/rules/space-infix-ops.js as requested in #444 (Michael Ficarra)
+* remove "function invalid(){ return D }" from some tests (Michael Ficarra)
+* fixes #429: require spaces around infix operators; enabled by default (Michael Ficarra)
+* simplify fix for #442 (Michael Ficarra)
+* Fix broken test, ensure tests get run before a release is pushed (Nicholas C. Zakas)
+* 0.1.4 (Nicholas C. Zakas)
+
+v0.1.4 - December 5, 2013
+
+* 0.1.4 (Nicholas C. Zakas)
+* Add release scripts to package.json (Nicholas C. Zakas)
+* Fixed release error in Makefile (Nicholas C. Zakas)
+* Fix JSHint warnings (Nicholas C. Zakas)
+* Make sure 'default' isn't flagged by no-space-returns-throw rule (fixes #442) (Nicholas C. Zakas)
+* Fixing documentation (Ilya Volodin)
+* Fixing disabling rules with invalid comments Closes #435 (Ilya Volodin)
+* improve assertion on wrong number of errors (Christoph Neuroth)
+* fixes #431: no-unused-expressions should not flag statement level void (Michael Ficarra)
+* fixes #437: fragile no-extend-native rule (Michael Ficarra)
+* change space-* rule documentation headers to be more descriptive (Michael Ficarra)
+* Moved to tabs, added comments, a few more tests (Jamund Ferguson)
+* split GH-332 rule into space-unary-word-ops and space-return-throw-case (Michael Ficarra)
+* fixes #346: validate strings passed to the RegExp constructor (Michael Ficarra)
+* change some documentation extensions from js to md (Michael Ficarra)
+* fixes #332: unary word operators must be followed by whitespace (Michael Ficarra)
+* Add some docs (Jamund Ferguson)
+* DRYing cli tests and improving code coverage (Ilya Volodin)
+* fixes #371: add no-shadow-restricted-names rule (Michael Ficarra)
+* Added Support for Object.defineProperty() checking (Jamund Ferguson)
+* fixes #333: add rule to disallow gratuitously parenthesised expressions (Michael Ficarra)
+* improve rule test coverage (Michael Ficarra)
+* No Extend Native (Jamund Ferguson)
+* change getTokens 2nd/3rd arguments to count tokens, not characters (Michael Ficarra)
+* fixes #416: no-fallthrough flagging last case + reporting wrong line num (Michael Ficarra)
+* fixes #415: fix unnecessary-strict rule false positives (Michael Ficarra)
+* Add missing dependency (Nicholas C. Zakas)
+* Update docs related to running unit tests (Nicholas C. Zakas)
+* Add JSHint as missing dependency (Nicholas C. Zakas)
+* Switch to using ShellJS makefile (fixes #418) (Nicholas C. Zakas)
+* Updated documentation to reflect test changes (refs #417) (Nicholas C. Zakas)
+* Change to eslintTester.addRuleTest (fixes #417) (Nicholas C. Zakas)
+* Fix false positives for no-script-url (fixes #400) (Nicholas C. Zakas)
+* Fix lint warning (Nicholas C. Zakas)
+* Fixing ESLint warnings, introducing Makefile.js (not yet wired in) (Nicholas C. Zakas)
+* fixes #384: include builtin module list to avoid repl dependency (Michael Ficarra)
+* 0.1.3 (Nicholas C. Zakas)
+
+v0.1.3 - November 25, 2013
+
+* 0.1.3 (Nicholas C. Zakas)
+* Updated changelog (Nicholas C. Zakas)
+* Vows is gone. Mocha is now default (Ilya Volodin)
+* fixes #412: remove last remaining false positives in no-spaced-func (Michael Ficarra)
+* fixes #407: no-spaced-func rule flagging non-argument-list spaced parens (Michael Ficarra)
+* Add no-extra-semi to configuration (fixes #386) (Nicholas C. Zakas)
+* Converting formatter tests and core (Ilya Volodin)
+* Don't output anything when there are no errors in compact formatter (fixes #408) (Nicholas C. Zakas)
+* Removing Node 0.11 test - it fails all the time (Nicholas C. Zakas)
+* Completing conversion of rule's tests to mocha (Ilya Volodin)
+* added mocha conversion tests for strict, quote-props and one-var; enhanced one of the invalid one-var tests that was expecting two messages (Michael Paulukonis)
+
+
+v0.1.2 - November 23, 2013
+
+* 0.1.2 (Nicholas C. Zakas)
+* added mocha tests for radix and quotes; fixed some of the internals on quotes from vows annotations (Michael Paulukonis)
+* added tests for regex-spaces, strict, unnecessary-strict; fixed some types in overview/author notes in other tests. (Michael Paulukonis)
+* Converting unittests to mocha (Ilya Volodin)
+* mocha conversions of tests for 'use-isnan' and 'wrap-iife' (Michael Paulukonis)
+* added mocha tests semi.js and wrap-regex.js (Michael Paulukonis)
+* Converting more tests to mocha (Ilya Volodin)
+* Update CONTRIBUTING.md (Nicholas C. Zakas)
+* Cleaning up eslintTester (Ilya Volodin)
+* DRYing unittests and converting them to mocha (Ilya Volodin)
+* Reformatted Gruntfile (Nicholas C. Zakas)
+* Add tests to config load order: base, env, user. (icebox)
+* Fixing indent in gruntfile (Ilya Volodin)
+* Removing jake, adding Grunt, Travis now runs grunt (Ilya Volodin)
+* Add rules per environments to config. (icebox)
+* Add globals property to the environments. (icebox)
+* Fix error about IIFE if the function is in a new (Marsup)
+* Fix a broken link in the docs (Brian J Brennan)
+* Add test coverage for additional cases, fix open paren at beginning of expr (Matthew DuVall)
+* Fixing no-undef for eval use case (Ilya Volodin)
+* fixes #372: disallow negated left operand in `in` operator (Michael Ficarra)
+* Fixing no-self-compare rule to check for operator (Ilya Volodin)
+* bug: open parens in args causes no-spaced-func to trigger (Matthew DuVall)
+* fixes #369: restrict UnaryExpressions to delete in no-unused-expressions (Michael Ficarra)
+* Make sure delete operator isn't flagged as unused expression (fixes #364) (Nicholas C. Zakas)
+* Don't flag ++ or -- as unused expressions (fixes #366) (Nicholas C. Zakas)
+* Ensure that 'use strict' isn't flagged as an unused expression (fixes #361) (Nicholas C. Zakas)
+* Increase test coverage for strict-related rules (refs #361) (Nicholas C. Zakas)
+* Up code coverage numbers (Nicholas C. Zakas)
+* Fixes error in new-cap rule when 'new' is used without a constructor (fixes #360) (Nicholas C. Zakas)
+* added files array in package json (Christian)
+* removed unused jshint dependency (Christian)
+* Add test coverage for new Foo constructor usage (Matt DuVall)
+* Pull code coverage up by removing unused method (Matt DuVall)
+* recognise CallExpression variant of RegExp ctor in no-control-regex rule (Michael Ficarra)
+* Merge smart-eqeqeq into eqeqeq (Matt DuVall)
+* Catch additional cases for a.b, new F, iife (Matt DuVall)
+* 0.2.0-dev (Nicholas C. Zakas)
+* Version 0.1.0 (Nicholas C. Zakas)
+* rule: no-spaced-func disallow spaces between function identifier and application (Matt DuVall)
+
+v0.1.1 - November 09, 2013
+
+* Ensure mergeConfigs() doesn't thrown an error when keys are missing in base config (fixes #358) (Nicholas C. Zakas)
+
+v0.1.0 - November 03, 2013
+
+* Version 0.1.0 (Nicholas C. Zakas)
+* Updated Readme for v0.1.0 (Nicholas C. Zakas)
+* Bump code coverage verification to 95% across the board (Nicholas C. Zakas)
+* Fixed broken links (Nicholas C. Zakas)
+* Added information about runtime rules (Nicholas C. Zakas)
+* Added documentation about configuration files (Nicholas C. Zakas)
+* Added description of -v option (Nicholas C. Zakas)
+* Updated architecture documentation (Nicholas C. Zakas)
+* Fix bug in no-control-regex (fixes #347) (Nicholas C. Zakas)
+* Fix link to architecture doc in readme (azu)
+* Rule: No control characters in regular expressions (fixes #338) (Nicholas C. Zakas)
+* Add escaping \= test (Matt DuVall)
+* Add docs for rule (Matt DuVall)
+* rule: no-div-regex for catching ambiguous division operators in regexes (Matt DuVall)
+* Change context-var to block-scoped-var (Matt DuVall)
+* Implement config.globals (Oleg Grenrus)
+* Add 'config-declared global' test (Oleg Grenrus)
+* Adding ability to separate rules with comma (Ilya Volodin)
+* Added rule for missing 'use strict' (fixes #321) (Nicholas C. Zakas)
+* Fixing unittests and finishing code (Ilya Volodin)
+* Disabling/enabling rules through comments (Ilya Volodin)
+* Rename rule to context-var and add documentation (Matt DuVall)
+* Added link to no-global-strict doc in readme (Nicholas C. Zakas)
+* Add try-catch scoping with tests (Matt DuVall)
+* Fix linting error (Matt DuVall)
+* Store FunctionDeclarations in scope as they can be used as literals (Matt DuVall)
+* Fix to use getTokens and add test for MemberExpression usage (Matt DuVall)
+* rule: block-scope-var to check for variables declared in block-scope (Matt DuVall)
+* no-unused-expressions rule: add test and doc mention for `a && b()` (Michael Ficarra)
+* rule: wrap-regex for parens around regular expression literals (Matt DuVall)
+* fixes #308: implement no-unused-expressions rule; ref. jshint rule W030 (Michael Ficarra)
+* Updated change log script to filter out merge messages (Nicholas C. Zakas)
+* Updated changelog (Nicholas C. Zakas)
+* 0.1.0-dev (Nicholas C. Zakas)
+
+v0.0.9 - October 5, 2013
+
+* Version 0.0.9 release (Nicholas C. Zakas)
+* Added rule for no global strict mode (fixes #322) (Nicholas C. Zakas)
+* Change default on to be errors instead of warnings (fixes #326) (Nicholas C. Zakas)
+* Fixed bug where JSHint was using the wrong file in lint task (Nicholas C. Zakas)
+* Updated docs for no-unused vars rule. (Andrew de Andrade)
+* Removed console.log in tests. (Andrew de Andrade)
+* Added link to roadmap and JSHint feature parity list. (Andrew de Andrade)
+* Fixed warning when unused var declared as param in FunctionExpression/Declaration can be ignored because later param is used (Andrew de Andrade)
+* Rename test for smartereqeqeq.js to smarter-eqeqeq.js (Andrew de Andrade)
+* Keep test filename inline with rule name (Andrew de Andrade)
+* Added further instructions for multiline test cases. (Andrew de Andrade)
+* Protecting private method (Seth McLaughlin)
+* Updating look up algorithm for local config files (Seth McLaughlin)
+* Fixing ESLint errors (Ilya Volodin)
+* Implemented local default config file (Seth McLaughlin)
+* Upgrading escope version and fixing related bugs (Ilya Volodin)
+* Fixing assignment during initialization issue (Ilya Volodin)
+* add plain-English regexp description to no-empty-class rule (Michael Ficarra)
+* fixes #289: no-empty-class flags regexps with... flags (Michael Ficarra)
+* Rule: no-catch-shadow (Ian Christian Myers)
+* Update no-empty for compatibility with esprima@1.0.4 (fixes #290) (Mark Macdonald)
+* Fixing bug with _ in MemberExpression (Ilya Volodin)
+* Rule: no-func-assign (Ian Christian Myers)
+* Fix false warning from no-undef rule (fixes #283) (Mark Macdonald)
+* Adding eslint to jake (Ilya Volodin)
+* Rule no redeclare (Ilya Volodin)
+* Fixing no use before define issues (Ilya Volodin)
+* Rule: no-octal-escape (Ian Christian Myers)
+* Fix for `no-proto` and `no-iterator` false positive (Ian Christian Myers)
+* Rule: no-iterator (Ian Christian Myers)
+* Fixing type in guard-for-in documentation (Ilya Volodin)
+* Rule No use before define (Ilya Volodin)
+* Added documentation for the `no-new` rule (Ian Christian Myers)
+* Added documentation for the `no-eval` rule (Ian Christian Myers)
+* Added documentation for the `no-caller` rule (Ian Christian Myers)
+* Added documentation for the `no-bitwise` rule (Ian Christian Myers)
+* simplify no-empty-class rule (Michael Ficarra)
+* Fix `no-empty-class` false negatives (Ian Christian Myers)
+* Added documentation for the `no-alert` rule (Ian Christian Myers)
+* Added documentation for the `new-parens` rule (Ian Christian Myers)
+* Added documentation for the `max-params` rule (Ian Christian Myers)
+* Added documentation for `max-len` rule (Ian Christian Myers)
+* Created link from rules README.md to no-plusplus.md documentation (Ian Christian Myers)
+* Added documentation for `guard-for-in` rule (Ian Christian Myers)
+* Added documentation for `dot-notation` rule (Ian Christian Myers)
+* Added documentation for `curly` rule (Ian Christian Myers)
+* Updated `camelcase` rule documentation (Ian Christian Myers)
+* Added documentation for `complexity` rule (Ian Christian Myers)
+* Changed `no-dangle` documentation to `no-comma-dangle` (Ian Christian Myers)
+* Rule: no-empty-class (Ian Christian Myers)
+* Increased test coverage for max-depth (Ian Christian Myers)
+* Increased test coverage for no-shadow (Ian Christian Myers)
+* Increased test coverage on no-mixed-requires (Ian Christian Myers)
+* Added docs for eqeqeq and no-with (fixes #262) (Raphael Pigulla)
+* Create camelcase.md (Micah Eschbacher)
+* Fix issues with function in no-unused-vars (Ilya Volodin)
+* Rule: No shadow (Ilya Volodin)
+* fixes #252: semi rule errors on VariableDeclarations in ForInStatements (Michael Ficarra)
+* rule: max-len to lint maximum length of a line (Matt DuVall)
+* Fixes #249 (Raphael Pigulla)
+* Merge branch 'master' of https://github.com/beardtwizzle/eslint (Jonathan Mahoney)
+* Re-add lines that were accidentally deleted from config (Jonathan Mahoney)
+* Add support for pre-defined environment globals (re: #228) (Jonathan Mahoney)
+* Rule: no-else-return (Ian Christian Myers)
+* Re-add lines that were accidentally deleted from config (Jonathan Mahoney)
+* Add support for pre-defined environment globals (re: #228) (Jonathan Mahoney)
+* Fix no-unused-vars to report correct line numbers (Ilya Volodin)
+* Rule: no proto (Ilya Volodin)
+* Rule: No Script URL (Ilya Volodin)
+* Rule: max-depth (Ian Christian Myers)
+* Fix: Error severity for rules with options. (Ian Christian Myers)
+* Rule: No wrap func (Ilya Volodin)
+* bug: Fixes semi rule for VariableDeclaration in ForStatement (Matt DuVall)
+* Individual perf tests for rules (Ilya Volodin)
+* Fix loading rules from a rules directory (Ian Christian Myers)
+* Rule no-mixed-requires (fixes #221) (Raphael Pigulla)
+* bug: Add ForStatement for no-cond-assign check (Matthew DuVall)
+* JSLint XML formatter now escapes special characters in the evidence and reason attributes. (Ian Christian Myers)
+* Formatter: JSLint XML (Ian Christian Myers)
+* Refactored `max-statements` rule. (Ian Christian Myers)
+* Fix tests broken due to new rule message text (James Allardice)
+* Merge branch 'master' into match-jshint-messages (James Allardice)
+* Refactored `one-var` rule. (Ian Christian Myers)
+* split eslint.define into eslint.defineRule and eslint.defineRules (Michael Ficarra)
+* Removed unnecessary rules.js test. (Ian Christian Myers)
+* Rule: one-var (Ian Christian Myers)
+* Rule: No unused variables (Ilya Volodin)
+* expose interface for defining new rules at runtime without fs access (Michael Ficarra)
+* disallow 00 in no-octal rule (Michael Ficarra)
+* Increased test coverage for `lib/cli.js`. (Ian Christian Myers)
+* Increased test coverage for `lib/rules.js` (Ian Christian Myers)
+* Increased test coverage for jUnit formatter. (Ian Christian Myers)
+* scripts/bundle: output bundle+map to /build directory (Michael Ficarra)
+* add test for 0X... hex literals in no-octal tests (Michael Ficarra)
+* fixes #200: no-octals should not see leading-0 floats as violations (Michael Ficarra)
+* add back tests for loading rules from a directory (Michael Ficarra)
+* add back in ability to load rules from a directory (Michael Ficarra)
+* Increased test coverage for `complexity` rule. (Ian Christian Myers)
+* Increased test coverage for `max-params` rule. (Ian Christian Myers)
+* also output source map when generating bundle (Michael Ficarra)
+* Rule: unnecessary-strict (Ian Christian Myers)
+* Improve performance of getTokens (Ilya Volodin)
+* Performance jake task (Ilya Volodin)
+* don't force explicit listing of rules; generate listing for bundle (Michael Ficarra)
+* Rule: no-dupe-keys (Ian Christian Myers)
+* fixes #145: create a browser bundle (Michael Ficarra)
+* Fixing no-caller bug (Ilya Volodin)
+* Check for use of underscore library as an exception for var declarations (Matthew DuVall)
+* Merge branch 'master' of https://github.com/nzakas/eslint into no-underscore-dangle (Matthew DuVall)
+* Fixing spelling (Ilya Volodin)
+* Rule: no-empty-label (Ilya Volodin)
+* Add builtin globals to the global scope (fixes #185) (Mark Macdonald)
+* Rule: no-loop-func (Ilya Volodin)
+* Merge branch 'master' of https://github.com/nzakas/eslint into no-underscore-dangle (Matt DuVall)
+* Use proper node declarations and __proto__ exception (Matt DuVall)
+* Updating no-undef patch (see pull request #164) - Simplify parseBoolean() - Make knowledge of```/*jshint*/``` and ```/*global */``` internal to eslint object - Put user-declared globals in Program scope (Mark Macdonald)
+* Rule: no-eq-null (Ian Christian Myers)
+* fixed broken merge (Raphael Pigulla)
+* fixes #143 (Raphael Pigulla)
+* added consistent-this rule (Raphael Pigulla)
+* Rule: no-sync to encourage async usage (Matt DuVall)
+* Update eslint.json with no-underscore-dangle rule (Matt DuVall)
+* Rule: no-underscore-dangle for func/var declarations (Matt DuVall)
+* Warn on finding the bitwise NOT operator (James Allardice)
+* Updating no-undef patch (see pull request #164) 3. Move parsing of ```/*global */``` and ```/*jshint */``` to eslint.js (Mark Macdonald)
+* Warn on finding a bitwise shift operator (fixes #170) (James Allardice)
+* Fix broken test (James Allardice)
+* Add support for the do-while statement to the curly rule (closes #167) (James Allardice)
+* Removing nasty leading underscores (Patrick Brosset)
+* Added tests and test cases for a few files (Patrick Brosset)
+* CLI: -f now accepts a file path (Ian Christian Myers)
+* Updating no-undef patch (see pull request #164) 1. Move predefined globals to ```conf/environments.json``` 2. Move mixin() to ```lib/util.js``` (Mark Macdonald)
+* Match messages to JS[LH]int where appropriate, and ensure consistent message formatting (closes #163) (James Allardice)
+* Add support for the do-while statement to the curly rule (closes #167) (James Allardice)
+* Removing nasty leading underscores (Patrick Brosset)
+* Added tests and test cases for a few files (Patrick Brosset)
+* Merge branch 'master' of github.com:nzakas/jscheck (Nicholas C. Zakas)
+* Added acceptance criteria for rules to docs (Nicholas C. Zakas)
+* Add no-undef (fixes #6) (Mark Macdonald)
+* Fixing no-self-compare (Ilya Volodin)
+* Rule: No multiline strings (Ilya Volodin)
+* CLI refactor to remove process.exit(), file not found now a regular error message, updated formatters to handle this case (Nicholas C. Zakas)
+* Rule: no-self-compare (Ilya Volodin)
+* Rule: No unnecessary semicolons (fixes #158) (Nicholas C. Zakas)
+* Fixed error in no-ex-assign when return statement as found in catch clause (Nicholas C. Zakas)
+* Rename no-exc-assign to no-ex-assign and add to config (Nicholas C. Zakas)
+* Renamed count-spaces to regex-spaces (Nicholas C. Zakas)
+* Documentation updates (Nicholas C. Zakas)
+* Put all rules into strict mode and update docs accordingly (Nicholas C. Zakas)
+* Merge branch 'master' of github.com:nzakas/jscheck (Nicholas C. Zakas)
+* Ensure getScope() works properly when called from Program node (fixes #148) (Nicholas C. Zakas)
+* Rule: wrap-iife (Ilya Volodin)
+* add additional test for no-cond-assign rule (Stephen Murray)
+* Merge branch 'master' of github.com:nzakas/jscheck (Nicholas C. Zakas)
+* Experimental support for Jake as a build system (fixes #151) (Nicholas C. Zakas)
+* fixes #152 (Stephen Murray)
+* add docs for no-exc-assign (Stephen Murray)
+* Merge branch 'master' of https://github.com/nzakas/eslint into no-new-object-array-literals (Matt DuVall)
+* Merge branch 'master' of https://github.com/nzakas/eslint into count-spaces (Matt DuVall)
+* Added a test for getting global scope from Program node (refs #148) (Nicholas C. Zakas)
+* Add positive test case for `object.Array` (Matthew DuVall)
+* Only support space characters for repetitions (Matthew DuVall)
+* fix line length per code conventions (Stephen Murray)
+* fix indentation per code conventions (Stephen Murray)
+* fixes #149 (Stephen Murray)
+* Rule: no-ternary (Ian Christian Myers)
+* Check that the return statement has an argument before checking its type (James Allardice)
+* Rule: count-spaces for multiple spaces in regular expressions (Matt DuVall)
+* Update eslint.json configuration file for literal rules (Matt DuVall)
+* Created no-label-var rule. (Ian Christian Myers)
+* Rule: no-new-array and no-new-object (Matt DuVall)
+* Added ability to retrieve scope using escope. (Ian Christian Myers)
+* Corrected unused arguments (Patrick Brosset)
+* Reporting function complexity on function:after and using array push/pop to handle nesting (Patrick Brosset)
+* Fixing style issues discovered while npm testing (Patrick Brosset)
+* First draft proposal for a cyclomatic complexity ESLint rule (Patrick Brosset)
+* Corrected file extension on no-plusplus rule documentation. (Ian Christian Myers)
+* Documentation for no-delete-var rule. Closes #129 (Ilya Volodin)
+* Rule: max-statements (Ian Christian Myers)
+* Better documentation for the `no-plusplus` rule. (Ian Christian Myers)
+* Rule: no-plusplus (Ian Christian Myers)
+* Rule: no assignment in return statement (Ilya Volodin)
+* Updating max-params rule name (Ilya Volodin)
+* Rule: Function has too many parameters (Ilya Volodin)
+* Removing merge originals (Ilya Volodin)
+* Rebasing on master (Ilya Volodin)
+* Rule: Variables should not be deleted (Ilya Volodin)
+* Fixes incorrect reporting of missing semicolon (Ian Christian Myers)
+* Rebase against master branch (Mathias Bynens)
+* Rule to warn on use of Math and JSON as functions (James Allardice)
+* Formatter: Checkstyle (Ian Christian Myers)
+* docs: Clean up structure (Mathias Bynens)
+* Merging no-native-reassign and no-redefine (Ilya Volodin)
+* Rule: no native reassignment (Ilya Volodin)
+* 0.0.8-dev (Nicholas C. Zakas)
+* v0.0.7 released (Nicholas C. Zakas)
+* Updated Tests, etc. (Jamund Ferguson)
+* Added jUnit Support (Fixes #16) (Jamund Ferguson)
+
+v0.0.7 - July 22, 2013
+
+* 0.0.7 (Nicholas C. Zakas)
+* Add code coverage checks to npm test and update rule tests to have better coverage (Nicholas C. Zakas)
+* Fixed CLI output on serial programatic executions (Ian Christian Myers)
+* Removes line length from code style convention docs (Josh Perez)
+* Adds escapeRegExp and fixes documentation (Josh Perez)
+* Add quotes rule and test coverage for configuration options (Matt DuVall)
+* Adds templating for lint messages and refactors rules to use it (Josh Perez)
+* Fixes lint rules for unchecked test file (Josh Perez)
+* Changes dotnotation rule to match JSHint style (Josh Perez)
+* Change configInfo to options and add test coverage (Matt DuVall)
+* Merge branch 'master' of https://github.com/nzakas/eslint into optional-args-for-rule (Matt DuVall)
+* Adds dot notation lint rule (Josh Perez)
+* Strip trailing underscores in camelcase rule - Fixes #94 (Patrick Brosset)
+* add mailing list link (Douglas Campos)
+* Strip leading underscores in camelcase rule - Fixes #94 (Patrick Brosset)
+* Created no-dangle rule. (Ian Christian Myers)
+* Fixed rule name (James Allardice)
+* Make sure the callee type is Identifier (James Allardice)
+* Add rule for implied eval via setTimeout/Interval (James Allardice)
+* Fix rule name in config (James Allardice)
+* Fixes #90 -- updates docstrings (Stephen Murray)
+* Fixes issue with fs.existsSync on NodeJS 0.6 (Ian Christian Myers)
+* Fixing -c config option. (Ian Christian Myers)
+* Allow arrays to be passed as multiple args to rule (Matt DuVall)
+* Test to make sure empty case with one line break is safe (Matt DuVall)
+* Rule: The Function constructor is eval (Ilya Volodin)
+* Enabled require("eslint") and exposed out CLI. (Ian Christian Myers)
+* Adds test and fix for issue #82 (Mark Macdonald)
+* Merge branch 'master' of https://github.com/nzakas/eslint into ok (Yusuke Suzuki)
+* Created brace-style rule. (Ian Christian Myers)
+* Formatters can now process multiple files at once (Jamund Ferguson)
+* Rule: Do not use 'new' for side effects (Ilya Volodin)
+* Adds smarter-eqeqeq rule (Josh Perez)
+* Add EditorConfig file for consistent editor/IDE behavior (Jed Hunsaker)
+* Fix the positive case for no-unreachable where there is no return statement at all, or if the return is at the end. Those cases should not return any errors. The error condition was not be checked before throwing the rule error. (Joel Feenstra)
+* Adds test and fix for no-octal on 0 literal (Mark Macdonald)
+* Don't report no-empty warnings when a parent is FunctionExpression / FunctionDeclaration (Yusuke Suzuki)
+* Add api.getAncestors (Yusuke Suzuki)
+* Ensure estraverse version 1.2.0 or later (Yusuke Suzuki)
+* Fixes no-alert lint rule for non identifier calls (Josh Perez)
+* Fixes exception when init is null (Josh Perez)
+* Fixes no-octal check to only check for numbers (Josh Perez)
+* 0.0.7-dev (Nicholas C. Zakas)
+* 0.0.6 (Nicholas C. Zakas)
+* Follow the rule naming conventions (James Allardice)
+* Add rule for missing radix argument to parseInt (James Allardice)
+* Allow return, falls-through comment, and throw for falls-through (Matt DuVall)
+* Merge branch 'master' of https://github.com/nzakas/eslint into rule-fall-through (Matt DuVall)
+* Globals are not good, declare len (Matt DuVall)
+* Rule to add no-fall-through (Matt DuVall)
+
+v0.0.6 - July 16, 2013
+
+* 0.0.6 (Nicholas C. Zakas)
+* Changed semi rule to use tokens instead of source (Nicholas C. Zakas)
+* Renaming new-parens rule (Ilya Volodin)
+* Renaming no-new-wrappers rule and adding tests (Ilya Volodin)
+* Add license URL (Nick Schonning)
+* Remove unused sinon requires (Nick Schonning)
+* Remove redundant JSHint directives (Nick Schonning)
+* Rule: Do not use constructor for wrapper objects (Ilya Volodin)
+* Test node 0.11 unstable but allow it to fail (Nick Schonning)
+* Rule: Constructor should use parentheses (Ilya Volodin)
+* Fix reference to "CSS Lint" in Contributing documentation (Brian McKenna)
+* Add git attributes file for line endings (Andy Hu)
+* Rename to create an 'index' file in GH web view (Evan Goer)
+* Avoid accidentally creating a markdown link (Evan Goer)
+* Add headings and correct internal links (Evan Goer)
+* Add wiki files to docs directory (Evan Goer)
+* Add rules for leading/trailing decimal points (James Allardice)
+* Add rule to prevent comparisons with value NaN (James Allardice)
+* Fixing jshint error (Ilya Volodin)
+* Rule: no octal literals (Ilya Volodin)
+* Rule: no undefined when initializing variables (Ilya Volodin)
+* Updated CONTRIBUTING.md (Nicholas C. Zakas)
+* Make sure namespaces are honored in new-cap (Nicholas C. Zakas)
+* Make sure no-empty also checks for ';;' (Nicholas C. Zakas)
+* Add CLI option to output version (Nicholas C. Zakas)
+* Updated contribution guidelines (Nicholas C. Zakas)
+* Fixing jshint complaints. (Joel Feenstra)
+* Converting to a switch statement and declaring variables. (Joel Feenstra)
+* Added .jshintrc file (until ESLint can lint itself) and cleaned up JSHint warnings (Nicholas C. Zakas)
+* Merge branch 'master' of github.com:nzakas/jscheck (Nicholas C. Zakas)
+* A bit of cleanup (Nicholas C. Zakas)
+* Add unreachable code detection for switch cases and after continue/break. (Joel Feenstra)
+* Add support for detecting unreachable code after a throw or return statement. (Joel Feenstra)
+* Fix curly brace check when an if statement is the alternate. (Joel Feenstra)
+* Check for empty switch statements with no cases. (Matt DuVall)
+* Added CONTRIBUTING.md (Nicholas C. Zakas)
+* Added rule to check for missing semicolons (fixes #9) (Nicholas C. Zakas)
+* Verify that file paths exist before reading the file (Matt DuVall)
+* Added guard-for-in rule (fixes #1) (Nicholas C. Zakas)
+* Run linting with npm test as well (Nicholas C. Zakas)
+* Removed foo.txt (Nicholas C. Zakas)
+* Updated config file with new no-caller ID (Nicholas C. Zakas)
+* Changed name of no-arg to no-caller (Nicholas C. Zakas)
+* Increased test coverage (Nicholas C. Zakas)
+* Got npm test to work with istanbul, huzzah\! (Nicholas C. Zakas)
+* Moved /config to /conf (Nicholas C. Zakas)
+* Added script to auto-generate changelog (Nicholas C. Zakas)
+* Add `quote-props` rule (Mathias Bynens)
+* Cleaned up relationship between bin/eslint, lib/cli.js, and lib/eslint.js (Nicholas C. Zakas)
+* Add problem count to compact formatter (Nicholas C. Zakas)
+* Fix merge conflict (Nicholas C. Zakas)
+* Change reporters to formatters, add format command line option. Also added tests for compact format. (Nicholas C. Zakas)
+* Change reporters to formatters, add format command line option (Nicholas C. Zakas)
+* Start development of 0.0.6-dev (Nicholas C. Zakas)
diff --git a/eslint/CODE_OF_CONDUCT.md b/eslint/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..2fc80dd
--- /dev/null
+++ b/eslint/CODE_OF_CONDUCT.md
@@ -0,0 +1 @@
+This project adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct).
diff --git a/eslint/CONTRIBUTING.md b/eslint/CONTRIBUTING.md
new file mode 100644
index 0000000..d0db77c
--- /dev/null
+++ b/eslint/CONTRIBUTING.md
@@ -0,0 +1,27 @@
+# Contributing
+
+Please be sure to read the contribution guidelines before making or requesting a change.
+
+## Code of Conduct
+
+This project adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct). We kindly request that you read over our code of conduct before contributing.
+
+## Filing Issues
+
+Before filing an issue, please be sure to read the guidelines for what you're reporting:
+
+* [Bug Report](https://eslint.org/docs/developer-guide/contributing/reporting-bugs)
+* [Propose a New Rule](https://eslint.org/docs/developer-guide/contributing/new-rules)
+* [Proposing a Rule Change](https://eslint.org/docs/developer-guide/contributing/rule-changes)
+* [Request a Change](https://eslint.org/docs/developer-guide/contributing/changes)
+
+To report a security vulnerability in ESLint, please use our [HackerOne program](https://hackerone.com/eslint).
+
+## Contributing Code
+
+Please sign our [Contributor License Agreement](https://cla.js.foundation/eslint/eslint) and read over the [Pull Request Guidelines](https://eslint.org/docs/developer-guide/contributing/pull-requests).
+
+## Full Documentation
+
+Our full contribution guidelines can be found at:
+https://eslint.org/docs/developer-guide/contributing/
diff --git a/eslint/LICENSE b/eslint/LICENSE
new file mode 100644
index 0000000..7fe552a
--- /dev/null
+++ b/eslint/LICENSE
@@ -0,0 +1,19 @@
+Copyright JS Foundation and other contributors, https://js.foundation
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/eslint/Makefile.js b/eslint/Makefile.js
new file mode 100644
index 0000000..a574f25
--- /dev/null
+++ b/eslint/Makefile.js
@@ -0,0 +1,1092 @@
+/**
+ * @fileoverview Build file
+ * @author nzakas
+ */
+
+/* global target */
+/* eslint no-use-before-define: "off", no-console: "off" */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+require("shelljs/make");
+
+const lodash = require("lodash"),
+    checker = require("npm-license"),
+    ReleaseOps = require("eslint-release"),
+    dateformat = require("dateformat"),
+    fs = require("fs"),
+    glob = require("glob"),
+    markdownlint = require("markdownlint"),
+    os = require("os"),
+    path = require("path"),
+    semver = require("semver"),
+    ejs = require("ejs"),
+    loadPerf = require("load-perf"),
+    yaml = require("js-yaml"),
+    { CLIEngine } = require("./lib/cli-engine"),
+    builtinRules = require("./lib/rules/index");
+
+const { cat, cd, cp, echo, exec, exit, find, ls, mkdir, pwd, rm, test } = require("shelljs");
+
+//------------------------------------------------------------------------------
+// Settings
+//------------------------------------------------------------------------------
+
+/*
+ * A little bit fuzzy. My computer has a first CPU speed of 3392 and the perf test
+ * always completes in < 3800ms. However, Travis is less predictable due to
+ * multiple different VM types. So I'm fudging this for now in the hopes that it
+ * at least provides some sort of useful signal.
+ */
+const PERF_MULTIPLIER = 13e6;
+
+const OPEN_SOURCE_LICENSES = [
+    /MIT/u, /BSD/u, /Apache/u, /ISC/u, /WTF/u, /Public Domain/u, /LGPL/u
+];
+
+//------------------------------------------------------------------------------
+// Data
+//------------------------------------------------------------------------------
+
+const NODE = "node ", // intentional extra space
+    NODE_MODULES = "./node_modules/",
+    TEMP_DIR = "./tmp/",
+    DEBUG_DIR = "./debug/",
+    BUILD_DIR = "build",
+    DOCS_DIR = "../website/docs",
+    SITE_DIR = "../website/",
+    PERF_TMP_DIR = path.join(TEMP_DIR, "eslint", "performance"),
+
+    // Utilities - intentional extra space at the end of each string
+    MOCHA = `${NODE_MODULES}mocha/bin/_mocha `,
+    ESLINT = `${NODE} bin/eslint.js --report-unused-disable-directives `,
+
+    // Files
+    RULE_FILES = glob.sync("lib/rules/*.js").filter(filePath => path.basename(filePath) !== "index.js"),
+    JSON_FILES = find("conf/").filter(fileType("json")),
+    MARKDOWN_FILES_ARRAY = find("docs/").concat(ls(".")).filter(fileType("md")),
+    TEST_FILES = "\"tests/{bin,lib,tools}/**/*.js\"",
+    PERF_ESLINTRC = path.join(PERF_TMP_DIR, "eslintrc.yml"),
+    PERF_MULTIFILES_TARGET_DIR = path.join(PERF_TMP_DIR, "eslint"),
+    PERF_MULTIFILES_TARGETS = `"${PERF_MULTIFILES_TARGET_DIR + path.sep}{lib,tests${path.sep}lib}${path.sep}**${path.sep}*.js"`,
+
+    // Settings
+    MOCHA_TIMEOUT = 10000;
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Simple JSON file validation that relies on ES JSON parser.
+ * @param {string} filePath Path to JSON.
+ * @throws Error If file contents is invalid JSON.
+ * @returns {undefined}
+ */
+function validateJsonFile(filePath) {
+    const contents = fs.readFileSync(filePath, "utf8");
+
+    JSON.parse(contents);
+}
+
+/**
+ * Generates a function that matches files with a particular extension.
+ * @param {string} extension The file extension (i.e. "js")
+ * @returns {Function} The function to pass into a filter method.
+ * @private
+ */
+function fileType(extension) {
+    return function(filename) {
+        return filename.slice(filename.lastIndexOf(".") + 1) === extension;
+    };
+}
+
+/**
+ * Executes a command and returns the output instead of printing it to stdout.
+ * @param {string} cmd The command string to execute.
+ * @returns {string} The result of the executed command.
+ */
+function execSilent(cmd) {
+    return exec(cmd, { silent: true }).stdout;
+}
+
+/**
+ * Generates a release blog post for eslint.org
+ * @param {Object} releaseInfo The release metadata.
+ * @param {string} [prereleaseMajorVersion] If this is a prerelease, the next major version after this prerelease
+ * @returns {void}
+ * @private
+ */
+function generateBlogPost(releaseInfo, prereleaseMajorVersion) {
+    const ruleList = RULE_FILES
+
+        // Strip the .js extension
+        .map(ruleFileName => path.basename(ruleFileName, ".js"))
+
+        /*
+         * Sort by length descending. This ensures that rule names which are substrings of other rule names are not
+         * matched incorrectly. For example, the string "no-undefined" should get matched with the `no-undefined` rule,
+         * instead of getting matched with the `no-undef` rule followed by the string "ined".
+         */
+        .sort((ruleA, ruleB) => ruleB.length - ruleA.length);
+
+    const renderContext = Object.assign({ prereleaseMajorVersion, ruleList }, releaseInfo);
+
+    const output = ejs.render(cat("./templates/blogpost.md.ejs"), renderContext),
+        now = new Date(),
+        month = now.getMonth() + 1,
+        day = now.getDate(),
+        filename = `../website/_posts/${now.getFullYear()}-${
+            month < 10 ? `0${month}` : month}-${
+            day < 10 ? `0${day}` : day}-eslint-v${
+            releaseInfo.version}-released.md`;
+
+    output.to(filename);
+}
+
+/**
+ * Generates a doc page with formatter result examples
+ * @param  {Object} formatterInfo Linting results from each formatter
+ * @param  {string} [prereleaseVersion] The version used for a prerelease. This
+ *      changes where the output is stored.
+ * @returns {void}
+ */
+function generateFormatterExamples(formatterInfo, prereleaseVersion) {
+    const output = ejs.render(cat("./templates/formatter-examples.md.ejs"), formatterInfo);
+    let filename = "../website/docs/user-guide/formatters/index.md",
+        htmlFilename = "../website/docs/user-guide/formatters/html-formatter-example.html";
+
+    if (prereleaseVersion) {
+        filename = filename.replace("/docs", `/docs/${prereleaseVersion}`);
+        htmlFilename = htmlFilename.replace("/docs", `/docs/${prereleaseVersion}`);
+        if (!test("-d", path.dirname(filename))) {
+            mkdir(path.dirname(filename));
+        }
+    }
+
+    output.to(filename);
+    formatterInfo.formatterResults.html.result.to(htmlFilename);
+}
+
+/**
+ * Generate a doc page that lists all of the rules and links to them
+ * @returns {void}
+ */
+function generateRuleIndexPage() {
+    const outputFile = "../website/_data/rules.yml",
+        categoryList = "conf/category-list.json",
+        categoriesData = JSON.parse(cat(path.resolve(categoryList)));
+
+    RULE_FILES
+        .map(filename => [filename, path.basename(filename, ".js")])
+        .sort((a, b) => a[1].localeCompare(b[1]))
+        .forEach(pair => {
+            const filename = pair[0];
+            const basename = pair[1];
+            const rule = require(path.resolve(filename));
+
+            if (rule.meta.deprecated) {
+                categoriesData.deprecated.rules.push({
+                    name: basename,
+                    replacedBy: rule.meta.replacedBy || []
+                });
+            } else {
+                const output = {
+                        name: basename,
+                        description: rule.meta.docs.description,
+                        recommended: rule.meta.docs.recommended || false,
+                        fixable: !!rule.meta.fixable
+                    },
+                    category = lodash.find(categoriesData.categories, { name: rule.meta.docs.category });
+
+                if (!category.rules) {
+                    category.rules = [];
+                }
+
+                category.rules.push(output);
+            }
+        });
+
+    const output = yaml.safeDump(categoriesData, { sortKeys: true });
+
+    output.to(outputFile);
+}
+
+/**
+ * Creates a git commit and tag in an adjacent `website` repository, without pushing it to
+ * the remote. This assumes that the repository has already been modified somehow (e.g. by adding a blogpost).
+ * @param {string} [tag] The string to tag the commit with
+ * @returns {void}
+ */
+function commitSiteToGit(tag) {
+    const currentDir = pwd();
+
+    cd(SITE_DIR);
+    exec("git add -A .");
+    exec(`git commit -m "Autogenerated new docs and demo at ${dateformat(new Date())}"`);
+
+    if (tag) {
+        exec(`git tag ${tag}`);
+    }
+
+    exec("git fetch origin && git rebase origin/master");
+    cd(currentDir);
+}
+
+/**
+ * Publishes the changes in an adjacent `website` repository to the remote. The
+ * site should already have local commits (e.g. from running `commitSiteToGit`).
+ * @returns {void}
+ */
+function publishSite() {
+    const currentDir = pwd();
+
+    cd(SITE_DIR);
+    exec("git push origin master --tags");
+    cd(currentDir);
+}
+
+/**
+ * Updates the changelog, bumps the version number in package.json, creates a local git commit and tag,
+ * and generates the site in an adjacent `website` folder.
+ * @returns {void}
+ */
+function generateRelease() {
+    ReleaseOps.generateRelease();
+    const releaseInfo = JSON.parse(cat(".eslint-release-info.json"));
+
+    echo("Generating site");
+    target.gensite();
+    generateBlogPost(releaseInfo);
+    commitSiteToGit(`v${releaseInfo.version}`);
+}
+
+/**
+ * Updates the changelog, bumps the version number in package.json, creates a local git commit and tag,
+ * and generates the site in an adjacent `website` folder.
+ * @param {string} prereleaseId The prerelease identifier (alpha, beta, etc.)
+ * @returns {void}
+ */
+function generatePrerelease(prereleaseId) {
+    ReleaseOps.generateRelease(prereleaseId);
+    const releaseInfo = JSON.parse(cat(".eslint-release-info.json"));
+    const nextMajor = semver.inc(releaseInfo.version, "major");
+
+    echo("Generating site");
+
+    // always write docs into the next major directory (so 2.0.0-alpha.0 writes to 2.0.0)
+    target.gensite(nextMajor);
+
+    /*
+     * Premajor release should have identical "next major version".
+     * Preminor and prepatch release will not.
+     * 5.0.0-alpha.0 --> next major = 5, current major = 5
+     * 4.4.0-alpha.0 --> next major = 5, current major = 4
+     * 4.0.1-alpha.0 --> next major = 5, current major = 4
+     */
+    if (semver.major(releaseInfo.version) === semver.major(nextMajor)) {
+
+        /*
+         * This prerelease is for a major release (not preminor/prepatch).
+         * Blog post generation logic needs to be aware of this (as well as
+         * know what the next major version is actually supposed to be).
+         */
+        generateBlogPost(releaseInfo, nextMajor);
+    } else {
+        generateBlogPost(releaseInfo);
+    }
+
+    commitSiteToGit(`v${releaseInfo.version}`);
+}
+
+/**
+ * Publishes a generated release to npm and GitHub, and pushes changes to the adjacent `website` repo
+ * to remote repo.
+ * @returns {void}
+ */
+function publishRelease() {
+    ReleaseOps.publishRelease();
+    publishSite();
+}
+
+/**
+ * Splits a command result to separate lines.
+ * @param {string} result The command result string.
+ * @returns {Array} The separated lines.
+ */
+function splitCommandResultToLines(result) {
+    return result.trim().split("\n");
+}
+
+/**
+ * Gets the first commit sha of the given file.
+ * @param {string} filePath The file path which should be checked.
+ * @returns {string} The commit sha.
+ */
+function getFirstCommitOfFile(filePath) {
+    let commits = execSilent(`git rev-list HEAD -- ${filePath}`);
+
+    commits = splitCommandResultToLines(commits);
+    return commits[commits.length - 1].trim();
+}
+
+/**
+ * Gets the tag name where a given file was introduced first.
+ * @param {string} filePath The file path to check.
+ * @returns {string} The tag name.
+ */
+function getFirstVersionOfFile(filePath) {
+    const firstCommit = getFirstCommitOfFile(filePath);
+    let tags = execSilent(`git tag --contains ${firstCommit}`);
+
+    tags = splitCommandResultToLines(tags);
+    return tags.reduce((list, version) => {
+        const validatedVersion = semver.valid(version.trim());
+
+        if (validatedVersion) {
+            list.push(validatedVersion);
+        }
+        return list;
+    }, []).sort(semver.compare)[0];
+}
+
+/**
+ * Gets the commit that deleted a file.
+ * @param {string} filePath The path to the deleted file.
+ * @returns {string} The commit sha.
+ */
+function getCommitDeletingFile(filePath) {
+    const commits = execSilent(`git rev-list HEAD -- ${filePath}`);
+
+    return splitCommandResultToLines(commits)[0];
+}
+
+/**
+ * Gets the first version number where a given file is no longer present.
+ * @param {string} filePath The path to the deleted file.
+ * @returns {string} The version number.
+ */
+function getFirstVersionOfDeletion(filePath) {
+    const deletionCommit = getCommitDeletingFile(filePath),
+        tags = execSilent(`git tag --contains ${deletionCommit}`);
+
+    return splitCommandResultToLines(tags)
+        .map(version => semver.valid(version.trim()))
+        .filter(version => version)
+        .sort(semver.compare)[0];
+}
+
+/**
+ * Lints Markdown files.
+ * @param {Array} files Array of file names to lint.
+ * @returns {Object} exec-style exit code object.
+ * @private
+ */
+function lintMarkdown(files) {
+    const config = yaml.safeLoad(fs.readFileSync(path.join(__dirname, "./.markdownlint.yml"), "utf8")),
+        result = markdownlint.sync({
+            files,
+            config,
+            resultVersion: 1
+        }),
+        resultString = result.toString(),
+        returnCode = resultString ? 1 : 0;
+
+    if (resultString) {
+        console.error(resultString);
+    }
+    return { code: returnCode };
+}
+
+/**
+ * Gets linting results from every formatter, based on a hard-coded snippet and config
+ * @returns {Object} Output from each formatter
+ */
+function getFormatterResults() {
+    const stripAnsi = require("strip-ansi");
+
+    const formatterFiles = fs.readdirSync("./lib/cli-engine/formatters/"),
+        rules = {
+            "no-else-return": "warn",
+            indent: ["warn", 4],
+            "space-unary-ops": "error",
+            semi: ["warn", "always"],
+            "consistent-return": "error"
+        },
+        cli = new CLIEngine({
+            useEslintrc: false,
+            baseConfig: { extends: "eslint:recommended" },
+            rules
+        }),
+        codeString = [
+            "function addOne(i) {",
+            "    if (i != NaN) {",
+            "        return i ++",
+            "    } else {",
+            "      return",
+            "    }",
+            "};"
+        ].join("\n"),
+        rawMessages = cli.executeOnText(codeString, "fullOfProblems.js", true),
+        rulesMap = cli.getRules(),
+        rulesMeta = {};
+
+    Object.keys(rules).forEach(ruleId => {
+        rulesMeta[ruleId] = rulesMap.get(ruleId).meta;
+    });
+
+    return formatterFiles.reduce((data, filename) => {
+        const fileExt = path.extname(filename),
+            name = path.basename(filename, fileExt);
+
+        if (fileExt === ".js") {
+            const formattedOutput = cli.getFormatter(name)(
+                rawMessages.results,
+                { rulesMeta }
+            );
+
+            data.formatterResults[name] = {
+                result: stripAnsi(formattedOutput)
+            };
+        }
+        return data;
+    }, { formatterResults: {} });
+}
+
+/**
+ * Gets a path to an executable in node_modules/.bin
+ * @param {string} command The executable name
+ * @returns {string} The executable path
+ */
+function getBinFile(command) {
+    return path.join("node_modules", ".bin", command);
+}
+
+//------------------------------------------------------------------------------
+// Tasks
+//------------------------------------------------------------------------------
+
+target.all = function() {
+    target.test();
+};
+
+target.lint = function([fix = false] = []) {
+    let errors = 0,
+        lastReturn;
+
+    echo("Validating JavaScript files");
+    lastReturn = exec(`${ESLINT}${fix ? "--fix" : ""} .`);
+    if (lastReturn.code !== 0) {
+        errors++;
+    }
+
+    echo("Validating JSON Files");
+    lodash.forEach(JSON_FILES, validateJsonFile);
+
+    echo("Validating Markdown Files");
+    lastReturn = lintMarkdown(MARKDOWN_FILES_ARRAY);
+    if (lastReturn.code !== 0) {
+        errors++;
+    }
+
+    if (errors) {
+        exit(1);
+    }
+};
+
+target.fuzz = function({ amount = 1000, fuzzBrokenAutofixes = false } = {}) {
+    const fuzzerRunner = require("./tools/fuzzer-runner");
+    const fuzzResults = fuzzerRunner.run({ amount, fuzzBrokenAutofixes });
+
+    if (fuzzResults.length) {
+
+        const uniqueStackTraceCount = new Set(fuzzResults.map(result => result.error)).size;
+
+        echo(`The fuzzer reported ${fuzzResults.length} error${fuzzResults.length === 1 ? "" : "s"} with a total of ${uniqueStackTraceCount} unique stack trace${uniqueStackTraceCount === 1 ? "" : "s"}.`);
+
+        const formattedResults = JSON.stringify({ results: fuzzResults }, null, 4);
+
+        if (process.env.CI) {
+            echo("More details can be found below.");
+            echo(formattedResults);
+        } else {
+            if (!test("-d", DEBUG_DIR)) {
+                mkdir(DEBUG_DIR);
+            }
+
+            let fuzzLogPath;
+            let fileSuffix = 0;
+
+            // To avoid overwriting any existing fuzzer log files, append a numeric suffix to the end of the filename.
+            do {
+                fuzzLogPath = path.join(DEBUG_DIR, `fuzzer-log-${fileSuffix}.json`);
+                fileSuffix++;
+            } while (test("-f", fuzzLogPath));
+
+            formattedResults.to(fuzzLogPath);
+
+            // TODO: (not-an-aardvark) Create a better way to isolate and test individual fuzzer errors from the log file
+            echo(`More details can be found in ${fuzzLogPath}.`);
+        }
+
+        exit(1);
+    }
+};
+
+target.mocha = () => {
+    let errors = 0,
+        lastReturn;
+
+    echo("Running unit tests");
+
+    lastReturn = exec(`${getBinFile("nyc")} -- ${MOCHA} -R progress -t ${MOCHA_TIMEOUT} -c ${TEST_FILES}`);
+    if (lastReturn.code !== 0) {
+        errors++;
+    }
+
+    lastReturn = exec(`${getBinFile("nyc")} check-coverage --statement 99 --branch 98 --function 99 --lines 99`);
+    if (lastReturn.code !== 0) {
+        errors++;
+    }
+
+    if (errors) {
+        exit(1);
+    }
+};
+
+target.karma = () => {
+    echo("Running unit tests on browsers");
+
+    target.webpack("production");
+
+    const lastReturn = exec(`${getBinFile("karma")} start karma.conf.js`);
+
+    if (lastReturn.code !== 0) {
+        exit(1);
+    }
+};
+
+target.test = function() {
+    target.lint();
+    target.checkRuleFiles();
+    target.mocha();
+    target.karma();
+    target.fuzz({ amount: 150, fuzzBrokenAutofixes: false });
+    target.checkLicenses();
+};
+
+target.docs = function() {
+    echo("Generating documentation");
+    exec(`${getBinFile("jsdoc")} -d jsdoc lib`);
+    echo("Documentation has been output to /jsdoc");
+};
+
+target.gensite = function(prereleaseVersion) {
+    echo("Generating eslint.org");
+
+    let docFiles = [
+        "/rules/",
+        "/user-guide/",
+        "/maintainer-guide/",
+        "/developer-guide/",
+        "/about/"
+    ];
+
+    // append version
+    if (prereleaseVersion) {
+        docFiles = docFiles.map(docFile => `/${prereleaseVersion}${docFile}`);
+    }
+
+    // 1. create temp and build directory
+    echo("> Creating a temporary directory (Step 1)");
+    if (!test("-d", TEMP_DIR)) {
+        mkdir(TEMP_DIR);
+    }
+
+    // 2. remove old files from the site
+    echo("> Removing old files (Step 2)");
+    docFiles.forEach(filePath => {
+        const fullPath = path.join(DOCS_DIR, filePath),
+            htmlFullPath = fullPath.replace(".md", ".html");
+
+        if (test("-f", fullPath)) {
+
+            rm("-rf", fullPath);
+
+            if (filePath.indexOf(".md") >= 0 && test("-f", htmlFullPath)) {
+                rm("-rf", htmlFullPath);
+            }
+        }
+    });
+
+    // 3. Copy docs folder to a temporary directory
+    echo("> Copying the docs folder (Step 3)");
+    cp("-rf", "docs/*", TEMP_DIR);
+
+    let versions = test("-f", "./versions.json") ? JSON.parse(cat("./versions.json")) : {};
+
+    if (!versions.added) {
+        versions = {
+            added: versions,
+            removed: {}
+        };
+    }
+
+    const rules = require(".").linter.getRules();
+
+    const RECOMMENDED_TEXT = "\n\n(recommended) The `\"extends\": \"eslint:recommended\"` property in a configuration file enables this rule.";
+    const FIXABLE_TEXT = "\n\n(fixable) The `--fix` option on the [command line](../user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.";
+
+    // 4. Loop through all files in temporary directory
+    process.stdout.write("> Updating files (Steps 4-9): 0/... - ...\r");
+    const tempFiles = find(TEMP_DIR);
+    const length = tempFiles.length;
+
+    tempFiles.forEach((filename, i) => {
+        if (test("-f", filename) && path.extname(filename) === ".md") {
+
+            const rulesUrl = "https://github.com/eslint/eslint/tree/master/lib/rules/",
+                docsUrl = "https://github.com/eslint/eslint/tree/master/docs/rules/",
+                baseName = path.basename(filename),
+                sourceBaseName = `${path.basename(filename, ".md")}.js`,
+                sourcePath = path.join("lib/rules", sourceBaseName),
+                ruleName = path.basename(filename, ".md"),
+                filePath = path.join("docs", path.relative("tmp", filename));
+            let text = cat(filename),
+                ruleType = "",
+                title;
+
+            process.stdout.write(`> Updating files (Steps 4-9): ${i}/${length} - ${filePath + " ".repeat(30)}\r`);
+
+            // 5. Prepend page title and layout variables at the top of rules
+            if (path.dirname(filename).indexOf("rules") >= 0) {
+
+                // Find out if the rule requires a special docs portion (e.g. if it is recommended and/or fixable)
+                const rule = rules.get(ruleName);
+                const isRecommended = rule && rule.meta.docs.recommended;
+                const isFixable = rule && rule.meta.fixable;
+
+                // Incorporate the special portion into the documentation content
+                const textSplit = text.split("\n");
+                const ruleHeading = textSplit[0];
+                const ruleDocsContent = textSplit.slice(1).join("\n");
+
+                text = `${ruleHeading}${isRecommended ? RECOMMENDED_TEXT : ""}${isFixable ? FIXABLE_TEXT : ""}\n${ruleDocsContent}`;
+                title = `${ruleName} - Rules`;
+
+                if (rule && rule.meta) {
+                    ruleType = `rule_type: ${rule.meta.type}`;
+                }
+            } else {
+
+                // extract the title from the file itself
+                title = text.match(/#([^#].+)\n/u);
+                if (title) {
+                    title = title[1].trim();
+                } else {
+                    title = "Documentation";
+                }
+            }
+
+            text = [
+                "---",
+                `title: ${title}`,
+                "layout: doc",
+                `edit_link: https://github.com/eslint/eslint/edit/master/${filePath}`,
+                ruleType,
+                "---",
+                "",
+                "",
+                text
+            ].join("\n");
+
+            // 6. Remove .md extension for relative links and change README to empty string
+            text = text.replace(/\((?!https?:\/\/)([^)]*?)\.md(.*?)\)/gu, "($1$2)").replace("README.html", "");
+
+            // 7. Check if there's a trailing white line at the end of the file, if there isn't one, add it
+            if (!/\n$/u.test(text)) {
+                text = `${text}\n`;
+            }
+
+            // 8. Append first version of ESLint rule was added at.
+            if (filename.indexOf("rules/") !== -1) {
+                if (!versions.added[baseName]) {
+                    versions.added[baseName] = getFirstVersionOfFile(sourcePath);
+                }
+                const added = versions.added[baseName];
+
+                if (!versions.removed[baseName] && !test("-f", sourcePath)) {
+                    versions.removed[baseName] = getFirstVersionOfDeletion(sourcePath);
+                }
+                const removed = versions.removed[baseName];
+
+                text += "\n## Version\n\n";
+                text += removed
+                    ? `This rule was introduced in ESLint ${added} and removed in ${removed}.\n`
+                    : `This rule was introduced in ESLint ${added}.\n`;
+
+                text += "\n## Resources\n\n";
+                if (!removed) {
+                    text += `* [Rule source](${rulesUrl}${sourceBaseName})\n`;
+                }
+                text += `* [Documentation source](${docsUrl}${baseName})\n`;
+            }
+
+            // 9. Update content of the file with changes
+            text.to(filename.replace("README.md", "index.md"));
+        }
+    });
+    JSON.stringify(versions).to("./versions.json");
+    echo(`> Updating files (Steps 4-9)${" ".repeat(50)}`);
+
+    // 10. Copy temporary directory to site's docs folder
+    echo("> Copying the temporary directory the site (Step 10)");
+    let outputDir = DOCS_DIR;
+
+    if (prereleaseVersion) {
+        outputDir += `/${prereleaseVersion}`;
+        if (!test("-d", outputDir)) {
+            mkdir(outputDir);
+        }
+    }
+    cp("-rf", `${TEMP_DIR}*`, outputDir);
+
+    // 11. Generate rule listing page
+    echo("> Generating the rule listing (Step 11)");
+    generateRuleIndexPage();
+
+    // 12. Delete temporary directory
+    echo("> Removing the temporary directory (Step 12)");
+    rm("-rf", TEMP_DIR);
+
+    // 13. Create Example Formatter Output Page
+    echo("> Creating the formatter examples (Step 14)");
+    generateFormatterExamples(getFormatterResults(), prereleaseVersion);
+
+    echo("Done generating eslint.org");
+};
+
+target.webpack = function(mode = "none") {
+    exec(`${getBinFile("webpack")} --mode=${mode} --output-path=${BUILD_DIR}`);
+};
+
+target.checkRuleFiles = function() {
+
+    echo("Validating rules");
+
+    const ruleTypes = require("./tools/rule-types.json");
+    let errors = 0;
+
+    RULE_FILES.forEach(filename => {
+        const basename = path.basename(filename, ".js");
+        const docFilename = `docs/rules/${basename}.md`;
+
+        /**
+         * Check if basename is present in rule-types.json file.
+         * @returns {boolean} true if present
+         * @private
+         */
+        function isInRuleTypes() {
+            return Object.prototype.hasOwnProperty.call(ruleTypes, basename);
+        }
+
+        /**
+         * Check if id is present in title
+         * @param {string} id id to check for
+         * @returns {boolean} true if present
+         * @private
+         */
+        function hasIdInTitle(id) {
+            const docText = cat(docFilename);
+            const idOldAtEndOfTitleRegExp = new RegExp(`^# (.*?) \\(${id}\\)`, "u"); // original format
+            const idNewAtBeginningOfTitleRegExp = new RegExp(`^# ${id}: `, "u"); // new format is same as rules index
+            /*
+             * 1. Added support for new format.
+             * 2. Will remove support for old format after all docs files have new format.
+             * 3. Will remove this check when the main heading is automatically generated from rule metadata.
+             */
+
+            return idNewAtBeginningOfTitleRegExp.test(docText) || idOldAtEndOfTitleRegExp.test(docText);
+        }
+
+        // check for docs
+        if (!test("-f", docFilename)) {
+            console.error("Missing documentation for rule %s", basename);
+            errors++;
+        } else {
+
+            // check for proper doc format
+            if (!hasIdInTitle(basename)) {
+                console.error("Missing id in the doc page's title of rule %s", basename);
+                errors++;
+            }
+        }
+
+        // check for recommended configuration
+        if (!isInRuleTypes()) {
+            console.error("Missing setting for %s in tools/rule-types.json", basename);
+            errors++;
+        }
+
+        // check parity between rules index file and rules directory
+        const ruleIdsInIndex = require("./lib/rules/index");
+        const ruleDef = ruleIdsInIndex.get(basename);
+
+        if (!ruleDef) {
+            console.error(`Missing rule from index (./lib/rules/index.js): ${basename}. If you just added a new rule then add an entry for it in this file.`);
+            errors++;
+        }
+
+        // check eslint:recommended
+        const recommended = require("./conf/eslint-recommended");
+
+        if (ruleDef) {
+            if (ruleDef.meta.docs.recommended) {
+                if (recommended.rules[basename] !== "error") {
+                    console.error(`Missing rule from eslint:recommended (./conf/eslint-recommended.js): ${basename}. If you just made a rule recommended then add an entry for it in this file.`);
+                    errors++;
+                }
+            } else {
+                if (basename in recommended.rules) {
+                    console.error(`Extra rule in eslint:recommended (./conf/eslint-recommended.js): ${basename}. If you just added a rule then don't add an entry for it in this file.`);
+                    errors++;
+                }
+            }
+        }
+
+        // check for tests
+        if (!test("-f", `tests/lib/rules/${basename}.js`)) {
+            console.error("Missing tests for rule %s", basename);
+            errors++;
+        }
+
+    });
+
+    if (errors) {
+        exit(1);
+    }
+
+};
+
+target.checkLicenses = function() {
+
+    /**
+     * Check if a dependency is eligible to be used by us
+     * @param {Object} dependency dependency to check
+     * @returns {boolean} true if we have permission
+     * @private
+     */
+    function isPermissible(dependency) {
+        const licenses = dependency.licenses;
+
+        if (Array.isArray(licenses)) {
+            return licenses.some(license => isPermissible({
+                name: dependency.name,
+                licenses: license
+            }));
+        }
+
+        return OPEN_SOURCE_LICENSES.some(license => license.test(licenses));
+    }
+
+    echo("Validating licenses");
+
+    checker.init({
+        start: __dirname
+    }, deps => {
+        const impermissible = Object.keys(deps).map(dependency => ({
+            name: dependency,
+            licenses: deps[dependency].licenses
+        })).filter(dependency => !isPermissible(dependency));
+
+        if (impermissible.length) {
+            impermissible.forEach(dependency => {
+                console.error(
+                    "%s license for %s is impermissible.",
+                    dependency.licenses,
+                    dependency.name
+                );
+            });
+            exit(1);
+        }
+    });
+};
+
+/**
+ * Downloads a repository which has many js files to test performance with multi files.
+ * Here, it's eslint@1.10.3 (450 files)
+ * @param {Function} cb A callback function.
+ * @returns {void}
+ */
+function downloadMultifilesTestTarget(cb) {
+    if (test("-d", PERF_MULTIFILES_TARGET_DIR)) {
+        process.nextTick(cb);
+    } else {
+        mkdir("-p", PERF_MULTIFILES_TARGET_DIR);
+        echo("Downloading the repository of multi-files performance test target.");
+        exec(`git clone -b v1.10.3 --depth 1 https://github.com/eslint/eslint.git "${PERF_MULTIFILES_TARGET_DIR}"`, { silent: true }, cb);
+    }
+}
+
+/**
+ * Creates a config file to use performance tests.
+ * This config is turning all core rules on.
+ * @returns {void}
+ */
+function createConfigForPerformanceTest() {
+    const content = [
+        "root: true",
+        "env:",
+        "    node: true",
+        "    es6: true",
+        "rules:"
+    ];
+
+    for (const [ruleId] of builtinRules) {
+        content.push(`    ${ruleId}: 1`);
+    }
+
+    content.join("\n").to(PERF_ESLINTRC);
+}
+
+/**
+ * Calculates the time for each run for performance
+ * @param {string} cmd cmd
+ * @param {int} runs Total number of runs to do
+ * @param {int} runNumber Current run number
+ * @param {int[]} results Collection results from each run
+ * @param {Function} cb Function to call when everything is done
+ * @returns {int[]} calls the cb with all the results
+ * @private
+ */
+function time(cmd, runs, runNumber, results, cb) {
+    const start = process.hrtime();
+
+    exec(cmd, { maxBuffer: 64 * 1024 * 1024, silent: true }, (code, stdout, stderr) => {
+        const diff = process.hrtime(start),
+            actual = (diff[0] * 1e3 + diff[1] / 1e6); // ms
+
+        if (code) {
+            echo(`  Performance Run #${runNumber} failed.`);
+            if (stdout) {
+                echo(`STDOUT:\n${stdout}\n\n`);
+            }
+
+            if (stderr) {
+                echo(`STDERR:\n${stderr}\n\n`);
+            }
+            return cb(null);
+        }
+
+        results.push(actual);
+        echo(`  Performance Run #${runNumber}:  %dms`, actual);
+        if (runs > 1) {
+            return time(cmd, runs - 1, runNumber + 1, results, cb);
+        }
+        return cb(results);
+
+    });
+
+}
+
+/**
+ * Run a performance test.
+ * @param {string} title A title.
+ * @param {string} targets Test targets.
+ * @param {number} multiplier A multiplier for limitation.
+ * @param {Function} cb A callback function.
+ * @returns {void}
+ */
+function runPerformanceTest(title, targets, multiplier, cb) {
+    const cpuSpeed = os.cpus()[0].speed,
+        max = multiplier / cpuSpeed,
+        cmd = `${ESLINT}--config "${PERF_ESLINTRC}" --no-eslintrc --no-ignore ${targets}`;
+
+    echo("");
+    echo(title);
+    echo("  CPU Speed is %d with multiplier %d", cpuSpeed, multiplier);
+
+    time(cmd, 5, 1, [], results => {
+        if (!results || results.length === 0) { // No results? Something is wrong.
+            throw new Error("Performance test failed.");
+        }
+
+        results.sort((a, b) => a - b);
+
+        const median = results[~~(results.length / 2)];
+
+        echo("");
+        if (median > max) {
+            echo("  Performance budget exceeded: %dms (limit: %dms)", median, max);
+        } else {
+            echo("  Performance budget ok:  %dms (limit: %dms)", median, max);
+        }
+        echo("");
+        cb();
+    });
+}
+
+/**
+ * Run the load performance for eslint
+ * @returns {void}
+ * @private
+ */
+function loadPerformance() {
+    echo("");
+    echo("Loading:");
+
+    const results = [];
+
+    for (let cnt = 0; cnt < 5; cnt++) {
+        const loadPerfData = loadPerf({
+            checkDependencies: false
+        });
+
+        echo(`  Load performance Run #${cnt + 1}:  %dms`, loadPerfData.loadTime);
+        results.push(loadPerfData.loadTime);
+    }
+
+    results.sort((a, b) => a - b);
+    const median = results[~~(results.length / 2)];
+
+    echo("");
+    echo("  Load Performance median:  %dms", median);
+    echo("");
+}
+
+target.perf = function() {
+    downloadMultifilesTestTarget(() => {
+        createConfigForPerformanceTest();
+
+        loadPerformance();
+
+        runPerformanceTest(
+            "Single File:",
+            "tests/performance/jshint.js",
+            PERF_MULTIPLIER,
+            () => {
+
+                // Count test target files.
+                const count = glob.sync(
+                    process.platform === "win32"
+                        ? PERF_MULTIFILES_TARGETS.slice(2).replace(/\\/gu, "/")
+                        : PERF_MULTIFILES_TARGETS
+                ).length;
+
+                runPerformanceTest(
+                    `Multi Files (${count} files):`,
+                    PERF_MULTIFILES_TARGETS,
+                    3 * PERF_MULTIPLIER,
+                    () => {}
+                );
+            }
+        );
+    });
+};
+
+target.generateRelease = generateRelease;
+target.generatePrerelease = ([prereleaseType]) => generatePrerelease(prereleaseType);
+target.publishRelease = publishRelease;
diff --git a/eslint/README.md b/eslint/README.md
new file mode 100644
index 0000000..da75b5c
--- /dev/null
+++ b/eslint/README.md
@@ -0,0 +1,260 @@
+[![NPM version](https://img.shields.io/npm/v/eslint.svg)](https://www.npmjs.com/package/eslint)
+[![Downloads](https://img.shields.io/npm/dm/eslint.svg)](https://www.npmjs.com/package/eslint)
+[![Build Status](https://github.com/eslint/eslint/workflows/CI/badge.svg)](https://github.com/eslint/eslint/actions)
+[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Feslint%2Feslint.svg?type=shield)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Feslint%2Feslint?ref=badge_shield)
+
+[![Open Collective Backers](https://img.shields.io/opencollective/backers/eslint)](https://opencollective.com/eslint) +[![Open Collective Sponsors](https://img.shields.io/opencollective/sponsors/eslint)](https://opencollective.com/eslint) +[![Join the chat at https://gitter.im/eslint/eslint](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/eslint/eslint?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Follow us on Twitter](https://img.shields.io/twitter/follow/geteslint?label=Follow&style=social)](https://twitter.com/intent/user?screen_name=geteslint) + +# ESLint + +[Website](https://eslint.org) | +[Configuring](https://eslint.org/docs/user-guide/configuring) | +[Rules](https://eslint.org/docs/rules/) | +[Contributing](https://eslint.org/docs/developer-guide/contributing) | +[Reporting Bugs](https://eslint.org/docs/developer-guide/contributing/reporting-bugs) | +[Code of Conduct](https://js.foundation/community/code-of-conduct) | +[Twitter](https://twitter.com/geteslint) | +[Mailing List](https://groups.google.com/group/eslint) | +[Chat Room](https://gitter.im/eslint/eslint) + +ESLint is a tool for identifying and reporting on patterns found in ECMAScript/JavaScript code. In many ways, it is similar to JSLint and JSHint with a few exceptions: + +* ESLint uses [Espree](https://github.com/eslint/espree) for JavaScript parsing. +* ESLint uses an AST to evaluate patterns in code. +* ESLint is completely pluggable, every single rule is a plugin and you can add more at runtime. + +## Table of Contents + +1. [Installation and Usage](#installation-and-usage) +2. [Configuration](#configuration) +3. [Code of Conduct](#code-of-conduct) +4. [Filing Issues](#filing-issues) +5. [Frequently Asked Questions](#faq) +6. [Releases](#releases) +7. [Semantic Versioning Policy](#semantic-versioning-policy) +8. [License](#license) +9. [Team](#team) +10. [Sponsors](#sponsors) +11. [Technology Sponsors](#technology-sponsors) + +## Installation and Usage + +Prerequisites: [Node.js](https://nodejs.org/) (`^10.12.0`, or `>=12.0.0`) built with SSL support. (If you are using an official Node.js distribution, SSL is always built in.) + +You can install ESLint using npm: + +``` +$ npm install eslint --save-dev +``` + +You should then set up a configuration file: + +``` +$ ./node_modules/.bin/eslint --init +``` + +After that, you can run ESLint on any file or directory like this: + +``` +$ ./node_modules/.bin/eslint yourfile.js +``` + +## Configuration + +After running `eslint --init`, you'll have a `.eslintrc` file in your directory. In it, you'll see some rules configured like this: + +```json +{ + "rules": { + "semi": ["error", "always"], + "quotes": ["error", "double"] + } +} +``` + +The names `"semi"` and `"quotes"` are the names of [rules](https://eslint.org/docs/rules) in ESLint. The first value is the error level of the rule and can be one of these values: + +* `"off"` or `0` - turn the rule off +* `"warn"` or `1` - turn the rule on as a warning (doesn't affect exit code) +* `"error"` or `2` - turn the rule on as an error (exit code will be 1) + +The three error levels allow you fine-grained control over how ESLint applies rules (for more configuration options and details, see the [configuration docs](https://eslint.org/docs/user-guide/configuring)). + +## Code of Conduct + +ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct). + +## Filing Issues + +Before filing an issue, please be sure to read the guidelines for what you're reporting: + +* [Bug Report](https://eslint.org/docs/developer-guide/contributing/reporting-bugs) +* [Propose a New Rule](https://eslint.org/docs/developer-guide/contributing/new-rules) +* [Proposing a Rule Change](https://eslint.org/docs/developer-guide/contributing/rule-changes) +* [Request a Change](https://eslint.org/docs/developer-guide/contributing/changes) + +## Frequently Asked Questions + +### I'm using JSCS, should I migrate to ESLint? + +Yes. [JSCS has reached end of life](https://eslint.org/blog/2016/07/jscs-end-of-life) and is no longer supported. + +We have prepared a [migration guide](https://eslint.org/docs/user-guide/migrating-from-jscs) to help you convert your JSCS settings to an ESLint configuration. + +We are now at or near 100% compatibility with JSCS. If you try ESLint and believe we are not yet compatible with a JSCS rule/configuration, please create an issue (mentioning that it is a JSCS compatibility issue) and we will evaluate it as per our normal process. + +### Does Prettier replace ESLint? + +No, ESLint does both traditional linting (looking for problematic patterns) and style checking (enforcement of conventions). You can use ESLint for everything, or you can combine both using Prettier to format your code and ESLint to catch possible errors. + +### Why can't ESLint find my plugins? + +* Make sure your plugins (and ESLint) are both in your project's `package.json` as devDependencies (or dependencies, if your project uses ESLint at runtime). +* Make sure you have run `npm install` and all your dependencies are installed. +* Make sure your plugins' peerDependencies have been installed as well. You can use `npm view eslint-plugin-myplugin peerDependencies` to see what peer dependencies `eslint-plugin-myplugin` has. + +### Does ESLint support JSX? + +Yes, ESLint natively supports parsing JSX syntax (this must be enabled in [configuration](https://eslint.org/docs/user-guide/configuring)). Please note that supporting JSX syntax *is not* the same as supporting React. React applies specific semantics to JSX syntax that ESLint doesn't recognize. We recommend using [eslint-plugin-react](https://www.npmjs.com/package/eslint-plugin-react) if you are using React and want React semantics. + +### What ECMAScript versions does ESLint support? + +ESLint has full support for ECMAScript 3, 5 (default), 2015, 2016, 2017, 2018, and 2019. You can set your desired ECMAScript syntax (and other settings, like global variables or your target environments) through [configuration](https://eslint.org/docs/user-guide/configuring). + +### What about experimental features? + +ESLint's parser only officially supports the latest final ECMAScript standard. We will make changes to core rules in order to avoid crashes on stage 3 ECMAScript syntax proposals (as long as they are implemented using the correct experimental ESTree syntax). We may make changes to core rules to better work with language extensions (such as JSX, Flow, and TypeScript) on a case-by-case basis. + +In other cases (including if rules need to warn on more or fewer cases due to new syntax, rather than just not crashing), we recommend you use other parsers and/or rule plugins. If you are using Babel, you can use the [babel-eslint](https://github.com/babel/babel-eslint) parser and [eslint-plugin-babel](https://github.com/babel/eslint-plugin-babel) to use any option available in Babel. + +Once a language feature has been adopted into the ECMAScript standard (stage 4 according to the [TC39 process](https://tc39.github.io/process-document/)), we will accept issues and pull requests related to the new feature, subject to our [contributing guidelines](https://eslint.org/docs/developer-guide/contributing). Until then, please use the appropriate parser and plugin(s) for your experimental feature. + +### Where to ask for help? + +Join our [Mailing List](https://groups.google.com/group/eslint) or [Chatroom](https://gitter.im/eslint/eslint). + +## Releases + +We have scheduled releases every two weeks on Friday or Saturday. You can follow a [release issue](https://github.com/eslint/eslint/issues?q=is%3Aopen+is%3Aissue+label%3Arelease) for updates about the scheduling of any particular release. + +## Semantic Versioning Policy + +ESLint follows [semantic versioning](https://semver.org). However, due to the nature of ESLint as a code quality tool, it's not always clear when a minor or major version bump occurs. To help clarify this for everyone, we've defined the following semantic versioning policy for ESLint: + +* Patch release (intended to not break your lint build) + * A bug fix in a rule that results in ESLint reporting fewer errors. + * A bug fix to the CLI or core (including formatters). + * Improvements to documentation. + * Non-user-facing changes such as refactoring code, adding, deleting, or modifying tests, and increasing test coverage. + * Re-releasing after a failed release (i.e., publishing a release that doesn't work for anyone). +* Minor release (might break your lint build) + * A bug fix in a rule that results in ESLint reporting more errors. + * A new rule is created. + * A new option to an existing rule that does not result in ESLint reporting more errors by default. + * An existing rule is deprecated. + * A new CLI capability is created. + * New capabilities to the public API are added (new classes, new methods, new arguments to existing methods, etc.). + * A new formatter is created. + * `eslint:recommended` is updated and will result in strictly fewer errors (e.g., rule removals). +* Major release (likely to break your lint build) + * `eslint:recommended` is updated and may result in new errors (e.g., rule additions, most rule option updates). + * A new option to an existing rule that results in ESLint reporting more errors by default. + * An existing formatter is removed. + * Part of the public API is removed or changed in an incompatible way. + +According to our policy, any minor update may report more errors than the previous release (ex: from a bug fix). As such, we recommend using the tilde (`~`) in `package.json` e.g. `"eslint": "~3.1.0"` to guarantee the results of your builds. + +## License + +[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Feslint%2Feslint.svg?type=large)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Feslint%2Feslint?ref=badge_large) + +## Team + +These folks keep the project moving and are resources for help. + + + + +### Technical Steering Committee (TSC) + +The people who manage releases, review feature requests, and meet regularly to ensure ESLint is properly maintained. + +
+ +
+Nicholas C. Zakas +
+
+ +
+Brandon Mills +
+
+ +
+Toru Nagashima +
+
+ +
+Kai Cataldo +
+
+ + +### Reviewers + +The people who review and implement new features. + +
+ +
+薛定谔的猫 +
+
+ + + + +### Committers + +The people who review and fix bugs and help triage issues. + +
+ +
+Pig Fang +
+
+ +
+YeonJuan +
+
+ +
+Milos Djermanovic +
+
+ + + + +## Sponsors + +The following companies, organizations, and individuals support ESLint's ongoing maintenance and development. [Become a Sponsor](https://opencollective.com/eslint) to get your logo on our README and website. + + + +

Gold Sponsors

+

Shopify Salesforce MagicLab Airbnb

Silver Sponsors

+

AMP Project

Bronze Sponsors

+

Kasinot.fi Pelisivut Nettikasinot.org BonusFinder Deutschland Top Web Design Agencies Bugsnag Stability Monitoring Mixpanel VPS Server Free Icons by Icons8 UI UX Design Agencies clay Discord ThemeIsle TekHattan Marfeel Fire Stick Tricks

+ + +## Technology Sponsors + +* Site search ([eslint.org](https://eslint.org)) is sponsored by [Algolia](https://www.algolia.com) diff --git a/eslint/SUPPORT.md b/eslint/SUPPORT.md new file mode 100644 index 0000000..0894260 --- /dev/null +++ b/eslint/SUPPORT.md @@ -0,0 +1 @@ +If you have a question about how to use ESLint, please ask it in our [Gitter channel](https://gitter.im/eslint/eslint). diff --git a/eslint/bin/eslint.js b/eslint/bin/eslint.js new file mode 100755 index 0000000..a9f51f1 --- /dev/null +++ b/eslint/bin/eslint.js @@ -0,0 +1,108 @@ +#!/usr/bin/env node + +/** + * @fileoverview Main CLI that is run via the eslint command. + * @author Nicholas C. Zakas + */ + +/* eslint no-console:off */ + +"use strict"; + +// to use V8's code cache to speed up instantiation time +require("v8-compile-cache"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const useStdIn = process.argv.includes("--stdin"), + init = process.argv.includes("--init"), + debug = process.argv.includes("--debug"); + +// must do this initialization *before* other requires in order to work +if (debug) { + require("debug").enable("eslint:*,-eslint:code-path"); +} + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +// now we can safely include the other modules that use debug +const path = require("path"), + fs = require("fs"), + cli = require("../lib/cli"); + +//------------------------------------------------------------------------------ +// Execution +//------------------------------------------------------------------------------ + +process.once("uncaughtException", err => { + + // lazy load + const lodash = require("lodash"); + + if (typeof err.messageTemplate === "string" && err.messageTemplate.length > 0) { + const template = lodash.template(fs.readFileSync(path.resolve(__dirname, `../messages/${err.messageTemplate}.txt`), "utf-8")); + const pkg = require("../package.json"); + + console.error("\nOops! Something went wrong! :("); + console.error(`\nESLint: ${pkg.version}.\n\n${template(err.messageData || {})}`); + } else { + console.error(err.stack); + } + + process.exitCode = 2; +}); + +if (useStdIn) { + + /* + * Note: See + * - https://github.com/nodejs/node/blob/master/doc/api/process.md#processstdin + * - https://github.com/nodejs/node/blob/master/doc/api/process.md#a-note-on-process-io + * - https://lists.gnu.org/archive/html/bug-gnu-emacs/2016-01/msg00419.html + * - https://github.com/nodejs/node/issues/7439 (historical) + * + * On Windows using `fs.readFileSync(STDIN_FILE_DESCRIPTOR, "utf8")` seems + * to read 4096 bytes before blocking and never drains to read further data. + * + * The investigation on the Emacs thread indicates: + * + * > Emacs on MS-Windows uses pipes to communicate with subprocesses; a + * > pipe on Windows has a 4K buffer. So as soon as Emacs writes more than + * > 4096 bytes to the pipe, the pipe becomes full, and Emacs then waits for + * > the subprocess to read its end of the pipe, at which time Emacs will + * > write the rest of the stuff. + * + * Using the nodejs code example for reading from stdin. + */ + let contents = "", + chunk = ""; + + process.stdin.setEncoding("utf8"); + process.stdin.on("readable", () => { + + // Use a loop to make sure we read all available data. + while ((chunk = process.stdin.read()) !== null) { + contents += chunk; + } + }); + + process.stdin.on("end", () => { + process.exitCode = cli.execute(process.argv, contents, "utf8"); + }); +} else if (init) { + const configInit = require("../lib/init/config-initializer"); + + configInit.initializeConfig().then(() => { + process.exitCode = 0; + }).catch(err => { + process.exitCode = 1; + console.error(err.message); + console.error(err.stack); + }); +} else { + process.exitCode = cli.execute(process.argv); +} diff --git a/eslint/conf/category-list.json b/eslint/conf/category-list.json new file mode 100644 index 0000000..6609734 --- /dev/null +++ b/eslint/conf/category-list.json @@ -0,0 +1,40 @@ +{ + "categories": [ + { "name": "Possible Errors", "description": "These rules relate to possible syntax or logic errors in JavaScript code:" }, + { "name": "Best Practices", "description": "These rules relate to better ways of doing things to help you avoid problems:" }, + { "name": "Strict Mode", "description": "These rules relate to strict mode directives:" }, + { "name": "Variables", "description": "These rules relate to variable declarations:" }, + { "name": "Node.js and CommonJS", "description": "These rules relate to code running in Node.js, or in browsers with CommonJS:" }, + { "name": "Stylistic Issues", "description": "These rules relate to style guidelines, and are therefore quite subjective:" }, + { "name": "ECMAScript 6", "description": "These rules relate to ES6, also known as ES2015:" } + ], + "deprecated": { + "name": "Deprecated", + "description": "These rules have been deprecated in accordance with the deprecation policy, and replaced by newer rules:", + "rules": [] + }, + "removed": { + "name": "Removed", + "description": "These rules from older versions of ESLint (before the deprecation policy existed) have been replaced by newer rules:", + "rules": [ + { "removed": "generator-star", "replacedBy": ["generator-star-spacing"] }, + { "removed": "global-strict", "replacedBy": ["strict"] }, + { "removed": "no-arrow-condition", "replacedBy": ["no-confusing-arrow", "no-constant-condition"] }, + { "removed": "no-comma-dangle", "replacedBy": ["comma-dangle"] }, + { "removed": "no-empty-class", "replacedBy": ["no-empty-character-class"] }, + { "removed": "no-empty-label", "replacedBy": ["no-labels"] }, + { "removed": "no-extra-strict", "replacedBy": ["strict"] }, + { "removed": "no-reserved-keys", "replacedBy": ["quote-props"] }, + { "removed": "no-space-before-semi", "replacedBy": ["semi-spacing"] }, + { "removed": "no-wrap-func", "replacedBy": ["no-extra-parens"] }, + { "removed": "space-after-function-name", "replacedBy": ["space-before-function-paren"] }, + { "removed": "space-after-keywords", "replacedBy": ["keyword-spacing"] }, + { "removed": "space-before-function-parentheses", "replacedBy": ["space-before-function-paren"] }, + { "removed": "space-before-keywords", "replacedBy": ["keyword-spacing"] }, + { "removed": "space-in-brackets", "replacedBy": ["object-curly-spacing", "array-bracket-spacing"] }, + { "removed": "space-return-throw-case", "replacedBy": ["keyword-spacing"] }, + { "removed": "space-unary-word-ops", "replacedBy": ["space-unary-ops"] }, + { "removed": "spaced-line-comment", "replacedBy": ["spaced-comment"] } + ] + } +} diff --git a/eslint/conf/config-schema.js b/eslint/conf/config-schema.js new file mode 100644 index 0000000..712fc42 --- /dev/null +++ b/eslint/conf/config-schema.js @@ -0,0 +1,81 @@ +/** + * @fileoverview Defines a schema for configs. + * @author Sylvan Mably + */ + +"use strict"; + +const baseConfigProperties = { + $schema: { type: "string" }, + env: { type: "object" }, + extends: { $ref: "#/definitions/stringOrStrings" }, + globals: { type: "object" }, + overrides: { + type: "array", + items: { $ref: "#/definitions/overrideConfig" }, + additionalItems: false + }, + parser: { type: ["string", "null"] }, + parserOptions: { type: "object" }, + plugins: { type: "array" }, + processor: { type: "string" }, + rules: { type: "object" }, + settings: { type: "object" }, + noInlineConfig: { type: "boolean" }, + reportUnusedDisableDirectives: { type: "boolean" }, + + ecmaFeatures: { type: "object" } // deprecated; logs a warning when used +}; + +const configSchema = { + definitions: { + stringOrStrings: { + oneOf: [ + { type: "string" }, + { + type: "array", + items: { type: "string" }, + additionalItems: false + } + ] + }, + stringOrStringsRequired: { + oneOf: [ + { type: "string" }, + { + type: "array", + items: { type: "string" }, + additionalItems: false, + minItems: 1 + } + ] + }, + + // Config at top-level. + objectConfig: { + type: "object", + properties: { + root: { type: "boolean" }, + ignorePatterns: { $ref: "#/definitions/stringOrStrings" }, + ...baseConfigProperties + }, + additionalProperties: false + }, + + // Config in `overrides`. + overrideConfig: { + type: "object", + properties: { + excludedFiles: { $ref: "#/definitions/stringOrStrings" }, + files: { $ref: "#/definitions/stringOrStringsRequired" }, + ...baseConfigProperties + }, + required: ["files"], + additionalProperties: false + } + }, + + $ref: "#/definitions/objectConfig" +}; + +module.exports = configSchema; diff --git a/eslint/conf/default-cli-options.js b/eslint/conf/default-cli-options.js new file mode 100644 index 0000000..e09a829 --- /dev/null +++ b/eslint/conf/default-cli-options.js @@ -0,0 +1,31 @@ +/** + * @fileoverview Default CLIEngineOptions. + * @author Ian VanSchooten + */ + +"use strict"; + +module.exports = { + configFile: null, + baseConfig: false, + rulePaths: [], + useEslintrc: true, + envs: [], + globals: [], + extensions: null, + ignore: true, + ignorePath: void 0, + cache: false, + + /* + * in order to honor the cacheFile option if specified + * this option should not have a default value otherwise + * it will always be used + */ + cacheLocation: "", + cacheFile: ".eslintcache", + fix: false, + allowInlineConfig: true, + reportUnusedDisableDirectives: void 0, + globInputPaths: true +}; diff --git a/eslint/conf/environments.js b/eslint/conf/environments.js new file mode 100644 index 0000000..90589b1 --- /dev/null +++ b/eslint/conf/environments.js @@ -0,0 +1,168 @@ +/** + * @fileoverview Defines environment settings and globals. + * @author Elan Shanker + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const globals = require("globals"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Get the object that has difference. + * @param {Record} current The newer object. + * @param {Record} prev The older object. + * @returns {Record} The difference object. + */ +function getDiff(current, prev) { + const retv = {}; + + for (const [key, value] of Object.entries(current)) { + if (!Object.hasOwnProperty.call(prev, key)) { + retv[key] = value; + } + } + + return retv; +} + +const newGlobals2015 = getDiff(globals.es2015, globals.es5); // 19 variables such as Promise, Map, ... +const newGlobals2017 = { + Atomics: false, + SharedArrayBuffer: false +}; +const newGlobals2020 = { + BigInt: false, + BigInt64Array: false, + BigUint64Array: false, + globalThis: false +}; + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +/** @type {Map} */ +module.exports = new Map(Object.entries({ + + // Language + builtin: { + globals: globals.es5 + }, + es6: { + globals: newGlobals2015, + parserOptions: { + ecmaVersion: 6 + } + }, + es2015: { + globals: newGlobals2015, + parserOptions: { + ecmaVersion: 6 + } + }, + es2017: { + globals: { ...newGlobals2015, ...newGlobals2017 }, + parserOptions: { + ecmaVersion: 8 + } + }, + es2020: { + globals: { ...newGlobals2015, ...newGlobals2017, ...newGlobals2020 }, + parserOptions: { + ecmaVersion: 11 + } + }, + + // Platforms + browser: { + globals: globals.browser + }, + node: { + globals: globals.node, + parserOptions: { + ecmaFeatures: { + globalReturn: true + } + } + }, + "shared-node-browser": { + globals: globals["shared-node-browser"] + }, + worker: { + globals: globals.worker + }, + serviceworker: { + globals: globals.serviceworker + }, + + // Frameworks + commonjs: { + globals: globals.commonjs, + parserOptions: { + ecmaFeatures: { + globalReturn: true + } + } + }, + amd: { + globals: globals.amd + }, + mocha: { + globals: globals.mocha + }, + jasmine: { + globals: globals.jasmine + }, + jest: { + globals: globals.jest + }, + phantomjs: { + globals: globals.phantomjs + }, + jquery: { + globals: globals.jquery + }, + qunit: { + globals: globals.qunit + }, + prototypejs: { + globals: globals.prototypejs + }, + shelljs: { + globals: globals.shelljs + }, + meteor: { + globals: globals.meteor + }, + mongo: { + globals: globals.mongo + }, + protractor: { + globals: globals.protractor + }, + applescript: { + globals: globals.applescript + }, + nashorn: { + globals: globals.nashorn + }, + atomtest: { + globals: globals.atomtest + }, + embertest: { + globals: globals.embertest + }, + webextensions: { + globals: globals.webextensions + }, + greasemonkey: { + globals: globals.greasemonkey + } +})); diff --git a/eslint/conf/replacements.json b/eslint/conf/replacements.json new file mode 100644 index 0000000..c047811 --- /dev/null +++ b/eslint/conf/replacements.json @@ -0,0 +1,22 @@ +{ + "rules": { + "generator-star": ["generator-star-spacing"], + "global-strict": ["strict"], + "no-arrow-condition": ["no-confusing-arrow", "no-constant-condition"], + "no-comma-dangle": ["comma-dangle"], + "no-empty-class": ["no-empty-character-class"], + "no-empty-label": ["no-labels"], + "no-extra-strict": ["strict"], + "no-reserved-keys": ["quote-props"], + "no-space-before-semi": ["semi-spacing"], + "no-wrap-func": ["no-extra-parens"], + "space-after-function-name": ["space-before-function-paren"], + "space-after-keywords": ["keyword-spacing"], + "space-before-function-parentheses": ["space-before-function-paren"], + "space-before-keywords": ["keyword-spacing"], + "space-in-brackets": ["object-curly-spacing", "array-bracket-spacing", "computed-property-spacing"], + "space-return-throw-case": ["keyword-spacing"], + "space-unary-word-ops": ["space-unary-ops"], + "spaced-line-comment": ["spaced-comment"] + } +} diff --git a/eslint/docs/README.md b/eslint/docs/README.md new file mode 100644 index 0000000..8f5223a --- /dev/null +++ b/eslint/docs/README.md @@ -0,0 +1,17 @@ +# Documentation + +Welcome to our documentation pages! What would you like to view? + +## [User Guide](user-guide) + +Intended for end users of ESLint. Contains information about core rules, configuration, command line options, formatters, and integrations, +as well as guides for migrating from earlier versions of ESLint. + +## [Developer Guide](developer-guide) + +Intended for contributors to ESLint and people who wish to extend ESLint. Contains information about contributing to ESLint; creating custom +rules, configurations, plugins, and formatters; and information about our architecture and Node.js API. + +## [Maintainer Guide](maintainer-guide) + +Intended for maintainers of ESLint. diff --git a/eslint/docs/about/index.md b/eslint/docs/about/index.md new file mode 100644 index 0000000..a9c5acb --- /dev/null +++ b/eslint/docs/about/index.md @@ -0,0 +1,38 @@ +# About + +ESLint is an open source JavaScript linting utility originally created by Nicholas C. Zakas in June 2013. Code [linting][] is a type of static analysis that is frequently used to find problematic patterns or code that doesn't adhere to certain style guidelines. There are code linters for most programming languages, and compilers sometimes incorporate linting into the compilation process. + +JavaScript, being a dynamic and loosely-typed language, is especially prone to developer error. Without the benefit of a compilation process, JavaScript code is typically executed in order to find syntax or other errors. Linting tools like ESLint allow developers to discover problems with their JavaScript code without executing it. + +The primary reason ESLint was created was to allow developers to create their own linting rules. ESLint is designed to have all rules completely pluggable. The default rules are written just like any plugin rules would be. They can all follow the same pattern, both for the rules themselves as well as tests. While ESLint will ship with some built-in rules to make it useful from the start, you'll be able to dynamically load rules at any point in time. + +ESLint is written using Node.js to provide a fast runtime environment and easy installation via [npm][]. + +[linting]: https://en.wikipedia.org/wiki/Lint_(software) +[npm]: https://npmjs.org/ + +## Philosophy + +Everything is pluggable: + +* Rule API is used both by bundled and custom rules +* Formatter API is used both by bundled and custom formatters +* Additional rules and formatters can be specified at runtime +* Rules and formatters don't have to be bundled to be used + +Every rule: + +* Is standalone +* Can be turned off or on (nothing can be deemed "too important to turn off") +* Can be set to a warning or error individually + +Additionally: + +* Rules are "agenda free" - ESLint does not promote any particular coding style +* Any bundled rules are generalizable + +The project: + +* Values documentation and clear communication +* Is as transparent as possible +* Believes in the importance of testing diff --git a/eslint/docs/developer-guide/README.md b/eslint/docs/developer-guide/README.md new file mode 100644 index 0000000..ea19430 --- /dev/null +++ b/eslint/docs/developer-guide/README.md @@ -0,0 +1,47 @@ +# Developer Guide + +This guide is intended for those who wish to: + +* Contribute code to ESLint +* Create their own rules for ESLint + +In order to work with ESLint as a developer, it's recommended that: + +* You know JavaScript, since ESLint is written in JavaScript. +* You have some familiarity with Node.js, since ESLint runs on it. +* You're comfortable with command-line programs. +* You understand unit tests and why they're important. + +If that sounds like you, then continue reading to get started. + +## Section 1: Get the [Source Code](source-code.md) + +Before you can get started, you'll need to get a copy of the ESLint source code. This section explains how to do that and a little about the source code structure. + +## Section 2: Set up a [Development Environment](development-environment.md) + +Developing for ESLint is a bit different than running it on the command line. This section shows you how to set up a development environment and get you ready to write code. + +## Section 3: Run the [Unit Tests](unit-tests.md) + +There are a lot of unit tests included with ESLint to make sure that we're keeping on top of code quality. This section explains how to run the unit tests. + +## Section 4: [Working with Rules](working-with-rules.md) + +You're finally ready to start working with rules. You may want to fix an existing rule or create a new one. This section explains how to do all of that. + +## Section 5: [Working with Plugins](working-with-plugins.md) + +You've developed library-specific rules for ESLint and you want to share it with the community. You can publish an ESLint plugin on npm. + +## Section 6: [Working with Custom Parsers](working-with-custom-parsers.md) + +If you aren't going to use the default parser of ESLint, this section explains about using custom parsers. + +## Section 7: [Node.js API](nodejs-api.md) + +If you're interested in writing a tool that uses ESLint, then you can use the Node.js API to get programmatic access to functionality. + +## Section 8: [Contributing](contributing/) + +Once you've made changes that you want to share with the community, the next step is to submit those changes back via a pull request. diff --git a/eslint/docs/developer-guide/architecture.md b/eslint/docs/developer-guide/architecture.md new file mode 100644 index 0000000..1ea74bb --- /dev/null +++ b/eslint/docs/developer-guide/architecture.md @@ -0,0 +1,92 @@ +# Architecture + +
dependency graph
+ +At a high level, there are a few key parts to ESLint: + +* `bin/eslint.js` - this is the file that actually gets executed with the command line utility. It's a dumb wrapper that does nothing more than bootstrap ESLint, passing the command line arguments to `cli`. This is intentionally small so as not to require heavy testing. +* `lib/api.js` - this is the entry point of `require("eslint")`. This file exposes an object that contains public classes `Linter`, `CLIEngine`, `RuleTester`, and `SourceCode`. +* `lib/cli.js` - this is the heart of the ESLint CLI. It takes an array of arguments and then uses `eslint` to execute the commands. By keeping this as a separate utility, it allows others to effectively call ESLint from within another Node.js program as if it were done on the command line. The main call is `cli.execute()`. This is also the part that does all the file reading, directory traversing, input, and output. +* `lib/init/` - this module contains `--init` functionality that set up a configuration file for end users. +* `lib/cli-engine/` - this module is `CLIEngine` class that finds source code files and configuration files then does code verifying with the `Linter` class. This includes the loading logic of configuration files, parsers, plugins, and formatters. +* `lib/linter/` - this module is the core `Linter` class that does code verifying based on configuration options. This file does no file I/O and does not interact with the `console` at all. For other Node.js programs that have JavaScript text to verify, they would be able to use this interface directly. +* `lib/rule-tester/` - this module is `RuleTester` class that is a wrapper around Mocha so that rules can be unit tested. This class lets us write consistently formatted tests for each rule that is implemented and be confident that each of the rules work. The RuleTester interface was modeled after Mocha and works with Mocha's global testing methods. RuleTester can also be modified to work with other testing frameworks. +* `lib/source-code/` - this module is `SourceCode` class that is used to represent the parsed source code. It takes in source code and the Program node of the AST representing the code. +* `lib/rules/` - this contains built-in rules that verify source code. + +## The `cli` object + +The `cli` object is the API for the command line interface. Literally, the `bin/eslint.js` file simply passes arguments to the `cli` object and then sets `process.exitCode` to the returned exit code. + +The main method is `cli.execute()`, which accepts an array of strings that represent the command line options (as if `process.argv` were passed without the first two arguments). If you want to run ESLint from inside of another program and have it act like the CLI, then `cli` is the object to use. + +This object's responsibilities include: + +* Interpreting command line arguments +* Reading from the file system +* Outputting to the console +* Outputting to the filesystem +* Use a formatter +* Returning the correct exit code + +This object may not: + +* Call `process.exit()` directly +* Perform any asynchronous operations + +## The `CLIEngine` object + +The `CLIEngine` type represents the core functionality of the CLI except that it reads nothing from the command line and doesn't output anything by default. Instead, it accepts many (but not all) of the arguments that are passed into the CLI. It reads both configuration and source files as well as managing the environment that is passed into the `Linter` object. + +The main method of the `CLIEngine` is `executeOnFiles()`, which accepts an array of file and directory names to run the linter on. + +This object's responsibilities include: + +* Managing the execution environment for `Linter` +* Reading from the file system +* Reading configuration information from config files (including `.eslintrc` and `package.json`) + +This object may not: + +* Call `process.exit()` directly +* Perform any asynchronous operations +* Output to the console +* Use formatters + +## The `Linter` object + +The main method of the `Linter` object is `verify()` and accepts two arguments: the source text to verify and a configuration object (the baked configuration of the given configuration file plus command line options). The method first parses the given text with `espree` (or whatever the configured parser is) and retrieves the AST. The AST is produced with both line/column and range locations which are useful for reporting location of issues and retrieving the source text related to an AST node, respectively. + +Once the AST is available, `estraverse` is used to traverse the AST from top to bottom. At each node, the `Linter` object emits an event that has the same name as the node type (i.e., "Identifier", "WithStatement", etc.). On the way back up the subtree, an event is emitted with the AST type name and suffixed with ":exit", such as "Identifier:exit" - this allows rules to take action both on the way down and on the way up in the traversal. Each event is emitted with the appropriate AST node available. + +This object's responsibilities include: + +* Inspecting JavaScript code strings +* Creating an AST for the code +* Executing rules on the AST +* Reporting back the results of the execution + +This object may not: + +* Call `process.exit()` directly +* Perform any asynchronous operations +* Use Node.js-specific features +* Access the file system +* Call `console.log()` or any other similar method + +## Rules + +Individual rules are the most specialized part of the ESLint architecture. Rules can do very little, they are simply a set of instructions executed against an AST that is provided. They do get some context information passed in, but the primary responsibility of a rule is to inspect the AST and report warnings. + +These objects' responsibilities are: + +* Inspect the AST for specific patterns +* Reporting warnings when certain patterns are found + +These objects may not: + +* Call `process.exit()` directly +* Perform any asynchronous operations +* Use Node.js-specific features +* Access the file system +* Call `console.log()` or any other similar method diff --git a/eslint/docs/developer-guide/architecture/dependency.svg b/eslint/docs/developer-guide/architecture/dependency.svg new file mode 100644 index 0000000..3b0c74c --- /dev/null +++ b/eslint/docs/developer-guide/architecture/dependency.svg @@ -0,0 +1,52 @@ +binlibeslint.jscli.jsapi.jsinitcli-enginelintersource-coderule-testerrules \ No newline at end of file diff --git a/eslint/docs/developer-guide/code-conventions.md b/eslint/docs/developer-guide/code-conventions.md new file mode 100644 index 0000000..3ee0228 --- /dev/null +++ b/eslint/docs/developer-guide/code-conventions.md @@ -0,0 +1,925 @@ +# Code Conventions + +Programming language style guides are important for the long-term maintainability of software. This guide is based on the [Code Conventions for the Java Programming Language](https://java.sun.com/docs/codeconv/) and [Douglas Crockford's Code Conventions for the JavaScript Programming Language](http://javascript.crockford.com/code.html). Modifications have been made due to my personal experience and preferences. + +## File Format + +Each file has this same basic format: + +```js +/** + * @fileoverview Description of the file + * @author Your Name + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +// require() statements + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +// private methods/data + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +// exported objects/methods +module.exports = { + +}; +``` + +The `@author` field gives you credit for having created the file. + +## Indentation + +Each indentation level is made up of four spaces. Do not use tabs. + + // Good + if (true) { + doSomething(); + } + +## Primitive Literals + +Strings should always use double quotes (never single quotes) and should always appear on a single line. Never use a slash to create a new line in a string. + + // Good + var name = "Nicholas"; + + // Bad: Single quotes + var name = 'Nicholas'; + + // Bad: Wrapping to second line + var longString = "Here's the story, of a man \ + named Brady."; + +Numbers should be written as decimal integers, e-notation integers, hexadecimal integers or floating-point decimals with at least one digit before and one digit after the decimal point. Never use octal literals. + + // Good + var count = 10; + + // Good + var price = 10.0; + var price = 10.00; + + // Good + var num = 0xA2; + + // Good + var num = 1e23; + + // Bad: Hanging decimal point + var price = 10.; + + // Bad: Leading decimal point + var price = .1; + + // Bad: Octal (base 8) is deprecated + var num = 010; + +The special value `null` should be used only in the following situations: + +1. To initialize a variable that may later be assign an object value. +1. To compare against an initialized variable that may or may not have an object value. +1. To pass into a function where an object is expected. +1. To return from a function where an object is expected. + +Examples: + + // Good + var person = null; + + // Good + function getPerson() { + if (condition) { + return new Person("Nicholas"); + } else { + return null; + } + } + + // Good + var person = getPerson(); + if (person !== null){ + doSomething(); + } + + // Bad: Testing against uninitialized variable + var person; + if (person != null){ + doSomething(); + } + + // Bad: Testing to see if an argument was passed + function doSomething(arg1, arg2, arg3, arg4){ + if (arg4 != null){ + doSomethingElse(); + } + } + +Never use the special value `undefined`. To see if a variable has been defined, use the `typeof` operator: + + // Good + if (typeof variable == "undefined") { + // do something + } + + // Bad: Using undefined literal + if (variable == undefined) { + // do something + } + +## Operator Spacing + +Operators with two operands must be preceded and followed by a single space to make the expression clear. Operators include assignments and logical operators. + + // Good + var found = (values[i] === item); + + // Good + if (found && (count > 10)) { + doSomething(); + } + + // Good + for (i = 0; i < count; i++) { + process(i); + } + + // Bad: Missing spaces + var found = (values[i]===item); + + // Bad: Missing spaces + if (found&&(count>10)) { + doSomething(); + } + + // Bad: Missing spaces + for (i=0; i 10)) { + doSomething(); + } + + // Good + for (i = 0; i < count; i++) { + process(i); + } + + // Bad: Extra space after opening paren + var found = ( values[i] === item); + + // Bad: Extra space before closing paren + if (found && (count > 10) ) { + doSomething(); + } + + // Bad: Extra space around argument + for (i = 0; i < count; i++) { + process( i ); + } + +## Object Literals + +Object literals should have the following format: + +* The opening brace should be on the same line as the containing statement. +* Each property-value pair should be indented one level with the first property appearing on the next line after the opening brace. +* Each property-value pair should have an unquoted property name, followed by a colon (no space preceding it), followed by the value. +* If the value is a function, it should wrap under the property name and should have a blank line both before and after the function. +* Additional empty lines may be inserted to group related properties or otherwise improve readability. +* The closing brace should be on a separate line. + +Examples: + + // Good + var object = { + + key1: value1, + key2: value2, + + func: function() { + // do something + }, + + key3: value3 + }; + + // Bad: Improper indentation + var object = { + key1: value1, + key2: value2 + }; + + // Bad: Missing blank lines around function + var object = { + + key1: value1, + key2: value2, + func: function() { + // do something + }, + key3: value3 + }; + +When an object literal is passed to a function, the opening brace should be on the same line as if the value is a variable. All other formatting rules from above still apply. + + // Good + doSomething({ + key1: value1, + key2: value2 + }); + + // Bad: All on one line + doSomething({ key1: value1, key2: value2 }); + +## Comments + +Make frequent use of comments to aid others in understanding your code. Use comments when: + +* Code is difficult to understand. +* The code might be mistaken for an error. +* Browser-specific code is necessary but not obvious. +* Documentation generation is necessary for an object, method, or property (use appropriate documentation comments). + +### Single-Line Comments + +Single-line comments should be used to document one line of code or a group of related lines of code. A single-line comment may be used in three ways: + +1. On a separate line, describing the code beneath it. +1. At the end of a line, describing the code before it. +1. On multiple lines, to comment out sections of code. + +When on a separate line, a single-line comment should be at the same indentation level as the code it describes and be preceded by a single line. Never use multiple single-line comments on consecutive lines, use a multi-line comment instead. + + // Good + if (condition){ + + // if you made it here, then all security checks passed + allowed(); + } + + // Bad: No empty line preceding comment + if (condition){ + // if you made it here, then all security checks passed + allowed(); + } + + // Bad: Wrong indentation + if (condition){ + + // if you made it here, then all security checks passed + allowed(); + } + + // Bad: This should be a multi-line comment + // This next piece of code is quite difficult, so let me explain. + // What you want to do is determine if the condition is true + // and only then allow the user in. The condition is calculated + // from several different functions and may change during the + // lifetime of the session. + if (condition){ + // if you made it here, then all security checks passed + allowed(); + } + +For single-line comments at the end of a line, ensure there is at least one indentation level between the end of the code and the beginning of the comment: + + // Good + var result = something + somethingElse; // somethingElse will never be null + + // Bad: Not enough space between code and comment + var result = something + somethingElse;// somethingElse will never be null + +The only acceptable time to have multiple single-line comments on successive lines is to comment out large sections of code. Multi-line comments should not be used for this purpose. + + // Good + // if (condition){ + // doSomething(); + // thenDoSomethingElse(); + // } + +### Multi-Line Comments + +Multi-line comments should be used to document code that requires more explanation. Each multi-line comment should have at least three lines: + +1. The first line contains only the `/*` comment opening. No further text is allowed on this line. +1. The next line(s) have a `*` aligned with the `*` in the first line. Text is allowed on these lines. +1. The last line has the `*/` comment opening aligned with the preceding lines. No other text is allowed on this line. + +The first line of multi-comments should be indented to the same level as the code it describes. Each subsequent line should have the same indentation plus one space (for proper alignment of the `*` characters). Each multi-line comment should be preceded by one empty line. + + // Good + if (condition){ + + /* + * if you made it here, + * then all security checks passed + */ + allowed(); + } + + // Bad: No empty line preceding comment + if (condition){ + /* + * if you made it here, + * then all security checks passed + */ + allowed(); + } + + // Bad: Missing a space after asterisk + if (condition){ + + /* + *if you made it here, + *then all security checks passed + */ + allowed(); + } + + // Bad: Wrong indentation + if (condition){ + + /* + * if you made it here, + * then all security checks passed + */ + allowed(); + } + + // Bad: Don't use multi-line comments for trailing comments + var result = something + somethingElse; /*somethingElse will never be null*/ + +### Comment Annotations + +Comments may be used to annotate pieces of code with additional information. These annotations take the form of a single word followed by a colon. The acceptable annotations are: + +* `TODO` - indicates that the code is not yet complete. Information about the next steps should be included. +* `HACK` - indicates that the code is using a shortcut. Information about why the hack is being used should be included. This may also indicate that it would be nice to come up with a better way to solve the problem. +* `XXX` - indicates that the code is problematic and should be fixed as soon as possible. +* `FIXME` - indicates that the code is problematic and should be fixed soon. Less important than `XXX`. +* `REVIEW` - indicates that the code needs to be reviewed for potential changes. + +These annotations may be used with either single-line or multi-line comments and should follow the same formatting rules as the general comment type. Examples: + + // Good + // TODO: I'd like to find a way to make this faster + doSomething(); + + // Good + /* + * HACK: Have to do this for IE. I plan on revisiting in + * the future when I have more time. This probably should + * get replaced before v1.2. + */ + if (document.all) { + doSomething(); + } + + // Good + // REVIEW: Is there a better way to do this? + if (document.all) { + doSomething(); + } + + // Bad: Annotation spacing is incorrect + // TODO : I'd like to find a way to make this faster + doSomething(); + + // Bad: Comment should be at the same indentation as code + // REVIEW: Is there a better way to do this? + if (document.all) { + doSomething(); + } + + +## Variable Declarations + +All variables should be declared before they are used. Variable declarations should take place at the beginning of a function using a single `var` statement with one variable per line. All lines after the first should be indented one level so the variable names line up. Variables should be initialized when declared if applicable and the equals operator should be at a consistent indentation level. Initialized variables should come first followed by uninitialized variables. + + // Good + var count = 10, + name = "Nicholas", + found = false, + empty; + + // Bad: Improper initialization alignment + var count = 10, + name = "Nicholas", + found= false, + empty; + + // Bad: Incorrect indentation + var count = 10, + name = "Nicholas", + found = false, + empty; + + // Bad: Multiple declarations on one line + var count = 10, name = "Nicholas", + found = false, empty; + + // Bad: Uninitialized variables first + var empty, + count = 10, + name = "Nicholas", + found = false; + + // Bad: Multiple var statements + var count = 10, + name = "Nicholas"; + + var found = false, + empty; + +Always declare variables. Implied globals should not be used. + +## Function Declarations + +Functions should be declared before they are used. When a function is not a method (not attached to an object) it should be defined using function declaration format (not function expression format nor using the `Function` constructor). There should be no space between the function name and the opening parentheses. There should be one space between the closing parentheses and the right brace. The right brace should be on the same line as the `function` keyword. There should be no space after the opening parentheses or before the closing parentheses. Named arguments should have a space after the comma but not before it. The function body should be indented one level. + + // Good + function doSomething(arg1, arg2) { + return arg1 + arg2; + } + + // Bad: Improper spacing of first line + function doSomething (arg1, arg2){ + return arg1 + arg2; + } + + // Bad: Function expression + var doSomething = function(arg1, arg2) { + return arg1 + arg2; + }; + + // Bad: Left brace on wrong line + function doSomething(arg1, arg2) + { + return arg1 + arg2; + } + + // Bad: Using Function constructor + var doSomething = new Function("arg1", "arg2", "return arg1 + arg2"); + +Functions declared inside of other functions should be declared immediately after the `var` statement. + + // Good + function outer() { + + var count = 10, + name = "Nicholas", + found = false, + empty; + + function inner() { + // code + } + + // code that uses inner() + } + + // Bad: Inner function declared before variables + function outer() { + + function inner() { + // code + } + + var count = 10, + name = "Nicholas", + found = false, + empty; + + // code that uses inner() + } + +Anonymous functions may be used for assignment of object methods or as arguments to other functions. There should be no space between the `function` keyword and the opening parentheses. + + // Good + object.method = function() { + // code + }; + + // Bad: Incorrect spacing + object.method = function () { + // code + }; + +Immediately-invoked functions should surround the entire function call with parentheses. + + // Good + var value = (function() { + + // function body + + return { + message: "Hi" + } + }()); + + // Bad: No parentheses around function call + var value = function() { + + // function body + + return { + message: "Hi" + } + }(); + + // Bad: Improper parentheses placement + var value = (function() { + + // function body + + return { + message: "Hi" + } + })(); + +## Naming + +Care should be taken to name variables and functions properly. Names should be limited to alphanumeric characters and, in some cases, the underscore character. Do not use the dollar sign (`$`) or back slash (`\`) characters in any names. + +Variable names should be formatted in camel case with the first letter being lowercase and the first letter of each subsequent word being uppercase. The first word of a variable name should be a noun (not a verb) to avoid confusion with functions. Do not use underscore for variable names. + + // Good + var accountNumber = "8401-1"; + + // Bad: Begins with uppercase letter + var AccountNumber = "8401-1"; + + // Bad: Begins with verb + var getAccountNumber = "8401-1"; + + // Bad: Uses underscore + var account_number = "8401-1"; + +Function names should also be formatted using camel case. The first word of a function name should be a verb (not a noun) to avoid confusion with variables. Do not use underscore for function names. + + // Good + function doSomething() { + // code + } + + // Bad: Begins with uppercase letter + function DoSomething() { + // code + } + + // Bad: Begins with noun + function car() { + // code + } + + // Bad: Uses underscores + function do_something() { + // code + } + +Constructor functions, those functions used with the `new` operator to create new objects, should be formatted in camel case but must begin with an uppercase letter. Constructor function names should begin with a non-verb because `new` is the action of creating an object instance. + + // Good + function MyObject() { + // code + } + + // Bad: Begins with lowercase letter + function myObject() { + // code + } + + // Bad: Uses underscores + function My_Object() { + // code + } + + // Bad: Begins with verb + function getMyObject() { + // code + } + +Variables that act as constants (values that won't be changed) should be formatted using all uppercase letters with words separated by a single underscore. + + // Good + var TOTAL_COUNT = 10; + + // Bad: Camel case + var totalCount = 10; + + // Bad: Mixed case + var total_COUNT = 10; + +Object properties follow the same naming conventions as variables. Object methods follow the same naming conventions as functions. If a property or method is meant to be private, then it should be prefixed with an underscore character. + + // Good + var object = { + _count: 10, + + _getCount: function () { + return this._count; + } + }; + +## Strict Mode + +Strict mode should be used in all modules, specified below the file overview comment and above everything else: + + // Bad: Strict mode in functions + function doSomething() { + "use strict"; + + // code + } + + // Bad: Strict mode in global scope and redundant strict mode directive in function + "use strict"; // This one is good + + function doSomething() { + "use strict"; // This one is bad + + // code + } + + // Good: Global strict mode + "use strict"; + + function doSomething() { + // no "use strict" here + + // code + } + +## Assignments + +When assigning a value to a variable, use parentheses around a right-side expression that contains a comparison. + + // Good + var flag = (i < count); + + // Bad: Missing parentheses + var flag = i < count; + +## Equality Operators + +Use `===` and `!==` instead of `==` and `!=`. This avoids type coercion errors. + + // Good + var same = (a === b); + + // Bad: Using == + var same = (a == b); + +## Ternary Operator + +The ternary operator should be used only for assigning values conditionally and never as a shortcut for an `if` statement. + + // Good + var value = condition ? value1 : value2; + + // Bad: no assignment, should be an if statement + condition ? doSomething() : doSomethingElse(); + +## Statements + +### Simple Statements + +Each line should contain at most one statement. All simple statements should end with a semicolon (`;`). + + // Good + count++; + a = b; + + // Bad: Multiple statements on one line + count++; a = b; + +### return Statement + +A return statement with a value should not use parentheses unless they make the return value more obvious in some way. Example: + + return; + + return collection.size(); + + return (size > 0 ? size : defaultSize); + +### Compound Statements + +Compound statements are lists of statements enclosed inside of braces. + +* The enclosed statements should be indented one more level than the compound statement. +* The opening brace should be at the end of the line that begins the compound statement; the closing brace should begin a line and be indented to the beginning of the compound statement. +* Braces are used around all statements, even single statements, when they are part of a control structure, such as a `if` or `for` statement. This makes it easier to add statements without accidentally introducing bugs due to forgetting to add braces. +* The statement beginning keyword, such as `if`, should be followed by one space and the opening brace should be preceded by a space. + +### if Statement + +The `if` class of statements should have the following form: + + if (condition) { + statements + } + + if (condition) { + statements + } else { + statements + } + + if (condition) { + statements + } else if (condition) { + statements + } else { + statements + } + +It is never permissible to omit the braces in any part of an `if` statement. + + // Good + if (condition) { + doSomething(); + } + + // Bad: Improper spacing + if(condition){ + doSomething(); + } + + // Bad: Missing braces + if (condition) + doSomething(); + + // Bad: All on one line + if (condition) { doSomething(); } + + // Bad: All on one line without braces + if (condition) doSomething(); + +### for Statement + +The `for` class of statements should have the following form: + + for (initialization; condition; update) { + statements + } + + for (variable in object) { + statements + } + +Variables should not be declared in the initialization section of a `for` statement. + + // Good + var i, + len; + + for (i=0, len=10; i < len; i++) { + // code + } + + // Bad: Variables declared during initialization + for (var i=0, len=10; i < len; i++) { + // code + } + + // Bad: Variables declared during initialization + for (var prop in object) { + // code + } + +When using a `for-in` statement, double-check whether or not you need to use `hasOwnProperty()` to filter out object members. + +### while Statement + +The `while` class of statements should have the following form: + + while (condition) { + statements + } + +### do Statement + +The `do` class of statements should have the following form: + + do { + statements + } while (condition); + +Note the use of a semicolon as the final part of this statement. There should be a space before and after the `while` keyword. + +### switch Statement + +The `switch` class of statements should have the following form: + + switch (expression) { + case expression: + statements + + default: + statements + } + +Each `case` is indented one level under the `switch`. Each `case` after the first, including `default`, should be preceded by a single empty line. + +Each group of statements (except the default) should end with `break`, `return`, `throw`, or a comment indicating fall through. + + // Good + switch (value) { + case 1: + /* falls through */ + + case 2: + doSomething(); + break; + + case 3: + return true; + + default: + throw new Error("This shouldn't happen.); + } + +If a `switch` doesn't have a `default` case, then it should be indicated with a comment. + + // Good + switch (value) { + case 1: + /*falls through*/ + + case 2: + doSomething(); + break; + + case 3: + return true; + + // no default + } + +### try Statement + +The `try` class of statements should have the following form: + + try { + statements + } catch (variable) { + statements + } + + try { + statements + } catch (variable) { + statements + } finally { + statements + } + +## Whitespace + +Blank lines improve readability by setting off sections of code that are logically related. + +Two blank lines should always be used in the following circumstances: + +* Between sections of a source file +* Between class and interface definitions + +One blank line should always be used in the following circumstances: + +* Between methods +* Between the local variables in a method and its first statement +* Before a multi-line or single-line comment +* Between logical sections inside a method to improve readability + +Blank spaces should be used in the following circumstances: + +* A keyword followed by a parenthesis should be separated by a space. +* A blank space should appear after commas in argument lists. +* All binary operators except dot (`.`) should be separated from their operands by spaces. Blank spaces should never separate unary operators such as unary minus, increment (`++`), and decrement (`--`) from their operands. +* The expressions in a `for` statement should be separated by blank spaces. Blank spaces should only be used after semicolons, not before. + +## Things to Avoid + +* Never use the primitive wrapper types, such as `String`, to create new objects. +* Never use `eval()`. +* Never use the `with` statement. This statement isn't available in strict mode and likely won't be available in future ECMAScript editions. diff --git a/eslint/docs/developer-guide/code-path-analysis.md b/eslint/docs/developer-guide/code-path-analysis.md new file mode 100644 index 0000000..7762bc5 --- /dev/null +++ b/eslint/docs/developer-guide/code-path-analysis.md @@ -0,0 +1,551 @@ +# Code Path Analysis Details + +ESLint's rules can use code paths. +The code path is execution routes of programs. +It forks/joins at such as `if` statements. + +```js +if (a && b) { + foo(); +} +bar(); +``` + +![Code Path Example](./code-path-analysis/helo.svg) + +## Objects + +Program is expressed with several code paths. +A code path is expressed with objects of two kinds: `CodePath` and `CodePathSegment`. + +### `CodePath` + +`CodePath` expresses whole of one code path. +This object exists for each function and the global. +This has references of both the initial segment and the final segments of a code path. + +`CodePath` has the following properties: + +* `id` (`string`) - A unique string. Respective rules can use `id` to save additional information for each code path. +* `initialSegment` (`CodePathSegment`) - The initial segment of this code path. +* `finalSegments` (`CodePathSegment[]`) - The final segments which includes both returned and thrown. +* `returnedSegments` (`CodePathSegment[]`) - The final segments which includes only returned. +* `thrownSegments` (`CodePathSegment[]`) - The final segments which includes only thrown. +* `currentSegments` (`CodePathSegment[]`) - Segments of the current position. +* `upper` (`CodePath|null`) - The code path of the upper function/global scope. +* `childCodePaths` (`CodePath[]`) - Code paths of functions this code path contains. + +### `CodePathSegment` + +`CodePathSegment` is a part of a code path. +A code path is expressed with plural `CodePathSegment` objects, it's similar to doubly linked list. +Difference from doubly linked list is what there are forking and merging (the next/prev are plural). + +`CodePathSegment` has the following properties: + +* `id` (`string`) - A unique string. Respective rules can use `id` to save additional information for each segment. +* `nextSegments` (`CodePathSegment[]`) - The next segments. If forking, there are two or more. If final, there is nothing. +* `prevSegments` (`CodePathSegment[]`) - The previous segments. If merging, there are two or more. If initial, there is nothing. +* `reachable` (`boolean`) - A flag which shows whether or not it's reachable. This becomes `false` when preceded by `return`, `throw`, `break`, or `continue`. + +## Events + +There are five events related to code paths, and you can define event handlers in rules. + +```js +module.exports = function(context) { + return { + /** + * This is called at the start of analyzing a code path. + * In this time, the code path object has only the initial segment. + * + * @param {CodePath} codePath - The new code path. + * @param {ASTNode} node - The current node. + * @returns {void} + */ + "onCodePathStart": function(codePath, node) { + // do something with codePath + }, + + /** + * This is called at the end of analyzing a code path. + * In this time, the code path object is complete. + * + * @param {CodePath} codePath - The completed code path. + * @param {ASTNode} node - The current node. + * @returns {void} + */ + "onCodePathEnd": function(codePath, node) { + // do something with codePath + }, + + /** + * This is called when a code path segment was created. + * It meant the code path is forked or merged. + * In this time, the segment has the previous segments and has been + * judged reachable or not. + * + * @param {CodePathSegment} segment - The new code path segment. + * @param {ASTNode} node - The current node. + * @returns {void} + */ + "onCodePathSegmentStart": function(segment, node) { + // do something with segment + }, + + /** + * This is called when a code path segment was leaved. + * In this time, the segment does not have the next segments yet. + * + * @param {CodePathSegment} segment - The leaved code path segment. + * @param {ASTNode} node - The current node. + * @returns {void} + */ + "onCodePathSegmentEnd": function(segment, node) { + // do something with segment + }, + + /** + * This is called when a code path segment was looped. + * Usually segments have each previous segments when created, + * but when looped, a segment is added as a new previous segment into a + * existing segment. + * + * @param {CodePathSegment} fromSegment - A code path segment of source. + * @param {CodePathSegment} toSegment - A code path segment of destination. + * @param {ASTNode} node - The current node. + * @returns {void} + */ + "onCodePathSegmentLoop": function(fromSegment, toSegment, node) { + // do something with segment + } + }; +}; +``` + +### About `onCodePathSegmentLoop` + +This event is always fired when the next segment has existed already. +That timing is the end of loops mainly. + +For Example 1: + +```js +while (a) { + a = foo(); +} +bar(); +``` + +1. First, the analysis advances to the end of loop. + + ![Loop Event's Example 1](./code-path-analysis/loop-event-example-while-1.svg) + +2. Second, it creates the looping path. + At this time, the next segment has existed already, so the `onCodePathSegmentStart` event is not fired. + It fires `onCodePathSegmentLoop` instead. + + ![Loop Event's Example 2](./code-path-analysis/loop-event-example-while-2.svg) + +3. Last, it advances to the end. + + ![Loop Event's Example 3](./code-path-analysis/loop-event-example-while-3.svg) + +For example 2: + +```js +for (let i = 0; i < 10; ++i) { + foo(i); +} +bar(); +``` + +1. `for` statements are more complex. + First, the analysis advances to `ForStatement.update`. + The `update` segment is hovered at first. + + ![Loop Event's Example 1](./code-path-analysis/loop-event-example-for-1.svg) + +2. Second, it advances to `ForStatement.body`. + Of course the `body` segment is preceded by the `test` segment. + It keeps the `update` segment hovering. + + ![Loop Event's Example 2](./code-path-analysis/loop-event-example-for-2.svg) + +3. Third, it creates the looping path from `body` segment to `update` segment. + At this time, the next segment has existed already, so the `onCodePathSegmentStart` event is not fired. + It fires `onCodePathSegmentLoop` instead. + + ![Loop Event's Example 3](./code-path-analysis/loop-event-example-for-3.svg) + +4. Fourth, also it creates the looping path from `update` segment to `test` segment. + At this time, the next segment has existed already, so the `onCodePathSegmentStart` event is not fired. + It fires `onCodePathSegmentLoop` instead. + + ![Loop Event's Example 4](./code-path-analysis/loop-event-example-for-4.svg) + +5. Last, it advances to the end. + + ![Loop Event's Example 5](./code-path-analysis/loop-event-example-for-5.svg) + + + +## Usage Examples + +### To check whether or not this is reachable + +```js +var last = require("lodash").last; + +function isReachable(segment) { + return segment.reachable; +} + +module.exports = function(context) { + var codePathStack = []; + + return { + // Stores CodePath objects. + "onCodePathStart": function(codePath) { + codePathStack.push(codePath); + }, + "onCodePathEnd": function(codePath) { + codePathStack.pop(); + }, + + // Checks reachable or not. + "ExpressionStatement": function(node) { + var codePath = last(codePathStack); + + // Checks the current code path segments. + if (!codePath.currentSegments.some(isReachable)) { + context.report({message: "Unreachable!", node: node}); + } + } + }; +}; +``` + +See Also: +[no-unreachable](https://github.com/eslint/eslint/blob/master/lib/rules/no-unreachable.js), +[no-fallthrough](https://github.com/eslint/eslint/blob/master/lib/rules/no-fallthrough.js), +[consistent-return](https://github.com/eslint/eslint/blob/master/lib/rules/consistent-return.js) + +### To check state of a code path + +This example is checking whether or not the parameter `cb` is called in every path. +Instances of `CodePath` and `CodePathSegment` are shared to every rule. +So a rule must not modify those instances. +Please use a map of information instead. + +```js +var last = require("lodash").last; + +function hasCb(node, context) { + if (node.type.indexOf("Function") !== -1) { + return context.getDeclaredVariables(node).some(function(v) { + return v.type === "Parameter" && v.name === "cb"; + }); + } + return false; +} + +function isCbCalled(info) { + return info.cbCalled; +} + +module.exports = function(context) { + var funcInfoStack = []; + var segmentInfoMap = Object.create(null); + + return { + // Checks `cb`. + "onCodePathStart": function(codePath, node) { + funcInfoStack.push({ + codePath: codePath, + hasCb: hasCb(node, context) + }); + }, + "onCodePathEnd": function(codePath, node) { + funcInfoStack.pop(); + + // Checks `cb` was called in every paths. + var cbCalled = codePath.finalSegments.every(function(segment) { + var info = segmentInfoMap[segment.id]; + return info.cbCalled; + }); + + if (!cbCalled) { + context.report({ + message: "`cb` should be called in every path.", + node: node + }); + } + }, + + // Manages state of code paths. + "onCodePathSegmentStart": function(segment) { + // Ignores if `cb` doesn't exist. + if (!last(funcInfoStack).hasCb) { + return; + } + + // Initialize state of this path. + var info = segmentInfoMap[segment.id] = { + cbCalled: false + }; + + // If there are the previous paths, merges state. + // Checks `cb` was called in every previous path. + if (segment.prevSegments.length > 0) { + info.cbCalled = segment.prevSegments.every(isCbCalled); + } + }, + + // Checks reachable or not. + "CallExpression": function(node) { + var funcInfo = last(funcInfoStack); + + // Ignores if `cb` doesn't exist. + if (!funcInfo.hasCb) { + return; + } + + // Sets marks that `cb` was called. + var callee = node.callee; + if (callee.type === "Identifier" && callee.name === "cb") { + funcInfo.codePath.currentSegments.forEach(function(segment) { + var info = segmentInfoMap[segment.id]; + info.cbCalled = true; + }); + } + } + }; +}; +``` + +See Also: +[constructor-super](https://github.com/eslint/eslint/blob/master/lib/rules/constructor-super.js), +[no-this-before-super](https://github.com/eslint/eslint/blob/master/lib/rules/no-this-before-super.js) + +## Code Path Examples + +### Hello World + +```js +console.log("Hello world!"); +``` + +![Hello World](./code-path-analysis/example-hello-world.svg) + +### `IfStatement` + +```js +if (a) { + foo(); +} else { + bar(); +} +``` + +![`IfStatement`](./code-path-analysis/example-ifstatement.svg) + +### `IfStatement` (chain) + +```js +if (a) { + foo(); +} else if (b) { + bar(); +} else if (c) { + hoge(); +} +``` + +![`IfStatement` (chain)](./code-path-analysis/example-ifstatement-chain.svg) + +### `SwitchStatement` + +```js +switch (a) { + case 0: + foo(); + break; + + case 1: + case 2: + bar(); + // fallthrough + + case 3: + hoge(); + break; +} +``` + +![`SwitchStatement`](./code-path-analysis/example-switchstatement.svg) + +### `SwitchStatement` (has `default`) + +```js +switch (a) { + case 0: + foo(); + break; + + case 1: + case 2: + bar(); + // fallthrough + + case 3: + hoge(); + break; + + default: + fuga(); + break; +} +``` + +![`SwitchStatement` (has `default`)](./code-path-analysis/example-switchstatement-has-default.svg) + +### `TryStatement` (try-catch) + +```js +try { + foo(); + if (a) { + throw new Error(); + } + bar(); +} catch (err) { + hoge(err); +} +last(); +``` + +It creates the paths from `try` block to `catch` block at: + +* `throw` statements. +* The first throwable node (e.g. a function call) in the `try` block. +* The end of the `try` block. + +![`TryStatement` (try-catch)](./code-path-analysis/example-trystatement-try-catch.svg) + +### `TryStatement` (try-finally) + +```js +try { + foo(); + bar(); +} finally { + fuga(); +} +last(); +``` + +If there is not `catch` block, `finally` block has two current segments. +At this time, `CodePath.currentSegments.length` is `2`. +One is the normal path, and another is the leaving path (`throw` or `return`). + +![`TryStatement` (try-finally)](./code-path-analysis/example-trystatement-try-finally.svg) + +### `TryStatement` (try-catch-finally) + +```js +try { + foo(); + bar(); +} catch (err) { + hoge(err); +} finally { + fuga(); +} +last(); +``` + +![`TryStatement` (try-catch-finally)](./code-path-analysis/example-trystatement-try-catch-finally.svg) + +### `WhileStatement` + +```js +while (a) { + foo(); + if (b) { + continue; + } + bar(); +} +``` + +![`WhileStatement`](./code-path-analysis/example-whilestatement.svg) + +### `DoWhileStatement` + +```js +do { + foo(); + bar(); +} while (a); +``` + +![`DoWhileStatement`](./code-path-analysis/example-dowhilestatement.svg) + +### `ForStatement` + +```js +for (let i = 0; i < 10; ++i) { + foo(); + if (b) { + break; + } + bar(); +} +``` + +![`ForStatement`](./code-path-analysis/example-forstatement.svg) + +### `ForStatement` (for ever) + +```js +for (;;) { + foo(); +} +bar(); +``` + +![`ForStatement` (for ever)](./code-path-analysis/example-forstatement-for-ever.svg) + +### `ForInStatement` + +```js +for (let key in obj) { + foo(key); +} +``` + +![`ForInStatement`](./code-path-analysis/example-forinstatement.svg) + +### When there is a function + +```js +function foo(a) { + if (a) { + return; + } + bar(); +} + +foo(false); +``` + +It creates two code paths. + +* The global's + + ![When there is a function](./code-path-analysis/example-when-there-is-a-function-g.svg) + +* The function's + + ![When there is a function](./code-path-analysis/example-when-there-is-a-function-f.svg) diff --git a/eslint/docs/developer-guide/code-path-analysis/README.md b/eslint/docs/developer-guide/code-path-analysis/README.md new file mode 100644 index 0000000..c283d51 --- /dev/null +++ b/eslint/docs/developer-guide/code-path-analysis/README.md @@ -0,0 +1,551 @@ +# Code Path Analysis Details + +ESLint's rules can use code paths. +The code path is execution routes of programs. +It forks/joins at such as `if` statements. + +```js +if (a && b) { + foo(); +} +bar(); +``` + +![Code Path Example](./helo.svg) + +## Objects + +Program is expressed with several code paths. +A code path is expressed with objects of two kinds: `CodePath` and `CodePathSegment`. + +### `CodePath` + +`CodePath` expresses whole of one code path. +This object exists for each function and the global. +This has references of both the initial segment and the final segments of a code path. + +`CodePath` has the following properties: + +* `id` (`string`) - A unique string. Respective rules can use `id` to save additional information for each code path. +* `initialSegment` (`CodePathSegment`) - The initial segment of this code path. +* `finalSegments` (`CodePathSegment[]`) - The final segments which includes both returned and thrown. +* `returnedSegments` (`CodePathSegment[]`) - The final segments which includes only returned. +* `thrownSegments` (`CodePathSegment[]`) - The final segments which includes only thrown. +* `currentSegments` (`CodePathSegment[]`) - Segments of the current position. +* `upper` (`CodePath|null`) - The code path of the upper function/global scope. +* `childCodePaths` (`CodePath[]`) - Code paths of functions this code path contains. + +### `CodePathSegment` + +`CodePathSegment` is a part of a code path. +A code path is expressed with plural `CodePathSegment` objects, it's similar to doubly linked list. +Difference from doubly linked list is what there are forking and merging (the next/prev are plural). + +`CodePathSegment` has the following properties: + +* `id` (`string`) - A unique string. Respective rules can use `id` to save additional information for each segment. +* `nextSegments` (`CodePathSegment[]`) - The next segments. If forking, there are two or more. If final, there is nothing. +* `prevSegments` (`CodePathSegment[]`) - The previous segments. If merging, there are two or more. If initial, there is nothing. +* `reachable` (`boolean`) - A flag which shows whether or not it's reachable. This becomes `false` when preceded by `return`, `throw`, `break`, or `continue`. + +## Events + +There are five events related to code paths, and you can define event handlers in rules. + +```js +module.exports = function(context) { + return { + /** + * This is called at the start of analyzing a code path. + * In this time, the code path object has only the initial segment. + * + * @param {CodePath} codePath - The new code path. + * @param {ASTNode} node - The current node. + * @returns {void} + */ + "onCodePathStart": function(codePath, node) { + // do something with codePath + }, + + /** + * This is called at the end of analyzing a code path. + * In this time, the code path object is complete. + * + * @param {CodePath} codePath - The completed code path. + * @param {ASTNode} node - The current node. + * @returns {void} + */ + "onCodePathEnd": function(codePath, node) { + // do something with codePath + }, + + /** + * This is called when a code path segment was created. + * It meant the code path is forked or merged. + * In this time, the segment has the previous segments and has been + * judged reachable or not. + * + * @param {CodePathSegment} segment - The new code path segment. + * @param {ASTNode} node - The current node. + * @returns {void} + */ + "onCodePathSegmentStart": function(segment, node) { + // do something with segment + }, + + /** + * This is called when a code path segment was leaved. + * In this time, the segment does not have the next segments yet. + * + * @param {CodePathSegment} segment - The leaved code path segment. + * @param {ASTNode} node - The current node. + * @returns {void} + */ + "onCodePathSegmentEnd": function(segment, node) { + // do something with segment + }, + + /** + * This is called when a code path segment was looped. + * Usually segments have each previous segments when created, + * but when looped, a segment is added as a new previous segment into a + * existing segment. + * + * @param {CodePathSegment} fromSegment - A code path segment of source. + * @param {CodePathSegment} toSegment - A code path segment of destination. + * @param {ASTNode} node - The current node. + * @returns {void} + */ + "onCodePathSegmentLoop": function(fromSegment, toSegment, node) { + // do something with segment + } + }; +}; +``` + +### About `onCodePathSegmentLoop` + +This event is always fired when the next segment has existed already. +That timing is the end of loops mainly. + +For Example 1: + +```js +while (a) { + a = foo(); +} +bar(); +``` + +1. First, the analysis advances to the end of loop. + + ![Loop Event's Example 1](./loop-event-example-while-1.svg) + +2. Second, it creates the looping path. + At this time, the next segment has existed already, so the `onCodePathSegmentStart` event is not fired. + It fires `onCodePathSegmentLoop` instead. + + ![Loop Event's Example 2](./loop-event-example-while-2.svg) + +3. Last, it advances to the end. + + ![Loop Event's Example 3](./loop-event-example-while-3.svg) + +For example 2: + +```js +for (let i = 0; i < 10; ++i) { + foo(i); +} +bar(); +``` + +1. `for` statements are more complex. + First, the analysis advances to `ForStatement.update`. + The `update` segment is hovered at first. + + ![Loop Event's Example 1](./loop-event-example-for-1.svg) + +2. Second, it advances to `ForStatement.body`. + Of course the `body` segment is preceded by the `test` segment. + It keeps the `update` segment hovering. + + ![Loop Event's Example 2](./loop-event-example-for-2.svg) + +3. Third, it creates the looping path from `body` segment to `update` segment. + At this time, the next segment has existed already, so the `onCodePathSegmentStart` event is not fired. + It fires `onCodePathSegmentLoop` instead. + + ![Loop Event's Example 3](./loop-event-example-for-3.svg) + +4. Fourth, also it creates the looping path from `update` segment to `test` segment. + At this time, the next segment has existed already, so the `onCodePathSegmentStart` event is not fired. + It fires `onCodePathSegmentLoop` instead. + + ![Loop Event's Example 4](./loop-event-example-for-4.svg) + +5. Last, it advances to the end. + + ![Loop Event's Example 5](./loop-event-example-for-5.svg) + + + +## Usage Examples + +### To check whether or not this is reachable + +```js +var last = require("lodash").last; + +function isReachable(segment) { + return segment.reachable; +} + +module.exports = function(context) { + var codePathStack = []; + + return { + // Stores CodePath objects. + "onCodePathStart": function(codePath) { + codePathStack.push(codePath); + }, + "onCodePathEnd": function(codePath) { + codePathStack.pop(); + }, + + // Checks reachable or not. + "ExpressionStatement": function(node) { + var codePath = last(codePathStack); + + // Checks the current code path segments. + if (!codePath.currentSegments.some(isReachable)) { + context.report({message: "Unreachable!", node: node}); + } + } + }; +}; +``` + +See Also: +[no-unreachable](https://github.com/eslint/eslint/blob/master/lib/rules/no-unreachable.js), +[no-fallthrough](https://github.com/eslint/eslint/blob/master/lib/rules/no-fallthrough.js), +[consistent-return](https://github.com/eslint/eslint/blob/master/lib/rules/consistent-return.js) + +### To check state of a code path + +This example is checking whether or not the parameter `cb` is called in every path. +Instances of `CodePath` and `CodePathSegment` are shared to every rule. +So a rule must not modify those instances. +Please use a map of information instead. + +```js +var last = require("lodash").last; + +function hasCb(node, context) { + if (node.type.indexOf("Function") !== -1) { + return context.getDeclaredVariables(node).some(function(v) { + return v.type === "Parameter" && v.name === "cb"; + }); + } + return false; +} + +function isCbCalled(info) { + return info.cbCalled; +} + +module.exports = function(context) { + var funcInfoStack = []; + var segmentInfoMap = Object.create(null); + + return { + // Checks `cb`. + "onCodePathStart": function(codePath, node) { + funcInfoStack.push({ + codePath: codePath, + hasCb: hasCb(node, context) + }); + }, + "onCodePathEnd": function(codePath, node) { + funcInfoStack.pop(); + + // Checks `cb` was called in every paths. + var cbCalled = codePath.finalSegments.every(function(segment) { + var info = segmentInfoMap[segment.id]; + return info.cbCalled; + }); + + if (!cbCalled) { + context.report({ + message: "`cb` should be called in every path.", + node: node + }); + } + }, + + // Manages state of code paths. + "onCodePathSegmentStart": function(segment) { + // Ignores if `cb` doesn't exist. + if (!last(funcInfoStack).hasCb) { + return; + } + + // Initialize state of this path. + var info = segmentInfoMap[segment.id] = { + cbCalled: false + }; + + // If there are the previous paths, merges state. + // Checks `cb` was called in every previous path. + if (segment.prevSegments.length > 0) { + info.cbCalled = segment.prevSegments.every(isCbCalled); + } + }, + + // Checks reachable or not. + "CallExpression": function(node) { + var funcInfo = last(funcInfoStack); + + // Ignores if `cb` doesn't exist. + if (!funcInfo.hasCb) { + return; + } + + // Sets marks that `cb` was called. + var callee = node.callee; + if (callee.type === "Identifier" && callee.name === "cb") { + funcInfo.codePath.currentSegments.forEach(function(segment) { + var info = segmentInfoMap[segment.id]; + info.cbCalled = true; + }); + } + } + }; +}; +``` + +See Also: +[constructor-super](https://github.com/eslint/eslint/blob/master/lib/rules/constructor-super.js), +[no-this-before-super](https://github.com/eslint/eslint/blob/master/lib/rules/no-this-before-super.js) + +## Code Path Examples + +### Hello World + +```js +console.log("Hello world!"); +``` + +![Hello World](./example-hello-world.svg) + +### `IfStatement` + +```js +if (a) { + foo(); +} else { + bar(); +} +``` + +![`IfStatement`](./example-ifstatement.svg) + +### `IfStatement` (chain) + +```js +if (a) { + foo(); +} else if (b) { + bar(); +} else if (c) { + hoge(); +} +``` + +![`IfStatement` (chain)](./example-ifstatement-chain.svg) + +### `SwitchStatement` + +```js +switch (a) { + case 0: + foo(); + break; + + case 1: + case 2: + bar(); + // fallthrough + + case 3: + hoge(); + break; +} +``` + +![`SwitchStatement`](./example-switchstatement.svg) + +### `SwitchStatement` (has `default`) + +```js +switch (a) { + case 0: + foo(); + break; + + case 1: + case 2: + bar(); + // fallthrough + + case 3: + hoge(); + break; + + default: + fuga(); + break; +} +``` + +![`SwitchStatement` (has `default`)](./example-switchstatement-has-default.svg) + +### `TryStatement` (try-catch) + +```js +try { + foo(); + if (a) { + throw new Error(); + } + bar(); +} catch (err) { + hoge(err); +} +last(); +``` + +It creates the paths from `try` block to `catch` block at: + +* `throw` statements. +* The first throwable node (e.g. a function call) in the `try` block. +* The end of the `try` block. + +![`TryStatement` (try-catch)](./example-trystatement-try-catch.svg) + +### `TryStatement` (try-finally) + +```js +try { + foo(); + bar(); +} finally { + fuga(); +} +last(); +``` + +If there is not `catch` block, `finally` block has two current segments. +At this time, `CodePath.currentSegments.length` is `2`. +One is the normal path, and another is the leaving path (`throw` or `return`). + +![`TryStatement` (try-finally)](./example-trystatement-try-finally.svg) + +### `TryStatement` (try-catch-finally) + +```js +try { + foo(); + bar(); +} catch (err) { + hoge(err); +} finally { + fuga(); +} +last(); +``` + +![`TryStatement` (try-catch-finally)](./example-trystatement-try-catch-finally.svg) + +### `WhileStatement` + +```js +while (a) { + foo(); + if (b) { + continue; + } + bar(); +} +``` + +![`WhileStatement`](./example-whilestatement.svg) + +### `DoWhileStatement` + +```js +do { + foo(); + bar(); +} while (a); +``` + +![`DoWhileStatement`](./example-dowhilestatement.svg) + +### `ForStatement` + +```js +for (let i = 0; i < 10; ++i) { + foo(); + if (b) { + break; + } + bar(); +} +``` + +![`ForStatement`](./example-forstatement.svg) + +### `ForStatement` (for ever) + +```js +for (;;) { + foo(); +} +bar(); +``` + +![`ForStatement` (for ever)](./example-forstatement-for-ever.svg) + +### `ForInStatement` + +```js +for (let key in obj) { + foo(key); +} +``` + +![`ForInStatement`](./example-forinstatement.svg) + +### When there is a function + +```js +function foo(a) { + if (a) { + return; + } + bar(); +} + +foo(false); +``` + +It creates two code paths. + +* The global's + + ![When there is a function](./example-when-there-is-a-function-g.svg) + +* The function's + + ![When there is a function](./example-when-there-is-a-function-f.svg) diff --git a/eslint/docs/developer-guide/code-path-analysis/example-dowhilestatement.svg b/eslint/docs/developer-guide/code-path-analysis/example-dowhilestatement.svg new file mode 100644 index 0000000..4a3d528 --- /dev/null +++ b/eslint/docs/developer-guide/code-path-analysis/example-dowhilestatement.svg @@ -0,0 +1,100 @@ + + + +_anonymous_0 + +initial + + + +s1_1 + + + + + + + + + + + + + +Program +DoWhileStatement + + +initial->s1_1 + + + + +final + + + + +s1_2 + + + + + + + + + + + + + +BlockStatement +ExpressionStatement +CallExpression +Identifier (foo) +ExpressionStatement +CallExpression +Identifier (bar) +Identifier (a) + + +s1_1->s1_2 + + + + +s1_2->s1_2 + + + + +s1_3 + + + + + + + + + + + + + +DoWhileStatement:exit +Program:exit + + +s1_2->s1_3 + + + + +s1_3->final + + + + + \ No newline at end of file diff --git a/eslint/docs/developer-guide/code-path-analysis/example-forinstatement.svg b/eslint/docs/developer-guide/code-path-analysis/example-forinstatement.svg new file mode 100644 index 0000000..00da11e --- /dev/null +++ b/eslint/docs/developer-guide/code-path-analysis/example-forinstatement.svg @@ -0,0 +1,148 @@ + + + +_anonymous_0 + +initial + + + +s1_1 + + + + + + + + + + + + + +Program +ForInStatement + + +initial->s1_1 + + + + +final + + + + +s1_3 + + + + + + + + + + + + + +Identifier (obj) + + +s1_1->s1_3 + + + + +s1_2 + + + + + + + + + + + + + +VariableDeclaration +VariableDeclarator +Identifier (key) + + +s1_3->s1_2 + + + + +s1_5 + + + + + + + + + + + + + +ForInStatement:exit +Program:exit + + +s1_3->s1_5 + + + + +s1_4 + + + + + + + + + + + + + +BlockStatement +ExpressionStatement +CallExpression +Identifier (foo) +Identifier (key) + + +s1_2->s1_4 + + + + +s1_4->s1_2 + + + + +s1_4->s1_5 + + + + +s1_5->final + + + + + \ No newline at end of file diff --git a/eslint/docs/developer-guide/code-path-analysis/example-forstatement-for-ever.svg b/eslint/docs/developer-guide/code-path-analysis/example-forstatement-for-ever.svg new file mode 100644 index 0000000..b4bdb23 --- /dev/null +++ b/eslint/docs/developer-guide/code-path-analysis/example-forstatement-for-ever.svg @@ -0,0 +1,63 @@ + + + +_anonymous_0 + +initial + + + +s1_1 + + + + + + + + + + + + + +Program +ForStatement + + +initial->s1_1 + + + + +s1_2 + + + + + + + + + + + + + +BlockStatement +ExpressionStatement +CallExpression +Identifier (foo) + + +s1_1->s1_2 + + + + +s1_2->s1_2 + + + + + \ No newline at end of file diff --git a/eslint/docs/developer-guide/code-path-analysis/example-forstatement.svg b/eslint/docs/developer-guide/code-path-analysis/example-forstatement.svg new file mode 100644 index 0000000..376d91b --- /dev/null +++ b/eslint/docs/developer-guide/code-path-analysis/example-forstatement.svg @@ -0,0 +1,201 @@ + + + +_anonymous_0 + +initial + + + +s1_1 + + + + + + + + + + + + + +Program +ForStatement +VariableDeclaration +VariableDeclarator +Identifier (i) +Literal (0) + + +initial->s1_1 + + + + +final + + + + +s1_2 + + + + + + + + + + + + + +BinaryExpression +Identifier (i) +Literal (10) + + +s1_1->s1_2 + + + + +s1_3 + + + + + + + + + + + + + +BlockStatement +ExpressionStatement +CallExpression +Identifier (foo) +IfStatement +Identifier (b) + + +s1_2->s1_3 + + + + +s1_8 + + + + + + + + + + + + + +ForStatement:exit +Program:exit + + +s1_2->s1_8 + + + + +s1_5 + + + + + + + + + + + + + +BlockStatement +BreakStatement + + +s1_3->s1_5 + + + + +s1_7 + + + + + + + + + + + + + +ExpressionStatement +CallExpression +Identifier (bar) + + +s1_3->s1_7 + + + + +s1_5->s1_8 + + + + +s1_4 + + + + + + + + + + + + + +UpdateExpression +Identifier (i) + + +s1_7->s1_4 + + + + +s1_4->s1_2 + + + + +s1_8->final + + + + + \ No newline at end of file diff --git a/eslint/docs/developer-guide/code-path-analysis/example-hello-world.svg b/eslint/docs/developer-guide/code-path-analysis/example-hello-world.svg new file mode 100644 index 0000000..26c4038 --- /dev/null +++ b/eslint/docs/developer-guide/code-path-analysis/example-hello-world.svg @@ -0,0 +1,48 @@ + + + +_anonymous_0 + +initial + + + +s1_1 + + + + + + + + + + + + + +Program +ExpressionStatement +CallExpression +MemberExpression +Identifier (console) +Identifier (log) +Literal (Hello world!) + + +initial->s1_1 + + + + +final + + + + +s1_1->final + + + + + \ No newline at end of file diff --git a/eslint/docs/developer-guide/code-path-analysis/example-ifstatement-chain.svg b/eslint/docs/developer-guide/code-path-analysis/example-ifstatement-chain.svg new file mode 100644 index 0000000..88c2b6e --- /dev/null +++ b/eslint/docs/developer-guide/code-path-analysis/example-ifstatement-chain.svg @@ -0,0 +1,203 @@ + + + +_anonymous_0 + +initial + + + +s1_1 + + + + + + + + + + + + + +Program +IfStatement +Identifier (a) + + +initial->s1_1 + + + + +final + + + + +s1_2 + + + + + + + + + + + + + +BlockStatement +ExpressionStatement +CallExpression +Identifier (foo) + + +s1_1->s1_2 + + + + +s1_3 + + + + + + + + + + + + + +IfStatement +Identifier (b) + + +s1_1->s1_3 + + + + +s1_9 + + + + + + + + + + + + + +IfStatement:exit +Program:exit + + +s1_2->s1_9 + + + + +s1_9->final + + + + +s1_4 + + + + + + + + + + + + + +BlockStatement +ExpressionStatement +CallExpression +Identifier (bar) + + +s1_3->s1_4 + + + + +s1_5 + + + + + + + + + + + + + +IfStatement +Identifier (c) + + +s1_3->s1_5 + + + + +s1_4->s1_9 + + + + +s1_5->s1_9 + + + + +s1_6 + + + + + + + + + + + + + +BlockStatement +ExpressionStatement +CallExpression +Identifier (hoge) + + +s1_5->s1_6 + + + + +s1_6->s1_9 + + + + + \ No newline at end of file diff --git a/eslint/docs/developer-guide/code-path-analysis/example-ifstatement.svg b/eslint/docs/developer-guide/code-path-analysis/example-ifstatement.svg new file mode 100644 index 0000000..7ea670a --- /dev/null +++ b/eslint/docs/developer-guide/code-path-analysis/example-ifstatement.svg @@ -0,0 +1,122 @@ + + + +_anonymous_0 + +initial + + + +s1_1 + + + + + + + + + + + + + +Program +IfStatement +Identifier (a) + + +initial->s1_1 + + + + +final + + + + +s1_2 + + + + + + + + + + + + + +BlockStatement +ExpressionStatement +CallExpression +Identifier (foo) + + +s1_1->s1_2 + + + + +s1_3 + + + + + + + + + + + + + +BlockStatement +ExpressionStatement +CallExpression +Identifier (bar) + + +s1_1->s1_3 + + + + +s1_4 + + + + + + + + + + + + + +IfStatement:exit +Program:exit + + +s1_2->s1_4 + + + + +s1_4->final + + + + +s1_3->s1_4 + + + + + \ No newline at end of file diff --git a/eslint/docs/developer-guide/code-path-analysis/example-switchstatement-has-default.svg b/eslint/docs/developer-guide/code-path-analysis/example-switchstatement-has-default.svg new file mode 100644 index 0000000..26c45fa --- /dev/null +++ b/eslint/docs/developer-guide/code-path-analysis/example-switchstatement-has-default.svg @@ -0,0 +1,279 @@ + + + +_anonymous_0 + +initial + + + +s1_1 + + + + + + + + + + + + + +Program +SwitchStatement +Identifier (a) +SwitchCase +Literal (0) + + +initial->s1_1 + + + + +final + + + + +s1_2 + + + + + + + + + + + + + +ExpressionStatement +CallExpression +Identifier (foo) +BreakStatement + + +s1_1->s1_2 + + + + +s1_4 + + + + + + + + + + + + + +SwitchCase +Literal (1) + + +s1_1->s1_4 + + + + +s1_14 + + + + + + + + + + + + + +SwitchStatement:exit +Program:exit + + +s1_2->s1_14 + + + + +s1_7 + + + + + + + + + + + + + +ExpressionStatement +CallExpression +Identifier (bar) + + +s1_9 + + + + + + + + + + + + + +ExpressionStatement +CallExpression +Identifier (hoge) +BreakStatement + + +s1_7->s1_9 + + + + +s1_9->s1_14 + + + + +s1_12 + + + + + + + + + + + + + +ExpressionStatement +CallExpression +Identifier (fuga) +BreakStatement + + +s1_12->s1_14 + + + + +s1_14->final + + + + +s1_4->s1_7 + + + + +s1_6 + + + + + + + + + + + + + +SwitchCase +Literal (2) + + +s1_4->s1_6 + + + + +s1_6->s1_7 + + + + +s1_8 + + + + + + + + + + + + + +SwitchCase +Literal (3) + + +s1_6->s1_8 + + + + +s1_8->s1_9 + + + + +s1_11 + + + + + + + + + + + + + +SwitchCase + + +s1_8->s1_11 + + + + +s1_11->s1_12 + + + + + \ No newline at end of file diff --git a/eslint/docs/developer-guide/code-path-analysis/example-switchstatement.svg b/eslint/docs/developer-guide/code-path-analysis/example-switchstatement.svg new file mode 100644 index 0000000..778017e --- /dev/null +++ b/eslint/docs/developer-guide/code-path-analysis/example-switchstatement.svg @@ -0,0 +1,232 @@ + + + +_anonymous_0 + +initial + + + +s1_1 + + + + + + + + + + + + + +Program +SwitchStatement +Identifier (a) +SwitchCase +Literal (0) + + +initial->s1_1 + + + + +final + + + + +s1_2 + + + + + + + + + + + + + +ExpressionStatement +CallExpression +Identifier (foo) +BreakStatement + + +s1_1->s1_2 + + + + +s1_4 + + + + + + + + + + + + + +SwitchCase +Literal (1) + + +s1_1->s1_4 + + + + +s1_11 + + + + + + + + + + + + + +SwitchStatement:exit +Program:exit + + +s1_2->s1_11 + + + + +s1_7 + + + + + + + + + + + + + +ExpressionStatement +CallExpression +Identifier (bar) + + +s1_9 + + + + + + + + + + + + + +ExpressionStatement +CallExpression +Identifier (hoge) +BreakStatement + + +s1_7->s1_9 + + + + +s1_9->s1_11 + + + + +s1_11->final + + + + +s1_4->s1_7 + + + + +s1_6 + + + + + + + + + + + + + +SwitchCase +Literal (2) + + +s1_4->s1_6 + + + + +s1_6->s1_7 + + + + +s1_8 + + + + + + + + + + + + + +SwitchCase +Literal (3) + + +s1_6->s1_8 + + + + +s1_8->s1_9 + + + + +s1_8->s1_11 + + + + + \ No newline at end of file diff --git a/eslint/docs/developer-guide/code-path-analysis/example-trystatement-try-catch-finally.svg b/eslint/docs/developer-guide/code-path-analysis/example-trystatement-try-catch-finally.svg new file mode 100644 index 0000000..fbc0083 --- /dev/null +++ b/eslint/docs/developer-guide/code-path-analysis/example-trystatement-try-catch-finally.svg @@ -0,0 +1,137 @@ + + + +_anonymous_0 + +initial + + + +s1_1 + + + + + + + + + + + + + +Program +TryStatement +BlockStatement +ExpressionStatement +CallExpression +Identifier (foo) + + +initial->s1_1 + + + + +final + + + + +s1_2 + + + + + + + + + + + + + +ExpressionStatement +CallExpression +Identifier (bar) + + +s1_1->s1_2 + + + + +s1_3 + + + + + + + + + + + + + +CatchClause +Identifier (err) +BlockStatement +ExpressionStatement +CallExpression +Identifier (hoge) +Identifier (err) + + +s1_1->s1_3 + + + + +s1_2->s1_3 + + + + +s1_4 + + + + + + + + + + + + + +BlockStatement +ExpressionStatement +CallExpression +Identifier (fuga) +ExpressionStatement +CallExpression +Identifier (last) + + +s1_2->s1_4 + + + + +s1_3->s1_4 + + + + +s1_4->final + + + + + \ No newline at end of file diff --git a/eslint/docs/developer-guide/code-path-analysis/example-trystatement-try-catch.svg b/eslint/docs/developer-guide/code-path-analysis/example-trystatement-try-catch.svg new file mode 100644 index 0000000..c6f1879 --- /dev/null +++ b/eslint/docs/developer-guide/code-path-analysis/example-trystatement-try-catch.svg @@ -0,0 +1,186 @@ + + + +_anonymous_0 + +initial + + + +s1_1 + + + + + + + + + + + + + +Program +TryStatement +BlockStatement +ExpressionStatement +CallExpression +Identifier (foo) + + +initial->s1_1 + + + + +final + + + + +s1_2 + + + + + + + + + + + + + +IfStatement +Identifier (a) + + +s1_1->s1_2 + + + + +s1_6 + + + + + + + + + + + + + +CatchClause +Identifier (err) +BlockStatement +ExpressionStatement +CallExpression +Identifier (hoge) +Identifier (err) + + +s1_1->s1_6 + + + + +s1_3 + + + + + + + + + + + + + +BlockStatement +ThrowStatement +NewExpression +Identifier (Error) + + +s1_2->s1_3 + + + + +s1_5 + + + + + + + + + + + + + +ExpressionStatement +CallExpression +Identifier (bar) + + +s1_2->s1_5 + + + + +s1_3->s1_6 + + + + +s1_5->s1_6 + + + + +s1_7 + + + + + + + + + + + + + +ExpressionStatement +CallExpression +Identifier (last) + + +s1_5->s1_7 + + + + +s1_6->s1_7 + + + + +s1_7->final + + + + + \ No newline at end of file diff --git a/eslint/docs/developer-guide/code-path-analysis/example-trystatement-try-finally.svg b/eslint/docs/developer-guide/code-path-analysis/example-trystatement-try-finally.svg new file mode 100644 index 0000000..8b1fb92 --- /dev/null +++ b/eslint/docs/developer-guide/code-path-analysis/example-trystatement-try-finally.svg @@ -0,0 +1,139 @@ + + + +_anonymous_0 + +initial + + + +s1_1 + + + + + + + + + + + + + +Program +TryStatement +BlockStatement +ExpressionStatement +CallExpression +Identifier (foo) + + +initial->s1_1 + + + + +final + + + + +thrown + +✘ + + +s1_2 + + + + + + + + + + + + + +ExpressionStatement +CallExpression +Identifier (bar) + + +s1_1->s1_2 + + + + +s1_4 + + + + + + + + + + + + + +BlockStatement +ExpressionStatement +CallExpression +Identifier (fuga) + + +s1_1->s1_4 + + + + +s1_3 + + + + + + + + + + + + + +BlockStatement +ExpressionStatement +CallExpression +Identifier (fuga) +ExpressionStatement +CallExpression +Identifier (last) + + +s1_2->s1_3 + + + + +s1_2->s1_4 + + + + +s1_3->final + + + + +s1_4->thrown + + + + + \ No newline at end of file diff --git a/eslint/docs/developer-guide/code-path-analysis/example-when-there-is-a-function-f.svg b/eslint/docs/developer-guide/code-path-analysis/example-when-there-is-a-function-f.svg new file mode 100644 index 0000000..a912700 --- /dev/null +++ b/eslint/docs/developer-guide/code-path-analysis/example-when-there-is-a-function-f.svg @@ -0,0 +1,99 @@ + + + +_anonymous_0 + +initial + + + +s2_1 + + + + + + + + + + + + + +FunctionDeclaration +Identifier (foo) +Identifier (a) +BlockStatement +IfStatement +Identifier (a) + + +initial->s2_1 + + + + +final + + + + +s2_2 + + + + + + + + + + + + + +BlockStatement +ReturnStatement + + +s2_1->s2_2 + + + + +s2_4 + + + + + + + + + + + + + +ExpressionStatement +CallExpression +Identifier (bar) + + +s2_1->s2_4 + + + + +s2_2->final + + + + +s2_4->final + + + + + \ No newline at end of file diff --git a/eslint/docs/developer-guide/code-path-analysis/example-when-there-is-a-function-g.svg b/eslint/docs/developer-guide/code-path-analysis/example-when-there-is-a-function-g.svg new file mode 100644 index 0000000..7bde3ea --- /dev/null +++ b/eslint/docs/developer-guide/code-path-analysis/example-when-there-is-a-function-g.svg @@ -0,0 +1,47 @@ + + + +_anonymous_0 + +initial + + + +s1_1 + + + + + + + + + + + + + +Program +FunctionDeclaration +ExpressionStatement +CallExpression +Identifier (foo) +Literal (false) + + +initial->s1_1 + + + + +final + + + + +s1_1->final + + + + + \ No newline at end of file diff --git a/eslint/docs/developer-guide/code-path-analysis/example-whilestatement.svg b/eslint/docs/developer-guide/code-path-analysis/example-whilestatement.svg new file mode 100644 index 0000000..d833dc0 --- /dev/null +++ b/eslint/docs/developer-guide/code-path-analysis/example-whilestatement.svg @@ -0,0 +1,172 @@ + + + +_anonymous_0 + +initial + + + +s1_1 + + + + + + + + + + + + + +Program +WhileStatement + + +initial->s1_1 + + + + +final + + + + +s1_2 + + + + + + + + + + + + + +Identifier (a) + + +s1_1->s1_2 + + + + +s1_3 + + + + + + + + + + + + + +BlockStatement +ExpressionStatement +CallExpression +Identifier (foo) +IfStatement +Identifier (b) + + +s1_2->s1_3 + + + + +s1_7 + + + + + + + + + + + + + +WhileStatement:exit +Program:exit + + +s1_2->s1_7 + + + + +s1_4 + + + + + + + + + + + + + +BlockStatement +ContinueStatement + + +s1_3->s1_4 + + + + +s1_6 + + + + + + + + + + + + + +ExpressionStatement +CallExpression +Identifier (bar) + + +s1_3->s1_6 + + + + +s1_4->s1_2 + + + + +s1_7->final + + + + +s1_6->s1_2 + + + + + \ No newline at end of file diff --git a/eslint/docs/developer-guide/code-path-analysis/helo.svg b/eslint/docs/developer-guide/code-path-analysis/helo.svg new file mode 100644 index 0000000..e2dd9f2 --- /dev/null +++ b/eslint/docs/developer-guide/code-path-analysis/helo.svg @@ -0,0 +1,113 @@ + + + +Code Path 1 +initial + + +s1_1 + + + + + + + + + + + + + +Program +IfStatement +LogicalExpression +Identifier (a) + +initial->s1_1 + + + +final + + + +s1_2 + + + + + + + + + + + + + +Identifier (b) + +s1_1->s1_2 + + + +s1_4 + + + + + + + + + + + + + +ExpressionStatement +CallExpression +Identifier (bar) + +s1_1->s1_4 + + + +s1_3 + + + + + + + + + + + + + +BlockStatement +ExpressionStatement +CallExpression +Identifier (foo) + +s1_2->s1_3 + + + +s1_2->s1_4 + + + +s1_3->s1_4 + + + +s1_4->final + + + + + \ No newline at end of file diff --git a/eslint/docs/developer-guide/code-path-analysis/loop-event-example-for-1.svg b/eslint/docs/developer-guide/code-path-analysis/loop-event-example-for-1.svg new file mode 100644 index 0000000..4975540 --- /dev/null +++ b/eslint/docs/developer-guide/code-path-analysis/loop-event-example-for-1.svg @@ -0,0 +1,84 @@ + + + +_anonymous_0 + +initial + + + +s1_1 + + + + + + + + + + + + + +Program +ForStatement +VariableDeclaration +VariableDeclarator +Identifier (i) +Literal (0) + + +initial->s1_1 + + + + +s1_2 + + + + + + + + + + + + + +BinaryExpression +Identifier (i) +Literal (10) + + +s1_1->s1_2 + + + + +s1_4 + + + + + + + + + + + + + +UpdateExpression +Identifier (i) + + +s1_2->s1_4 + + + + + \ No newline at end of file diff --git a/eslint/docs/developer-guide/code-path-analysis/loop-event-example-for-2.svg b/eslint/docs/developer-guide/code-path-analysis/loop-event-example-for-2.svg new file mode 100644 index 0000000..d35bddf --- /dev/null +++ b/eslint/docs/developer-guide/code-path-analysis/loop-event-example-for-2.svg @@ -0,0 +1,110 @@ + + + +_anonymous_0 + +initial + + + +s1_1 + + + + + + + + + + + + + +Program +ForStatement +VariableDeclaration +VariableDeclarator +Identifier (i) +Literal (0) + + +initial->s1_1 + + + + +s1_2 + + + + + + + + + + + + + +BinaryExpression +Identifier (i) +Literal (10) + + +s1_1->s1_2 + + + + +s1_3 + + + + + + + + + + + + + +BlockStatement +ExpressionStatement +CallExpression +Identifier (foo) +Identifier (i) + + +s1_2->s1_3 + + + + +s1_4 + + + + + + + + + + + + + +UpdateExpression +Identifier (i) + + +s1_2->s1_4 + + + + + \ No newline at end of file diff --git a/eslint/docs/developer-guide/code-path-analysis/loop-event-example-for-3.svg b/eslint/docs/developer-guide/code-path-analysis/loop-event-example-for-3.svg new file mode 100644 index 0000000..a1af0e6 --- /dev/null +++ b/eslint/docs/developer-guide/code-path-analysis/loop-event-example-for-3.svg @@ -0,0 +1,115 @@ + + + +_anonymous_0 + +initial + + + +s1_1 + + + + + + + + + + + + + +Program +ForStatement +VariableDeclaration +VariableDeclarator +Identifier (i) +Literal (0) + + +initial->s1_1 + + + + +s1_2 + + + + + + + + + + + + + +BinaryExpression +Identifier (i) +Literal (10) + + +s1_1->s1_2 + + + + +s1_3 + + + + + + + + + + + + + +BlockStatement +ExpressionStatement +CallExpression +Identifier (foo) +Identifier (i) + + +s1_2->s1_3 + + + + +s1_4 + + + + + + + + + + + + + +UpdateExpression +Identifier (i) + + +s1_3->s1_4 + + + + +s1_4->s1_2 + + + + + \ No newline at end of file diff --git a/eslint/docs/developer-guide/code-path-analysis/loop-event-example-for-4.svg b/eslint/docs/developer-guide/code-path-analysis/loop-event-example-for-4.svg new file mode 100644 index 0000000..a4ee87e --- /dev/null +++ b/eslint/docs/developer-guide/code-path-analysis/loop-event-example-for-4.svg @@ -0,0 +1,115 @@ + + + +_anonymous_0 + +initial + + + +s1_1 + + + + + + + + + + + + + +Program +ForStatement +VariableDeclaration +VariableDeclarator +Identifier (i) +Literal (0) + + +initial->s1_1 + + + + +s1_2 + + + + + + + + + + + + + +BinaryExpression +Identifier (i) +Literal (10) + + +s1_1->s1_2 + + + + +s1_3 + + + + + + + + + + + + + +BlockStatement +ExpressionStatement +CallExpression +Identifier (foo) +Identifier (i) + + +s1_2->s1_3 + + + + +s1_4 + + + + + + + + + + + + + +UpdateExpression +Identifier (i) + + +s1_3->s1_4 + + + + +s1_4->s1_2 + + + + + \ No newline at end of file diff --git a/eslint/docs/developer-guide/code-path-analysis/loop-event-example-for-5.svg b/eslint/docs/developer-guide/code-path-analysis/loop-event-example-for-5.svg new file mode 100644 index 0000000..cba3a01 --- /dev/null +++ b/eslint/docs/developer-guide/code-path-analysis/loop-event-example-for-5.svg @@ -0,0 +1,149 @@ + + + +_anonymous_0 + +initial + + + +s1_1 + + + + + + + + + + + + + +Program +ForStatement +VariableDeclaration +VariableDeclarator +Identifier (i) +Literal (0) + + +initial->s1_1 + + + + +final + + + + +s1_2 + + + + + + + + + + + + + +BinaryExpression +Identifier (i) +Literal (10) + + +s1_1->s1_2 + + + + +s1_3 + + + + + + + + + + + + + +BlockStatement +ExpressionStatement +CallExpression +Identifier (foo) +Identifier (i) + + +s1_2->s1_3 + + + + +s1_5 + + + + + + + + + + + + + +ExpressionStatement +CallExpression +Identifier (bar) + + +s1_2->s1_5 + + + + +s1_4 + + + + + + + + + + + + + +UpdateExpression +Identifier (i) + + +s1_3->s1_4 + + + + +s1_4->s1_2 + + + + +s1_5->final + + + + + \ No newline at end of file diff --git a/eslint/docs/developer-guide/code-path-analysis/loop-event-example-while-1.svg b/eslint/docs/developer-guide/code-path-analysis/loop-event-example-while-1.svg new file mode 100644 index 0000000..8036529 --- /dev/null +++ b/eslint/docs/developer-guide/code-path-analysis/loop-event-example-while-1.svg @@ -0,0 +1,82 @@ + + + +_anonymous_0 + +initial + + + +s1_1 + + + + + + + + + + + + + +Program +WhileStatement + + +initial->s1_1 + + + + +s1_2 + + + + + + + + + + + + + +Identifier (a) + + +s1_1->s1_2 + + + + +s1_3 + + + + + + + + + + + + + +BlockStatement +ExpressionStatement +AssignmentExpression +Identifier (a) +CallExpression +Identifier (foo) + + +s1_2->s1_3 + + + + + \ No newline at end of file diff --git a/eslint/docs/developer-guide/code-path-analysis/loop-event-example-while-2.svg b/eslint/docs/developer-guide/code-path-analysis/loop-event-example-while-2.svg new file mode 100644 index 0000000..63355dd --- /dev/null +++ b/eslint/docs/developer-guide/code-path-analysis/loop-event-example-while-2.svg @@ -0,0 +1,87 @@ + + + +_anonymous_0 + +initial + + + +s1_1 + + + + + + + + + + + + + +Program +WhileStatement + + +initial->s1_1 + + + + +s1_2 + + + + + + + + + + + + + +Identifier (a) + + +s1_1->s1_2 + + + + +s1_3 + + + + + + + + + + + + + +BlockStatement +ExpressionStatement +AssignmentExpression +Identifier (a) +CallExpression +Identifier (foo) + + +s1_2->s1_3 + + + + +s1_3->s1_2 + + + + + \ No newline at end of file diff --git a/eslint/docs/developer-guide/code-path-analysis/loop-event-example-while-3.svg b/eslint/docs/developer-guide/code-path-analysis/loop-event-example-while-3.svg new file mode 100644 index 0000000..cb21c43 --- /dev/null +++ b/eslint/docs/developer-guide/code-path-analysis/loop-event-example-while-3.svg @@ -0,0 +1,121 @@ + + + +_anonymous_0 + +initial + + + +s1_1 + + + + + + + + + + + + + +Program +WhileStatement + + +initial->s1_1 + + + + +final + + + + +s1_2 + + + + + + + + + + + + + +Identifier (a) + + +s1_1->s1_2 + + + + +s1_3 + + + + + + + + + + + + + +BlockStatement +ExpressionStatement +AssignmentExpression +Identifier (a) +CallExpression +Identifier (foo) + + +s1_2->s1_3 + + + + +s1_4 + + + + + + + + + + + + + +ExpressionStatement +CallExpression +Identifier (bar) + + +s1_2->s1_4 + + + + +s1_3->s1_2 + + + + +s1_4->final + + + + + \ No newline at end of file diff --git a/eslint/docs/developer-guide/contributing/README.md b/eslint/docs/developer-guide/contributing/README.md new file mode 100644 index 0000000..a22ea5d --- /dev/null +++ b/eslint/docs/developer-guide/contributing/README.md @@ -0,0 +1,41 @@ +# Contributing + +One of the great things about open source projects is that anyone can contribute in any number of meaningful ways. ESLint couldn't exist without the help of the many contributors it's had since the project began, and we want you to feel like you can contribute and make a difference as well. + +This guide is intended for anyone who wants to contribute to an ESLint project. Please read it carefully as it answers a lot of the questions many newcomers have when first working with our projects. + +## Read the [Code of Conduct](https://js.foundation/community/code-of-conduct) + +ESLint welcomes contributions from everyone and adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct). We kindly request that you read over our code of conduct before contributing. + +## [Signing the CLA](https://js.foundation/CLA) + +In order to submit code or documentation to an ESLint project, you will need to electronically sign our [Contributor License Agreement](https://cla.js.foundation/eslint/eslint). The CLA is you giving us permission to use your contribution. + +## [Bug Reporting](reporting-bugs) + +Think you found a problem? We'd love to hear about it. This section explains how to submit a bug, the type of information we need to properly verify it, and the overall process. + +## Proposing a [New Rule](new-rules.md) + +We get a lot of proposals for new rules in ESLint. This section explains how we determine which rules are accepted and what information you should provide to help us evaluate your proposal. + +## Proposing a [Rule Change](rule-changes.md) + +Want to make a change to an existing rule? This section explains the process and how we evaluate such proposals. + +## Requesting a [Change](changes.md) + +If you'd like to request a change other than a bug fix or new rule, this section explains that process. + +## Reporting a security vulnerability + +To report a security vulnerability in ESLint, please use our [HackerOne program](https://hackerone.com/eslint). + +## [Working on Issues](working-on-issues.md) + +Have some extra time and want to contribute? This section talks about the process of working on issues. + +## Submitting a [Pull Request](pull-requests.md) + +We're always looking for contributions from the community. This section explains the requirements for pull requests and the process of contributing code. diff --git a/eslint/docs/developer-guide/contributing/changes.md b/eslint/docs/developer-guide/contributing/changes.md new file mode 100644 index 0000000..6b5f159 --- /dev/null +++ b/eslint/docs/developer-guide/contributing/changes.md @@ -0,0 +1,17 @@ +# Change Requests + +If you'd like to request a change to ESLint, please [create a new issue](https://github.com/eslint/eslint/issues/new?template=CHANGE.md) on GitHub. Be sure to include the following information: + +1. The version of ESLint you are using. +1. The problem you want to solve. +1. Your take on the correct solution to problem. + +If you're requesting a change to a rule, it's helpful to include this information as well: + +1. What you did. +1. What you would like to happen. +1. What actually happened. + +Please include as much detail as possible to help us properly address your issue. If we need to triage issues and constantly ask people for more detail, that's time taken away from actually fixing issues. Help us be as efficient as possible by including a lot of detail in your issues. + +**Note:** If you just have a question that won't necessarily result in a change to ESLint, such as asking how something works or how to contribute, please use the [mailing list](https://groups.google.com/group/eslint) or [chat](https://gitter.im/eslint/eslint) instead of filing an issue. diff --git a/eslint/docs/developer-guide/contributing/new-rules.md b/eslint/docs/developer-guide/contributing/new-rules.md new file mode 100644 index 0000000..a351377 --- /dev/null +++ b/eslint/docs/developer-guide/contributing/new-rules.md @@ -0,0 +1,42 @@ +# New Rules + +ESLint is all about rules. For most of the project's lifetime, we've had over 200 rules, and that list continues to grow. However, we can't just accept any proposed rule because all rules need to work cohesively together. As such, we have some guidelines around which rules can be part of the ESLint core and which are better off as custom rules and plugins. + +**Note:** As of 2016, we accept only rules that are deemed extremely important for inclusion. We prefer that new rules be implemented in plugins. + +## Core Rule Guidelines + +In general, ESLint core rules must be: + +1. **Widely applicable.** The rules we distribute need to be of importance to a large number of developers. Individual preferences for uncommon patterns are not supported. +1. **Generic.** Rules cannot be so specific that users will have trouble understanding when to use them. A rule is typically too specific if describing what it does requires more than two "and"s (if a and b and c and d, then this rule warns). +1. **Atomic.** Rules must function completely on their own. Rules are expressly forbidden from knowing about the state or presence of other rules. +1. **Unique.** No two rules can produce the same warning. Overlapping rules confuse end users and there is an expectation that core ESLint rules do not overlap. +1. **Library agnostic.** Rules must be based solely on JavaScript runtime environments and not on specific libraries or frameworks. For example, core rules shouldn't only apply if you're using jQuery but we may have some rules that apply only if you're using Node.js (a runtime). +1. **No conflicts.** No rule must directly conflict with another rule. For example, if we have a rule requiring semicolons, we cannot also have a rule disallowing semicolons (which is why we have one rule, `semi`, that does both). + +Even though these are the formal criteria for inclusion, each rule is evaluated on its own basis. + +## Proposing a Rule + +If you want to propose a new rule, please see how to [create a pull request](/docs/developer-guide/contributing/pull-requests) or submit an issue by filling out a [new rule template](https://github.com/eslint/eslint/issues/new?template=NEW_RULE.md). + +We need all of this information in order to determine whether or not the rule is a good core rule candidate. + +## Accepting a Rule + +In order for a rule to be accepted in the ESLint core, it must: + +1. Fulfill all the criteria listed in the "Core Rule Guidelines" section +1. Have an ESLint team member champion inclusion of the rule +1. Be very important for ESLint users because it either catches a serious problem or allows styling of code in accordance with a popular style guide + +Keep in mind that we have over 200 rules, and that is daunting both for end users and the ESLint team (who has to maintain them). As such, any new rules must be deemed of high importance to be considered for inclusion in ESLint. + +## Implementation is Your Responsibility + +The ESLint team doesn't implement new rules that are suggested by users because we have a limited number of people and need to focus on the overall roadmap. Once a rule is accepted, you are responsible for implementing and documenting the rule. You may, alternately, recruit another person to help you implement the rule. The ESLint team member who championed the rule is your resource to help guide you through the rest of this process. + +## Alternative: Creating Your Own Rules + +Remember that ESLint is completely pluggable, which means you can create your own rules and distribute them using plugins. We did this on purpose because we don't want to be the gatekeepers for all possible rules. Even if we don't accept a rule into the core, that doesn't mean you can't have the exact rule that you want. See the [working with rules](../working-with-rules.md) and [working with plugins](../working-with-plugins.md) documentation for more information. diff --git a/eslint/docs/developer-guide/contributing/pull-requests.md b/eslint/docs/developer-guide/contributing/pull-requests.md new file mode 100644 index 0000000..ef4169e --- /dev/null +++ b/eslint/docs/developer-guide/contributing/pull-requests.md @@ -0,0 +1,182 @@ +# Pull Requests + +If you want to contribute to an ESLint repo, please use a GitHub pull request. This is the fastest way for us to evaluate your code and to merge it into the code base. Please don't file an issue with snippets of code. Doing so means that we need to manually merge the changes in and update any appropriate tests. That decreases the likelihood that your code is going to get included in a timely manner. Please use pull requests. + +## Getting Started + +If you'd like to work on a pull request and you've never submitted code before, follow these steps: + +1. Sign our [Contributor License Agreement](https://cla.js.foundation/eslint/eslint). +1. Set up a [development environment](../development-environment.md). +1. If you want to implement a breaking change or a change to the core, ensure there's an issue that describes what you're doing and the issue has been accepted. You can create a new issue or just indicate you're [working on an existing issue](working-on-issues.md). Bug fixes, documentation changes, and other pull requests do not require an issue. + +After that, you're ready to start working on code. + +## Working with Code + +The process of submitting a pull request is fairly straightforward and generally follows the same pattern each time: + +1. [Create a new branch](#step1) +2. [Make your changes](#step2) +3. [Rebase onto upstream](#step3) +4. [Run the tests](#step4) +5. [Double check your submission](#step5) +6. [Push your changes](#step6) +7. [Submit the pull request](#step7) + +Details about each step are found below. + +### Step 1: Create a new branch + +The first step to sending a pull request is to create a new branch in your ESLint fork. Give the branch a descriptive name that describes what it is you're fixing, such as: + +``` +$ git checkout -b issue1234 +``` + +You should do all of your development for the issue in this branch. + +**Note:** Do not combine fixes for multiple issues into one branch. Use a separate branch for each issue you're working on. + +### Step 2: Make your changes + +Make the changes to the code and tests, following the [code conventions](../code-conventions.md) as you go. Once you have finished, commit the changes to your branch: + +``` +$ git add -A +$ git commit +``` + +Our commit message format is as follows: + +``` +Tag: Short description (fixes #1234) + +Longer description here if necessary +``` + +The first line of the commit message (the summary) must have a specific format. This format is checked by our build tools. + +The `Tag` is one of the following: + +* `Fix` - for a bug fix. +* `Update` - either for a backwards-compatible enhancement or for a rule change that adds reported problems. +* `New` - implemented a new feature. +* `Breaking` - for a backwards-incompatible enhancement or feature. +* `Docs` - changes to documentation only. +* `Build` - changes to build process only. +* `Upgrade` - for a dependency upgrade. +* `Chore` - for refactoring, adding tests, etc. (anything that isn't user-facing). + +Use the [labels of the issue you are working on](working-on-issues.md#issue-labels) to determine the best tag. + +The message summary should be a one-sentence description of the change, and it must be 72 characters in length or shorter. If the pull request addresses an issue, then the issue number should be mentioned at the end. If the commit doesn't completely fix the issue, then use `(refs #1234)` instead of `(fixes #1234)`. + +Here are some good commit message summary examples: + +``` +Build: Update Travis to only test Node 0.10 (refs #734) +Fix: Semi rule incorrectly flagging extra semicolon (fixes #840) +Upgrade: Esprima to 1.2, switch to using comment attachment (fixes #730) +``` + +The commit message format is important because these messages are used to create a changelog for each release. The tag and issue number help to create more consistent and useful changelogs. + +### Step 3: Rebase onto upstream + +Before you send the pull request, be sure to rebase onto the upstream source. This ensures your code is running on the latest available code. + +``` +git fetch upstream +git rebase upstream/master +``` + +### Step 4: Run the tests + +After rebasing, be sure to run all of the tests once again to make sure nothing broke: + +``` +npm test +``` + +If there are any failing tests, update your code until all tests pass. + +### Step 5: Double check your submission + +With your code ready to go, this is a good time to double-check your submission to make sure it follows our conventions. Here are the things to check: + +* Make sure your commit is formatted correctly. +* The pull request must have a description. The description should explain what you did and how its effects can be seen. +* The commit message is properly formatted. +* The change introduces no functional regression. Be sure to run `npm test` to verify your changes before submitting a pull request. +* Make separate pull requests for unrelated changes. Large pull requests with multiple unrelated changes may be closed without merging. +* All changes must be accompanied by tests, even if the feature you're working on previously had no tests. +* All user-facing changes must be accompanied by appropriate documentation. +* Follow the [Code Conventions](../code-conventions.md). + +### Step 6: Push your changes + +Next, push your changes to your clone: + +``` +git push origin issue1234 +``` + +If you are unable to push because some references are old, do a forced push instead: + +``` +git push -f origin issue1234 +``` + +### Step 7: Send the pull request + +Now you're ready to send the pull request. Go to your ESLint fork and then follow the [GitHub documentation](https://help.github.com/articles/creating-a-pull-request) on how to send a pull request. + +## Following Up + +Once your pull request is sent, it's time for the team to review it. As such, please make sure to: + +1. Monitor the status of the Travis CI build for your pull request. If it fails, please investigate why. We cannot merge pull requests that fail Travis for any reason. +1. Respond to comments left on the pull request from team members. Remember, we want to help you land your code, so please be receptive to our feedback. +1. We may ask you to make changes, rebase, or squash your commits. + +### Updating the Commit Message + +If your commit message is in the incorrect format, you'll be asked to update it. You can do so via: + +``` +$ git commit --amend +``` + +This will open up your editor so you can make changes. After that, you'll need to do a forced push to your branch: + +``` +$ git push origin issue1234 -f +``` + +### Updating the Code + +If we ask you to make code changes, there's no need to close the pull request and create a new one. Just go back to the branch on your fork and make your changes. Then, when you're ready, you can add your changes into the branch: + +``` +$ git add -A +$ git commit +$ git push origin issue1234 +``` + +When updating the code, it's usually better to add additional commits to your branch rather than amending the original commit, because reviewers can easily tell which changes were made in response to a particular review. When we merge pull requests, we will squash all the commits from your branch into a single commit on the `master` branch. + +### Rebasing + +If your code is out-of-date, we might ask you to rebase. That means we want you to apply your changes on top of the latest upstream code. Make sure you have set up a [development environment](../development-environment.md) and then you can rebase using these commands: + +``` +$ git fetch upstream +$ git rebase upstream/master +``` + +You might find that there are merge conflicts when you attempt to rebase. Please [resolve the conflicts](https://help.github.com/articles/resolving-merge-conflicts-after-a-git-rebase/) and then do a forced push to your branch: + +``` +$ git push origin issue1234 -f +``` diff --git a/eslint/docs/developer-guide/contributing/reporting-bugs.md b/eslint/docs/developer-guide/contributing/reporting-bugs.md new file mode 100644 index 0000000..81f5e85 --- /dev/null +++ b/eslint/docs/developer-guide/contributing/reporting-bugs.md @@ -0,0 +1,7 @@ +# Reporting Bugs + +If you think you've found a bug in ESLint, please [create a new issue](https://github.com/eslint/eslint/issues/new) or a [pull request](/docs/developer-guide/contributing/pull-requests.md) on GitHub. + +Please include as much detail as possible to help us properly address your issue. If we need to triage issues and constantly ask people for more detail, that's time taken away from actually fixing issues. Help us be as efficient as possible by including a lot of detail in your issues. + +**Note:** If you just have a question that won't necessarily result in a change to ESLint, such as asking how something works or how to contribute, please use the [mailing list](https://groups.google.com/group/eslint) or [chat](https://gitter.im/eslint/eslint) instead of filing an issue. diff --git a/eslint/docs/developer-guide/contributing/rule-changes.md b/eslint/docs/developer-guide/contributing/rule-changes.md new file mode 100644 index 0000000..d45ddbe --- /dev/null +++ b/eslint/docs/developer-guide/contributing/rule-changes.md @@ -0,0 +1,21 @@ +# Rule Changes + +Occasionally, a core ESLint rule needs to be changed. This is not necessarily a bug, but rather, an enhancement that makes a rule more configurable. In those situations, we will consider making changes to rules. + +## Proposing a Rule Change + +To propose a change to an existing rule, [create a pull request](/docs/developer-guide/contributing/pull-requests.md) or [new issue](https://github.com/eslint/eslint/issues/new?template=RULE_CHANGE.md) and fill out the template. + +We need all of this information in order to determine whether or not the change is a good candidate for inclusion. + +## Accepting a Rule Change + +In order for a rule change to be accepted into ESLint, it must: + +1. Adhere to the [Core Rule Guidelines](new-rules.md#core-rule-guidelines) +1. Have an ESLint team member champion the change +1. Be important enough that rule is deemed incomplete without this change + +## Implementation is Your Responsibility + +The ESLint team doesn't implement rule changes that are suggested by users because we have a limited number of people and need to focus on the overall roadmap. Once a rule change is accepted, you are responsible for implementing and documenting it. You may, alternately, recruit another person to help you. The ESLint team member who championed the rule is your resource to help guide you through the rest of this process. diff --git a/eslint/docs/developer-guide/contributing/working-on-issues.md b/eslint/docs/developer-guide/contributing/working-on-issues.md new file mode 100644 index 0000000..9e666d6 --- /dev/null +++ b/eslint/docs/developer-guide/contributing/working-on-issues.md @@ -0,0 +1,36 @@ +# Working on Issues + +Our public [issues tracker](https://github.com/eslint/eslint/issues) lists all of the things we plan on doing as well as suggestions from the community. Before starting to work on an issue, be sure you read through the rest of this page. + +## Issue Labels + +We use labels to indicate the status of issues. The most complete documentation on the labels is found in the [Maintainer Guide](https://eslint.org/docs/maintainer-guide/issues.html#when-an-issue-is-opened), but most contributors should find the information on this page sufficient. The most important questions that labels can help you, as a contributor, answer are: + +1. Is this issue available for me to work on? If you have little or no experience contributing to ESLint, the [`good first issue`](https://github.com/eslint/eslint/labels/good%20first%20issue) label marks appropriate issues. Otherwise, the [`help wanted`](https://github.com/eslint/eslint/labels/help%20wanted) label is an invitation to work on the issue. If you have more experience, you can try working on other issues labeled [`accepted`](https://github.com/eslint/eslint/labels/accepted). Conversely, issues not yet ready to work on are labeled `triage`, `evaluating`, and/or `needs bikeshedding`, and issues that cannot currently be worked on because of something else, such as a bug in a dependency, are labeled `blocked`. +1. What is this issue about? Labels describing the nature of issues include `bug`, `enhancement`, `feature`, `question`, `rule`, `documentation`, `core`, `build`, `cli`, `infrastructure`, `breaking`, and `chore`. These are documented in the [Maintainer Guide](https://eslint.org/docs/maintainer-guide/issues.html#types-of-issues). +1. What is the priority of this issue? Because we have a lot of issues, we prioritize certain issues above others. The following is the list of priorities, from highest to lowest: + + 1. **Bugs** - problems with the project are actively affecting users. We want to get these resolved as quickly as possible. + 1. **Documentation** - documentation issues are a type of bug in that they actively affect current users. As such, we want to address documentation issues as quickly as possible. + 1. **Features** - new functionality that will aid users in the future. + 1. **Enhancements** - requested improvements for existing functionality. + 1. **Other** - anything else. + + Some issues have had monetary rewards attached to them. Those are labeled `bounty`. Bounties are assigned via [BountySource](https://www.bountysource.com/teams/eslint/issues). + +## Starting Work + +If you're going to work on an issue, please add a comment to that issue saying so and indicating when you think you will complete it. It will help us to avoid duplication of effort. Some examples of good comments are: + +* "I'll take a look at this over the weekend." +* "I'm going to do this, give me two weeks." +* "Working on this" (as in, I'm working on it right now) + +If an issue has already been claimed by someone, please be respectful of that person's desire to complete the work and don't work on it unless you verify that they are no longer interested. + +If you find you can't finish the work, then simply add a comment letting people know, for example: + +* "Sorry, it looks like I don't have time to do this." +* "I thought I knew enough to fix this, but it turns out I don't." + +No one will blame you for backing out of an issue if you are unable to complete it. We just want to keep the process moving along as efficiently as possible. diff --git a/eslint/docs/developer-guide/development-environment.md b/eslint/docs/developer-guide/development-environment.md new file mode 100644 index 0000000..a93e1ef --- /dev/null +++ b/eslint/docs/developer-guide/development-environment.md @@ -0,0 +1,107 @@ +# Development Environment + +ESLint has a very lightweight development environment that makes updating code fast and easy. This is a step-by-step guide to setting up a local development environment that will let you contribute back to the project. + +## Step 1: Install Node.js + +Go to to download and install the latest stable version for your operating system. + +Most of the installers come with [npm](https://www.npmjs.com/) already installed, but if for some reason it doesn't work on your system, you can install it manually using the instructions on the site. + +## Step 2: Fork and checkout your own ESLint repository + +Go to and click the "Fork" button. Follow the [GitHub documentation](https://help.github.com/articles/fork-a-repo) for forking and cloning. + +Once you've cloned the repository, run `npm install` to get all the necessary dependencies: + +``` +$ cd eslint +$ npm install +``` + +You must be connected to the Internet for this step to work. You'll see a lot of utilities being downloaded. + +## Step 3: Add the upstream source + +The *upstream source* is the main ESLint repository that active development happens on. While you won't have push access to upstream, you will have pull access, allowing you to pull in the latest code whenever you want. + +To add the upstream source for ESLint, run the following in your repository: + +``` +git remote add upstream git@github.com:eslint/eslint.git +``` + +Now, the remote `upstream` points to the upstream source. + +## Step 4: Install the Yeoman Generator + +[Yeoman](http://yeoman.io) is a scaffold generator that ESLint uses to help streamline development of new rules. If you don't already have Yeoman installed, you can install it via npm: + + npm install -g yo + +Then, you can install the ESLint Yeoman generator: + + npm install -g generator-eslint + +Please see the [generator documentation](https://github.com/eslint/generator-eslint) for instructions on how to use it. + +## Step 5: Run the tests + +Running the tests is the best way to ensure you have correctly set up your development environment. Make sure you're in the `eslint` directory and run: + +``` +npm test +``` + +The testing takes a few minutes to complete. If any tests fail, that likely means one or more parts of the environment setup didn't complete correctly. The upstream tests always pass. + +## Reference Information + +### Workflow + +Once you have your development environment installed, you can make and submit changes to the ESLint source files. Doing this successfully requires careful adherence to our [pull-request submission workflow](contributing/pull-requests.md). + +### Build Scripts + +ESLint has several build scripts that help with various parts of development. + +#### npm test + +The primary script to use is `npm test`, which does several things: + +1. Lints all JavaScript (including tests) and JSON +1. Runs all tests on Node.js +1. Checks code coverage targets +1. Generates `build/eslint.js` for use in a browser +1. Runs a subset of tests in PhantomJS + +Be sure to run this after making changes and before sending a pull request with your changes. + +**Note:** The full code coverage report is output into `/coverage`. + +#### npm run lint + +Runs just the JavaScript and JSON linting on the repository + +#### npm run webpack + +Generates `build/eslint.js`, a version of ESLint for use in the browser + +#### npm run docs + +Generates JSDoc documentation and places it into `/jsdoc`. + +#### npm run profile + +This command is used for intensive profiling of ESLint using Chrome Developer Tools. It starts a development server that runs through three profiles: + +* Large - Runs ESLint on JSHint +* Medium - Runs ESLint on jQuery +* Small - Runs ESLint on KnockoutJS + +Your browser should automatically open to the page in question. When that happens: + +1. Open up developer tools +1. Click on Profiles + +You should start to see profiles for each run show up on the left side. If not, reload the page in the browser. Once all three profiles have completed, they will be available for inspection. diff --git a/eslint/docs/developer-guide/nodejs-api.md b/eslint/docs/developer-guide/nodejs-api.md new file mode 100644 index 0000000..ee1a7a4 --- /dev/null +++ b/eslint/docs/developer-guide/nodejs-api.md @@ -0,0 +1,1013 @@ +# Node.js API + +While ESLint is designed to be run on the command line, it's possible to use ESLint programmatically through the Node.js API. The purpose of the Node.js API is to allow plugin and tool authors to use the ESLint functionality directly, without going through the command line interface. + +**Note:** Use undocumented parts of the API at your own risk. Only those parts that are specifically mentioned in this document are approved for use and will remain stable and reliable. Anything left undocumented is unstable and may change or be removed at any point. + +## Table of Contents + +* [SourceCode](#sourcecode) + * [splitLines()](#sourcecode-splitlines) +* [Linter](#linter) + * [verify()](#linter-verify) + * [verifyAndFix()](#linter-verifyandfix) + * [defineRule()](#linter-definerule) + * [defineRules()](#linter-definerules) + * [getRules()](#linter-getrules) + * [defineParser()](#linter-defineparser) + * [version](#linter-version) +* [linter (deprecated)](#linter-1) +* [CLIEngine](#cliengine) + * [executeOnFiles()](#cliengine-executeonfiles) + * [resolveFileGlobPatterns()](#cliengine-resolvefileglobpatterns) + * [getConfigForFile()](#cliengine-getconfigforfile) + * [executeOnText()](#cliengine-executeontext) + * [addPlugin()](#cliengine-addplugin) + * [isPathIgnored()](#cliengine-ispathignored) + * [getFormatter()](#cliengine-getformatter) + * [getErrorResults()](#cliengine-geterrorresults) + * [outputFixes()](#cliengine-outputfixes) + * [getRules()](#cliengine-getrules) + * [version](#cliengine-version) +* [RuleTester](#ruletester) + * [Customizing RuleTester](#customizing-ruletester) +* [Deprecated APIs](#deprecated-apis) + +## SourceCode + +The `SourceCode` type represents the parsed source code that ESLint executes on. It's used internally in ESLint and is also available so that already-parsed code can be used. You can create a new instance of `SourceCode` by passing in the text string representing the code and an abstract syntax tree (AST) in [ESTree](https://github.com/estree/estree) format (including location information, range information, comments, and tokens): + +```js +const SourceCode = require("eslint").SourceCode; + +const code = new SourceCode("var foo = bar;", ast); +``` + +The `SourceCode` constructor throws an error if the AST is missing any of the required information. + +The `SourceCode` constructor strips Unicode BOM. +Please note the AST also should be parsed from stripped text. + +```js +const SourceCode = require("eslint").SourceCode; + +const code = new SourceCode("\uFEFFvar foo = bar;", ast); + +assert(code.hasBOM === true); +assert(code.text === "var foo = bar;"); +``` + +### SourceCode#splitLines() + +This is a static function on `SourceCode` that is used to split the source code text into an array of lines. + +```js +const SourceCode = require("eslint").SourceCode; + +const code = "var a = 1;\nvar b = 2;" + +// split code into an array +const codeLines = SourceCode.splitLines(code); + +/* + Value of codeLines will be + [ + "var a = 1;", + "var b = 2;" + ] + */ +``` + +## Linter + +The `Linter` object does the actual evaluation of the JavaScript code. It doesn't do any filesystem operations, it simply parses and reports on the code. In particular, the `Linter` object does not process configuration objects or files. +The `Linter` is a constructor, and you can create a new instance by passing in the options you want to use. The available options are: + +* `cwd` - Path to a directory that should be considered as the current working directory. It is accessible to rules by calling `context.getCwd()` (see [The Context Object](./working-with-rules.md#The-Context-Object)). If `cwd` is `undefined`, it will be normalized to `process.cwd()` if the global `process` object is defined (for example, in the Node.js runtime) , or `undefined` otherwise. + +For example: + +```js +const Linter = require("eslint").Linter; +const linter1 = new Linter({ cwd: 'path/to/project' }); +const linter2 = new Linter(); +``` + +In this example, rules run on `linter1` will get `path/to/project` when calling `context.getCwd()`. +Those run on `linter2` will get `process.cwd()` if the global `process` object is defined or `undefined` otherwise (e.g. on the browser https://eslint.org/demo). + +### Linter#verify + +The most important method on `Linter` is `verify()`, which initiates linting of the given text. This method accepts three arguments: + +* `code` - the source code to lint (a string or instance of `SourceCode`). +* `config` - a configuration object that has been processed and normalized by CLIEngine using eslintrc files and/or other configuration arguments. + * **Note**: If you want to lint text and have your configuration be read and processed, use CLIEngine's [`executeOnFiles`](#cliengineexecuteonfiles) or [`executeOnText`](#cliengineexecuteontext) instead. +* `options` - (optional) Additional options for this run. + * `filename` - (optional) the filename to associate with the source code. + * `preprocess` - (optional) A function that [Processors in Plugins](/docs/developer-guide/working-with-plugins.md#processors-in-plugins) documentation describes as the `preprocess` method. + * `postprocess` - (optional) A function that [Processors in Plugins](/docs/developer-guide/working-with-plugins.md#processors-in-plugins) documentation describes as the `postprocess` method. + * `filterCodeBlock` - (optional) A function that decides which code blocks the linter should adopt. The function receives two arguments. The first argument is the virtual filename of a code block. The second argument is the text of the code block. If the function returned `true` then the linter adopts the code block. If the function was omitted, the linter adopts only `*.js` code blocks. If you provided a `filterCodeBlock` function, it overrides this default behavior, so the linter doesn't adopt `*.js` code blocks automatically. + * `disableFixes` - (optional) when set to `true`, the linter doesn't make either the `fix` or `suggestions` property of the lint result. + * `allowInlineConfig` - (optional) set to `false` to disable inline comments from changing ESLint rules. + * `reportUnusedDisableDirectives` - (optional) when set to `true`, adds reported errors for unused `eslint-disable` directives when no problems would be reported in the disabled area anyway. + +If the third argument is a string, it is interpreted as the `filename`. + +You can call `verify()` like this: + +```js +const Linter = require("eslint").Linter; +const linter = new Linter(); + +const messages = linter.verify("var foo;", { + rules: { + semi: 2 + } +}, { filename: "foo.js" }); + +// or using SourceCode + +const Linter = require("eslint").Linter, + linter = new Linter(), + SourceCode = require("eslint").SourceCode; + +const code = new SourceCode("var foo = bar;", ast); + +const messages = linter.verify(code, { + rules: { + semi: 2 + } +}, { filename: "foo.js" }); +``` + +The `verify()` method returns an array of objects containing information about the linting warnings and errors. Here's an example: + +```js +{ + fatal: false, + ruleId: "semi", + severity: 2, + line: 1, + column: 23, + message: "Expected a semicolon.", + fix: { + range: [1, 15], + text: ";" + } +} +``` + +The information available for each linting message is: + +* `column` - the column on which the error occurred. +* `fatal` - usually omitted, but will be set to true if there's a parsing error (not related to a rule). +* `line` - the line on which the error occurred. +* `message` - the message that should be output. +* `nodeType` - the node or token type that was reported with the problem. +* `ruleId` - the ID of the rule that triggered the messages (or null if `fatal` is true). +* `severity` - either 1 or 2, depending on your configuration. +* `endColumn` - the end column of the range on which the error occurred (this property is omitted if it's not range). +* `endLine` - the end line of the range on which the error occurred (this property is omitted if it's not range). +* `fix` - an object describing the fix for the problem (this property is omitted if no fix is available). +* `suggestions` - an array of objects describing possible lint fixes for editors to programmatically enable (see details in the [Working with Rules docs](./working-with-rules.md#providing-suggestions)). + +Linting message objects have a deprecated `source` property. This property **will be removed** from linting messages in an upcoming breaking release. If you depend on this property, you should now use the `SourceCode` instance provided by the linter. + +You can also get an instance of the `SourceCode` object used inside of `linter` by using the `getSourceCode()` method: + +```js +const Linter = require("eslint").Linter; +const linter = new Linter(); + +const messages = linter.verify("var foo = bar;", { + rules: { + semi: 2 + } +}, { filename: "foo.js" }); + +const code = linter.getSourceCode(); + +console.log(code.text); // "var foo = bar;" +``` + +In this way, you can retrieve the text and AST used for the last run of `linter.verify()`. + +### Linter#verifyAndFix() + +This method is similar to verify except that it also runs autofixing logic, similar to the `--fix` flag on the command line. The result object will contain the autofixed code, along with any remaining linting messages for the code that were not autofixed. + +```js +const Linter = require("eslint").Linter; +const linter = new Linter(); + +const messages = linter.verifyAndFix("var foo", { + rules: { + semi: 2 + } +}); +``` + +Output object from this method: + +```js +{ + fixed: true, + output: "var foo;", + messages: [] +} +``` + +The information available is: + +* `fixed` - True, if the code was fixed. +* `output` - Fixed code text (might be the same as input if no fixes were applied). +* `messages` - Collection of all messages for the given code (It has the same information as explained above under `verify` block). + +### Linter#defineRule + +Each `Linter` instance holds a map of rule names to loaded rule objects. By default, all ESLint core rules are loaded. If you want to use `Linter` with custom rules, you should use the `defineRule` method to register your rules by ID. + +```js +const Linter = require("eslint").Linter; +const linter = new Linter(); + +linter.defineRule("my-custom-rule", { + // (an ESLint rule) + + create(context) { + // ... + } +}); + +const results = linter.verify("// some source text", { rules: { "my-custom-rule": "error" } }); +``` + +### Linter#defineRules + +This is a convenience method similar to `Linter#defineRule`, except that it allows you to define many rules at once using an object. + +```js +const Linter = require("eslint").Linter; +const linter = new Linter(); + +linter.defineRules({ + "my-custom-rule": { /* an ESLint rule */ create() {} }, + "another-custom-rule": { /* an ESLint rule */ create() {} } +}); + +const results = linter.verify("// some source text", { + rules: { + "my-custom-rule": "error", + "another-custom-rule": "warn" + } +}); +``` + +### Linter#getRules + +This method returns a map of all loaded rules. + +```js +const Linter = require("eslint").Linter; +const linter = new Linter(); + +linter.getRules(); + +/* +Map { + 'accessor-pairs' => { meta: { docs: [Object], schema: [Array] }, create: [Function: create] }, + 'array-bracket-newline' => { meta: { docs: [Object], schema: [Array] }, create: [Function: create] }, + ... +} +*/ +``` + +### Linter#defineParser + +Each instance of `Linter` holds a map of custom parsers. If you want to define a parser programmatically, you can add this function +with the name of the parser as first argument and the [parser object](/docs/developer-guide/working-with-custom-parsers.md) as second argument. The default `"espree"` parser will already be loaded for every `Linter` instance. + +```js +const Linter = require("eslint").Linter; +const linter = new Linter(); + +linter.defineParser("my-custom-parser", { + parse(code, options) { + // ... + } +}); + +const results = linter.verify("// some source text", { parser: "my-custom-parser" }); +``` + +### Linter#version/Linter.version + +Each instance of `Linter` has a `version` property containing the semantic version number of ESLint that the `Linter` instance is from. + +```js +const Linter = require("eslint").Linter; +const linter = new Linter(); + +linter.version; // => '4.5.0' +``` + +There is also a `Linter.version` property that you can read without instantiating `Linter`: + +```js +const Linter = require("eslint").Linter; + +Linter.version; // => '4.5.0' +``` + +## linter + +The `eslint.linter` object (deprecated) is an instance of the `Linter` class as defined [above](#linter). `eslint.linter` exists for backwards compatibility, but we do not recommend using it because any mutations to it are shared among every module that uses `eslint`. Instead, please create your own instance of `eslint.Linter`. + +```js +const linter = require("eslint").linter; + +const messages = linter.verify("var foo;", { + rules: { + semi: 2 + } +}, { filename: "foo.js" }); +``` + +Note: This API is deprecated as of 4.0.0. + +## CLIEngine + +The primary Node.js API is `CLIEngine`, which is the underlying utility that runs the ESLint command line interface. This object will read the filesystem for configuration and file information but will not output any results. Instead, it allows you direct access to the important information so you can deal with the output yourself. + +You can get a reference to the `CLIEngine` by doing the following: + +```js +const CLIEngine = require("eslint").CLIEngine; +``` + +The `CLIEngine` is a constructor, and you can create a new instance by passing in the options you want to use. The available options are: + +* `allowInlineConfig` - Set to `false` to disable the use of configuration comments (such as `/*eslint-disable*/`). Corresponds to `--no-inline-config`. +* `baseConfig` - Can optionally be set to a config object that has the same schema as `.eslintrc.*`. This will used as a default config, and will be merged with any configuration defined in `.eslintrc.*` files, with the `.eslintrc.*` files having precedence. +* `cache` - Operate only on changed files (default: `false`). Corresponds to `--cache`. +* `cacheFile` - Name of the file where the cache will be stored (default: `.eslintcache`). Corresponds to `--cache-file`. Deprecated: use `cacheLocation` instead. +* `cacheLocation` - Name of the file or directory where the cache will be stored (default: `.eslintcache`). Corresponds to `--cache-location`. +* `configFile` - The configuration file to use (default: null). If `useEslintrc` is true or not specified, this configuration will be merged with any configuration defined in `.eslintrc.*` files, with options in this configuration having precedence. Corresponds to `-c`. +* `cwd` - Path to a directory that should be considered as the current working directory. +* `envs` - An array of environments to load (default: empty array). Corresponds to `--env`. Note: This differs from `.eslintrc.*` / `baseConfig`, where instead the option is called `env` and is an object. +* `errorOnUnmatchedPattern` - Set to `false` to prevent errors when pattern is unmatched. Corresponds to `--no-error-on-unmatched-pattern`. +* `extensions` - An array of filename extensions that should be checked for code. The default is an array containing just `".js"`. Corresponds to `--ext`. It is only used in conjunction with directories, not with filenames, glob patterns or when using `executeOnText()`. +* `fix` - A boolean or a function (default: `false`). If a function, it will be passed each linting message and should return a boolean indicating whether the fix should be included with the output report (errors and warnings will not be listed if fixed). Files on disk are never changed regardless of the value of `fix`. To persist changes to disk, call [`outputFixes()`](#cliengineoutputfixes). +* `fixTypes` - An array of rule types for which fixes should be applied (default: `null`). This array acts like a filter, only allowing rules of the given types to apply fixes. Possible array values are `"problem"`, `"suggestion"`, and `"layout"`. +* `globals` - An array of global variables to declare (default: empty array). Corresponds to `--global`, and similarly supports passing `'name:true'` to denote a writeable global. Note: This differs from `.eslintrc.*` / `baseConfig`, where `globals` is an object. +* `ignore` - False disables use of `.eslintignore`, `ignorePath` and `ignorePattern` (default: true). Corresponds to `--no-ignore`. +* `ignorePath` - The ignore file to use instead of `.eslintignore` (default: null). Corresponds to `--ignore-path`. +* `ignorePattern` - Glob patterns for paths to ignore. String or array of strings. +* `parser` - Specify the parser to be used (default: `espree`). Corresponds to `--parser`. +* `parserOptions` - An object containing parser options (default: empty object). Corresponds to `--parser-options`. +* `plugins` - An array of plugins to load (default: empty array). Corresponds to `--plugin`. +* `reportUnusedDisableDirectives` - When set to `true`, adds reported errors for unused `eslint-disable` directives when no problems would be reported in the disabled area anyway (default: false). Corresponds to `--report-unused-disable-directives`. +* `resolvePluginsRelativeTo` - Determines the folder where plugins should be resolved from. Should be used when an integration installs plugins and uses those plugins to lint code on behalf of the end user. Corresponds to `--resolve-plugins-relative-to`. +* `rulePaths` - An array of directories to load custom rules from (default: empty array). Corresponds to `--rulesdir`. +* `rules` - An object of rules to use (default: null). Corresponds to `--rule`. +* `useEslintrc` - Set to false to disable use of `.eslintrc` files (default: true). Corresponds to `--no-eslintrc`. +* `globInputPaths` - Set to false to skip glob resolution of input file paths to lint (default: true). If false, each input file paths is assumed to be a non-glob path to an existing file. + +To programmatically set `.eslintrc.*` options not supported above (such as `extends`, +`overrides` and `settings`), define them in a config object passed to `baseConfig` instead. + +For example: + +```js +const CLIEngine = require("eslint").CLIEngine; + +const cli = new CLIEngine({ + baseConfig: { + extends: ["eslint-config-shared"], + settings: { + sharedData: "Hello" + } + }, + envs: ["browser", "mocha"], + useEslintrc: false, + rules: { + semi: 2 + } +}); +``` + +In this example, a new `CLIEngine` instance is created that extends a configuration called +`"eslint-config-shared"`, a setting named `"sharedData"` and two environments (`"browser"` +and `"mocha"`) are defined, loading of `.eslintrc` and `package.json` files are disabled, +and the `semi` rule enabled as an error. You can then call methods on `cli` and these options +will be used to perform the correct action. + +Note: Currently `CLIEngine` does not validate options passed to it, but may start doing so in the future. + +### CLIEngine#executeOnFiles() + +If you want to lint one or more files, use the `executeOnFiles()` method. This method accepts a single argument, which is an array of files and/or directories to traverse for files. You can pass the same values as you would using the ESLint command line interface, such as `"."` to search all JavaScript files in the current directory. Here's an example: + +```js +const CLIEngine = require("eslint").CLIEngine; + +const cli = new CLIEngine({ + envs: ["browser", "mocha"], + useEslintrc: false, + rules: { + semi: 2 + } +}); + +// lint myfile.js and all files in lib/ +const report = cli.executeOnFiles(["myfile.js", "lib/"]); +``` + +The return value is an object containing the results of the linting operation. Here's an example of a report object: + +```js +{ + results: [ + { + filePath: "/Users/eslint/project/myfile.js", + messages: [{ + ruleId: "semi", + severity: 2, + message: "Missing semicolon.", + line: 1, + column: 13, + nodeType: "ExpressionStatement", + fix: { range: [12, 12], text: ";" } + }, { + ruleId: "no-useless-escape", + severity: 1, + message: "disallow unnecessary escape characters", + line: 1, + column: 10, + nodeType: "ExpressionStatement", + suggestions: [{ + desc: "Remove unnecessary escape. This maintains the current functionality.", + fix: { range: [9, 10], text: "" } + }, { + desc: "Escape backslash to include it in the RegExp.", + fix: { range: [9, 9], text: "\\" } + }] + }], + errorCount: 1, + warningCount: 1, + fixableErrorCount: 1, + fixableWarningCount: 0, + source: "\"use strict\"\n" + } + ], + errorCount: 1, + warningCount: 0, + fixableErrorCount: 1, + fixableWarningCount: 0, + usedDeprecatedRules: [] +} +``` + +You can also pass `fix: true` when instantiating the `CLIEngine` in order to have it figure out what fixes can be applied. + +```js +const CLIEngine = require("eslint").CLIEngine; + +const cli = new CLIEngine({ + envs: ["browser", "mocha"], + fix: true, // difference from last example + useEslintrc: false, + rules: { + semi: 2, + quotes: [2, "double"] + } +}); + +// lint myfile.js and all files in lib/ +const report = cli.executeOnFiles(["myfile.js", "lib/"]); +``` + +```js +{ + results: [ + { + filePath: "/Users/eslint/project/myfile.js", + messages: [ + { + ruleId: "semi", + severity: 2, + message: "Missing semicolon.", + line: 1, + column: 13, + nodeType: "ExpressionStatement", + fix: { range: [12, 12], text: ";" } + }, + { + ruleId: "func-name-matching", + severity: 2, + message: "Function name `bar` should match variable name `foo`", + line: 2, + column: 5, + nodeType: "VariableDeclarator" + } + ], + errorCount: 2, + warningCount: 0, + fixableErrorCount: 1, + fixableWarningCount: 0, + output: "\"use strict\";\nvar foo = function bar() {};\nfoo();\n" + } + ], + errorCount: 2, + warningCount: 0, + fixableErrorCount: 1, + fixableWarningCount: 0, + usedDeprecatedRules: [] +} +``` + +If the operation ends with a parsing error, you will get a single message for this file, with `fatal: true` added as an extra property. + +```js +{ + results: [ + { + filePath: "./myfile.js", + messages: [ + { + ruleId: null, + fatal: true, + severity: 2, + message: "Parsing error: Unexpected token foo", + line: 1, + column: 10 + } + ], + errorCount: 1, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + source: "function foo() {}" + } + ], + errorCount: 1, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + usedDeprecatedRules: [] +} +``` + +The top-level report object has a `results` array containing all linting results for files that had warnings or errors (any files that did not produce a warning or error are omitted). Each file result includes: + +* `filePath` - Path to the given file. +* `messages` - Array containing the result of calling `linter.verify()` on the given file. +* `errorCount` and `warningCount` - The exact number of errors and warnings respectively on the given file. +* `source` - The source code for the given file. This property is omitted if this file has no errors/warnings or if the `output` property is present. +* `output` - The source code for the given file with as many fixes applied as possible, so you can use that to rewrite the files if necessary. This property is omitted if no fix is available. + +The top-level report object also has `errorCount` and `warningCount` which give the exact number of errors and warnings respectively on all the files. Additionally, `usedDeprecatedRules` signals any deprecated rules used and their replacement (if available). Specifically, it is array of objects with properties like so: + +* `ruleId` - The name of the rule (e.g. `indent-legacy`). +* `replacedBy` - An array of rules that replace the deprecated rule (e.g. `["indent"]`). + +Once you get a report object, it's up to you to determine how to output the results. Fixes will not be automatically applied to the files, even if you set `fix: true` when constructing the `CLIEngine` instance. To apply fixes to the files, call [`outputFixes`](#cliengineoutputfixes). + +### CLIEngine#resolveFileGlobPatterns() + +You can pass filesystem-style or glob patterns to ESLint and have it function properly. In order to achieve this, ESLint must resolve non-glob patterns into glob patterns before determining which files to execute on. The `resolveFileGlobPatterns()` methods uses the current settings from `CLIEngine` to resolve non-glob patterns into glob patterns. Pass an array of patterns that might be passed to the ESLint CLI and it will return an array of glob patterns that mean the same thing. Here's an example: + +```js +const CLIEngine = require("eslint").CLIEngine; + +const cli = new CLIEngine({ +}); + +// pass an array of patterns +const globPatterns = cli.resolveFileGlobPatterns(["."]); +console.log(globPatterns[i]); // ["**/*.js"] +``` + +### CLIEngine#getConfigForFile() + +If you want to retrieve a configuration object for a given file, use the `getConfigForFile()` method. This method accepts one argument, a file path, and returns an object represented the calculated configuration of the file. Here's an example: + +```js +const CLIEngine = require("eslint").CLIEngine; + +const cli = new CLIEngine({ + envs: ["browser", "mocha"], + useEslintrc: false, + rules: { + semi: 2 + } +}); + +const config = cli.getConfigForFile("myfile.js"); +``` + +Once you have the configuration information, you can pass it into the `linter` object: + +```js +const CLIEngine = require("eslint").CLIEngine, + Linter = require("eslint").Linter; + +const linter = new Linter(); +const cli = new CLIEngine({ + envs: ["browser", "mocha"], + useEslintrc: false, + rules: { + semi: 2 + } +}); + +const config = cli.getConfigForFile("myfile.js"); + +const messages = linter.verify('var foo;', config); +``` + +### CLIEngine#executeOnText() + +If you already have some text to lint, then you can use the `executeOnText()` method to lint that text. The linter will assume that the text is a file in the current working directory, and so will still obey any `.eslintrc` and `.eslintignore` files that may be present. Here's an example: + +```js +const CLIEngine = require("eslint").CLIEngine; + +const cli = new CLIEngine({ + envs: ["browser", "mocha"], + useEslintrc: false, + rules: { + semi: 2 + } +}); + +// Lint the supplied text and optionally set a filename that is displayed in the report +const report = cli.executeOnText("var foo = 'bar';", "foo.js"); + +// In addition to the above, warn if the resolved file name is ignored. +const reportAndWarnOnIgnoredFile = cli.executeOnText("var foo = 'bar';", "foo.js", true); +``` + +The `report` returned from `executeOnText()` is in the same format as from `executeOnFiles()`, but there is only ever one result in `report.results`. + +If a filename in the optional second parameter matches a file that is configured to be ignored, then this function returns no errors or warnings. The method includes an additional optional boolean third parameter. When `true`, a resolved file name that is ignored will return a warning. + +### CLIEngine#addPlugin() + +Loads a plugin from configuration object with specified name. Name can include plugin prefix ("eslint-plugin-") + +```js +const CLIEngine = require("eslint").CLIEngine; +const cli = new CLIEngine({ + ignore: true +}); +cli.addPlugin("eslint-plugin-processor", { + processors: { + ".txt": { + preprocess: function(text) { + return [text]; + }, + postprocess: function(messages) { + return messages[0]; + } + } + } +}); +``` + +### CLIEngine#isPathIgnored() + +Checks if a given path is ignored by ESLint. + +```js +const CLIEngine = require("eslint").CLIEngine; + +const cli = new CLIEngine({ + ignore: true, + ignorePath: ".customIgnoreFile" +}); + +const isIgnored = cli.isPathIgnored("foo/bar.js"); +``` + +### CLIEngine#getFormatter() + +Retrieves a formatter, which you can then use to format a report object. The argument is either the name of a built-in formatter: + +* "[checkstyle](../user-guide/formatters#checkstyle)" +* "[codeframe](../user-guide/formatters#codeframe)" +* "[compact](../user-guide/formatters#compact)" +* "[html](../user-guide/formatters#html)" +* "[jslint-xml](../user-guide/formatters#jslint-xml)" +* "[json](../user-guide/formatters#json)" +* "[junit](../user-guide/formatters#junit)" +* "[stylish](../user-guide/formatters#stylish)" (the default) +* "[table](../user-guide/formatters#table)" +* "[tap](../user-guide/formatters#tap)" +* "[unix](../user-guide/formatters#unix)" +* "[visualstudio](../user-guide/formatters#visualstudio)" + +or the full path to a JavaScript file containing a custom formatter. You can also omit the argument to retrieve the default formatter. + +```js +const CLIEngine = require("eslint").CLIEngine; + +const cli = new CLIEngine({ + envs: ["browser", "mocha"], + useEslintrc: false, + rules: { + semi: 2 + } +}); + +// lint myfile.js and all files in lib/ +const report = cli.executeOnFiles(["myfile.js", "lib/"]); + +// get the default formatter +const formatter = cli.getFormatter(); + +// Also could do... +// const formatter = cli.getFormatter("compact"); +// const formatter = cli.getFormatter("./my/formatter.js"); + +// output to console +console.log(formatter(report.results)); +``` + +**Note:** Also available as a static function on `CLIEngine`. + +```js +// get the default formatter by calling the static function +const formatter = CLIEngine.getFormatter(); +``` + +**Important:** You must pass in the `results` property of the report. Passing in `report` directly will result in an error. + +### CLIEngine#getErrorResults() + +This is a static function on `CLIEngine`. It can be used to filter out all the non error messages from the report object. + +```js +const CLIEngine = require("eslint").CLIEngine; + +const cli = new CLIEngine({ + envs: ["browser", "mocha"], + useEslintrc: false, + rules: { + semi: 2 + } +}); + +// lint myfile.js and all files in lib/ +const report = cli.executeOnFiles(["myfile.js", "lib/"]); + +// only get the error messages +const errorReport = CLIEngine.getErrorResults(report.results) +``` + +**Important:** You must pass in the `results` property of the report. Passing in `report` directly will result in an error. + +### CLIEngine#outputFixes() + +This is a static function on `CLIEngine` that is used to output fixes from `report` to disk. It does by looking for files that have an `output` property in their results. Here's an example: + +```js +const CLIEngine = require("eslint").CLIEngine; + +const cli = new CLIEngine({ + envs: ["browser", "mocha"], + fix: true, + useEslintrc: false, + rules: { + semi: 2 + } +}); + +// lint myfile.js and all files in lib/ +const report = cli.executeOnFiles(["myfile.js", "lib/"]); + +// output fixes to disk +CLIEngine.outputFixes(report); +``` + +### CLIEngine#getRules() + +This method returns a map of all loaded rules. Under the hood, it calls [Linter#getRules](#lintergetrules). + +```js +const CLIEngine = require("eslint").CLIEngine; +const cli = new CLIEngine(); + +cli.getRules(); + +/* +Map { + 'accessor-pairs' => { meta: { docs: [Object], schema: [Array] }, create: [Function: create] }, + 'array-bracket-newline' => { meta: { docs: [Object], schema: [Array] }, create: [Function: create] }, + ... +} +*/ +``` + + +### CLIEngine.version + +`CLIEngine` has a static `version` property containing the semantic version number of ESLint that it comes from. + +```js +require("eslint").CLIEngine.version; // '4.5.0' +``` + +## RuleTester + +`eslint.RuleTester` is a utility to write tests for ESLint rules. It is used internally for the bundled rules that come with ESLint, and it can also be used by plugins. + +Example usage: + +```js +"use strict"; + +const rule = require("../../../lib/rules/my-rule"), + RuleTester = require("eslint").RuleTester; + +const ruleTester = new RuleTester(); + +ruleTester.run("my-rule", rule, { + valid: [ + { + code: "var foo = true", + options: [{ allowFoo: true }] + } + ], + + invalid: [ + { + code: "var invalidVariable = true", + errors: [{ message: "Unexpected invalid variable." }] + }, + { + code: "var invalidVariable = true", + errors: [{ message: /^Unexpected.+variable/ }] + } + ] +}); +``` + +The `RuleTester` constructor accepts an optional object argument, which can be used to specify defaults for your test cases. For example, if all of your test cases use ES2015, you can set it as a default: + +```js +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2015 } }); +``` + +The `RuleTester#run()` method is used to run the tests. It should be passed the following arguments: + +* The name of the rule (string) +* The rule object itself (see ["working with rules"](./working-with-rules)) +* An object containing `valid` and `invalid` properties, each of which is an array containing test cases. + +A test case is an object with the following properties: + +* `code` (string, required): The source code that the rule should be run on +* `options` (array, optional): The options passed to the rule. The rule severity should not be included in this list. +* `filename` (string, optional): The filename for the given case (useful for rules that make assertions about filenames). + +In addition to the properties above, invalid test cases can also have the following properties: + +* `errors` (number or array, required): Asserts some properties of the errors that the rule is expected to produce when run on this code. If this is a number, asserts the number of errors produced. Otherwise, this should be a list of objects, each containing information about a single reported error. The following properties can be used for an error (all are optional): + * `message` (string/regexp): The message for the error + * `messageId` (string): The Id for the error. See [testing errors with messageId](#testing-errors-with-messageid) for details + * `data` (object): Placeholder data which can be used in combination with `messageId` + * `type` (string): The type of the reported AST node + * `line` (number): The 1-based line number of the reported location + * `column` (number): The 1-based column number of the reported location + * `endLine` (number): The 1-based line number of the end of the reported location + * `endColumn` (number): The 1-based column number of the end of the reported location + * `suggestions` (array): An array of objects with suggestion details to check. See [Testing Suggestions](#testing-suggestions) for details + + If a string is provided as an error instead of an object, the string is used to assert the `message` of the error. +* `output` (string, required if the rule fixes code): Asserts the output that will be produced when using this rule for a single pass of autofixing (e.g. with the `--fix` command line flag). If this is `null`, asserts that none of the reported problems suggest autofixes. + +Any additional properties of a test case will be passed directly to the linter as config options. For example, a test case can have a `parserOptions` property to configure parser behavior: + +```js +{ + code: "let foo;", + parserOptions: { ecmaVersion: 2015 } +} +``` + +If a valid test case only uses the `code` property, it can optionally be provided as a string containing the code, rather than an object with a `code` key. + +#### Testing errors with `messageId` + +If the rule under test uses `messageId`s, you can use `messageId` property in a test case to assert reported error's `messageId` instead of its `message`. + +```js +{ + code: "let foo;", + errors: [{ messageId: "unexpected" }] +} +``` + +For messages with placeholders, a test case can also use `data` property to additionally assert reported error's `message`. + +```js +{ + code: "let foo;", + errors: [{ messageId: "unexpected", data: { name: "foo" } }] +} +``` + +Please note that `data` in a test case does not assert `data` passed to `context.report`. Instead, it is used to form the expected message text which is then compared with the received `message`. + +#### Testing Suggestions + +Suggestions can be tested by defining a `suggestions` key on an errors object. The options to check for the suggestions are the following (all are optional): + +* `desc` (string): The suggestion `desc` value +* `messageId` (string): The suggestion `messageId` value for suggestions that use `messageId`s +* `data` (object): Placeholder data which can be used in combination with `messageId` +* `output` (string): A code string representing the result of applying the suggestion fix to the input code + +Example: + +```js +ruleTester.run("my-rule-for-no-foo", rule, { + valid: [], + invalid: [{ + code: "var foo;", + errors: [{ + suggestions: [{ + desc: "Rename identifier 'foo' to 'bar'", + output: "var bar;" + }] + }] + }] +}) +``` + +`messageId` and `data` properties in suggestion test objects work the same way as in error test objects. See [testing errors with messageId](#testing-errors-with-messageid) for details. + +```js +ruleTester.run("my-rule-for-no-foo", rule, { + valid: [], + invalid: [{ + code: "var foo;", + errors: [{ + suggestions: [{ + messageId: "renameFoo", + data: { newName: "bar" }, + output: "var bar;" + }] + }] + }] +}) +``` + +### Customizing RuleTester + +`RuleTester` depends on two functions to run tests: `describe` and `it`. These functions can come from various places: + +1. If `RuleTester.describe` and `RuleTester.it` have been set to function values, `RuleTester` will use `RuleTester.describe` and `RuleTester.it` to run tests. You can use this to customize the behavior of `RuleTester` to match a test framework that you're using. +1. Otherwise, if `describe` and `it` are present as globals, `RuleTester` will use `global.describe` and `global.it` to run tests. This allows `RuleTester` to work when using frameworks like [Mocha](https://mochajs.org/) without any additional configuration. +1. Otherwise, `RuleTester#run` will simply execute all of the tests in sequence, and will throw an error if one of them fails. This means you can simply execute a test file that calls `RuleTester.run` using `node`, without needing a testing framework. + +`RuleTester#run` calls the `describe` function with two arguments: a string describing the rule, and a callback function. The callback calls the `it` function with a string describing the test case, and a test function. The test function will return successfully if the test passes, and throw an error if the test fails. (Note that this is the standard behavior for test suites when using frameworks like [Mocha](https://mochajs.org/); this information is only relevant if you plan to customize `RuleTester.it` and `RuleTester.describe`.) + +Example of customizing `RuleTester`: + +```js +"use strict"; + +const RuleTester = require("eslint").RuleTester, + test = require("my-test-runner"), + myRule = require("../../../lib/rules/my-rule"); + +RuleTester.describe = function(text, method) { + RuleTester.it.title = text; + return method.call(this); +}; + +RuleTester.it = function(text, method) { + test(RuleTester.it.title + ": " + text, method); +}; + +// then use RuleTester as documented + +const ruleTester = new RuleTester(); + +ruleTester.run("my-rule", myRule, { + valid: [ + // valid test cases + ], + invalid: [ + // invalid test cases + ] +}) +``` + +## Deprecated APIs + +* `cli` - the `cli` object has been deprecated in favor of `CLIEngine`. As of v1.0.0, `cli` is no longer exported and should not be used by external tools. +* `linter` - the `linter` object has been deprecated in favor of `Linter` as of v4.0.0. diff --git a/eslint/docs/developer-guide/scope-manager-interface.md b/eslint/docs/developer-guide/scope-manager-interface.md new file mode 100644 index 0000000..3ee5b23 --- /dev/null +++ b/eslint/docs/developer-guide/scope-manager-interface.md @@ -0,0 +1,386 @@ +# ScopeManager + +This document was written based on the implementation of [eslint-scope](https://github.com/eslint/eslint-scope), a fork of [escope](https://github.com/estools/escope), and deprecates some members ESLint is not using. + +---- + +## ScopeManager interface + +`ScopeManager` object has all variable scopes. + +### Fields + +#### scopes + +* **Type:** `Scope[]` +* **Description:** All scopes. + +#### globalScope + +* **Type:** `Scope` +* **Description:** The root scope. + +### Methods + +#### acquire(node, inner = false) + +* **Parameters:** + * `node` (`ASTNode`) ... An AST node to get their scope. + * `inner` (`boolean`) ... If the node has multiple scope, this returns the outermost scope normally. If `inner` is `true` then this returns the innermost scope. Default is `false`. +* **Return type:** `Scope | null` +* **Description:** Get the scope of a given AST node. The gotten scope's `block` property is the node. This method never returns `function-expression-name` scope. If the node does not have their scope, this returns `null`. + +#### getDeclaredVariables(node) + +* **Parameters:** + * `node` (`ASTNode`) ... An AST node to get their variables. +* **Return type:** `Variable[]` +* **Description:** Get the variables that a given AST node defines. The gotten variables' `def[].node`/`def[].parent` property is the node. If the node does not define any variable, this returns an empty array. + +### Deprecated members + +Those members are defined but not used in ESLint. + +#### isModule() + +* **Parameters:** +* **Return type:** `boolean` +* **Description:** `true` if this program is module. + +#### isImpliedStrict() + +* **Parameters:** +* **Return type:** `boolean` +* **Description:** `true` if this program is strict mode implicitly. I.e., `options.impliedStrict === true`. + +#### isStrictModeSupported() + +* **Parameters:** +* **Return type:** `boolean` +* **Description:** `true` if this program supports strict mode. I.e., `options.ecmaVersion >= 5`. + +#### acquireAll(node) + +* **Parameters:** + * `node` (`ASTNode`) ... An AST node to get their scope. +* **Return type:** `Scope[] | null` +* **Description:** Get the scopes of a given AST node. The gotten scopes' `block` property is the node. If the node does not have their scope, this returns `null`. + +---- + +## Scope interface + +`Scope` object has all variables and references in the scope. + +### Fields + +#### type + +* **Type:** `string` +* **Description:** The type of this scope. This is one of `"block"`, `"catch"`, `"class"`, `"for"`, `"function"`, `"function-expression-name"`, `"global"`, `"module"`, `"switch"`, `"with"` + +#### isStrict + +* **Type:** `boolean` +* **Description:** `true` if this scope is strict mode. + +#### upper + +* **Type:** `Scope | null` +* **Description:** The parent scope. If this is the global scope then this property is `null`. + +#### childScopes + +* **Type:** `Scope[]` +* **Description:** The array of child scopes. This does not include grandchild scopes. + +#### variableScope + +* **Type:** `Scope` +* **Description:** The scope which hosts variables which are defined by `var` declarations. + +#### block + +* **Type:** `ASTNode` +* **Description:** The AST node which created this scope. + +#### variables + +* **Type:** `Variable[]` +* **Description:** The array of all variables which are defined on this scope. This does not include variables which are defined in child scopes. + +#### set + +* **Type:** `Map` +* **Description:** The map from variable names to variable objects. + +> I hope to rename `set` field or replace by a method. + +#### references + +* **Type:** `Reference[]` +* **Description:** The array of all references on this scope. This does not include references in child scopes. + +#### through + +* **Type:** `Reference[]` +* **Description:** The array of references which could not be resolved in this scope. + +#### functionExpressionScope + +* **Type:** `boolean` +* **Description:** `true` if this scope is `"function-expression-name"` scope. + +> I hope to deprecate `functionExpressionScope` field as replacing by `scope.type === "function-expression-name"`. + +### Deprecated members + +Those members are defined but not used in ESLint. + +#### taints + +* **Type:** `Map` +* **Description:** The map from variable names to `tainted` flag. + +#### dynamic + +* **Type:** `boolean` +* **Description:** `true` if this scope is dynamic. I.e., the type of this scope is `"global"` or `"with"`. + +#### directCallToEvalScope + +* **Type:** `boolean` +* **Description:** `true` if this scope contains `eval()` invocations. + +#### thisFound + +* **Type:** `boolean` +* **Description:** `true` if this scope contains `this`. + +#### resolve(node) + +* **Parameters:** + * `node` (`ASTNode`) ... An AST node to get their reference object. The type of the node must be `"Identifier"`. +* **Return type:** `Reference | null` +* **Description:** Returns `this.references.find(r => r.identifier === node)`. + +#### isStatic() + +* **Parameters:** +* **Return type:** `boolean` +* **Description:** Returns `!this.dynamic`. + +#### isArgumentsMaterialized() + +* **Parameters:** +* **Return type:** `boolean` +* **Description:** `true` if this is a `"function"` scope which has used `arguments` variable. + +#### isThisMaterialized() + +* **Parameters:** +* **Return type:** `boolean` +* **Description:** Returns `this.thisFound`. + +#### isUsedName(name) + +* **Parameters:** + * `name` (`string`) ... The name to check. +* **Return type:** `boolean` +* **Description:** `true` if a given name is used in variable names or reference names. + +---- + +## Variable interface + +`Variable` object is variable's information. + +### Fields + +#### name + +* **Type:** `string` +* **Description:** The name of this variable. + +#### identifiers + +* **Type:** `ASTNode[]` +* **Description:** The array of `Identifier` nodes which define this variable. If this variable is redeclared, this array includes two or more nodes. + +> I hope to deprecate `identifiers` field as replacing by `defs[].name` field. + +#### references + +* **Type:** `Reference[]` +* **Description:** The array of the references of this variable. + +#### defs + +* **Type:** `Definition[]` +* **Description:** The array of the definitions of this variable. + +### Deprecated members + +Those members are defined but not used in ESLint. + +#### tainted + +* **Type:** `boolean` +* **Description:** The `tainted` flag. (always `false`) + +#### stack + +* **Type:** `boolean` +* **Description:** The `stack` flag. (I'm not sure what this means.) + +---- + +## Reference interface + +`Reference` object is reference's information. + +### Fields + +#### identifier + +* **Type:** `ASTNode` +* **Description:** The `Identifier` node of this reference. + +#### from + +* **Type:** `Scope` +* **Description:** The `Scope` object that this reference is on. + +#### resolved + +* **Type:** `Variable | null` +* **Description:** The `Variable` object that this reference refers. If such variable was not defined, this is `null`. + +#### writeExpr + +* **Type:** `ASTNode | null` +* **Description:** The ASTNode object which is right-hand side. + +#### init + +* **Type:** `boolean` +* **Description:** `true` if this writing reference is a variable initializer or a default value. + +### Methods + +#### isWrite() + +* **Parameters:** +* **Return type:** `boolean` +* **Description:** `true` if this reference is writing. + +#### isRead() + +* **Parameters:** +* **Return type:** `boolean` +* **Description:** `true` if this reference is reading. + +#### isWriteOnly() + +* **Parameters:** +* **Return type:** `boolean` +* **Description:** `true` if this reference is writing but not reading. + +#### isReadOnly() + +* **Parameters:** +* **Return type:** `boolean` +* **Description:** `true` if this reference is reading but not writing. + +#### isReadWrite() + +* **Parameters:** +* **Return type:** `boolean` +* **Description:** `true` if this reference is reading and writing. + +### Deprecated members + +Those members are defined but not used in ESLint. + +#### tainted + +* **Type:** `boolean` +* **Description:** The `tainted` flag. (always `false`) + +#### flag + +* **Type:** `number` +* **Description:** `1` is reading, `2` is writing, `3` is reading/writing. + +#### partial + +* **Type:** `boolean` +* **Description:** The `partial` flag. + +#### isStatic() + +* **Parameters:** +* **Return type:** `boolean` +* **Description:** `true` if this reference is resolved statically. + +---- + +## Definition interface + +`Definition` object is variable definition's information. + +### Fields + +#### type + +* **Type:** `string` +* **Description:** The type of this definition. One of `"CatchClause"`, `"ClassName"`, `"FunctionName"`, `"ImplicitGlobalVariable"`, `"ImportBinding"`, `"Parameter"`, and `"Variable"`. + +#### name + +* **Type:** `ASTNode` +* **Description:** The `Identifier` node of this definition. + +#### node + +* **Type:** `ASTNode` +* **Description:** The enclosing node of the name. + +| type | node | +|:---------------------------|:-----| +| `"CatchClause"` | `CatchClause` +| `"ClassName"` | `ClassDeclaration` or `ClassExpression` +| `"FunctionName"` | `FunctionDeclaration` or `FunctionExpression` +| `"ImplicitGlobalVariable"` | `Program` +| `"ImportBinding"` | `ImportSpecifier`, `ImportDefaultSpecifier`, or `ImportNamespaceSpecifier` +| `"Parameter"` | `FunctionDeclaration`, `FunctionExpression`, or `ArrowFunctionExpression` +| `"Variable"` | `VariableDeclarator` + +#### parent + +* **Type:** `ASTNode | undefined | null` +* **Description:** The enclosing statement node of the name. + +| type | parent | +|:---------------------------|:-------| +| `"CatchClause"` | `null` +| `"ClassName"` | `null` +| `"FunctionName"` | `null` +| `"ImplicitGlobalVariable"` | `null` +| `"ImportBinding"` | `ImportDeclaration` +| `"Parameter"` | `null` +| `"Variable"` | `VariableDeclaration` + +### Deprecated members + +Those members are defined but not used in ESLint. + +#### index + +* **Type:** `number | undefined | null` +* **Description:** The index in the declaration statement. + +#### kind + +* **Type:** `string | undefined | null` +* **Description:** The kind of the declaration statement. diff --git a/eslint/docs/developer-guide/selectors.md b/eslint/docs/developer-guide/selectors.md new file mode 100644 index 0000000..ac1ae0d --- /dev/null +++ b/eslint/docs/developer-guide/selectors.md @@ -0,0 +1,133 @@ +# Selectors + +Some rules and APIs allow the use of selectors to query an AST. This page is intended to: + +1. Explain what selectors are +1. Describe the syntax for creating selectors +1. Describe what selectors can be used for + +## What is a selector? + +A selector is a string that can be used to match nodes in an Abstract Syntax Tree (AST). This is useful for describing a particular syntax pattern in your code. + +The syntax for AST selectors is similar to the syntax for [CSS selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors). If you've used CSS selectors before, the syntax for AST selectors should be easy to understand. + +The simplest selector is just a node type. A node type selector will match all nodes with the given type. For example, consider the following program: + +```js +var foo = 1; +bar.baz(); +``` + +The selector "`Identifier`" will match all `Identifier` nodes in the program. In this case, the selector will match the nodes for `foo`, `bar`, and `baz`. + +Selectors are not limited to matching against single node types. For example, the selector `VariableDeclarator > Identifier` will match all `Identifier` nodes that have a `VariableDeclarator` as a direct parent. In the program above, this will match the node for `foo`, but not the nodes for `bar` and `baz`. + +## What syntax can selectors have? + +The following selectors are supported: + +* AST node type: `ForStatement` +* wildcard (matches all nodes): `*` +* attribute existence: `[attr]` +* attribute value: `[attr="foo"]` or `[attr=123]` +* attribute regex: `[attr=/foo.*/]` +* attribute conditions: `[attr!="foo"]`, `[attr>2]`, `[attr<3]`, `[attr>=2]`, or `[attr<=3]` +* nested attribute: `[attr.level2="foo"]` +* field: `FunctionDeclaration > Identifier.id` +* First or last child: `:first-child` or `:last-child` +* nth-child (no ax+b support): `:nth-child(2)` +* nth-last-child (no ax+b support): `:nth-last-child(1)` +* descendant: `FunctionExpression ReturnStatement` +* child: `UnaryExpression > Literal` +* following sibling: `ArrayExpression > Literal + SpreadElement` +* adjacent sibling: `VariableDeclaration ~ VariableDeclaration` +* negation: `:not(ForStatement)` +* matches-any: `:matches([attr] > :first-child, :last-child)` +* class of AST node: `:statement`, `:expression`, `:declaration`, `:function`, or `:pattern` + +This syntax is very powerful, and can be used to precisely select many syntactic patterns in your code. + +The examples in this section were adapted from the [esquery](https://github.com/estools/esquery) documentation. + +## What can selectors be used for? + +If you're writing custom ESLint rules, you might be interested in using selectors to examine specific parts of the AST. If you're configuring ESLint for your codebase, you might be interested in restricting particular syntax patterns with selectors. + +### Listening for selectors in rules + +When writing a custom ESLint rule, you can listen for nodes that match a particular selector as the AST is traversed. + +```js +module.exports = { + create(context) { + // ... + + return { + + // This listener will be called for all IfStatement nodes with blocks. + "IfStatement > BlockStatement": function(blockStatementNode) { + // ...your logic here + }, + + // This listener will be called for all function declarations with more than 3 parameters. + "FunctionDeclaration[params.length>3]": function(functionDeclarationNode) { + // ...your logic here + } + }; + } +}; +``` + +Adding `:exit` to the end of a selector will cause the listener to be called when the matching nodes are exited during traversal, rather than when they are entered. + +If two or more selectors match the same node, their listeners will be called in order of increasing specificity. The specificity of an AST selector is similar to the specificity of a CSS selector: + +* When comparing two selectors, the selector that contains more class selectors, attribute selectors, and pseudo-class selectors (excluding `:not()`) has higher specificity. +* If the class/attribute/pseudo-class count is tied, the selector that contains more node type selectors has higher specificity. + +If multiple selectors have equal specificity, their listeners will be called in alphabetical order for that node. + +### Restricting syntax with selectors + +With the [no-restricted-syntax](/docs/rules/no-restricted-syntax.md) rule, you can restrict the usage of particular syntax in your code. For example, you can use the following configuration to disallow using `if` statements that do not have block statements as their body: + +```json +{ + "rules": { + "no-restricted-syntax": ["error", "IfStatement > :not(BlockStatement).consequent"] + } +} +``` + +...or equivalently, you can use this configuration: + +```json +{ + "rules": { + "no-restricted-syntax": ["error", "IfStatement[consequent.type!='BlockStatement']"] + } +} +``` + +As another example, you can disallow calls to `require()`: + +```json +{ + "rules": { + "no-restricted-syntax": ["error", "CallExpression[callee.name='require']"] + } +} +``` + +Or you can enforce that calls to `setTimeout` always have two arguments: + +```json +{ + "rules": { + "no-restricted-syntax": ["error", "CallExpression[callee.name='setTimeout'][arguments.length!=2]"] + } +} +``` + +Using selectors in the `no-restricted-syntax` rule can give you a lot of control over problematic patterns in your codebase, without needing to write custom rules to detect each pattern. diff --git a/eslint/docs/developer-guide/shareable-configs.md b/eslint/docs/developer-guide/shareable-configs.md new file mode 100644 index 0000000..2181d1b --- /dev/null +++ b/eslint/docs/developer-guide/shareable-configs.md @@ -0,0 +1,209 @@ +# Shareable Configs + +The configuration that you have in your `.eslintrc` file is an important part of your project, and as such, you may want to share it with other projects or people. Shareable configs allow you to publish your configuration settings on [npm](https://www.npmjs.com/) and have others download and use it in their ESLint projects. + +## Creating a Shareable Config + +Shareable configs are simply npm packages that export a configuration object. To start, [create a Node.js module](https://docs.npmjs.com/getting-started/creating-node-modules) like you normally would. Make sure the module name begins with `eslint-config-`, such as `eslint-config-myconfig`. + +npm [scoped modules](https://docs.npmjs.com/misc/scope) are also supported, by naming or prefixing the module with `@scope/eslint-config`, such as `@scope/eslint-config` or `@scope/eslint-config-myconfig`. + +Create a new `index.js` file and export an object containing your settings: + +```js +module.exports = { + + globals: { + MyGlobal: true + }, + + rules: { + semi: [2, "always"] + } + +}; +``` + +Since `index.js` is just JavaScript, you can optionally read these settings from a file or generate them dynamically. + +## Publishing a Shareable Config + +Once your shareable config is ready, you can [publish to npm](https://docs.npmjs.com/getting-started/publishing-npm-packages) to share with others. We recommend using the `eslint` and `eslintconfig` keywords so others can easily find your module. + +You should declare your dependency on ESLint in `package.json` using the [peerDependencies](https://docs.npmjs.com/files/package.json#peerdependencies) field. The recommended way to declare a dependency for future proof compatibility is with the ">=" range syntax, using the lowest required ESLint version. For example: + +``` +"peerDependencies": { + "eslint": ">= 3" +} +``` + +If your shareable config depends on a plugin, you should also specify it as a `peerDependency` (plugins will be loaded relative to the end user's project, so the end user is required to install the plugins they need). However, if your shareable config depends on a third-party parser or another shareable config, you can specify these packages as `dependencies`. + +You can also test your shareable config on your computer before publishing by linking your module globally. Type: + +```bash +npm link +``` + +Then, in your project that wants to use your shareable config, type: + +```bash +npm link eslint-config-myconfig +``` + +Be sure to replace `eslint-config-myconfig` with the actual name of your module. + +## Using a Shareable Config + +Shareable configs are designed to work with the `extends` feature of `.eslintrc` files. Instead of using a file path for the value of `extends`, use your module name. For example: + +```json +{ + "extends": "eslint-config-myconfig" +} +``` + +You can also omit the `eslint-config-` and it will be automatically assumed by ESLint: + +```json +{ + "extends": "myconfig" +} +``` + +### npm scoped modules + +npm [scoped modules](https://docs.npmjs.com/misc/scope) are also supported in a number of ways. + + +By using the module name: + +```json +{ + "extends": "@scope/eslint-config" +} +``` + +You can also omit the `eslint-config` and it will be automatically assumed by ESLint: + +```json +{ + "extends": "@scope" +} +``` + +The module name can also be customized, just note that when using [scoped modules](https://docs.npmjs.com/misc/scope) it is not possible to omit the `eslint-config-` prefix. Doing so would result in package naming conflicts, and thus in resolution errors in most of cases. For example a package named `@scope/eslint-config-myconfig` vs `@scope/my-config`, since both are valid scoped package names, the configuration should be specified as: + +```json +{ + "extends": "@scope/eslint-config-myconfig" +} +``` + +You can override settings from the shareable config by adding them directly into your `.eslintrc` file. + +## Sharing Multiple Configs + +It's possible to share multiple configs in the same npm package. You can specify a default config for the package by following the directions in the first section. You can specify additional configs by simply adding a new file to your npm package and then referencing it from your ESLint config. + +As an example, you can create a file called `my-special-config.js` in the root of your npm package and export a config, such as: + +```js +module.exports = { + rules: { + quotes: [2, "double"] + } +}; +``` + +Then, assuming you're using the package name `eslint-config-myconfig`, you can access the additional config via: + +```json +{ + "extends": "myconfig/my-special-config" +} +``` + +When using [scoped modules](https://docs.npmjs.com/misc/scope) it is not possible to omit the `eslint-config` namespace. Doing so would result in resolution errors as explained above. Assuming the package name is `@scope/eslint-config`, the additional config can be accessed as: + +```json +{ + "extends": "@scope/eslint-config/my-special-config" +} +``` + +Note that you can leave off the `.js` from the filename. In this way, you can add as many additional configs to your package as you'd like. + +**Important:** We strongly recommend always including a default config for your plugin to avoid errors. + +## Local Config File Resolution + +If you need to make multiple configs that can extend from each other and live in different directories, you can create a single shareable config that handles this scenario. + +As an example, let's assume you're using the package name `eslint-config-myconfig` and your package looks something like this: + +```text +myconfig +├── index.js +└─┬ lib + ├── defaults.js + ├── dev.js + ├── ci.js + └─┬ ci + ├── frontend.js + ├── backend.js + └── common.js +``` + +In your `index.js` you can do something like this: + +```js +module.exports = require('./lib/ci.js'); +``` + +Now inside your package you have `/lib/defaults.js`, which contains: + +```js +module.exports = { + rules: { + 'no-console': 1 + } +}; +``` + +Inside your `/lib/ci.js` you have + +```js +module.exports = require('./ci/backend'); +``` + +Inside your `/lib/ci/common.js` + +```js +module.exports = { + rules: { + 'no-alert': 2 + }, + extends: 'myconfig/lib/defaults' +}; +``` + +Despite being in an entirely different directory, you'll see that all `extends` must use the full package path to the config file you wish to extend. + +Now inside your `/lib/ci/backend.js` + +```js +module.exports = { + rules: { + 'no-console': 1 + }, + extends: 'myconfig/lib/ci/common' +}; +``` + +In the last file, you'll once again see that to properly resolve your config, you'll need include the full package path. + +## Further Reading + +* [npm Developer Guide](https://docs.npmjs.com/misc/developers) diff --git a/eslint/docs/developer-guide/source-code.md b/eslint/docs/developer-guide/source-code.md new file mode 100644 index 0000000..a7e9434 --- /dev/null +++ b/eslint/docs/developer-guide/source-code.md @@ -0,0 +1,40 @@ +# Source Code + +ESLint is hosted at [GitHub](https://github.com/eslint/eslint) and uses [Git](https://git-scm.com/) for source control. In order to obtain the source code, you must first install Git on your system. Instructions for installing and setting up Git can be found at [https://help.github.com/articles/set-up-git/](https://help.github.com/articles/set-up-git/). + +If you simply want to create a local copy of the source to play with, you can clone the main repository using this command: + + git clone git://github.com/eslint/eslint.git + +If you're planning on contributing to ESLint, then it's a good idea to fork the repository. You can find instructions for forking a repository at [https://help.github.com/articles/fork-a-repo/](https://help.github.com/articles/fork-a-repo/). After forking the ESLint repository, you'll want to create a local copy of your fork. + +## Start Developing + +Before you can get started developing, you'll need to have a couple of things installed: + +* [Node.JS](https://nodejs.org) +* [npm](https://www.npmjs.com/) + +Once you have a local copy and have Node.JS and npm installed, you'll need to install the ESLint dependencies: + + cd eslint + npm install + +Now when you run `eslint`, it will be running your local copy and showing your changes. + +**Note:** It's a good idea to re-rerun `npm install` whenever you pull from the main repository to ensure you have the latest development dependencies. + +## Directory structure + +The ESLint directory and file structure is as follows: + +* `bin` - executable files that are available when ESLint is installed +* `conf` - default configuration information +* `docs` - documentation for the project +* `lib` - contains the source code + * `formatters` - all source files defining formatters + * `rules` - all source files defining rules +* `tests` - the main unit test folder + * `lib` - tests for the source code + * `formatters` - tests for the formatters + * `rules` - tests for the rules diff --git a/eslint/docs/developer-guide/unit-tests.md b/eslint/docs/developer-guide/unit-tests.md new file mode 100644 index 0000000..281c396 --- /dev/null +++ b/eslint/docs/developer-guide/unit-tests.md @@ -0,0 +1,21 @@ +# Unit Tests + +Most parts of ESLint have unit tests associated with them. Unit tests are written using [Mocha](https://mochajs.org/) and are required when making contributions to ESLint. You'll find all of the unit tests in the `tests` directory. + +When you first get the source code, you need to run `npm install` once initially to set ESLint for development. Once you've done that, you can run the tests via: + + npm test + +This automatically starts Mocha and runs all tests in the `tests` directory. You need only add yours and it will automatically be picked up when running tests. + +## Running Individual Tests + +If you want to quickly run just one test, you can do so by running Mocha directly and passing in the filename. For example: + + npm run test:cli tests/lib/rules/no-wrap-func.js + +Running individual tests is useful when you're working on a specific bug and iterating on the solution. You should be sure to run `npm test` before submitting a pull request. + +## More Control on Unit Testing + +`npm run test:cli` is an alias of the Mocha cli in `./node_modules/.bin/mocha`. [Options](https://mochajs.org/#command-line-usage) are available to be provided to help to better control the test to run. diff --git a/eslint/docs/developer-guide/working-with-custom-formatters.md b/eslint/docs/developer-guide/working-with-custom-formatters.md new file mode 100644 index 0000000..8cb6738 --- /dev/null +++ b/eslint/docs/developer-guide/working-with-custom-formatters.md @@ -0,0 +1,368 @@ +# Working with Custom Formatters + +While ESLint has some built-in formatters available to format the linting results, it's also possible to create and distribute your own custom formatters. You can include custom formatters in your project directly or create an npm package to distribute them separately. + +Each formatter is just a function that receives a `results` object and returns a string. For example, the following is how the `json` built-in formatter is implemented: + +```js +//my-awesome-formatter.js +module.exports = function(results) { + return JSON.stringify(results, null, 2); +}; +``` + +To run ESLint with this formatter, you can use the `-f` (or `--formatter`) command line flag: + +```bash +eslint -f ./my-awesome-formatter.js src/ +``` + +In order to use a local file as a custom formatter, you must begin the filename with a dot (such as `./my-awesome-formatter.js` or `../formatters/my-awesome-formatter.js`). + +### The `data` Argument + +The exported function receives an optional second argument named `data`. The `data` object provides extended information related to the analysis results. Currently, the `data` object consists of a single property named `rulesMeta`. This property is a dictionary of rule metadata, keyed with `ruleId`. The value for each entry is the `meta` property from the corresponding rule object. The dictionary contains an entry for each rule that was run during the analysis. + +Here's what the `data` object would look like if one rule, `no-extra-semi`, had been run: + +```js +{ + rulesMeta: { + "no-extra-semi": { + type: "suggestion", + docs: { + description: "disallow unnecessary semicolons", + category: "Possible Errors", + recommended: true, + url: "https://eslint.org/docs/rules/no-extra-semi" + }, + fixable: "code", + schema: [], + messages: { + unexpected: "Unnecessary semicolon." + } + } + } +} +``` + +The [Using Rule metadata](#using-rule-metadata) example shows how to use the `data` object in a custom formatter. See the [Working with Rules](https://eslint.org/docs/developer-guide/working-with-rules) page for more information about rules. + +## Packaging the Custom Formatter + +Custom formatters can also be distributed through npm packages. To do so, create an npm package with a name in the format of `eslint-formatter-*`, where `*` is the name of your formatter (such as `eslint-formatter-awesome`). Projects should then install the package and can use the custom formatter with the `-f` (or `--formatter`) flag like this: + +```bash +eslint -f awesome src/ +``` + +Because ESLint knows to look for packages beginning with `eslint-formatter-` when the specified formatter doesn't begin with a dot, there is no need to type `eslint-formatter-` when using a packaged custom formatter. + +Tips for `package.json`: + +* The `main` entry should be the JavaScript file implementing your custom formatter. +* Add these `keywords` to help users find your formatter: + * `"eslint"` + * `"eslint-formatter"` + * `"eslintformatter"` + +See all [formatters on npm](https://www.npmjs.com/search?q=eslint-formatter); + +## The `results` Object + +The `results` object passed into a formatter is an array of objects containing the lint results for individual files. Here's some example output: + +```js +[ + { + filePath: "path/to/file.js", + messages: [ + { + ruleId: "curly", + severity: 2, + message: "Expected { after 'if' condition.", + line: 2, + column: 1, + nodeType: "IfStatement" + }, + { + ruleId: "no-process-exit", + severity: 2, + message: "Don't use process.exit(); throw an error instead.", + line: 3, + column: 1, + nodeType: "CallExpression" + } + ], + errorCount: 2, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + source: + "var err = doStuff();\nif (err) console.log('failed tests: ' + err);\nprocess.exit(1);\n" + }, + { + filePath: "Gruntfile.js", + messages: [], + errorCount: 0, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0 + } +] +``` + +### The `result` Object + + + +Each object in the `results` array is a `result` object. Each `result` object contains the path of the file that was linted and information about linting issues that were encountered. Here are the properties available on each `result` object: + +* **filePath**: The absolute path to the file that was linted. +* **messages**: An array of `message` objects. See below for more info about messages. +* **errorCount**: The number of errors for the given file. +* **warningCount**: The number of warnings for the given file. +* **source**: The source code for the given file. This property is omitted if this file has no errors/warnings or if the `output` property is present. +* **output**: The source code for the given file with as many fixes applied as possible. This property is omitted if no fix is available. + +### The `message` Object + +Each `message` object contains information about the ESLint rule that was triggered by some source code. The properties available on each `message` object are: + +* **ruleId**: the ID of the rule that produced the error or warning. +* **severity**: the severity of the failure, `1` for warnings and `2` for errors. +* **message**: the human readable description of the error. +* **line**: the line where the issue is located. +* **column**: the column where the issue is located. +* **nodeType**: the type of the node in the [AST](https://github.com/estree/estree/blob/master/spec.md#node-objects) + +## Examples + +### Summary formatter + +A formatter that only cares about the total count of errors and warnings will look like this: + +```javascript +module.exports = function(results) { + // accumulate the errors and warnings + var summary = results.reduce( + function(seq, current) { + seq.errors += current.errorCount; + seq.warnings += current.warningCount; + return seq; + }, + { errors: 0, warnings: 0 } + ); + + if (summary.errors > 0 || summary.warnings > 0) { + return ( + "Errors: " + + summary.errors + + ", Warnings: " + + summary.warnings + + "\n" + ); + } + + return ""; +}; +``` + +Running `eslint` with the previous custom formatter, + +```bash +eslint -f ./my-awesome-formatter.js src/ +``` + +Will produce the following output: + +```bash +Errors: 2, Warnings: 4 +``` + +### Detailed formatter + +A more complex report will look something like this: + +```javascript +module.exports = function(results, data) { + var results = results || []; + + var summary = results.reduce( + function(seq, current) { + current.messages.forEach(function(msg) { + var logMessage = { + filePath: current.filePath, + ruleId: msg.ruleId, + ruleUrl: data.rulesMeta[msg.ruleId].url, + message: msg.message, + line: msg.line, + column: msg.column + }; + + if (msg.severity === 1) { + logMessage.type = "warning"; + seq.warnings.push(logMessage); + } + if (msg.severity === 2) { + logMessage.type = "error"; + seq.errors.push(logMessage); + } + }); + return seq; + }, + { + errors: [], + warnings: [] + } + ); + + if (summary.errors.length > 0 || summary.warnings.length > 0) { + var lines = summary.errors + .concat(summary.warnings) + .map(function(msg) { + return ( + "\n" + + msg.type + + " " + + msg.ruleId + (msg.ruleUrl ? " (" + msg.ruleUrl + ")" : "" + "\n " + + msg.filePath + + ":" + + msg.line + + ":" + + msg.column + ); + }) + .join("\n"); + + return lines + "\n"; + } +}; +``` + +So running `eslint` with this custom formatter: + +```bash +eslint -f ./my-awesome-formatter.js src/ +``` + +The output will be + +```bash +error space-infix-ops (https://eslint.org/docs/rules/space-infix-ops) + src/configs/bundler.js:6:8 +error semi (https://eslint.org/docs/rules/semi) + src/configs/bundler.js:6:10 +warning no-unused-vars (https://eslint.org/docs/rules/no-unused-vars) + src/configs/bundler.js:5:6 +warning no-unused-vars (https://eslint.org/docs/rules/no-unused-vars) + src/configs/bundler.js:6:6 +warning no-shadow (https://eslint.org/docs/rules/no-shadow) + src/configs/bundler.js:65:32 +warning no-unused-vars (https://eslint.org/docs/rules/no-unused-vars) + src/configs/clean.js:3:6 +``` + +## Passing Arguments to Formatters + +While custom formatter do not receive arguments in addition to the results object, it is possible to pass additional data into formatters. + +## Using Environment Variables + +Custom formatters have access to environment variables and so can change their behavior based on environment variable data. Here's an example that uses a `AF_SKIP_WARNINGS` environment variable to determine whether or not to show warnings in the results: + +```js +module.exports = function(results) { + var skipWarnings = process.env.AF_SKIP_WARNINGS === "true"; //af stands for awesome-formatter + + var results = results || []; + var summary = results.reduce( + function(seq, current) { + current.messages.forEach(function(msg) { + var logMessage = { + filePath: current.filePath, + ruleId: msg.ruleId, + message: msg.message, + line: msg.line, + column: msg.column + }; + + if (msg.severity === 1) { + logMessage.type = "warning"; + seq.warnings.push(logMessage); + } + if (msg.severity === 2) { + logMessage.type = "error"; + seq.errors.push(logMessage); + } + }); + return seq; + }, + { + errors: [], + warnings: [] + } + ); + + if (summary.errors.length > 0 || summary.warnings.length > 0) { + var warnings = !skipWarnings ? summary.warnings : []; // skip the warnings in that case + + var lines = summary.errors + .concat(warnings) + .map(function(msg) { + return ( + "\n" + + msg.type + + " " + + msg.ruleId + + "\n " + + msg.filePath + + ":" + + msg.line + + ":" + + msg.column + ); + }) + .join("\n"); + + return lines + "\n"; + } +}; +``` + +You would run ESLint with this custom formatter and an environment variable set like this: + +```bash +AF_SKIP_WARNINGS=true eslint -f ./my-awesome-formatter.js src/ +``` + +The output would be: + +```bash +error space-infix-ops + src/configs/bundler.js:6:8 + +error semi + src/configs/bundler.js:6:10 +``` + + +### Complex Argument Passing + +If you find the custom formatter pattern doesn't provide enough options for the way you'd like to format ESLint results, the best option is to use ESLint's built-in [JSON formatter](https://eslint.org/docs/user-guide/formatters/) and pipe the output to a second program. For example: + +```bash +eslint -f json src/ | your-program-that-reads-JSON --option +``` + +In this example, the `your-program-that-reads-json` program can accept the raw JSON of ESLint results and process it before outputting its own format of the results. You can pass as many command line arguments to that program as are necessary to customize the output. + +## Note: Formatting for Terminals + +Modern terminals like [iTerm2](https://www.iterm2.com/) or [Guake](http://guake-project.org/) expect a specific results format to automatically open filenames when they are clicked. Most terminals support this format for that purpose: + +```bash +file:line:column +``` diff --git a/eslint/docs/developer-guide/working-with-custom-parsers.md b/eslint/docs/developer-guide/working-with-custom-parsers.md new file mode 100644 index 0000000..6387b84 --- /dev/null +++ b/eslint/docs/developer-guide/working-with-custom-parsers.md @@ -0,0 +1,73 @@ +# Working with Custom Parsers + +If you want to use your own parser and provide additional capabilities for your rules, you can specify your own custom parser. If a `parseForESLint` method is exposed on the parser, this method will be used to parse the code. Otherwise, the `parse` method will be used. Both methods should take in the source code as the first argument, and an optional configuration object as the second argument (provided as `parserOptions` in a config file). The `parse` method should simply return the AST. The `parseForESLint` method should return an object that contains the required property `ast` and optional properties `services`, `scopeManager`, and `visitorKeys`. + +* `ast` should contain the AST. +* `services` can contain any parser-dependent services (such as type checkers for nodes). The value of the `services` property is available to rules as `context.parserServices`. Default is an empty object. +* `scopeManager` can be a [ScopeManager](./scope-manager-interface.md) object. Custom parsers can use customized scope analysis for experimental/enhancement syntaxes. Default is the `ScopeManager` object which is created by [eslint-scope](https://github.com/eslint/eslint-scope). + * Support for `scopeManager` was added in ESLint v4.14.0. ESLint versions which support `scopeManager` will provide an `eslintScopeManager: true` property in `parserOptions`, which can be used for feature detection. +* `visitorKeys` can be an object to customize AST traversal. The keys of the object are the type of AST nodes. Each value is an array of the property names which should be traversed. Default is [KEYS of `eslint-visitor-keys`](https://github.com/eslint/eslint-visitor-keys#evkkeys). + * Support for `visitorKeys` was added in ESLint v4.14.0. ESLint versions which support `visitorKeys` will provide an `eslintVisitorKeys: true` property in `parserOptions`, which can be used for feature detection. + +You can find an ESLint parser project [here](https://github.com/typescript-eslint/typescript-eslint). + +```json +{ + "parser": "./path/to/awesome-custom-parser.js" +} +``` + +```javascript +var espree = require("espree"); +// awesome-custom-parser.js +exports.parseForESLint = function(code, options) { + return { + ast: espree.parse(code, options), + services: { + foo: function() { + console.log("foo"); + } + }, + scopeManager: null, + visitorKeys: null + }; +}; + +``` + +## The AST specification + +The AST that custom parsers should create is based on [ESTree](https://github.com/estree/estree). The AST requires some additional properties about detail information of the source code. + +### All nodes: + +All nodes must have `range` property. + +* `range` (`number[]`) is an array of two numbers. Both numbers are a 0-based index which is the position in the array of source code characters. The first is the start position of the node, the second is the end position of the node. `code.slice(node.range[0], node.range[1])` must be the text of the node. This range does not include spaces/parentheses which are around the node. +* `loc` (`SourceLocation`) must not be `null`. [The `loc` property is defined as nullable by ESTree](https://github.com/estree/estree/blob/25834f7247d44d3156030f8e8a2d07644d771fdb/es5.md#node-objects), but ESLint requires this property. On the other hand, `SourceLocation#source` property can be `undefined`. ESLint does not use the `SourceLocation#source` property. + +The `parent` property of all nodes must be rewritable. ESLint sets each node's `parent` property to its parent node while traversing, before any rules have access to the AST. + +### The `Program` node: + +The `Program` node must have `tokens` and `comments` properties. Both properties are an array of the below Token interface. + +```ts +interface Token { + type: string; + loc: SourceLocation; + range: [number, number]; // See "All nodes:" section for details of `range` property. + value: string; +} +``` + +* `tokens` (`Token[]`) is the array of tokens which affect the behavior of programs. Arbitrary spaces can exist between tokens, so rules check the `Token#range` to detect spaces between tokens. This must be sorted by `Token#range[0]`. +* `comments` (`Token[]`) is the array of comment tokens. This must be sorted by `Token#range[0]`. + +The range indexes of all tokens and comments must not overlap with the range of other tokens and comments. + +### The `Literal` node: + +The `Literal` node must have `raw` property. + +* `raw` (`string`) is the source code of this literal. This is the same as `code.slice(node.range[0], node.range[1])`. diff --git a/eslint/docs/developer-guide/working-with-plugins.md b/eslint/docs/developer-guide/working-with-plugins.md new file mode 100644 index 0000000..a2b238c --- /dev/null +++ b/eslint/docs/developer-guide/working-with-plugins.md @@ -0,0 +1,232 @@ +# Working with Plugins + +Each plugin is an npm module with a name in the format of `eslint-plugin-`, such as `eslint-plugin-jquery`. You can also use scoped packages in the format of `@/eslint-plugin-` such as `@jquery/eslint-plugin-jquery` or even `@/eslint-plugin` such as `@jquery/eslint-plugin`. + +## Create a Plugin + +The easiest way to start creating a plugin is to use the [Yeoman generator](https://www.npmjs.com/package/generator-eslint). The generator will guide you through setting up the skeleton of a plugin. + +### Rules in Plugins + +Plugins can expose additional rules for use in ESLint. To do so, the plugin must export a `rules` object containing a key-value mapping of rule ID to rule. The rule ID does not have to follow any naming convention (so it can just be `dollar-sign`, for instance). + +```js +module.exports = { + rules: { + "dollar-sign": { + create: function (context) { + // rule implementation ... + } + } + } +}; +``` + +To use the rule in ESLint, you would use the unprefixed plugin name, followed by a slash, followed by the rule name. So if this plugin were named `eslint-plugin-myplugin`, then in your configuration you'd refer to the rule by the name `myplugin/dollar-sign`. Example: `"rules": {"myplugin/dollar-sign": 2}`. + +### Environments in Plugins + +Plugins can expose additional environments for use in ESLint. To do so, the plugin must export an `environments` object. The keys of the `environments` object are the names of the different environments provided and the values are the environment settings. For example: + +```js +module.exports = { + environments: { + jquery: { + globals: { + $: false + } + } + } +}; +``` + +There's a `jquery` environment defined in this plugin. To use the environment in ESLint, you would use the unprefixed plugin name, followed by a slash, followed by the environment name. So if this plugin were named `eslint-plugin-myplugin`, then you would set the environment in your configuration to be `"myplugin/jquery"`. + +Plugin environments can define the following objects: + +1. `globals` - acts the same `globals` in a configuration file. The keys are the names of the globals and the values are `true` to allow the global to be overwritten and `false` to disallow. +1. `parserOptions` - acts the same as `parserOptions` in a configuration file. + +### Processors in Plugins + +You can also create plugins that would tell ESLint how to process files other than JavaScript. In order to create a processor, the object that is exported from your module has to conform to the following interface: + +```js +module.exports = { + processors: { + "processor-name": { + // takes text of the file and filename + preprocess: function(text, filename) { + // here, you can strip out any non-JS content + // and split into multiple strings to lint + + return [ // return an array of code blocks to lint + { text: code1, filename: "0.js" }, + { text: code2, filename: "1.js" }, + ]; + }, + + // takes a Message[][] and filename + postprocess: function(messages, filename) { + // `messages` argument contains two-dimensional array of Message objects + // where each top-level array item contains array of lint messages related + // to the text that was returned in array from preprocess() method + + // you need to return a one-dimensional array of the messages you want to keep + return [].concat(...messages); + }, + + supportsAutofix: true // (optional, defaults to false) + } + } +}; +``` + +**The `preprocess` method** takes the file contents and filename as arguments, and returns an array of code blocks to lint. The code blocks will be linted separately but still be registered to the filename. + +A code block has two properties `text` and `filename`; the `text` property is the content of the block and the `filename` property is the name of the block. Name of the block can be anything, but should include the file extension, that would tell the linter how to process the current block. The linter will check [`--ext` CLI option](../user-guide/command-line-interface.md#--ext) to see if the current block should be linted, and resolve `overrides` configs to check how to process the current block. + +It's up to the plugin to decide if it needs to return just one part, or multiple pieces. For example in the case of processing `.html` files, you might want to return just one item in the array by combining all scripts, but for `.md` file where each JavaScript block might be independent, you can return multiple items. + +**The `postprocess` method** takes a two-dimensional array of arrays of lint messages and the filename. Each item in the input array corresponds to the part that was returned from the `preprocess` method. The `postprocess` method must adjust the locations of all errors to correspond to locations in the original, unprocessed code, and aggregate them into a single flat array and return it. + +Reported problems have the following location information: + +```typescript +{ + line: number, + column: number, + + endLine?: number, + endColumn?: number +} +``` + +By default, ESLint will not perform autofixes when a processor is used, even when the `--fix` flag is enabled on the command line. To allow ESLint to autofix code when using your processor, you should take the following additional steps: + +1. Update the `postprocess` method to additionally transform the `fix` property of reported problems. All autofixable problems will have a `fix` property, which is an object with the following schema: + + ```js + { + range: [number, number], + text: string + } + ``` + + The `range` property contains two indexes in the code, referring to the start and end location of a contiguous section of text that will be replaced. The `text` property refers to the text that will replace the given range. + + In the initial list of problems, the `fix` property will refer to a fix in the processed JavaScript. The `postprocess` method should transform the object to refer to a fix in the original, unprocessed file. + +2. Add a `supportsAutofix: true` property to the processor. + +You can have both rules and processors in a single plugin. You can also have multiple processors in one plugin. +To support multiple extensions, add each one to the `processors` element and point them to the same object. + +#### Specifying Processor in Config Files + +To use a processor, add its ID to a `processor` section in the config file. Processor ID is a concatenated string of plugin name and processor name with a slash as a separator. This can also be added to a `overrides` section of the config, to specify which processors should handle which files. + +For example: + +```yml +plugins: + - a-plugin +overrides: + - files: "*.md" + processor: a-plugin/markdown +``` + +See [Specifying Processor](../user-guide/configuring.md#specifying-processor) for details. + +#### File Extension-named Processor + +If a processor name starts with `.`, ESLint handles the processor as a **file extension-named processor** especially and applies the processor to the kind of files automatically. People don't need to specify the file extension-named processors in their config files. + +For example: + +```js +module.exports = { + processors: { + // This processor will be applied to `*.md` files automatically. + // Also, people can use this processor as "plugin-id/.md" explicitly. + ".md": { + preprocess(text, filename) { /* ... */ }, + postprocess(messageLists, filename) { /* ... */ } + } + } +} +``` + +### Configs in Plugins + +You can bundle configurations inside a plugin by specifying them under the `configs` key. This can be useful when you want to provide not just code style, but also some custom rules to support it. Multiple configurations are supported per plugin. Note that it is not possible to specify a default configuration for a given plugin and that users must specify in their configuration file when they want to use one. + +```js +// eslint-plugin-myPlugin + +module.exports = { + configs: { + myConfig: { + plugins: ["myPlugin"], + env: ["browser"], + rules: { + semi: "error", + "myPlugin/my-rule": "error", + "eslint-plugin-myPlugin/another-rule": "error" + } + }, + myOtherConfig: { + plugins: ["myPlugin"], + env: ["node"], + rules: { + "myPlugin/my-rule": "off", + "eslint-plugin-myPlugin/another-rule": "off" + "eslint-plugin-myPlugin/yet-another-rule": "error" + } + } + } +}; +``` + +If the example plugin above were called `eslint-plugin-myPlugin`, the `myConfig` and `myOtherConfig` configurations would then be usable by extending off of `"plugin:myPlugin/myConfig"` and `"plugin:myPlugin/myOtherConfig"`, respectively. + +```json +{ + "extends": ["plugin:myPlugin/myConfig"] +} + +``` + +**Note:** Please note that configuration will not enable any of the plugin's rules by default, and instead should be treated as a standalone config. This means that you must specify your plugin name in the `plugins` array as well as any rules you want to enable that are part of the plugin. Any plugin rules must be prefixed with the short or long plugin name. See [Configuring Plugins](../user-guide/configuring.md#configuring-plugins) for more information. + +### Peer Dependency + +To make clear that the plugin requires ESLint to work correctly you have to declare ESLint as a `peerDependency` in your `package.json`. +The plugin support was introduced in ESLint version `0.8.0`. Ensure the `peerDependency` points to ESLint `0.8.0` or later. + +```json +{ + "peerDependencies": { + "eslint": ">=0.8.0" + } +} +``` + +### Testing + +ESLint provides the [`RuleTester`](/docs/developer-guide/nodejs-api.md#ruletester) utility to make it easy to test the rules of your plugin. + +## Share Plugins + +In order to make your plugin available to the community you have to publish it on npm. + +Recommended keywords: + +* `eslint` +* `eslintplugin` + +Add these keywords into your `package.json` file to make it easy for others to find. + +## Further Reading + +* [npm Developer Guide](https://docs.npmjs.com/misc/developers) diff --git a/eslint/docs/developer-guide/working-with-rules-deprecated.md b/eslint/docs/developer-guide/working-with-rules-deprecated.md new file mode 100644 index 0000000..a80f331 --- /dev/null +++ b/eslint/docs/developer-guide/working-with-rules-deprecated.md @@ -0,0 +1,575 @@ +# Working with Rules + +Each rule in ESLint has two files named with its identifier (for example, `no-extra-semi`). + +* in the `lib/rules` directory: a source file (for example, `no-extra-semi.js`) +* in the `tests/lib/rules` directory: a test file (for example, `no-extra-semi.js`) + +**Important:** If you submit a **core** rule to the ESLint repository, you **must** follow some conventions explained below. + +Here is the basic format of the source file for a rule: + +```js +/** + * @fileoverview Rule to disallow unnecessary semicolons + * @author Nicholas C. Zakas + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = function(context) { + return { + // callback functions + }; +}; + +module.exports.schema = []; // no options +``` + +## Rule Basics + +`schema` (array) specifies the [options](#options-schemas) so ESLint can prevent invalid [rule configurations](../user-guide/configuring.md#configuring-rules) + +`create` (function) returns an object with methods that ESLint calls to "visit" nodes while traversing the abstract syntax tree (AST as defined by [ESTree](https://github.com/estree/estree)) of JavaScript code: + +* if a key is a node type, ESLint calls that **visitor** function while going **down** the tree +* if a key is a node type plus `:exit`, ESLint calls that **visitor** function while going **up** the tree +* if a key is an event name, ESLint calls that **handler** function for [code path analysis](./code-path-analysis.md) + +A rule can use the current node and its surrounding tree to report or fix problems. + +Here are methods for the [array-callback-return](../rules/array-callback-return.md) rule: + +```js +function checkLastSegment (node) { + // report problem for function if last code path segment is reachable +} + +module.exports = function(context) { + // declare the state of the rule + return { + ReturnStatement: function(node) { + // at a ReturnStatement node while going down + }, + // at a function expression node while going up: + "FunctionExpression:exit": checkLastSegment, + "ArrowFunctionExpression:exit": checkLastSegment, + onCodePathStart: function (codePath, node) { + // at the start of analyzing a code path + }, + onCodePathEnd: function(codePath, node) { + // at the end of analyzing a code path + } + }; +}; +``` + +## The Context Object + +The `context` object contains additional functionality that is helpful for rules to do their jobs. As the name implies, the `context` object contains information that is relevant to the context of the rule. The `context` object has the following properties: + +* `parserOptions` - the parser options configured for this run (more details [here](../user-guide/configuring.md#specifying-parser-options)). +* `id` - the rule ID. +* `options` - an array of rule options. +* `settings` - the `settings` from configuration. +* `parserPath` - the full path to the `parser` from configuration. + +Additionally, the `context` object has the following methods: + +* `getAncestors()` - returns an array of ancestor nodes based on the current traversal. +* `getDeclaredVariables(node)` - returns the declared variables on the given node. +* `getFilename()` - returns the filename associated with the source. +* `getScope()` - returns the current scope. +* `getSourceCode()` - returns a `SourceCode` object that you can use to work with the source that was passed to ESLint +* `markVariableAsUsed(name)` - marks the named variable in scope as used. This affects the [no-unused-vars](../rules/no-unused-vars.md) rule. +* `report(descriptor)` - reports a problem in the code. + +**Deprecated:** The following methods on the `context` object are deprecated. Please use the corresponding methods on `SourceCode` instead: + +* `getAllComments()` - returns an array of all comments in the source. Use `sourceCode.getAllComments()` instead. +* `getComments(node)` - returns the leading and trailing comments arrays for the given node. Use `sourceCode.getComments(node)` instead. +* `getFirstToken(node)` - returns the first token representing the given node. Use `sourceCode.getFirstToken(node)` instead. +* `getFirstTokens(node, count)` - returns the first `count` tokens representing the given node. Use `sourceCode.getFirstTokens(node, count)` instead. +* `getJSDocComment(node)` - returns the JSDoc comment for a given node or `null` if there is none. Use `sourceCode.getJSDocComment(node)` instead. +* `getLastToken(node)` - returns the last token representing the given node. Use `sourceCode.getLastToken(node)` instead. +* `getLastTokens(node, count)` - returns the last `count` tokens representing the given node. Use `sourceCode.getLastTokens(node, count)` instead. +* `getNodeByRangeIndex(index)` - returns the deepest node in the AST containing the given source index. Use `sourceCode.getNodeByRangeIndex(index)` instead. +* `getSource(node)` - returns the source code for the given node. Omit `node` to get the whole source. Use `sourceCode.getText(node)` instead. +* `getSourceLines()` - returns the entire source code split into an array of string lines. Use `sourceCode.lines` instead. +* `getTokenAfter(nodeOrToken)` - returns the first token after the given node or token. Use `sourceCode.getTokenAfter(nodeOrToken)` instead. +* `getTokenBefore(nodeOrToken)` - returns the first token before the given node or token. Use `sourceCode.getTokenBefore(nodeOrToken)` instead. +* `getTokenByRangeStart(index)` - returns the token whose range starts at the given index in the source. Use `sourceCode.getTokenByRangeStart(index)` instead. +* `getTokens(node)` - returns all tokens for the given node. Use `sourceCode.getTokens(node)` instead. +* `getTokensAfter(nodeOrToken, count)` - returns `count` tokens after the given node or token. Use `sourceCode.getTokensAfter(nodeOrToken, count)` instead. +* `getTokensBefore(nodeOrToken, count)` - returns `count` tokens before the given node or token. Use `sourceCode.getTokensBefore(nodeOrToken, count)` instead. +* `getTokensBetween(node1, node2)` - returns the tokens between two nodes. Use `sourceCode.getTokensBetween(node1, node2)` instead. +* `report(node, [location], message)` - reports a problem in the code. + +### context.report() + +The main method you'll use is `context.report()`, which publishes a warning or error (depending on the configuration being used). This method accepts a single argument, which is an object containing the following properties: + +* `message` - the problem message. +* `node` - (optional) the AST node related to the problem. If present and `loc` is not specified, then the starting location of the node is used as the location of the problem. +* `loc` - (optional) an object specifying the location of the problem. If both `loc` and `node` are specified, then the location is used from `loc` instead of `node`. + * `line` - the 1-based line number at which the problem occurred. + * `column` - the 0-based column number at which the problem occurred. +* `data` - (optional) placeholder data for `message`. +* `fix` - (optional) a function that applies a fix to resolve the problem. + +Note that at least one of `node` or `loc` is required. + +The simplest example is to use just `node` and `message`: + +```js +context.report({ + node: node, + message: "Unexpected identifier" +}); +``` + +The node contains all of the information necessary to figure out the line and column number of the offending text as well the source text representing the node. + +You can also use placeholders in the message and provide `data`: + +```js +{% raw %} +context.report({ + node: node, + message: "Unexpected identifier: {{ identifier }}", + data: { + identifier: node.name + } +}); +{% endraw %} +``` + +Note that leading and trailing whitespace is optional in message parameters. + +The node contains all of the information necessary to figure out the line and column number of the offending text as well the source text representing the node. + +### Applying Fixes + +If you'd like ESLint to attempt to fix the problem you're reporting, you can do so by specifying the `fix` function when using `context.report()`. The `fix` function receives a single argument, a `fixer` object, that you can use to apply a fix. For example: + +```js +context.report({ + node: node, + message: "Missing semicolon". + fix: function(fixer) { + return fixer.insertTextAfter(node, ";"); + } +}); +``` + +Here, the `fix()` function is used to insert a semicolon after the node. Note that the fix is not immediately applied and may not be applied at all if there are conflicts with other fixes. If the fix cannot be applied, then the problem message is reported as usual; if the fix can be applied, then the problem message is not reported. + +The `fixer` object has the following methods: + +* `insertTextAfter(nodeOrToken, text)` - inserts text after the given node or token +* `insertTextAfterRange(range, text)` - inserts text after the given range +* `insertTextBefore(nodeOrToken, text)` - inserts text before the given node or token +* `insertTextBeforeRange(range, text)` - inserts text before the given range +* `remove(nodeOrToken)` - removes the given node or token +* `removeRange(range)` - removes text in the given range +* `replaceText(nodeOrToken, text)` - replaces the text in the given node or token +* `replaceTextRange(range, text)` - replaces the text in the given range + +Best practices for fixes: + +1. Make fixes that are as small as possible. Anything more than a single character is risky and could prevent other, simpler fixes from being made. +1. Only make one fix per message. This is enforced because you must return the result of the fixer operation from `fix()`. +1. Fixes should not introduce clashes with other rules. You can accidentally introduce a new problem that won't be reported until ESLint is run again. Another good reason to make as small a fix as possible. + +### context.options + +Some rules require options in order to function correctly. These options appear in configuration (`.eslintrc`, command line, or in comments). For example: + +```json +{ + "quotes": [2, "double"] +} +``` + +The `quotes` rule in this example has one option, `"double"` (the `2` is the error level). You can retrieve the options for a rule by using `context.options`, which is an array containing every configured option for the rule. In this case, `context.options[0]` would contain `"double"`: + +```js +module.exports = function(context) { + + var isDouble = (context.options[0] === "double"); + + // ... +} +``` + +Since `context.options` is just an array, you can use it to determine how many options have been passed as well as retrieving the actual options themselves. Keep in mind that the error level is not part of `context.options`, as the error level cannot be known or modified from inside a rule. + +When using options, make sure that your rule has some logic defaults in case the options are not provided. + +### context.getSourceCode() + +The `SourceCode` object is the main object for getting more information about the source code being linted. You can retrieve the `SourceCode` object at any time by using the `getSourceCode()` method: + +```js +module.exports = function(context) { + + var sourceCode = context.getSourceCode(); + + // ... +} +``` + +Once you have an instance of `SourceCode`, you can use the methods on it to work with the code: + +* `getAllComments()` - returns an array of all comments in the source. +* `getComments(node)` - returns the leading and trailing comments arrays for the given node. +* `getFirstToken(node)` - returns the first token representing the given node. +* `getFirstTokens(node, count)` - returns the first `count` tokens representing the given node. +* `getJSDocComment(node)` - returns the JSDoc comment for a given node or `null` if there is none. +* `getLastToken(node)` - returns the last token representing the given node. +* `getLastTokens(node, count)` - returns the last `count` tokens representing the given node. +* `getNodeByRangeIndex(index)` - returns the deepest node in the AST containing the given source index. +* `isSpaceBetweenTokens(first, second)` - returns true if there is a whitespace character between the two tokens. +* `getText(node)` - returns the source code for the given node. Omit `node` to get the whole source. +* `getTokenAfter(nodeOrToken)` - returns the first token after the given node or token. +* `getTokenBefore(nodeOrToken)` - returns the first token before the given node or token. +* `getTokenByRangeStart(index)` - returns the token whose range starts at the given index in the source. +* `getTokens(node)` - returns all tokens for the given node. +* `getTokensAfter(nodeOrToken, count)` - returns `count` tokens after the given node or token. +* `getTokensBefore(nodeOrToken, count)` - returns `count` tokens before the given node or token. +* `getTokensBetween(node1, node2)` - returns the tokens between two nodes. + +There are also some properties you can access: + +* `hasBOM` - the flag to indicate whether or not the source code has Unicode BOM. +* `text` - the full text of the code being linted. Unicode BOM has been stripped from this text. +* `ast` - the `Program` node of the AST for the code being linted. +* `lines` - an array of lines, split according to the specification's definition of line breaks. + +You should use a `SourceCode` object whenever you need to get more information about the code being linted. + +### Options Schemas + +Rules may export a `schema` property, which is a [JSON schema](http://json-schema.org/) format description of a rule's options which will be used by ESLint to validate configuration options and prevent invalid or unexpected inputs before they are passed to the rule in `context.options`. + +There are two formats for a rule's exported `schema`. The first is a full JSON Schema object describing all possible options the rule accepts, including the rule's error level as the first argument and any optional arguments thereafter. + +However, to simplify schema creation, rules may also export an array of schemas for each optional positional argument, and ESLint will automatically validate the required error level first. For example, the `yoda` rule accepts a primary mode argument, as well as an extra options object with named properties. + +```js +// "yoda": [2, "never", { "exceptRange": true }] +module.exports.schema = [ + { + "enum": ["always", "never"] + }, + { + "type": "object", + "properties": { + "exceptRange": { + "type": "boolean" + } + }, + "additionalProperties": false + } +]; +``` + +In the preceding example, the error level is assumed to be the first argument. It is followed by the first optional argument, a string which may be either `"always"` or `"never"`. The final optional argument is an object, which may have a Boolean property named `exceptRange`. + +To learn more about JSON Schema, we recommend looking at some [examples](http://json-schema.org/examples.html) to start, and also reading [Understanding JSON Schema](http://spacetelescope.github.io/understanding-json-schema/) (a free ebook). + +### Getting the Source + +If your rule needs to get the actual JavaScript source to work with, then use the `sourceCode.getText()` method. This method works as follows: + +```js + +// get all source +var source = sourceCode.getText(); + +// get source for just this AST node +var nodeSource = sourceCode.getText(node); + +// get source for AST node plus previous two characters +var nodeSourceWithPrev = sourceCode.getText(node, 2); + +// get source for AST node plus following two characters +var nodeSourceWithFollowing = sourceCode.getText(node, 0, 2); +``` + +In this way, you can look for patterns in the JavaScript text itself when the AST isn't providing the appropriate data (such as location of commas, semicolons, parentheses, etc.). + +### Accessing comments + +If you need to access comments for a specific node you can use `sourceCode.getComments(node)`: + +```js +// the "comments" variable has a "leading" and "trailing" property containing +// its leading and trailing comments, respectively +var comments = sourceCode.getComments(node); +``` + +Keep in mind that comments are technically not a part of the AST and are only attached to it on demand, i.e. when you call `getComments()`. + +**Note:** One of the libraries adds AST node properties for comments - do not use these properties. Always use `sourceCode.getComments()` as this is the only guaranteed API for accessing comments (we will likely change how comments are handled later). + +### Accessing Code Paths + +ESLint analyzes code paths while traversing AST. +You can access that code path objects with five events related to code paths. + +[details here](./code-path-analysis.md) + +## Rule Unit Tests + +Each rule must have a set of unit tests submitted with it to be accepted. The test file is named the same as the source file but lives in `tests/lib/`. For example, if your rule source file is `lib/rules/foo.js` then your test file should be `tests/lib/rules/foo.js`. + +For your rule, be sure to test: + +1. All instances that should be flagged as warnings. +1. At least one pattern that should **not** be flagged as a warning. + +The basic pattern for a rule unit test file is: + +```js +/** + * @fileoverview Tests for no-with rule. + * @author Nicholas C. Zakas + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +var rule = require("../../../lib/rules/no-with"), + RuleTester = require("../../../lib/testers/rule-tester"); + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +var ruleTester = new RuleTester(); +ruleTester.run("no-with", rule, { + valid: [ + "foo.bar()" + ], + invalid: [ + { + code: "with(foo) { bar() }", + errors: [{ message: "Unexpected use of 'with' statement.", type: "WithStatement"}] + } + ] +}); +``` + +Be sure to replace the value of `"no-with"` with your rule's ID. There are plenty of examples in the `tests/lib/rules/` directory. + +### Valid Code + +Each valid case can be either a string or an object. The object form is used when you need to specify additional global variables or arguments for the rule. For example, the following defines `window` as a global variable for code that should not trigger the rule being tested: + +```js +valid: [ + { + code: "window.alert()", + globals: [ "window" ] + } +] +``` + +You can also pass options to the rule (if it accepts them). These arguments are equivalent to how people can configure rules in their `.eslintrc` file. For example: + +```js +valid: [ + { + code: "var msg = 'Hello';", + options: [ "single" ] + } +] +``` + +The `options` property must be an array of options. This gets passed through to `context.options` in the rule. + +### Invalid Code + +Each invalid case must be an object containing the code to test and at least one message that is produced by the rule. The `errors` key specifies an array of objects, each containing a message (your rule may trigger multiple messages for the same code). You should also specify the type of AST node you expect to receive back using the `type` key. The AST node should represent the actual spot in the code where there is a problem. For example: + +```js +invalid: [ + { + code: "function doSomething() { var f; if (true) { var build = true; } f = build; }", + errors: [ + { message: "build used outside of binding context.", type: "Identifier" } + ] + } +] +``` + +In this case, the message is specific to the variable being used and the AST node type is `Identifier`. + +Similar to the valid cases, you can also specify `options` to be passed to the rule: + +```js +invalid: [ + { + code: "function doSomething() { var f; if (true) { var build = true; } f = build; }", + options: [ "double" ], + errors: [ + { message: "build used outside of binding context.", type: "Identifier" } + ] + } +] +``` + +For simpler cases where the only thing that really matters is the error message, you can also specify any `errors` as strings. You can also have some strings and some objects, if you like. + +```js +invalid: [ + { + code: "'single quotes'", + options: ["double"], + errors: ["Strings must use doublequote."] + } +] +``` + +### Specifying Parser Options + +Some tests require that a certain parser configuration must be used. This can be specified in test specifications via the `parserOptions` setting. + +For example, to set `ecmaVersion` to 6 (in order to use constructs like `for ... of`): + +```js +valid: [ + { + code: "for (x of a) doSomething();", + parserOptions: { ecmaVersion: 6 } + } +] +``` + +If you are working with ES6 modules: + +```js +valid: [ + { + code: "export default function () {};", + parserOptions: { ecmaVersion: 6, sourceType: "module" } + } +] +``` + +For non-version specific features such as JSX: + +```js +valid: [ + { + code: "var foo =
{bar}
", + parserOptions: { ecmaFeatures: { jsx: true } } + } +] +``` + +The options available and the expected syntax for `parserOptions` is the same as those used in [configuration](../user-guide/configuring.md#specifying-parser-options). + +### Write Several Tests + +Provide as many unit tests as possible. Your pull request will never be turned down for having too many tests submitted with it! + +## Performance Testing + +To keep the linting process efficient and unobtrusive, it is useful to verify the performance impact of new rules or modifications to existing rules. + +### Overall Performance + +The `npm run perf` command gives a high-level overview of ESLint running time with default rules (`eslint:recommended`) enabled. + +```bash +$ git checkout master +Switched to branch 'master' + +$ npm run perf +CPU Speed is 2200 with multiplier 7500000 +Performance Run #1: 1394.689313ms +Performance Run #2: 1423.295351ms +Performance Run #3: 1385.09515ms +Performance Run #4: 1382.406982ms +Performance Run #5: 1409.68566ms +Performance budget ok: 1394.689313ms (limit: 3409.090909090909ms) + +$ git checkout my-rule-branch +Switched to branch 'my-rule-branch' + +$ npm run perf +CPU Speed is 2200 with multiplier 7500000 +Performance Run #1: 1443.736547ms +Performance Run #2: 1419.193291ms +Performance Run #3: 1436.018228ms +Performance Run #4: 1473.605485ms +Performance Run #5: 1457.455283ms +Performance budget ok: 1443.736547ms (limit: 3409.090909090909ms) +``` + +### Per-rule Performance + +ESLint has a built-in method to track performance of individual rules. Setting the `TIMING` environment variable will trigger the display, upon linting completion, of the ten longest-running rules, along with their individual running time and relative performance impact as a percentage of total rule processing time. + +```bash +$ TIMING=1 eslint lib +Rule | Time (ms) | Relative +:-----------------------|----------:|--------: +no-multi-spaces | 52.472 | 6.1% +camelcase | 48.684 | 5.7% +no-irregular-whitespace | 43.847 | 5.1% +valid-jsdoc | 40.346 | 4.7% +handle-callback-err | 39.153 | 4.6% +space-infix-ops | 35.444 | 4.1% +no-undefined | 25.693 | 3.0% +no-shadow | 22.759 | 2.7% +no-empty-class | 21.976 | 2.6% +semi | 19.359 | 2.3% +``` + +To test one rule explicitly, combine the `--no-eslintrc`, and `--rule` options: + +```bash +$ TIMING=1 eslint --no-eslintrc --rule "quotes: [2, 'double']" lib +Rule | Time (ms) | Relative +:------|----------:|--------: +quotes | 18.066 | 100.0% +``` + +## Rule Naming Conventions + +The rule naming conventions for ESLint are fairly simple: + +* If your rule is disallowing something, prefix it with `no-` such as `no-eval` for disallowing `eval()` and `no-debugger` for disallowing `debugger`. +* If your rule is enforcing the inclusion of something, use a short name without a special prefix. +* Keep your rule names as short as possible, use abbreviations where appropriate, and no more than four words. +* Use dashes between words. + +## Rule Acceptance Criteria + +Because rules are highly personal (and therefore very contentious), accepted rules should: + +* Not be library-specific. +* Demonstrate a possible issue that can be resolved by rewriting the code. +* Be general enough so as to apply for a large number of developers. +* Not be the opposite of an existing rule. +* Not overlap with an existing rule. + +## Runtime Rules + +The thing that makes ESLint different from other linters is the ability to define custom rules at runtime. This is perfect for rules that are specific to your project or company and wouldn't make sense for ESLint to ship with. With runtime rules, you don't have to wait for the next version of ESLint or be disappointed that your rule isn't general enough to apply to the larger JavaScript community, just write your rules and include them at runtime. + +Runtime rules are written in the same format as all other rules. Create your rule as you would any other and then follow these steps: + +1. Place all of your runtime rules in the same directory (i.e., `eslint_rules`). +2. Create a [configuration file](../user-guide/configuring.md) and specify your rule ID error level under the `rules` key. Your rule will not run unless it has a value of `1` or `2` in the configuration file. +3. Run the [command line interface](../user-guide/command-line-interface.md) using the `--rulesdir` option to specify the location of your runtime rules. diff --git a/eslint/docs/developer-guide/working-with-rules.md b/eslint/docs/developer-guide/working-with-rules.md new file mode 100644 index 0000000..608a38b --- /dev/null +++ b/eslint/docs/developer-guide/working-with-rules.md @@ -0,0 +1,737 @@ +# Working with Rules + +**Note:** This page covers the most recent rule format for ESLint >= 3.0.0. There is also a [deprecated rule format](./working-with-rules-deprecated.md). + +Each rule in ESLint has three files named with its identifier (for example, `no-extra-semi`). + +* in the `lib/rules` directory: a source file (for example, `no-extra-semi.js`) +* in the `tests/lib/rules` directory: a test file (for example, `no-extra-semi.js`) +* in the `docs/rules` directory: a Markdown documentation file (for example, `no-extra-semi.md`) + +**Important:** If you submit a **core** rule to the ESLint repository, you **must** follow some conventions explained below. + +Here is the basic format of the source file for a rule: + +```js +/** + * @fileoverview Rule to disallow unnecessary semicolons + * @author Nicholas C. Zakas + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow unnecessary semicolons", + category: "Possible Errors", + recommended: true, + url: "https://eslint.org/docs/rules/no-extra-semi" + }, + fixable: "code", + schema: [] // no options + }, + create: function(context) { + return { + // callback functions + }; + } +}; +``` + +## Rule Basics + +The source file for a rule exports an object with the following properties. + +`meta` (object) contains metadata for the rule: + +* `type` (string) indicates the type of rule, which is one of `"problem"`, `"suggestion"`, or `"layout"`: + * `"problem"` means the rule is identifying code that either will cause an error or may cause a confusing behavior. Developers should consider this a high priority to resolve. + * `"suggestion"` means the rule is identifying something that could be done in a better way but no errors will occur if the code isn't changed. + * `"layout"` means the rule cares primarily about whitespace, semicolons, commas, and parentheses, all the parts of the program that determine how the code looks rather than how it executes. These rules work on parts of the code that aren't specified in the AST. + +* `docs` (object) is required for core rules of ESLint: + + * `description` (string) provides the short description of the rule in the [rules index](../rules/) + * `category` (string) specifies the heading under which the rule is listed in the [rules index](../rules/) + * `recommended` (boolean) is whether the `"extends": "eslint:recommended"` property in a [configuration file](../user-guide/configuring.md#extending-configuration-files) enables the rule + * `url` (string) specifies the URL at which the full documentation can be accessed + * `suggestion` (boolean) specifies whether rules can return suggestions (defaults to false if omitted) + + In a custom rule or plugin, you can omit `docs` or include any properties that you need in it. + +* `fixable` (string) is either `"code"` or `"whitespace"` if the `--fix` option on the [command line](../user-guide/command-line-interface.md#fix) automatically fixes problems reported by the rule + + **Important:** Without the `fixable` property, ESLint does not [apply fixes](#applying-fixes) even if the rule implements `fix` functions. Omit the `fixable` property if the rule is not fixable. + +* `schema` (array) specifies the [options](#options-schemas) so ESLint can prevent invalid [rule configurations](../user-guide/configuring.md#configuring-rules) + +* `deprecated` (boolean) indicates whether the rule has been deprecated. You may omit the `deprecated` property if the rule has not been deprecated. + +* `replacedBy` (array) in the case of a deprecated rule, specifies replacement rule(s) + +`create` (function) returns an object with methods that ESLint calls to "visit" nodes while traversing the abstract syntax tree (AST as defined by [ESTree](https://github.com/estree/estree)) of JavaScript code: + +* if a key is a node type or a [selector](./selectors.md), ESLint calls that **visitor** function while going **down** the tree +* if a key is a node type or a [selector](./selectors.md) plus `:exit`, ESLint calls that **visitor** function while going **up** the tree +* if a key is an event name, ESLint calls that **handler** function for [code path analysis](./code-path-analysis.md) + +A rule can use the current node and its surrounding tree to report or fix problems. + +Here are methods for the [array-callback-return](../rules/array-callback-return.md) rule: + +```js +function checkLastSegment (node) { + // report problem for function if last code path segment is reachable +} + +module.exports = { + meta: { ... }, + create: function(context) { + // declare the state of the rule + return { + ReturnStatement: function(node) { + // at a ReturnStatement node while going down + }, + // at a function expression node while going up: + "FunctionExpression:exit": checkLastSegment, + "ArrowFunctionExpression:exit": checkLastSegment, + onCodePathStart: function (codePath, node) { + // at the start of analyzing a code path + }, + onCodePathEnd: function(codePath, node) { + // at the end of analyzing a code path + } + }; + } +}; +``` + +## The Context Object + +The `context` object contains additional functionality that is helpful for rules to do their jobs. As the name implies, the `context` object contains information that is relevant to the context of the rule. The `context` object has the following properties: + +* `parserOptions` - the parser options configured for this run (more details [here](../user-guide/configuring.md#specifying-parser-options)). +* `id` - the rule ID. +* `options` - an array of the [configured options](/docs/user-guide/configuring.md#configuring-rules) for this rule. This array does not include the rule severity. For more information, see [here](#contextoptions). +* `settings` - the [shared settings](/docs/user-guide/configuring.md#adding-shared-settings) from configuration. +* `parserPath` - the name of the `parser` from configuration. +* `parserServices` - an object containing parser-provided services for rules. The default parser does not provide any services. However, if a rule is intended to be used with a custom parser, it could use `parserServices` to access anything provided by that parser. (For example, a TypeScript parser could provide the ability to get the computed type of a given node.) + +Additionally, the `context` object has the following methods: + +* `getAncestors()` - returns an array of the ancestors of the currently-traversed node, starting at the root of the AST and continuing through the direct parent of the current node. This array does not include the currently-traversed node itself. +* `getCwd()` - returns the `cwd` passed to [Linter](./nodejs-api.md#Linter). It is a path to a directory that should be considered as the current working directory. +* `getDeclaredVariables(node)` - returns a list of [variables](./scope-manager-interface.md#variable-interface) declared by the given node. This information can be used to track references to variables. + * If the node is a `VariableDeclaration`, all variables declared in the declaration are returned. + * If the node is a `VariableDeclarator`, all variables declared in the declarator are returned. + * If the node is a `FunctionDeclaration` or `FunctionExpression`, the variable for the function name is returned, in addition to variables for the function parameters. + * If the node is an `ArrowFunctionExpression`, variables for the parameters are returned. + * If the node is a `ClassDeclaration` or a `ClassExpression`, the variable for the class name is returned. + * If the node is a `CatchClause`, the variable for the exception is returned. + * If the node is an `ImportDeclaration`, variables for all of its specifiers are returned. + * If the node is an `ImportSpecifier`, `ImportDefaultSpecifier`, or `ImportNamespaceSpecifier`, the declared variable is returned. + * Otherwise, if the node does not declare any variables, an empty array is returned. +* `getFilename()` - returns the filename associated with the source. +* `getScope()` - returns the [scope](./scope-manager-interface.md#scope-interface) of the currently-traversed node. This information can be used to track references to variables. +* `getSourceCode()` - returns a [`SourceCode`](#contextgetsourcecode) object that you can use to work with the source that was passed to ESLint. +* `markVariableAsUsed(name)` - marks a variable with the given name in the current scope as used. This affects the [no-unused-vars](../rules/no-unused-vars.md) rule. Returns `true` if a variable with the given name was found and marked as used, otherwise `false`. +* `report(descriptor)` - reports a problem in the code (see the [dedicated section](#context-report)). + +**Note:** Earlier versions of ESLint supported additional methods on the `context` object. Those methods were removed in the new format and should not be relied upon. + +### context.getScope() + +This method returns the scope which has the following types: + +| AST Node Type | Scope Type | +|:--------------------------|:-----------| +| `Program` | `global` | +| `FunctionDeclaration` | `function` | +| `FunctionExpression` | `function` | +| `ArrowFunctionExpression` | `function` | +| `ClassDeclaration` | `class` | +| `ClassExpression` | `class` | +| `BlockStatement` ※1 | `block` | +| `SwitchStatement` ※1 | `switch` | +| `ForStatement` ※2 | `for` | +| `ForInStatement` ※2 | `for` | +| `ForOfStatement` ※2 | `for` | +| `WithStatement` | `with` | +| `CatchClause` | `catch` | +| others | ※3 | + +**※1** Only if the configured parser provided the block-scope feature. The default parser provides the block-scope feature if `parserOptions.ecmaVersion` is not less than `6`.
+**※2** Only if the `for` statement defines the iteration variable as a block-scoped variable (E.g., `for (let i = 0;;) {}`).
+**※3** The scope of the closest ancestor node which has own scope. If the closest ancestor node has multiple scopes then it chooses the innermost scope (E.g., the `Program` node has a `global` scope and a `module` scope if `Program#sourceType` is `"module"`. The innermost scope is the `module` scope.). + +The returned value is a [`Scope` object](scope-manager-interface.md) defined by the `eslint-scope` package. The `Variable` objects of global variables have some additional properties. + +* `variable.writeable` (`boolean | undefined`) ... If `true`, this global variable can be assigned arbitrary value. If `false`, this global variable is read-only. +* `variable.eslintExplicitGlobal` (`boolean | undefined`) ... If `true`, this global variable was defined by a `/* globals */` directive comment in the source code file. +* `variable.eslintExplicitGlobalComments` (`Comment[] | undefined`) ... The array of `/* globals */` directive comments which defined this global variable in the source code file. This property is `undefined` if there are no `/* globals */` directive comments. +* `variable.eslintImplicitGlobalSetting` (`"readonly" | "writable" | undefined`) ... The configured value in config files. This can be different from `variable.writeable` if there are `/* globals */` directive comments. + +### context.report() + +The main method you'll use is `context.report()`, which publishes a warning or error (depending on the configuration being used). This method accepts a single argument, which is an object containing the following properties: + +* `message` - the problem message. +* `node` - (optional) the AST node related to the problem. If present and `loc` is not specified, then the starting location of the node is used as the location of the problem. +* `loc` - (optional) an object specifying the location of the problem. If both `loc` and `node` are specified, then the location is used from `loc` instead of `node`. + * `start` - An object of the start location. + * `line` - the 1-based line number at which the problem occurred. + * `column` - the 0-based column number at which the problem occurred. + * `end` - An object of the end location. + * `line` - the 1-based line number at which the problem occurred. + * `column` - the 0-based column number at which the problem occurred. +* `data` - (optional) [placeholder](#using-message-placeholders) data for `message`. +* `fix` - (optional) a function that applies a [fix](#applying-fixes) to resolve the problem. + +Note that at least one of `node` or `loc` is required. + +The simplest example is to use just `node` and `message`: + +```js +context.report({ + node: node, + message: "Unexpected identifier" +}); +``` + +The node contains all of the information necessary to figure out the line and column number of the offending text as well the source text representing the node. + +### Using message placeholders + +You can also use placeholders in the message and provide `data`: + +```js +{% raw %} +context.report({ + node: node, + message: "Unexpected identifier: {{ identifier }}", + data: { + identifier: node.name + } +}); +{% endraw %} +``` + +Note that leading and trailing whitespace is optional in message parameters. + +The node contains all of the information necessary to figure out the line and column number of the offending text as well the source text representing the node. + +### `messageId`s + +Instead of typing out messages in both the `context.report()` call and your tests, you can use `messageId`s instead. + +This allows you to avoid retyping error messages. It also prevents errors reported in different sections of your rule from having out-of-date messages. + +```js +{% raw %} +// in your rule +module.exports = { + meta: { + messages: { + avoidName: "Avoid using variables named '{{ name }}'" + } + }, + create(context) { + return { + Identifier(node) { + if (node.name === "foo") { + context.report({ + node, + messageId: "avoidName", + data: { + name: "foo", + } + }); + } + } + }; + } +}; + +// in the file to lint: + +var foo = 2; +// ^ error: Avoid using variables named 'foo' + +// In your tests: +var rule = require("../../../lib/rules/my-rule"); +var RuleTester = require("eslint").RuleTester; + +var ruleTester = new RuleTester(); +ruleTester.run("my-rule", rule, { + valid: ["bar", "baz"], + invalid: [ + { + code: "foo", + errors: [ + { + messageId: "avoidName" + } + ] + } + ] +}); +{% endraw %} +``` + +### Applying Fixes + +If you'd like ESLint to attempt to fix the problem you're reporting, you can do so by specifying the `fix` function when using `context.report()`. The `fix` function receives a single argument, a `fixer` object, that you can use to apply a fix. For example: + +```js +context.report({ + node: node, + message: "Missing semicolon", + fix: function(fixer) { + return fixer.insertTextAfter(node, ";"); + } +}); +``` + +Here, the `fix()` function is used to insert a semicolon after the node. Note that a fix is not immediately applied, and may not be applied at all if there are conflicts with other fixes. After applying fixes, ESLint will run all of the enabled rules again on the fixed code, potentially applying more fixes. This process will repeat up to 10 times, or until no more fixable problems are found. Afterwards, any remaining problems will be reported as usual. + +**Important:** Unless the rule [exports](#rule-basics) the `meta.fixable` property, ESLint does not apply fixes even if the rule implements `fix` functions. + +The `fixer` object has the following methods: + +* `insertTextAfter(nodeOrToken, text)` - inserts text after the given node or token +* `insertTextAfterRange(range, text)` - inserts text after the given range +* `insertTextBefore(nodeOrToken, text)` - inserts text before the given node or token +* `insertTextBeforeRange(range, text)` - inserts text before the given range +* `remove(nodeOrToken)` - removes the given node or token +* `removeRange(range)` - removes text in the given range +* `replaceText(nodeOrToken, text)` - replaces the text in the given node or token +* `replaceTextRange(range, text)` - replaces the text in the given range + +The above methods return a `fixing` object. +The `fix()` function can return the following values: + +* A `fixing` object. +* An array which includes `fixing` objects. +* An iterable object which enumerates `fixing` objects. Especially, the `fix()` function can be a generator. + +If you make a `fix()` function which returns multiple `fixing` objects, those `fixing` objects must not be overlapped. + +Best practices for fixes: + +1. Avoid any fixes that could change the runtime behavior of code and cause it to stop working. +1. Make fixes as small as possible. Fixes that are unnecessarily large could conflict with other fixes, and prevent them from being applied. +1. Only make one fix per message. This is enforced because you must return the result of the fixer operation from `fix()`. +1. Since all rules are run again after the initial round of fixes is applied, it's not necessary for a rule to check whether the code style of a fix will cause errors to be reported by another rule. + * For example, suppose a fixer would like to surround an object key with quotes, but it's not sure whether the user would prefer single or double quotes. + + ```js + ({ foo : 1 }) + + // should get fixed to either + + ({ 'foo': 1 }) + + // or + + ({ "foo": 1 }) + ``` + + * This fixer can just select a quote type arbitrarily. If it guesses wrong, the resulting code will be automatically reported and fixed by the [`quotes`](/docs/rules/quotes.md) rule. + +### Providing Suggestions + +In some cases fixes aren't appropriate to be automatically applied, for example, if a fix potentially changes functionality or if there are multiple valid ways to fix a rule depending on the implementation intent (see the best practices for [applying fixes](#applying-fixes) listed above). In these cases, there is an alternative `suggest` option on `context.report()` that allows other tools, such as editors, to expose helpers for users to manually apply a suggestion. + +In order to provide suggestions, use the `suggest` key in the report argument with an array of suggestion objects. The suggestion objects represent individual suggestions that could be applied and require either a `desc` key string that describes what applying the suggestion would do or a `messageId` key (see [below](#suggestion-messageids)), and a `fix` key that is a function defining the suggestion result. This `fix` function follows the same API as regular fixes (described above in [applying fixes](#applying-fixes)). + +```js +{% raw %} +context.report({ + node: node, + message: "Unnecessary escape character: \\{{character}}.", + data: { character }, + suggest: [ + { + desc: "Remove the `\\`. This maintains the current functionality.", + fix: function(fixer) { + return fixer.removeRange(range); + } + }, + { + desc: "Replace the `\\` with `\\\\` to include the actual backslash character.", + fix: function(fixer) { + return fixer.insertTextBeforeRange(range, "\\"); + } + } + ] +}); +{% endraw %} +``` + +Note: Suggestions will be applied as a stand-alone change, without triggering multipass fixes. Each suggestion should focus on a singular change in the code and should not try to conform to user defined styles. For example, if a suggestion is adding a new statement into the codebase, it should not try to match correct indentation, or confirm to user preferences on presence/absence of semicolumns. All of those things can be corrected by multipass autofix when the user triggers it. + +Best practices for suggestions: + +1. Don't try to do too much and suggest large refactors that could introduce a lot of breaking changes. +1. As noted above, don't try to conform to user-defined styles. + +#### Suggestion `messageId`s + +Instead of using a `desc` key for suggestions a `messageId` can be used instead. This works the same way as `messageId`s for the overall error (see [messageIds](#messageIds)). Here is an example of how to use it in a rule: + +```js +{% raw %} +module.exports = { + meta: { + messages: { + unnecessaryEscape: "Unnecessary escape character: \\{{character}}.", + removeEscape: "Remove the `\\`. This maintains the current functionality.", + escapeBackslash: "Replace the `\\` with `\\\\` to include the actual backslash character." + } + }, + create: function(context) { + // ... + context.report({ + node: node, + messageId: 'unnecessaryEscape', + data: { character }, + suggest: [ + { + messageId: "removeEscape", + fix: function(fixer) { + return fixer.removeRange(range); + } + }, + { + messageId: "escapeBackslash", + fix: function(fixer) { + return fixer.insertTextBeforeRange(range, "\\"); + } + } + ] + }); + } +}; +{% endraw %} +``` + +#### Placeholders in suggestion messages + +You can also use placeholders in the suggestion message. This works the same way as placeholders for the overall error (see [using message placeholders](#using-message-placeholders)). + +Please note that you have to provide `data` on the suggestion's object. Suggestion messages cannot use properties from the overall error's `data`. + +```js +{% raw %} +module.exports = { + meta: { + messages: { + unnecessaryEscape: "Unnecessary escape character: \\{{character}}.", + removeEscape: "Remove `\\` before {{character}}.", + } + }, + create: function(context) { + // ... + context.report({ + node: node, + messageId: "unnecessaryEscape", + data: { character }, // data for the unnecessaryEscape overall message + suggest: [ + { + messageId: "removeEscape", + data: { character }, // data for the removeEscape suggestion message + fix: function(fixer) { + return fixer.removeRange(range); + } + } + ] + }); + } +}; +{% endraw %} +``` + +### context.options + +Some rules require options in order to function correctly. These options appear in configuration (`.eslintrc`, command line, or in comments). For example: + +```json +{ + "quotes": ["error", "double"] +} +``` + +The `quotes` rule in this example has one option, `"double"` (the `error` is the error level). You can retrieve the options for a rule by using `context.options`, which is an array containing every configured option for the rule. In this case, `context.options[0]` would contain `"double"`: + +```js +module.exports = { + create: function(context) { + var isDouble = (context.options[0] === "double"); + + // ... + } +}; +``` + +Since `context.options` is just an array, you can use it to determine how many options have been passed as well as retrieving the actual options themselves. Keep in mind that the error level is not part of `context.options`, as the error level cannot be known or modified from inside a rule. + +When using options, make sure that your rule has some logical defaults in case the options are not provided. + +### context.getSourceCode() + +The `SourceCode` object is the main object for getting more information about the source code being linted. You can retrieve the `SourceCode` object at any time by using the `getSourceCode()` method: + +```js +module.exports = { + create: function(context) { + var sourceCode = context.getSourceCode(); + + // ... + } +}; +``` + +Once you have an instance of `SourceCode`, you can use the methods on it to work with the code: + +* `getText(node)` - returns the source code for the given node. Omit `node` to get the whole source. +* `getAllComments()` - returns an array of all comments in the source. +* `getCommentsBefore(nodeOrToken)` - returns an array of comment tokens that occur directly before the given node or token. +* `getCommentsAfter(nodeOrToken)` - returns an array of comment tokens that occur directly after the given node or token. +* `getCommentsInside(node)` - returns an array of all comment tokens inside a given node. +* `getJSDocComment(node)` - returns the JSDoc comment for a given node or `null` if there is none. +* `isSpaceBetween(nodeOrToken, nodeOrToken)` - returns true if there is a whitespace character between the two tokens or, if given a node, the last token of the first node and the first token of the second node. +* `getFirstToken(node, skipOptions)` - returns the first token representing the given node. +* `getFirstTokens(node, countOptions)` - returns the first `count` tokens representing the given node. +* `getLastToken(node, skipOptions)` - returns the last token representing the given node. +* `getLastTokens(node, countOptions)` - returns the last `count` tokens representing the given node. +* `getTokenAfter(nodeOrToken, skipOptions)` - returns the first token after the given node or token. +* `getTokensAfter(nodeOrToken, countOptions)` - returns `count` tokens after the given node or token. +* `getTokenBefore(nodeOrToken, skipOptions)` - returns the first token before the given node or token. +* `getTokensBefore(nodeOrToken, countOptions)` - returns `count` tokens before the given node or token. +* `getFirstTokenBetween(nodeOrToken1, nodeOrToken2, skipOptions)` - returns the first token between two nodes or tokens. +* `getFirstTokensBetween(nodeOrToken1, nodeOrToken2, countOptions)` - returns the first `count` tokens between two nodes or tokens. +* `getLastTokenBetween(nodeOrToken1, nodeOrToken2, skipOptions)` - returns the last token between two nodes or tokens. +* `getLastTokensBetween(nodeOrToken1, nodeOrToken2, countOptions)` - returns the last `count` tokens between two nodes or tokens. +* `getTokens(node)` - returns all tokens for the given node. +* `getTokensBetween(nodeOrToken1, nodeOrToken2)` - returns all tokens between two nodes. +* `getTokenByRangeStart(index, rangeOptions)` - returns the token whose range starts at the given index in the source. +* `getNodeByRangeIndex(index)` - returns the deepest node in the AST containing the given source index. +* `getLocFromIndex(index)` - returns an object with `line` and `column` properties, corresponding to the location of the given source index. `line` is 1-based and `column` is 0-based. +* `getIndexFromLoc(loc)` - returns the index of a given location in the source code, where `loc` is an object with a 1-based `line` key and a 0-based `column` key. +* `commentsExistBetween(nodeOrToken1, nodeOrToken2)` - returns `true` if comments exist between two nodes. + +`skipOptions` is an object which has 3 properties; `skip`, `includeComments`, and `filter`. Default is `{skip: 0, includeComments: false, filter: null}`. + +* `skip` is a positive integer, the number of skipping tokens. If `filter` option is given at the same time, it doesn't count filtered tokens as skipped. +* `includeComments` is a boolean value, the flag to include comment tokens into the result. +* `filter` is a function which gets a token as the first argument, if the function returns `false` then the result excludes the token. + +`countOptions` is an object which has 3 properties; `count`, `includeComments`, and `filter`. Default is `{count: 0, includeComments: false, filter: null}`. + +* `count` is a positive integer, the maximum number of returning tokens. +* `includeComments` is a boolean value, the flag to include comment tokens into the result. +* `filter` is a function which gets a token as the first argument, if the function returns `false` then the result excludes the token. + +`rangeOptions` is an object which has 1 property: `includeComments`. + +* `includeComments` is a boolean value, the flag to include comment tokens into the result. + +There are also some properties you can access: + +* `hasBOM` - the flag to indicate whether or not the source code has Unicode BOM. +* `text` - the full text of the code being linted. Unicode BOM has been stripped from this text. +* `ast` - the `Program` node of the AST for the code being linted. +* `scopeManager` - the [ScopeManager](./scope-manager-interface.md#scopemanager-interface) object of the code. +* `visitorKeys` - the visitor keys to traverse this AST. +* `lines` - an array of lines, split according to the specification's definition of line breaks. + +You should use a `SourceCode` object whenever you need to get more information about the code being linted. + +#### Deprecated + +Please note that the following methods have been deprecated and will be removed in a future version of ESLint: + +* `getComments()` - replaced by `getCommentsBefore()`, `getCommentsAfter()`, and `getCommentsInside()` +* `getTokenOrCommentBefore()` - replaced by `getTokenBefore()` with the `{ includeComments: true }` option +* `getTokenOrCommentAfter()` - replaced by `getTokenAfter()` with the `{ includeComments: true }` option +* `isSpaceBetweenTokens()` - replaced by `isSpaceBetween()` + +### Options Schemas + +Rules may export a `schema` property, which is a [JSON schema](http://json-schema.org/) format description of a rule's options which will be used by ESLint to validate configuration options and prevent invalid or unexpected inputs before they are passed to the rule in `context.options`. + +There are two formats for a rule's exported `schema`. The first is a full JSON Schema object describing all possible options the rule accepts, including the rule's error level as the first argument and any optional arguments thereafter. + +However, to simplify schema creation, rules may also export an array of schemas for each optional positional argument, and ESLint will automatically validate the required error level first. For example, the `yoda` rule accepts a primary mode argument, as well as an extra options object with named properties. + +```js +// "yoda": [2, "never", { "exceptRange": true }] +module.exports = { + meta: { + schema: [ + { + "enum": ["always", "never"] + }, + { + "type": "object", + "properties": { + "exceptRange": { + "type": "boolean" + } + }, + "additionalProperties": false + } + ] + }, +}; +``` + +In the preceding example, the error level is assumed to be the first argument. It is followed by the first optional argument, a string which may be either `"always"` or `"never"`. The final optional argument is an object, which may have a Boolean property named `exceptRange`. + +To learn more about JSON Schema, we recommend looking at some examples in [website](http://json-schema.org/learn/) to start, and also reading [Understanding JSON Schema](http://spacetelescope.github.io/understanding-json-schema/) (a free ebook). + +**Note:** Currently you need to use full JSON Schema object rather than array in case your schema has references ($ref), because in case of array format ESLint transforms this array into a single schema without updating references that makes them incorrect (they are ignored). + +### Getting the Source + +If your rule needs to get the actual JavaScript source to work with, then use the `sourceCode.getText()` method. This method works as follows: + +```js + +// get all source +var source = sourceCode.getText(); + +// get source for just this AST node +var nodeSource = sourceCode.getText(node); + +// get source for AST node plus previous two characters +var nodeSourceWithPrev = sourceCode.getText(node, 2); + +// get source for AST node plus following two characters +var nodeSourceWithFollowing = sourceCode.getText(node, 0, 2); +``` + +In this way, you can look for patterns in the JavaScript text itself when the AST isn't providing the appropriate data (such as location of commas, semicolons, parentheses, etc.). + +### Accessing Comments + +While comments are not technically part of the AST, ESLint provides a few ways for rules to access them: + +#### sourceCode.getAllComments() + +This method returns an array of all the comments found in the program. This is useful for rules that need to check all comments regardless of location. + +#### sourceCode.getCommentsBefore(), sourceCode.getCommentsAfter(), and sourceCode.getCommentsInside() + +These methods return an array of comments that appear directly before, directly after, and inside nodes, respectively. They are useful for rules that need to check comments in relation to a given node or token. + +Keep in mind that the results of this method are calculated on demand. + +#### Token traversal methods + +Finally, comments can be accessed through many of `sourceCode`'s methods using the `includeComments` option. + +### Accessing Shebangs + +Shebangs are represented by tokens of type `"Shebang"`. They are treated as comments and can be accessed by the methods outlined above. + +### Accessing Code Paths + +ESLint analyzes code paths while traversing AST. +You can access that code path objects with five events related to code paths. + +[details here](./code-path-analysis.md) + +## Rule Unit Tests + +Each bundled rule for ESLint core must have a set of unit tests submitted with it to be accepted. The test file is named the same as the source file but lives in `tests/lib/`. For example, if the rule source file is `lib/rules/foo.js` then the test file should be `tests/lib/rules/foo.js`. + +ESLint provides the [`RuleTester`](/docs/developer-guide/nodejs-api.md#ruletester) utility to make it easy to write tests for rules. + +## Performance Testing + +To keep the linting process efficient and unobtrusive, it is useful to verify the performance impact of new rules or modifications to existing rules. + +### Overall Performance + +When developing in the ESLint core repository, the `npm run perf` command gives a high-level overview of ESLint running time with all core rules enabled. + +```bash +$ git checkout master +Switched to branch 'master' + +$ npm run perf +CPU Speed is 2200 with multiplier 7500000 +Performance Run #1: 1394.689313ms +Performance Run #2: 1423.295351ms +Performance Run #3: 1385.09515ms +Performance Run #4: 1382.406982ms +Performance Run #5: 1409.68566ms +Performance budget ok: 1394.689313ms (limit: 3409.090909090909ms) + +$ git checkout my-rule-branch +Switched to branch 'my-rule-branch' + +$ npm run perf +CPU Speed is 2200 with multiplier 7500000 +Performance Run #1: 1443.736547ms +Performance Run #2: 1419.193291ms +Performance Run #3: 1436.018228ms +Performance Run #4: 1473.605485ms +Performance Run #5: 1457.455283ms +Performance budget ok: 1443.736547ms (limit: 3409.090909090909ms) +``` + +### Per-rule Performance + +ESLint has a built-in method to track performance of individual rules. Setting the `TIMING` environment variable will trigger the display, upon linting completion, of the ten longest-running rules, along with their individual running time and relative performance impact as a percentage of total rule processing time. + +```bash +$ TIMING=1 eslint lib +Rule | Time (ms) | Relative +:-----------------------|----------:|--------: +no-multi-spaces | 52.472 | 6.1% +camelcase | 48.684 | 5.7% +no-irregular-whitespace | 43.847 | 5.1% +valid-jsdoc | 40.346 | 4.7% +handle-callback-err | 39.153 | 4.6% +space-infix-ops | 35.444 | 4.1% +no-undefined | 25.693 | 3.0% +no-shadow | 22.759 | 2.7% +no-empty-class | 21.976 | 2.6% +semi | 19.359 | 2.3% +``` + +To test one rule explicitly, combine the `--no-eslintrc`, and `--rule` options: + +```bash +$ TIMING=1 eslint --no-eslintrc --rule "quotes: [2, 'double']" lib +Rule | Time (ms) | Relative +:------|----------:|--------: +quotes | 18.066 | 100.0% +``` + +## Rule Naming Conventions + +The rule naming conventions for ESLint are fairly simple: + +* If your rule is disallowing something, prefix it with `no-` such as `no-eval` for disallowing `eval()` and `no-debugger` for disallowing `debugger`. +* If your rule is enforcing the inclusion of something, use a short name without a special prefix. +* Use dashes between words. + +## Runtime Rules + +The thing that makes ESLint different from other linters is the ability to define custom rules at runtime. This is perfect for rules that are specific to your project or company and wouldn't make sense for ESLint to ship with. With runtime rules, you don't have to wait for the next version of ESLint or be disappointed that your rule isn't general enough to apply to the larger JavaScript community, just write your rules and include them at runtime. + +Runtime rules are written in the same format as all other rules. Create your rule as you would any other and then follow these steps: + +1. Place all of your runtime rules in the same directory (e.g., `eslint_rules`). +2. Create a [configuration file](../user-guide/configuring.md) and specify your rule ID error level under the `rules` key. Your rule will not run unless it has a value of `1` or `2` in the configuration file. +3. Run the [command line interface](../user-guide/command-line-interface.md) using the `--rulesdir` option to specify the location of your runtime rules. diff --git a/eslint/docs/maintainer-guide/README.md b/eslint/docs/maintainer-guide/README.md new file mode 100644 index 0000000..941ad55 --- /dev/null +++ b/eslint/docs/maintainer-guide/README.md @@ -0,0 +1,23 @@ +# Maintainer Guide + +This guide is intended for those who work as part of the ESLint project team. + +## [Managing Issues](issues.md) + +Describes how to deal with issues when they're opened, when interacting with users, and how to close them effectively. + +## [Reviewing Pull Requests](pullrequests.md) + +Describes how to review incoming pull requests. + +## [Managing Releases](releases.md) + +Describes how to do an ESLint project release. + +## [Governance](governance.md) + +Describes the governance policy for ESLint, including the rights and privileges of individuals inside the project. + +## [Working Groups](working-groups.md) + +Describes how working groups are created and how they function within the ESLint project. diff --git a/eslint/docs/maintainer-guide/governance.md b/eslint/docs/maintainer-guide/governance.md new file mode 100644 index 0000000..b393d19 --- /dev/null +++ b/eslint/docs/maintainer-guide/governance.md @@ -0,0 +1,174 @@ +# Governance + +ESLint is an open source project that depends on contributions from the community. Anyone may contribute to the project at any time by submitting code, participating in discussions, making suggestions, or any other contribution they see fit. This document describes how various types of contributors work within the ESLint project. + +## Roles and Responsibilities + +### Users + +Users are community members who have a need for the project. Anyone can be a User; there are no special requirements. Common User contributions include evangelizing the project (e.g., display a link on a website and raise awareness through word-of-mouth), informing developers of strengths and weaknesses from a new user perspective, or providing moral support (a "thank you" goes a long way). + +Users who continue to engage with the project and its community will often become more and more involved. Such Users may find themselves becoming Contributors, as described in the next section. + +### Contributors + +Contributors are community members who contribute in concrete ways to the project, most often in the form of code and/or documentation. Anyone can become a Contributor, and contributions can take many forms. There is no expectation of commitment to the project, no specific skill requirements, and no selection process. + +Contributors have read-only access to source code and so submit changes via pull requests. Contributor pull requests have their contribution reviewed and merged by a TSC member. TSC members and Committers work with Contributors to review their code and prepare it for merging. + +As Contributors gain experience and familiarity with the project, their profile within, and commitment to, the community will increase. At some stage, they may find themselves being nominated for committership by an existing Committer. + +### Committers + +Committers are community members who have shown that they are committed to the continued development of the project through ongoing engagement with the community. Committers are given push access to the project's GitHub repos and must abide by the project's [Contribution Guidelines](contributing). + +Committers: + +* Are expected to work on public branches of the source repository and submit pull requests from that branch to the master branch. +* Are expected to delete their public branches when they are no longer necessary. +* Must submit pull requests for all changes. +* Have their work reviewed by TSC members before acceptance into the repository. +* May label and close issues (see [Managing Issues](issues.html)) +* May merge some pull requests (see [Managing Pull Requests](pullrequests.html)) + +To become a Committer: + +* One must have shown a willingness and ability to participate in the project as a team player. Typically, a potential Committer will need to show that they have an understanding of and alignment with the project, its objectives, and its strategy. +* Committers are expected to be respectful of every community member and to work collaboratively in the spirit of inclusion. +* Have submitted a minimum of 10 qualifying pull requests. What's a qualifying pull request? One that carries significant technical weight and requires little effort to accept because it's well documented and tested. + +New Committers can be nominated by any existing Committer. Once they have been nominated, there will be a vote by the TSC members. + +It is important to recognize that committership is a privilege, not a right. That privilege must be earned and once earned it can be removed by the TSC members by a standard TSC motion. However, under normal circumstances committership exists for as long as the Committer wishes to continue engaging with the project. + +A Committer who shows an above-average level of contribution to the project, particularly with respect to its strategic direction and long-term health, may be nominated to become a reviewer, described below. + +#### Process for Adding Committers + +1. Send email congratulating the new committer and confirming that they would like to accept. This should also outline the responsibilities of a committer with a link to the maintainer guide. +1. Add the GitHub user to the "ESLint Team" team +1. Add committer email to the ESLint team mailing list +1. Invite to Gitter team chatroom +1. Tweet congratulations to the new committer from the ESLint Twitter account + +### Reviewers + +Reviewers are community members who have contributed a significant amount of time to the project through triaging of issues, fixing bugs, implementing enhancements/features, and are trusted community leaders. + +Reviewers may perform all of the duties of Committers, and also: + +* May merge external pull requests for accepted issues upon reviewing and approving the changes. +* May merge their own pull requests once they have collected the feedback they deem necessary. (No pull request should be merged without at least one Committer/Reviewer/TSC member comment stating they've looked at the code.) + +To become a Reviewer: + +* Work in a helpful and collaborative way with the community. +* Have given good feedback on others' submissions and displayed an overall understanding of the code quality standards for the project. +* Commit to being a part of the community for the long-term. +* Have submitted a minimum of 50 qualifying pull requests. + +A Committer is invited to become a Reviewer by existing Reviewers and TSC members. A nomination will result in discussion and then a decision by the TSC. + +#### Process for Adding Reviewers + +1. Add the GitHub user to the "ESLint Reviewers" GitHub team +1. Tweet congratulations to the new Reviewer from the ESLint Twitter account + +### Technical Steering Committee (TSC) + +The ESLint project is jointly governed by a Technical Steering Committee (TSC) which is responsible for high-level guidance of the project. + +The TSC has final authority over this project including: + +* Technical direction +* Project governance and process (including this policy) +* Contribution policy +* GitHub repository hosting + +TSC seats are not time-limited. The size of the TSC can not be larger than five members. This size ensures adequate coverage of important areas of expertise balanced with the ability to make decisions efficiently. + +The TSC may add additional members to the TSC by a standard TSC motion. + +A TSC member may be removed from the TSC by voluntary resignation, by a standard TSC motion, or by missing four consecutive TSC meetings. In all cases, the TSC member will revert to Reviewer status unless they prefer Alumni status. + +Changes to TSC membership should be posted in the agenda, and may be suggested as any other agenda item (see "TSC Meetings" below). + +No more than 1/3 of the TSC members may be affiliated with the same employer. If removal or resignation of a TSC member, or a change of employment by a TSC member, creates a situation where more than 1/3 of the TSC membership shares an employer, then the situation must be immediately remedied by the resignation or removal of one or more TSC members affiliated with the over-represented employer(s). + +TSC members have additional responsibilities over and above those of a Reviewer. These responsibilities ensure the smooth running of the project. TSC members are expected to review code contributions, approve changes to this document, manage the copyrights within the project outputs, and attend regular TSC meetings. + +TSC members may perform all of the duties of Reviewers, and also: + +* May release new versions of all ESLint projects. +* May participate in TSC meetings. +* May propose budget items. +* May propose new ESLint projects. + +There is no specific set of requirements or qualifications for TSC members beyond those that are expected of Reviewers. + +A Reviewer is invited to become a TSC member by existing TSC members. A nomination will result in discussion and then a decision by the TSC. + +#### Process for Adding TSC Members + +1. Add the GitHub user to the "ESLint TSC" GitHub team +1. Set the GitHub user to be have the "Owner" role for the ESLint organization +1. Send a welcome email with a link to the [maintainer guide](./) and the [npm 2FA guide](./npm-2fa). +1. Invite to the Gitter TSC chatroom +1. Make the TSC member an admin on the ESLint team mailing list +1. Add the TSC member to the recurring TSC meeting event on Google Calendar +1. Add the TSC member as an admin to ESLint Twitter Account on Tweetdeck +1. Add the TSC member to the ESLint TSC mailing list as an "Owner" +1. Tweet congratulations to the new TSC member from the ESLint Twitter account + +#### TSC Meetings + +The TSC meets every other week in the [TSC Meeting](https://gitter.im/eslint/tsc-meetings) chatroom. The meeting is run by +a designated moderator approved by the TSC. + +Items are added to the TSC agenda which are considered contentious or +are modifications of governance, contribution policy, TSC membership, +or release process. + +The intention of the agenda is not to approve or review all patches. +That should happen continuously on GitHub and be handled by the larger +group of Committers. + +Any community member, Committer, or Reviewer can ask that something be added to +the next meeting's agenda by logging a GitHub Issue. Anyone can add the item to the agenda by adding +the "tsc agenda" tag to the issue. + +Prior to each TSC meeting, the moderator will share the Agenda with +members of the TSC. TSC members can add any items they like to the +agenda at the beginning of each meeting. The moderator and the TSC +cannot veto or remove items. + +No binding votes on TSC agenda items can take place without a quorum of +TSC members present in the meeting. Quorum is achieved when more than +half of the TSC members (minus non-attending members) are present. + +The TSC may invite persons or representatives from certain projects to +participate in a non-voting capacity. + +The moderator is responsible for summarizing the discussion of each +agenda item and sending it as a pull request after the meeting. + +## Consensus Seeking Process + +The TSC follows a +[Consensus Seeking](https://en.wikipedia.org/wiki/Consensus-seeking_decision-making) +decision making model. + +When an agenda item has appeared to reach a consensus, the moderator +will ask "Does anyone object?" as a final call for dissent from the +consensus. + +If an agenda item cannot reach a consensus, a TSC member can call for +either a closing vote or a vote to table the issue to the next +meeting. The call for a vote must be approved by a majority of the TSC +or else the discussion will continue. Simple majority wins. + +---- + +This work is a derivative of [YUI Contributor Model](https://github.com/yui/yui3/wiki/Contributor-Model) and the [Node.js Project Governance Model](https://github.com/nodejs/node/blob/master/GOVERNANCE.md). + +This work is licensed under a [Creative Commons Attribution-ShareAlike 2.0 UK: England & Wales License](https://creativecommons.org/licenses/by-sa/2.0/uk/). diff --git a/eslint/docs/maintainer-guide/issues.md b/eslint/docs/maintainer-guide/issues.md new file mode 100644 index 0000000..ad61025 --- /dev/null +++ b/eslint/docs/maintainer-guide/issues.md @@ -0,0 +1,121 @@ +# Managing Issues + +New issues are filed frequently, and how we respond to those issues directly affects the success of the project. Being part of the project team means helping to triage and address issues as they come in so the project can continue to run smoothly. + +## Things to Keep in Mind + +1. **Be nice.** Even if the people are being rude or aggressive on an issue, as a project team member you must be the mature one in the conversation. Do your best to work with everyone no matter their style. Remember, poor wording choice can also be a sign of someone who doesn't know English very well, so be sure to consider that when trying to determine the tone of someone's message. Being rude, even when someone is being rude to you, reflects poorly on the team and the project as a whole. +1. **Be inquisitive.** Ask questions on the issue whenever something isn't clear. Don't assume you understand what's being reported if there are details missing. Whenever you are unsure, it's best to ask for more information. +1. **Not all requests are equal.** It's unlikely we'll be able to accommodate every request, so don't be afraid to say that something doesn't fit into the scope of the project or isn't practical. It's better to give such feedback if that's the case. +1. **Close when appropriate.** Don't be afraid to close issues that you don't think will be done, or when it's become clear from the conversation that there's no further work to do. Issues can always be reopened if they are closed incorrectly, so feel free to close issues when appropriate. Just be sure to leave a comment explaining why the issue is being closed (if not closed by a commit). + +## Types of Issues + +There are four primary issue categories: + +1. **Bug** - something isn't working the way it's expected to work. +1. **Enhancement** - a change to something that already exists. For instance, adding a new option to an existing rule or a bug in a rule where fixing it will result in the rule reporting more problems (in this case, use both "Bug" and "Enhancement"). +1. **Feature** - adding something that doesn't already exist. For example, adding a new rule, new formatter, or new command line flag. +1. **Question** - an inquiry about how something works that won't result in a code change. We'd prefer if people use the mailing list or chatroom for questions, but sometimes they'll open an issue. + +The first goal when evaluating an issue is to determine which category the issue falls into. + +## When an Issue is Opened + +When an issue is opened, the bot will automatically apply the "triage" label. Issues labeled with "triage" are the ones that need to be looked at by team members to determine what to do next. + +The steps for triaging an issue are: + +1. Is it clear what is being requested? + * No: add the "needs info" label to the issue. The bot will add a comment asking for more information. You don't need to comment any further until the person who opened the issue responds with the information requested from the bot. + * Yes: + * Remove the "triage" label + * Label questions with the "question" label + * Label bug reports with the "bug" label (also use the "accepted" label if you can reproduce and verify the bug, otherwise add the "evaluating" label to indicate someone needs to verify) + * Label requests for changes to existing features (new rule options, new configuration options, etc.) with the "enhancement" and "evaluating" labels + * Label requests for completely new features (new rules, supporting a new file format, etc.) with the "feature" and "evaluating" labels + * Use an appropriate label for the part of the project the issue refers to: + * "build" - related to commands run during a build (testing, linting, release scripts, etc.) + * "cli" - related to command line input or output, or to `CLIEngine` + * "core" - related to internal APIs + * "documentation" - related to content on eslint.org + * "infrastructure" - related to resources needed for builds or deployment (VMs, CI tools, bots, etc.) +1. Once it's clear what type of issue it is, make sure all of the relevant information is provided: + * **Bugs**: See [bug reporting guidelines](/docs/developer-guide/contributing/reporting-bugs.md) + * **New Rules:** See [rule proposal guidelines](/docs/developer-guide/contributing/new-rules.md) + * **Rule Changes:** See [rule change proposal guidelines](/docs/developer-guide/contributing/rule-changes.md) + * **Other Changes:** See [change proposal guidelines](/docs/developer-guide/contributing/changes.md) +1. Next steps: + * **Questions:** answer the question and close the issue when the conversation is over. + * **Bugs:** if you can verify the bug, add the "accepted" label and ask if they would like to submit a pull request. + * **New Rules:** if you are willing to champion the rule (meaning you believe it should be included in ESLint core and you will take ownership of the process for including it), add a comment saying you will champion the issue, assign the issue to yourself, and follow the [guidelines](#championing-issues) below. + * **Rule Changes:** if you are willing to champion the change and it would not be a breaking change (requiring a major version increment), add a comment saying that you will champion the issue, assign the issue to yourself, and follow the [guidelines](#championing-issues) below. + * **Breaking Changes:** if you suspect or can verify that a change would be breaking, label it as "Breaking". + * **Duplicates:** if you can verify the issue is a duplicate, add a comment mentioning the duplicate issue (such as, "Duplicate of #1234") and close the issue. +1. Regardless of the above, always leave a comment. Don't just add labels, engage with the person who opened the issue by asking a question (request more information if necessary) or stating your opinion of the issue. If it's a verified bug, ask if the user would like to submit a pull request. + +**Note:** "Good first issue" issues are intended to help new contributors feel welcome and empowered to make a contribution to ESLint. To ensure that new contributors are given a chance to work on these issues, issues labeled "good first issue" must be open for 30 days *from the day the issue was labeled* before a team member is permitted to work on them. + +## Accepting Issues + +Issues may be labeled as "accepted" when the issue is: + +* A bug that you've been able to reproduce and verify (i.e. you're sure it's a bug) +* A new rule or rule change that you're championing and [consensus](#consensus) has been reached for its inclusion in the project + +The "accepted" label will be added to other issues by a TSC member if it's appropriate for the roadmap. + +## Championing Issues + +New rules and rule changes require a champion. As champion, it's your job to: + +* Gain [consensus](#consensus) from the ESLint team on inclusion +* Guide the rule creation process until it's complete (so only champion a rule that you have time to implement or help another contributor implement) + +Once consensus has been reached on inclusion, add the "accepted" and, optionally, "help wanted" and "good first issue" labels, as necessary. + +## Consensus + +Consensus is reached on issues when there are at least three team members who believe the change is a good idea and no one who believes the change is a bad idea. In order to indicate your support for an issue, leave a +1 reaction (thumbs up) on the original issue description in addition to any comments you might have. + +## When to Send to TSC + +If consensus cannot be reached on an issue, or an issue's progress has been stalled and it's not clear if the issue should be closed, then you can refer the issue to the TSC for resolution. To do so, add the "tsc agenda" label to the issue and add a comment including the following information: + +1. A one-paragraph summary of the discussion to this point. +2. The question you would like the TSC to answer. + +The issue will be discussed at the next TSC meeting and the resolution will be posted back to the issue. + +## Evaluating Core Features and Enhancements (TSC members only) + +In addition to the above, changes to the core (including CLI changes) that would result in a minor or major version release must be approved by the TSC by standard TSC motion. Add the label "tsc agenda" to the issue and it will be discussed at the next TSC meeting. In general, requests should meet the following criteria to be considered: + +1. The feature or enhancement is in scope for the project and should be added to the roadmap +1. Someone is committed to including the change within the next year +1. There is reasonable certainty about who will do the work + +When a suggestion is too ambitious or would take too much time to complete, it's better not to accept the proposal. Stick to small, incremental changes and lay out a roadmap of where you'd like the project to go eventually. Don't let the project get bogged down in big features that will take a long time to complete. + +**Breaking Changes:** Be on the lookout for changes that would be breaking. Issues that represent breaking changes should be labeled as "breaking". + +## When to Close an Issue + +All team members are allowed to close issues depending on how the issue has been resolved. + +Team members may close an issue **immediately** if: + +1. The issue is a duplicate of an existing issue. +1. The issue is just a question and has been answered. + +Team members may close an issue where the consensus is to not accept the issue after a waiting period (to ensure that other team members have a chance to review the issue before it is closed): + +* Wait **2 days** if the issue was opened Monday through Friday. +* Wait **3 days** if the issue was opened on Saturday or Sunday. + +In an effort to keep the issues backlog manageable, team members may also close an issue if the following conditions are met: + +* **Unaccepted**: Close after it has been open for 21 days, as these issues do not have enough support to move forward. +* **Accepted**: Close after 90 days if no one from the team or the community is willing to step forward and own the work to complete to it. +* **Help wanted:** Close after 90 days if it has not been completed. + diff --git a/eslint/docs/maintainer-guide/npm-2fa.md b/eslint/docs/maintainer-guide/npm-2fa.md new file mode 100644 index 0000000..e9a5177 --- /dev/null +++ b/eslint/docs/maintainer-guide/npm-2fa.md @@ -0,0 +1,16 @@ +# npm two-factor authentication + +The `eslint` npm account has two-factor authentication (2FA) enabled. The 2FA secret is distributed using a team on [Keybase](https://keybase.io). Anyone doing a release of a package from the Jenkins server needs to have access to the 2FA secret. + +If you're on ESLint's TSC, you should perform the following steps to obtain the 2FA secret: + +1. Download the [Keybase app](https://keybase.io/download) on a smartphone. +1. Open the app and create an account. +1. From the app, link your Keybase username with your GitHub username. (At the time of writing, the UI for this is to tap the face icon in the bottom-left of the app, then the profile picture in the top-right, then tap "Prove your GitHub" and follow the instructions.) +1. Mention your Keybase username in the team chatroom, and wait for someone to add you to the Keybase team. +1. Download an authenticator app like [Google Authenticator](https://support.google.com/accounts/answer/1066447) or [Authy](https://authy.com/), if you don't have one installed already. +1. In the Keybase app, navigate to the Keybase filesystem (at the time of writing, the UI for this is to tap the hamburger icon in the bottom-right, then tap "Files") and then navigate to `/team/eslint/auth`. + * If your authenticator app is downloaded on the same device as your Keybase app (this will usually be the case if you're using the Keybase mobile app), then open `npm_2fa_code.txt` and copy the contents to the clipboard. Open your authenticator app, and paste the contents as a new key (by selecting something like "Enter a provided key" or "Enter key manually"). + * If your authenticator app is downloaded on a *different* device from your Keybase app (e.g. if you're using a Keybase desktop app), then open `npm_2fa_code.png` and scan it as a QR code from your authenticator app. + +You should now be able to generate 6-digit 2FA codes for the `eslint` npm account using your authenticator app. diff --git a/eslint/docs/maintainer-guide/pullrequests.md b/eslint/docs/maintainer-guide/pullrequests.md new file mode 100644 index 0000000..2d3b13b --- /dev/null +++ b/eslint/docs/maintainer-guide/pullrequests.md @@ -0,0 +1,94 @@ +# Reviewing Pull Requests + +Pull requests are submitted frequently and represent our best opportunity to interact with the community. As such, it's important that pull requests are well-reviewed before being merged and that interactions on pull requests are positive. + +## Who Can Review Pull Requests? + +Anyone, both team members and the public, may leave comments on pull requests. + +## Reviewing a Pull Request + +When a pull request is opened, the bot will check the following: + +1. Has the submitter signed a CLA? +1. Is the commit message summary in the correct format? +1. Is the commit summary too long? + +The bot will add a comment specifying the problems that it finds. You do not need to look at the pull request any further until those problems have been addressed (there's no need to comment on the pull request to ask the submitter to do what the bot asked - that's why we have the bot!). + +Once the bot checks have been satisfied, you check the following: + +1. Double-check that the commit message tag ("Fix:", "New:", etc.) is correct based on the issue (or, if no issue is referenced, based on the stated problem). +1. If the pull request makes a change to core, ensure that an issue exists and the pull request references the issue in the commit message. +1. Does the code follow our conventions (including header comments, JSDoc comments, etc.)? If not, please leave that feedback and reference the conventions document. +1. For code changes: + * Are there tests that verify the change? If not, please ask for them. + * Is documentation needed for the change? If yes, please let the submitter know. +1. Are there any automated testing errors? If yes, please ask the submitter to check on them. +1. If you've reviewed the pull request and there are no outstanding issues, leave a comment "LGTM" to indicate your approval. If you would like someone else to verify the change, comment "LGTM but would like someone else to verify." + +**Note:** If you are a team member and you've left a comment on the pull request, please follow up to verify that your comments have been addressed. + +## Who Can Merge a Pull Request + +TSC members and committers may merge pull requests, depending on the contents of the pull request. + +Committers may merge a pull request if it is a non-breaking change and is: + +1. A documentation change +1. A bug fix (for either rules or core) +1. A dependency upgrade +1. Related to the build tool +1. A chore + +In addition, committers may merge any non-breaking pull request if it has been approved by at least one TSC member. + +TSC members may merge all pull requests, including those that committers may merge. + +## When to Merge a Pull Request + +We use the "Merge" button to merge requests into the repository. Before merging a pull request, verify that: + +1. All comments have been addressed +1. Any team members who made comments have verified that their concerns were addressed +1. All automated tests are passing (never merge a pull request with failing tests) + +Be sure to say thank you to the submitter before merging, especially if they put a lot of work into the pull request. + +Team members may merge a pull request immediately if it: + +1. Makes a small documentation change +1. Is a chore +1. Fixes a block of other work on the repository (build-related, test-related, dependency-related, etc.) +1. Is an important fix to get into a patch release + +Otherwise, team members should observe a waiting period before merging a pull request: + +* Wait **2 days** if the pull request was opened Monday through Friday. +* Wait **3 days** if the pull request was opened on Saturday or Sunday. + +The waiting period ensures that other team members have a chance to review the pull request before it is merged. + +If the pull request was created from a branch on the `eslint/eslint` repository (as opposed to a fork), delete the branch after merging the pull request. (GitHub will display a "Delete branch" button after the pull request is merged.) + +**Note:** You should not merge your own pull request unless you're received feedback from at least one other team member. + +## When to Close a Pull Request + +There are several times when it's appropriate to close a pull request without merging: + +1. The pull request addresses an issue that is already fixed +1. The pull request hasn't been updated in 30 days +1. The pull request submitter isn't willing to follow project guidelines. + +In any of these cases, please be sure to leave a comment stating why the pull request is being closed. + +### Example Closing Comments + +If a pull request hasn't been updated in 30 days: + +> Closing because there hasn't been activity for 30 days. If you're still interested in submitting this code, please feel free to resubmit. + +If a pull request submitter isn't willing to follow project guidelines. + +> Unfortunately, we can't accept pull requests that don't follow our guidelines. I'm going to close this pull request now, but if you'd like to resubmit following our guidelines, we'll be happy to review. diff --git a/eslint/docs/maintainer-guide/releases.md b/eslint/docs/maintainer-guide/releases.md new file mode 100644 index 0000000..94e1780 --- /dev/null +++ b/eslint/docs/maintainer-guide/releases.md @@ -0,0 +1,59 @@ +# Managing Releases + +Releases are when a project formally publishes a new version so the community can use it. There are two types of releases: + +* Regular releases that follow [semantic versioning](https://semver.org/) and are considered production-ready. +* Prereleases that are not considered production-ready and are intended to give the community a preview of upcoming changes. + +## Release Team + +A two-person release team is assigned to each scheduled release. This two-person team is responsible for: + +1. The scheduled release on Friday +1. Monitoring issues over the weekend +1. Determining if a patch release is necessary on Monday +1. Publishing the patch release (if necessary) + +The two-person team should seek input from the whole team on the Monday following a release to double-check if a patch release is necessary. + +At least one member of the release team needs to have access to [eslint's two-factor authentication for npm](./npm-2fa) in order to do a release. + +## Release Communication + +Each scheduled release should be associated with a release issue ([example](https://github.com/eslint/eslint/issues/8138)). The release issue is the source of information for the team about the status of a release. Be sure the release issue has the "release" label so that it's easy to find. + +## Process + +On the day of a scheduled release, the release team should follow these steps: + +1. Review open pull requests to see if any should be merged. In general, you can merge pull requests that: + * Have been open at least two days and have been reviewed (these are just waiting for merge). + * Important pull requests (as determined by the team). You should stop and have people review before merging if they haven't been already. + * Documentation changes. + * Small bugfixes written by a team member. +1. Log into Jenkins and schedule a build for the "ESLint Release" job. +1. Watch the console output of the build on Jenkins. At some point, the build will pause and a link will be produced with an input field for a six-digit 2FA code. +1. Enter the current six-digit 2FA code from your authenticator app. (Also see: [npm-2fa](./npm-2fa)) +1. Continue the build and wait for it to finish. +1. Update the release blog post with a "Highlights" section, including new rules and anything else that's important. +1. Make a release announcement in the public chatroom. +1. Make a release announcement on Twitter. +1. Make a release announcement on the release issue. Document any problems that occurred during the release, and remind the team not to merge anything other than documentation changes and bugfixes. Leave the release issue open. +1. Add the `patch release pending` label to the release issue. (When this label is present, `eslint-github-bot` will create a pending status check on non-semver-patch pull requests, to ensure that they aren't accidentally merged while a patch release is pending.) + +On the Monday following the scheduled release, the release team needs to determine if a patch release is necessary. A patch release is considered necessary if any of the following occurred since the scheduled release: + +* A regression bug is causing people's lint builds to fail when it previously passed. +* Any bug that is causing a lot of problems for users (frequently happens due to new functionality). + +The patch release decision should be made as early on Monday as possible. If a patch release is necessary, then follow the same steps as the scheduled release process. + +In rare cases, a second patch release might be necessary if the release is known to have a severe regression that hasn't been fixed by Monday. If this occurs, the release team should announce the situation on the release issue, and leave the issue open until all patch releases are complete. However, it's usually better to fix bugs for the next release cycle rather than doing a second patch release. + +After the patch release has been published (or no patch release is necessary), close the release issue and inform the team that they can start merging in semver-minor changes again. + +## Emergency Releases + +In general, we try not to do emergency releases (an emergency release is unplanned and isn't the regularly scheduled release or the anticipated patch release). Even if there is a regression, it's best to wait the weekend to see if any other problems arise so a patch release can fix as many issues as possible. + +The only real exception is if ESLint is completely unusable by most of the current users. For instance, we once pushed a release that errored for everyone because it was missing some core files. In that case, an emergency release is appropriate. diff --git a/eslint/docs/maintainer-guide/working-groups.md b/eslint/docs/maintainer-guide/working-groups.md new file mode 100644 index 0000000..d6d22fb --- /dev/null +++ b/eslint/docs/maintainer-guide/working-groups.md @@ -0,0 +1,22 @@ +# Working Groups + +The ESLint TSC may form working groups to focus on a specific area of the project. + +## Creating a Working Group + +Working groups are created by sending an email to the team mailing list. Each working group: + +1. Must have a GitHub team under the "ESLint Team" top-level GitHub team. (The GitHub team name should end with "WG" to distinguish it from other types of teams.) +1. Must have at least one TSC member. +1. May have any number of Committers and Reviewers. +1. Must have at least two members. + +Active working groups are listed on the [team page](https://eslint.org/team). + +## How Working Groups Work + +Each working group is responsible for its own inner working. Working groups can decide how large or small they should be (so long as there is at least two members), when and who to add or remove from the working group, and how to accomplish their objectives. + +Working groups may be temporary or permanent. + +If working groups intend to make a significant change to the ESLint project, the proposal must still be approved by the TSC. diff --git a/eslint/docs/rules/accessor-pairs.md b/eslint/docs/rules/accessor-pairs.md new file mode 100644 index 0000000..25028d9 --- /dev/null +++ b/eslint/docs/rules/accessor-pairs.md @@ -0,0 +1,289 @@ +# Enforces getter/setter pairs in objects and classes (accessor-pairs) + +It's a common mistake in JavaScript to create an object with just a setter for a property but never have a corresponding getter defined for it. Without a getter, you cannot read the property, so it ends up not being used. + +Here are some examples: + +```js +// Bad +var o = { + set a(value) { + this.val = value; + } +}; + +// Good +var o = { + set a(value) { + this.val = value; + }, + get a() { + return this.val; + } +}; + +``` + +This rule warns if setters are defined without getters. Using an option `getWithoutSet`, it will warn if you have a getter without a setter also. + +## Rule Details + +This rule enforces a style where it requires to have a getter for every property which has a setter defined. + +By activating the option `getWithoutSet` it enforces the presence of a setter for every property which has a getter defined. + +This rule always checks object literals and property descriptors. By default, it also checks class declarations and class expressions. + +## Options + +* `setWithoutGet` set to `true` will warn for setters without getters (Default `true`). +* `getWithoutSet` set to `true` will warn for getters without setters (Default `false`). +* `enforceForClassMembers` set to `true` additionally applies this rule to class getters/setters (Default `true`). Set `enforceForClassMembers` to `false` if you want this rule to ignore class declarations and class expressions. + +### setWithoutGet + +Examples of **incorrect** code for the default `{ "setWithoutGet": true }` option: + +```js +/*eslint accessor-pairs: "error"*/ + +var o = { + set a(value) { + this.val = value; + } +}; + +var o = {d: 1}; +Object.defineProperty(o, 'c', { + set: function(value) { + this.val = value; + } +}); +``` + +Examples of **correct** code for the default `{ "setWithoutGet": true }` option: + +```js +/*eslint accessor-pairs: "error"*/ + +var o = { + set a(value) { + this.val = value; + }, + get a() { + return this.val; + } +}; + +var o = {d: 1}; +Object.defineProperty(o, 'c', { + set: function(value) { + this.val = value; + }, + get: function() { + return this.val; + } +}); + +``` + +### getWithoutSet + +Examples of **incorrect** code for the `{ "getWithoutSet": true }` option: + +```js +/*eslint accessor-pairs: ["error", { "getWithoutSet": true }]*/ + +var o = { + set a(value) { + this.val = value; + } +}; + +var o = { + get a() { + return this.val; + } +}; + +var o = {d: 1}; +Object.defineProperty(o, 'c', { + set: function(value) { + this.val = value; + } +}); + +var o = {d: 1}; +Object.defineProperty(o, 'c', { + get: function() { + return this.val; + } +}); +``` + +Examples of **correct** code for the `{ "getWithoutSet": true }` option: + +```js +/*eslint accessor-pairs: ["error", { "getWithoutSet": true }]*/ +var o = { + set a(value) { + this.val = value; + }, + get a() { + return this.val; + } +}; + +var o = {d: 1}; +Object.defineProperty(o, 'c', { + set: function(value) { + this.val = value; + }, + get: function() { + return this.val; + } +}); + +``` + +### enforceForClassMembers + +When `enforceForClassMembers` is set to `true` (default): + +* `"getWithoutSet": true` will also warn for getters without setters in classes. +* `"setWithoutGet": true` will also warn for setters without getters in classes. + +Examples of **incorrect** code for `{ "getWithoutSet": true, "enforceForClassMembers": true }`: + +```js +/*eslint accessor-pairs: ["error", { "getWithoutSet": true, "enforceForClassMembers": true }]*/ + +class Foo { + get a() { + return this.val; + } +} + +class Bar { + static get a() { + return this.val; + } +} + +const Baz = class { + get a() { + return this.val; + } + static set a(value) { + this.val = value; + } +} +``` + +Examples of **incorrect** code for `{ "setWithoutGet": true, "enforceForClassMembers": true }`: + +```js +/*eslint accessor-pairs: ["error", { "setWithoutGet": true, "enforceForClassMembers": true }]*/ + +class Foo { + set a(value) { + this.val = value; + } +} + +const Bar = class { + static set a(value) { + this.val = value; + } +} +``` + +When `enforceForClassMembers` is set to `false`, this rule ignores classes. + +Examples of **correct** code for `{ "getWithoutSet": true, "setWithoutGet": true, "enforceForClassMembers": false }`: + +```js +/*eslint accessor-pairs: ["error", { + "getWithoutSet": true, "setWithoutGet": true, "enforceForClassMembers": false +}]*/ + +class Foo { + get a() { + return this.val; + } +} + +class Bar { + static set a(value) { + this.val = value; + } +} + +const Baz = class { + static get a() { + return this.val; + } +} + +const Quux = class { + set a(value) { + this.val = value; + } +} +``` + + +## Known Limitations + +Due to the limits of static analysis, this rule does not account for possible side effects and in certain cases +might not report a missing pair for a getter/setter that has a computed key, like in the following example: + +```js +/*eslint accessor-pairs: "error"*/ + +var a = 1; + +// no warnings +var o = { + get [a++]() { + return this.val; + }, + set [a++](value) { + this.val = value; + } +}; +``` + +Also, this rule does not disallow duplicate keys in object literals and class definitions, and in certain cases with duplicate keys +might not report a missing pair for a getter/setter, like in the following example: + +```js +/*eslint accessor-pairs: "error"*/ + +// no warnings +var o = { + get a() { + return this.val; + }, + a: 1, + set a(value) { + this.val = value; + } +}; +``` + +The code above creates an object with just a setter for the property `"a"`. + +See [no-dupe-keys](no-dupe-keys.md) if you also want to disallow duplicate keys in object literals. + +See [no-dupe-class-members](no-dupe-class-members.md) if you also want to disallow duplicate names in class definitions. + +## When Not To Use It + +You can turn this rule off if you are not concerned with the simultaneous presence of setters and getters on objects. + +## Further Reading + +* [Object Setters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set) +* [Object Getters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get) +* [Working with Objects](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects) diff --git a/eslint/docs/rules/array-bracket-newline.md b/eslint/docs/rules/array-bracket-newline.md new file mode 100644 index 0000000..eed3229 --- /dev/null +++ b/eslint/docs/rules/array-bracket-newline.md @@ -0,0 +1,281 @@ +# enforce line breaks after opening and before closing array brackets (array-bracket-newline) + +A number of style guides require or disallow line breaks inside of array brackets. + +## Rule Details + +This rule enforces line breaks after opening and before closing array brackets. + +## Options + +This rule has either a string option: + +* `"always"` requires line breaks inside brackets +* `"never"` disallows line breaks inside brackets +* `"consistent"` requires consistent usage of linebreaks for each pair of brackets. It reports an error if one bracket in the pair has a linebreak inside it and the other bracket does not. + +Or an object option (Requires line breaks if any of properties is satisfied. Otherwise, disallows line breaks): + +* `"multiline": true` (default) requires line breaks if there are line breaks inside elements or between elements. If this is false, this condition is disabled. +* `"minItems": null` (default) requires line breaks if the number of elements is at least the given integer. If this is 0, this condition will act the same as the option `"always"`. If this is `null` (the default), this condition is disabled. + +### always + +Examples of **incorrect** code for this rule with the `"always"` option: + +```js +/*eslint array-bracket-newline: ["error", "always"]*/ + +var a = []; +var b = [1]; +var c = [1, 2]; +var d = [1, + 2]; +var e = [function foo() { + dosomething(); +}]; +``` + +Examples of **correct** code for this rule with the `"always"` option: + +```js +/*eslint array-bracket-newline: ["error", "always"]*/ + +var a = [ +]; +var b = [ + 1 +]; +var c = [ + 1, 2 +]; +var d = [ + 1, + 2 +]; +var e = [ + function foo() { + dosomething(); + } +]; +``` + +### never + +Examples of **incorrect** code for this rule with the `"never"` option: + +```js +/*eslint array-bracket-newline: ["error", "never"]*/ + +var a = [ +]; +var b = [ + 1 +]; +var c = [ + 1, 2 +]; +var d = [ + 1, + 2 +]; +var e = [ + function foo() { + dosomething(); + } +]; +``` + +Examples of **correct** code for this rule with the `"never"` option: + +```js +/*eslint array-bracket-newline: ["error", "never"]*/ + +var a = []; +var b = [1]; +var c = [1, 2]; +var d = [1, + 2]; +var e = [function foo() { + dosomething(); +}]; +``` + +### consistent + +Examples of **incorrect** code for this rule with the `"consistent"` option: + +```js +/*eslint array-bracket-newline: ["error", "consistent"]*/ + +var a = [1 +]; +var b = [ + 1]; +var c = [function foo() { + dosomething(); +} +] +var d = [ + function foo() { + dosomething(); + }] +``` + +Examples of **correct** code for this rule with the `"consistent"` option: + +```js +/*eslint array-bracket-newline: ["error", "consistent"]*/ + +var a = []; +var b = [ +]; +var c = [1]; +var d = [ + 1 +]; +var e = [function foo() { + dosomething(); +}]; +var f = [ + function foo() { + dosomething(); + } +]; +``` + +### multiline + +Examples of **incorrect** code for this rule with the default `{ "multiline": true }` option: + +```js +/*eslint array-bracket-newline: ["error", { "multiline": true }]*/ + +var a = [ +]; +var b = [ + 1 +]; +var c = [ + 1, 2 +]; +var d = [1, + 2]; +var e = [function foo() { + dosomething(); +}]; +``` + +Examples of **correct** code for this rule with the default `{ "multiline": true }` option: + +```js +/*eslint array-bracket-newline: ["error", { "multiline": true }]*/ + +var a = []; +var b = [1]; +var c = [1, 2]; +var d = [ + 1, + 2 +]; +var e = [ + function foo() { + dosomething(); + } +]; +``` + +### minItems + +Examples of **incorrect** code for this rule with the `{ "minItems": 2 }` option: + +```js +/*eslint array-bracket-newline: ["error", { "minItems": 2 }]*/ + +var a = [ +]; +var b = [ + 1 +]; +var c = [1, 2]; +var d = [1, + 2]; +var e = [ + function foo() { + dosomething(); + } +]; +``` + +Examples of **correct** code for this rule with the `{ "minItems": 2 }` option: + +```js +/*eslint array-bracket-newline: ["error", { "minItems": 2 }]*/ + +var a = []; +var b = [1]; +var c = [ + 1, 2 +]; +var d = [ + 1, + 2 +]; +var e = [function foo() { + dosomething(); +}]; +``` + +### multiline and minItems + +Examples of **incorrect** code for this rule with the `{ "multiline": true, "minItems": 2 }` options: + +```js +/*eslint array-bracket-newline: ["error", { "multiline": true, "minItems": 2 }]*/ + +var a = [ +]; +var b = [ + 1 +]; +var c = [1, 2]; +var d = [1, + 2]; +var e = [function foo() { + dosomething(); +}]; +``` + +Examples of **correct** code for this rule with the `{ "multiline": true, "minItems": 2 }` options: + +```js +/*eslint array-bracket-newline: ["error", { "multiline": true, "minItems": 2 }]*/ + +var a = []; +var b = [1]; +var c = [ + 1, 2 +]; +var d = [ + 1, + 2 +]; +var e = [ + function foo() { + dosomething(); + } +]; +``` + + +## When Not To Use It + +If you don't want to enforce line breaks after opening and before closing array brackets, don't enable this rule. + +## Compatibility + +* **JSCS:** [validateNewlineAfterArrayElements](https://jscs-dev.github.io/rule/validateNewlineAfterArrayElements) + +## Related Rules + +* [array-bracket-spacing](array-bracket-spacing.md) diff --git a/eslint/docs/rules/array-bracket-spacing.md b/eslint/docs/rules/array-bracket-spacing.md new file mode 100644 index 0000000..a8c745f --- /dev/null +++ b/eslint/docs/rules/array-bracket-spacing.md @@ -0,0 +1,223 @@ +# Disallow or enforce spaces inside of brackets (array-bracket-spacing) + +A number of style guides require or disallow spaces between array brackets and other tokens. This rule +applies to both array literals and destructuring assignments (ECMAScript 6). + +```js +/*eslint-env es6*/ + +var arr = [ 'foo', 'bar' ]; +var [ x, y ] = z; + +var arr = ['foo', 'bar']; +var [x,y] = z; +``` + +## Rule Details + +This rule enforces consistent spacing inside array brackets. + +## Options + +This rule has a string option: + +* `"never"` (default) disallows spaces inside array brackets +* `"always"` requires one or more spaces or newlines inside array brackets + +This rule has an object option for exceptions to the `"never"` option: + +* `"singleValue": true` requires one or more spaces or newlines inside brackets of array literals that contain a single element +* `"objectsInArrays": true` requires one or more spaces or newlines between brackets of array literals and braces of their object literal elements `[ {` or `} ]` +* `"arraysInArrays": true` requires one or more spaces or newlines between brackets of array literals and brackets of their array literal elements `[ [` or `] ]` + +This rule has an object option for exceptions to the `"always"` option: + +* `"singleValue": false` disallows spaces inside brackets of array literals that contain a single element +* `"objectsInArrays": false` disallows spaces between brackets of array literals and braces of their object literal elements `[{` or `}]` +* `"arraysInArrays": false` disallows spaces between brackets of array literals and brackets of their array literal elements `[[` or `]]` + +This rule has built-in exceptions: + +* `"never"` (and also the exceptions to the `"always"` option) allows newlines inside array brackets, because this is a common pattern +* `"always"` does not require spaces or newlines in empty array literals `[]` + +### never + +Examples of **incorrect** code for this rule with the default `"never"` option: + +```js +/*eslint array-bracket-spacing: ["error", "never"]*/ +/*eslint-env es6*/ + +var arr = [ 'foo', 'bar' ]; +var arr = ['foo', 'bar' ]; +var arr = [ ['foo'], 'bar']; +var arr = [[ 'foo' ], 'bar']; +var arr = [ 'foo', + 'bar' +]; +var [ x, y ] = z; +var [ x,y ] = z; +var [ x, ...y ] = z; +var [ ,,x, ] = z; +``` + +Examples of **correct** code for this rule with the default `"never"` option: + +```js +/*eslint array-bracket-spacing: ["error", "never"]*/ +/*eslint-env es6*/ + +var arr = []; +var arr = ['foo', 'bar', 'baz']; +var arr = [['foo'], 'bar', 'baz']; +var arr = [ + 'foo', + 'bar', + 'baz' +]; +var arr = ['foo', + 'bar' +]; +var arr = [ + 'foo', + 'bar']; + +var [x, y] = z; +var [x,y] = z; +var [x, ...y] = z; +var [,,x,] = z; +``` + +### always + +Examples of **incorrect** code for this rule with the `"always"` option: + +```js +/*eslint array-bracket-spacing: ["error", "always"]*/ +/*eslint-env es6*/ + +var arr = ['foo', 'bar']; +var arr = ['foo', 'bar' ]; +var arr = [ ['foo'], 'bar' ]; +var arr = ['foo', + 'bar' +]; +var arr = [ + 'foo', + 'bar']; + +var [x, y] = z; +var [x,y] = z; +var [x, ...y] = z; +var [,,x,] = z; +``` + +Examples of **correct** code for this rule with the `"always"` option: + +```js +/*eslint array-bracket-spacing: ["error", "always"]*/ +/*eslint-env es6*/ + +var arr = []; +var arr = [ 'foo', 'bar', 'baz' ]; +var arr = [ [ 'foo' ], 'bar', 'baz' ]; +var arr = [ 'foo', + 'bar' +]; +var arr = [ + 'foo', + 'bar' ]; +var arr = [ + 'foo', + 'bar', + 'baz' +]; + +var [ x, y ] = z; +var [ x,y ] = z; +var [ x, ...y ] = z; +var [ ,,x, ] = z; +``` + +### singleValue + +Examples of **incorrect** code for this rule with the `"always", { "singleValue": false }` options: + +```js +/*eslint array-bracket-spacing: ["error", "always", { "singleValue": false }]*/ + +var foo = [ 'foo' ]; +var foo = [ 'foo']; +var foo = ['foo' ]; +var foo = [ 1 ]; +var foo = [ 1]; +var foo = [1 ]; +var foo = [ [ 1, 2 ] ]; +var foo = [ { 'foo': 'bar' } ]; +``` + +Examples of **correct** code for this rule with the `"always", { "singleValue": false }` options: + +```js +/*eslint array-bracket-spacing: ["error", "always", { "singleValue": false }]*/ + +var foo = ['foo']; +var foo = [1]; +var foo = [[ 1, 1 ]]; +var foo = [{ 'foo': 'bar' }]; +``` + +### objectsInArrays + +Examples of **incorrect** code for this rule with the `"always", { "objectsInArrays": false }` options: + +```js +/*eslint array-bracket-spacing: ["error", "always", { "objectsInArrays": false }]*/ + +var arr = [ { 'foo': 'bar' } ]; +var arr = [ { + 'foo': 'bar' +} ] +``` + +Examples of **correct** code for this rule with the `"always", { "objectsInArrays": false }` options: + +```js +/*eslint array-bracket-spacing: ["error", "always", { "objectsInArrays": false }]*/ + +var arr = [{ 'foo': 'bar' }]; +var arr = [{ + 'foo': 'bar' +}]; +``` + +### arraysInArrays + +Examples of **incorrect** code for this rule with the `"always", { "arraysInArrays": false }` options: + +```js +/*eslint array-bracket-spacing: ["error", "always", { "arraysInArrays": false }]*/ + +var arr = [ [ 1, 2 ], 2, 3, 4 ]; +var arr = [ [ 1, 2 ], 2, [ 3, 4 ] ]; +``` + +Examples of **correct** code for this rule with the `"always", { "arraysInArrays": false }` options: + +```js +/*eslint array-bracket-spacing: ["error", "always", { "arraysInArrays": false }]*/ + +var arr = [[ 1, 2 ], 2, 3, 4 ]; +var arr = [[ 1, 2 ], 2, [ 3, 4 ]]; +``` + +## When Not To Use It + +You can turn this rule off if you are not concerned with the consistency of spacing between array brackets. + +## Related Rules + +* [space-in-parens](space-in-parens.md) +* [object-curly-spacing](object-curly-spacing.md) +* [computed-property-spacing](computed-property-spacing.md) diff --git a/eslint/docs/rules/array-callback-return.md b/eslint/docs/rules/array-callback-return.md new file mode 100644 index 0000000..c0a5ad8 --- /dev/null +++ b/eslint/docs/rules/array-callback-return.md @@ -0,0 +1,154 @@ +# Enforces return statements in callbacks of array's methods (array-callback-return) + +`Array` has several methods for filtering, mapping, and folding. +If we forget to write `return` statement in a callback of those, it's probably a mistake. If you don't want to use a return or don't need the returned results, consider using [.forEach](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach) instead. + +```js +// example: convert ['a', 'b', 'c'] --> {a: 0, b: 1, c: 2} +var indexMap = myArray.reduce(function(memo, item, index) { + memo[item] = index; +}, {}); // Error: cannot set property 'b' of undefined +``` + +## Rule Details + +This rule enforces usage of `return` statement in callbacks of array's methods. +Additionaly, it may also enforce the `forEach` array method callback to __not__ return a value by using the `checkForEach` option. + +This rule finds callback functions of the following methods, then checks usage of `return` statement. + +* [`Array.from`](https://www.ecma-international.org/ecma-262/6.0/#sec-array.from) +* [`Array.prototype.every`](https://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.every) +* [`Array.prototype.filter`](https://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.filter) +* [`Array.prototype.find`](https://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.find) +* [`Array.prototype.findIndex`](https://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.findindex) +* [`Array.prototype.flatMap`](https://www.ecma-international.org/ecma-262/10.0/#sec-array.prototype.flatmap) +* [`Array.prototype.forEach`](https://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.foreach) (optional, based on `checkForEach` parameter) +* [`Array.prototype.map`](https://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.map) +* [`Array.prototype.reduce`](https://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.reduce) +* [`Array.prototype.reduceRight`](https://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.reduceright) +* [`Array.prototype.some`](https://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.some) +* [`Array.prototype.sort`](https://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.sort) +* And above of typed arrays. + +Examples of **incorrect** code for this rule: + +```js +/*eslint array-callback-return: "error"*/ + +var indexMap = myArray.reduce(function(memo, item, index) { + memo[item] = index; +}, {}); + +var foo = Array.from(nodes, function(node) { + if (node.tagName === "DIV") { + return true; + } +}); + +var bar = foo.filter(function(x) { + if (x) { + return true; + } else { + return; + } +}); +``` + +Examples of **correct** code for this rule: + +```js +/*eslint array-callback-return: "error"*/ + +var indexMap = myArray.reduce(function(memo, item, index) { + memo[item] = index; + return memo; +}, {}); + +var foo = Array.from(nodes, function(node) { + if (node.tagName === "DIV") { + return true; + } + return false; +}); + +var bar = foo.map(node => node.getAttribute("id")); +``` + +## Options + +This rule accepts a configuration object with two options: + +* `"allowImplicit": false` (default) When set to `true`, allows callbacks of methods that require a return value to implicitly return `undefined` with a `return` statement containing no expression. +* `"checkForEach": false` (default) When set to `true`, rule will also report `forEach` callbacks that return a value. + +### allowImplicit + +Examples of **correct** code for the `{ "allowImplicit": true }` option: + +```js +/*eslint array-callback-return: ["error", { allowImplicit: true }]*/ +var undefAllTheThings = myArray.map(function(item) { + return; +}); +``` + +### checkForEach + +Examples of **incorrect** code for the `{ "checkForEach": true }` option: + +```js +/*eslint array-callback-return: ["error", { checkForEach: true }]*/ + +myArray.forEach(function(item) { + return handleItem(item) +}); + +myArray.forEach(function(item) { + if (item < 0) { + return x; + } + handleItem(item); +}); + +myArray.forEach(item => handleItem(item)); + +myArray.forEach(item => { + return handleItem(item); +}); +``` + +Examples of **correct** code for the `{ "checkForEach": true }` option: + +```js +/*eslint array-callback-return: ["error", { checkForEach: true }]*/ + +myArray.forEach(function(item) { + handleItem(item) +}); + +myArray.forEach(function(item) { + if (item < 0) { + return; + } + handleItem(item); +}); + +myArray.forEach(function(item) { + handleItem(item); + return; +}); + +myArray.forEach(item => { + handleItem(item); +}); +``` + + +## Known Limitations + +This rule checks callback functions of methods with the given names, *even if* the object which has the method is *not* an array. + +## When Not To Use It + +If you don't want to warn about usage of `return` statement in callbacks of array's methods, then it's safe to disable this rule. diff --git a/eslint/docs/rules/array-element-newline.md b/eslint/docs/rules/array-element-newline.md new file mode 100644 index 0000000..4409905 --- /dev/null +++ b/eslint/docs/rules/array-element-newline.md @@ -0,0 +1,375 @@ +# enforce line breaks between array elements (array-element-newline) + +A number of style guides require or disallow line breaks between array elements. + +## Rule Details + +This rule enforces line breaks between array elements. + +## Options + +This rule has either a string option: + +* `"always"` (default) requires line breaks between array elements +* `"never"` disallows line breaks between array elements +* `"consistent"` requires consistent usage of linebreaks between array elements + +Or an object option (Requires line breaks if any of properties is satisfied. Otherwise, disallows line breaks): + +* `"multiline": ` requires line breaks if there are line breaks inside elements. If this is false, this condition is disabled. +* `"minItems": ` requires line breaks if the number of elements is at least the given integer. If this is 0, this condition will act the same as the option `"always"`. If this is `null` (the default), this condition is disabled. + +Alternatively, different configurations can be specified for array expressions and array patterns: + +```json +{ + "array-element-newline": ["error", { + "ArrayExpression": "consistent", + "ArrayPattern": { "minItems": 3 }, + }] +} +``` + +* `"ArrayExpression"` configuration for array expressions (if unspecified, this rule will not apply to array expressions) +* `"ArrayPattern"` configuration for array patterns of destructuring assignments (if unspecified, this rule will not apply to array patterns) + +### always + +Examples of **incorrect** code for this rule with the default `"always"` option: + +```js +/*eslint array-element-newline: ["error", "always"]*/ + +var c = [1, 2]; +var d = [1, 2, 3]; +var e = [ + function foo() { + dosomething(); + }, function bar() { + dosomething(); + } +]; +``` + +Examples of **correct** code for this rule with the default `"always"` option: + +```js +/*eslint array-element-newline: ["error", "always"]*/ + +var a = []; +var b = [1]; +var c = [1, + 2]; +var d = [1, + 2, + 3]; +var e = [ + function foo() { + dosomething(); + }, + function bar() { + dosomething(); + } +]; +``` + +### never + +Examples of **incorrect** code for this rule with the `"never"` option: + +```js +/*eslint array-element-newline: ["error", "never"]*/ + +var c = [ + 1, + 2 +]; +var d = [ + 1, + 2, + 3 +]; +var e = [ + function foo() { + dosomething(); + }, + function bar() { + dosomething(); + } +]; +``` + +Examples of **correct** code for this rule with the `"never"` option: + +```js +/*eslint array-element-newline: ["error", "never"]*/ + +var a = []; +var b = [1]; +var c = [1, 2]; +var d = [1, 2, 3]; +var e = [ + function foo() { + dosomething(); + }, function bar() { + dosomething(); + } +]; +``` + +### consistent + +Examples of **incorrect** code for this rule with the `"consistent"` option: + +```js +/*eslint array-element-newline: ["error", "consistent"]*/ + +var a = [ + 1, 2, + 3 +]; +var b = [ + function foo() { + dosomething(); + }, function bar() { + dosomething(); + }, + function baz() { + dosomething(); + } +]; +``` + +Examples of **correct** code for this rule with the `"consistent"` option: + +```js +/*eslint array-element-newline: ["error", "consistent"]*/ + +var a = []; +var b = [1]; +var c = [1, 2]; +var d = [1, 2, 3]; +var e = [ + 1, + 2 +]; +var f = [ + 1, + 2, + 3 +]; +var g = [ + function foo() { + dosomething(); + }, function bar() { + dosomething(); + }, function baz() { + dosomething(); + } +]; +var h = [ + function foo() { + dosomething(); + }, + function bar() { + dosomething(); + }, + function baz() { + dosomething(); + } +]; +``` + +### multiline + +Examples of **incorrect** code for this rule with the `{ "multiline": true }` option: + +```js +/*eslint array-element-newline: ["error", { "multiline": true }]*/ + +var d = [1, + 2, 3]; +var e = [ + function foo() { + dosomething(); + }, function bar() { + dosomething(); + } +]; +``` + +Examples of **correct** code for this rule with the `{ "multiline": true }` option: + +```js +/*eslint array-element-newline: ["error", { "multiline": true }]*/ + +var a = []; +var b = [1]; +var c = [1, 2]; +var d = [1, 2, 3]; +var e = [ + function foo() { + dosomething(); + }, + function bar() { + dosomething(); + } +]; +``` + +### minItems + +Examples of **incorrect** code for this rule with the `{ "minItems": 3 }` option: + +```js +/*eslint array-element-newline: ["error", { "minItems": 3 }]*/ + +var c = [1, + 2]; +var d = [1, 2, 3]; +var e = [ + function foo() { + dosomething(); + }, + function bar() { + dosomething(); + } +]; +``` + +Examples of **correct** code for this rule with the `{ "minItems": 3 }` option: + +```js +/*eslint array-element-newline: ["error", { "minItems": 3 }]*/ + +var a = []; +var b = [1]; +var c = [1, 2]; +var d = [1, + 2, + 3]; +var e = [ + function foo() { + dosomething(); + }, function bar() { + dosomething(); + } +]; +``` + +### multiline and minItems + +Examples of **incorrect** code for this rule with the `{ "multiline": true, "minItems": 3 }` options: + +```js +/*eslint array-element-newline: ["error", { "multiline": true, "minItems": 3 }]*/ + +var c = [1, +2]; +var d = [1, 2, 3]; +var e = [ + function foo() { + dosomething(); + }, function bar() { + dosomething(); + } +]; +``` + +Examples of **correct** code for this rule with the `{ "multiline": true, "minItems": 3 }` options: + +```js +/*eslint array-element-newline: ["error", { "multiline": true, "minItems": 3 }]*/ + +var a = []; +var b = [1]; +var c = [1, 2]; +var d = [1, + 2, + 3]; +var e = [ + function foo() { + dosomething(); + }, + function bar() { + dosomething(); + } +]; +``` + +### ArrayExpression and ArrayPattern + +Examples of **incorrect** code for this rule with the `{ "ArrayExpression": "always", "ArrayPattern": "never" }` options: + +```js +/*eslint array-element-newline: ["error", { "ArrayExpression": "always", "ArrayPattern": "never" }]*/ + +var a = [1, 2]; +var b = [1, 2, 3]; +var c = [ + function foo() { + dosomething(); + }, function bar() { + dosomething(); + } +]; + +var [d, + e] = arr; +var [f, + g, + h] = arr; +var [i = function foo() { + dosomething() +}, +j = function bar() { + dosomething() +}] = arr +``` + +Examples of **correct** code for this rule with the `{ "ArrayExpression": "always", "ArrayPattern": "never" }` options: + +```js +/*eslint object-curly-newline: ["error", { "ArrayExpression": "always", "ArrayPattern": "never" }]*/ + +var a = [1, + 2]; +var b = [1, + 2, + 3]; +var c = [ + function foo() { + dosomething(); + }, + function bar() { + dosomething(); + } +]; + +var [d, e] = arr +var [f, g, h] = arr +var [i = function foo() { + dosomething() +}, j = function bar() { + dosomething() +}] = arr +``` + +## When Not To Use It + +If you don't want to enforce linebreaks between array elements, don't enable this rule. + +## Compatibility + +* **JSCS:** [validateNewlineAfterArrayElements](https://jscs-dev.github.io/rule/validateNewlineAfterArrayElements) + +## Related Rules + +* [array-bracket-spacing](array-bracket-spacing.md) +* [array-bracket-newline](array-bracket-newline.md) +* [object-property-newline](object-property-newline.md) +* [object-curly-spacing](object-curly-spacing.md) +* [object-curly-newline](object-curly-newline.md) +* [max-statements-per-line](max-statements-per-line.md) +* [block-spacing](block-spacing.md) +* [brace-style](brace-style.md) diff --git a/eslint/docs/rules/arrow-body-style.md b/eslint/docs/rules/arrow-body-style.md new file mode 100644 index 0000000..31c0cfe --- /dev/null +++ b/eslint/docs/rules/arrow-body-style.md @@ -0,0 +1,140 @@ +# Require braces in arrow function body (arrow-body-style) + +Arrow functions have two syntactic forms for their function bodies. They may be defined with a *block* body (denoted by curly braces) `() => { ... }` or with a single expression `() => ...`, whose value is implicitly returned. + +## Rule Details + +This rule can enforce or disallow the use of braces around arrow function body. + +## Options + +The rule takes one or two options. The first is a string, which can be: + +* `"always"` enforces braces around the function body +* `"as-needed"` enforces no braces where they can be omitted (default) +* `"never"` enforces no braces around the function body (constrains arrow functions to the role of returning an expression) + +The second one is an object for more fine-grained configuration when the first option is `"as-needed"`. Currently, the only available option is `requireReturnForObjectLiteral`, a boolean property. It's `false` by default. If set to `true`, it requires braces and an explicit return for object literals. + +```json +"arrow-body-style": ["error", "always"] +``` + +### always + +Examples of **incorrect** code for this rule with the `"always"` option: + +```js +/*eslint arrow-body-style: ["error", "always"]*/ +/*eslint-env es6*/ +let foo = () => 0; +``` + +Examples of **correct** code for this rule with the `"always"` option: + +```js +let foo = () => { + return 0; +}; +let foo = (retv, name) => { + retv[name] = true; + return retv; +}; +``` + +### as-needed + +Examples of **incorrect** code for this rule with the default `"as-needed"` option: + +```js +/*eslint arrow-body-style: ["error", "as-needed"]*/ +/*eslint-env es6*/ + +let foo = () => { + return 0; +}; +let foo = () => { + return { + bar: { + foo: 1, + bar: 2, + } + }; +}; +``` + +Examples of **correct** code for this rule with the default `"as-needed"` option: + +```js +/*eslint arrow-body-style: ["error", "as-needed"]*/ +/*eslint-env es6*/ + +let foo = () => 0; +let foo = (retv, name) => { + retv[name] = true; + return retv; +}; +let foo = () => ({ + bar: { + foo: 1, + bar: 2, + } +}); +let foo = () => { bar(); }; +let foo = () => {}; +let foo = () => { /* do nothing */ }; +let foo = () => { + // do nothing. +}; +let foo = () => ({ bar: 0 }); +``` + +#### requireReturnForObjectLiteral + +> This option is only applicable when used in conjunction with the `"as-needed"` option. + +Examples of **incorrect** code for this rule with the `{ "requireReturnForObjectLiteral": true }` option: + +```js +/*eslint arrow-body-style: ["error", "as-needed", { "requireReturnForObjectLiteral": true }]*/ +/*eslint-env es6*/ +let foo = () => ({}); +let foo = () => ({ bar: 0 }); +``` + +Examples of **correct** code for this rule with the `{ "requireReturnForObjectLiteral": true }` option: + +```js +/*eslint arrow-body-style: ["error", "as-needed", { "requireReturnForObjectLiteral": true }]*/ +/*eslint-env es6*/ + +let foo = () => {}; +let foo = () => { return { bar: 0 }; }; +``` + +### never + +Examples of **incorrect** code for this rule with the `"never"` option: + +```js +/*eslint arrow-body-style: ["error", "never"]*/ +/*eslint-env es6*/ + +let foo = () => { + return 0; +}; +let foo = (retv, name) => { + retv[name] = true; + return retv; +}; +``` + +Examples of **correct** code for this rule with the `"never"` option: + +```js +/*eslint arrow-body-style: ["error", "never"]*/ +/*eslint-env es6*/ + +let foo = () => 0; +let foo = () => ({ foo: 0 }); +``` diff --git a/eslint/docs/rules/arrow-parens.md b/eslint/docs/rules/arrow-parens.md new file mode 100644 index 0000000..fbcdbae --- /dev/null +++ b/eslint/docs/rules/arrow-parens.md @@ -0,0 +1,223 @@ +# Require parens in arrow function arguments (arrow-parens) + +Arrow functions can omit parentheses when they have exactly one parameter. In all other cases the parameter(s) must +be wrapped in parentheses. This rule enforces the consistent use of parentheses in arrow functions. + +## Rule Details + +This rule enforces parentheses around arrow function parameters regardless of arity. For example: + +```js +/*eslint-env es6*/ + +// Bad +a => {} + +// Good +(a) => {} +``` + +Following this style will help you find arrow functions (`=>`) which may be mistakenly included in a condition +when a comparison such as `>=` was the intent. + + +```js +/*eslint-env es6*/ + +// Bad +if (a => 2) { +} + +// Good +if (a >= 2) { +} +``` + +The rule can also be configured to discourage the use of parens when they are not required: + +```js +/*eslint-env es6*/ + +// Bad +(a) => {} + +// Good +a => {} +``` + +## Options + +This rule has a string option and an object one. + +String options are: + +* `"always"` (default) requires parens around arguments in all cases. +* `"as-needed"` enforces no braces where they can be omitted. + +Object properties for variants of the `"as-needed"` option: + +* `"requireForBlockBody": true` modifies the as-needed rule in order to require parens if the function body is in an instructions block (surrounded by braces). + +### always + +Examples of **incorrect** code for this rule with the default `"always"` option: + +```js +/*eslint arrow-parens: ["error", "always"]*/ +/*eslint-env es6*/ + +a => {}; +a => a; +a => {'\n'}; +a.then(foo => {}); +a.then(foo => a); +a(foo => { if (true) {} }); +``` + +Examples of **correct** code for this rule with the default `"always"` option: + +```js +/*eslint arrow-parens: ["error", "always"]*/ +/*eslint-env es6*/ + +() => {}; +(a) => {}; +(a) => a; +(a) => {'\n'} +a.then((foo) => {}); +a.then((foo) => { if (true) {} }); +``` + +#### If Statements + +One of the benefits of this option is that it prevents the incorrect use of arrow functions in conditionals: + +```js +/*eslint-env es6*/ + +var a = 1; +var b = 2; +// ... +if (a => b) { + console.log('bigger'); +} else { + console.log('smaller'); +} +// outputs 'bigger', not smaller as expected +``` + +The contents of the `if` statement is an arrow function, not a comparison. + +If the arrow function is intentional, it should be wrapped in parens to remove ambiguity. + +```js +/*eslint-env es6*/ + +var a = 1; +var b = 0; +// ... +if ((a) => b) { + console.log('truthy value returned'); +} else { + console.log('falsey value returned'); +} +// outputs 'truthy value returned' +``` + +The following is another example of this behavior: + +```js +/*eslint-env es6*/ + +var a = 1, b = 2, c = 3, d = 4; +var f = a => b ? c: d; +// f = ? +``` + +`f` is an arrow function which takes `a` as an argument and returns the result of `b ? c: d`. + +This should be rewritten like so: + +```js +/*eslint-env es6*/ + +var a = 1, b = 2, c = 3, d = 4; +var f = (a) => b ? c: d; +``` + +### as-needed + +Examples of **incorrect** code for this rule with the `"as-needed"` option: + +```js +/*eslint arrow-parens: ["error", "as-needed"]*/ +/*eslint-env es6*/ + +(a) => {}; +(a) => a; +(a) => {'\n'}; +a.then((foo) => {}); +a.then((foo) => a); +a((foo) => { if (true) {} }); +``` + +Examples of **correct** code for this rule with the `"as-needed"` option: + +```js +/*eslint arrow-parens: ["error", "as-needed"]*/ +/*eslint-env es6*/ + +() => {}; +a => {}; +a => a; +a => {'\n'}; +a.then(foo => {}); +a.then(foo => { if (true) {} }); +(a, b, c) => a; +(a = 10) => a; +([a, b]) => a; +({a, b}) => a; +``` + +### requireForBlockBody + +Examples of **incorrect** code for the `{ "requireForBlockBody": true }` option: + +```js +/*eslint arrow-parens: [2, "as-needed", { "requireForBlockBody": true }]*/ +/*eslint-env es6*/ + +(a) => a; +a => {}; +a => {'\n'}; +a.map((x) => x * x); +a.map(x => { + return x * x; +}); +a.then(foo => {}); +``` + +Examples of **correct** code for the `{ "requireForBlockBody": true }` option: + +```js +/*eslint arrow-parens: [2, "as-needed", { "requireForBlockBody": true }]*/ +/*eslint-env es6*/ + +(a) => {}; +(a) => {'\n'}; +a => ({}); +() => {}; +a => a; +a.then((foo) => {}); +a.then((foo) => { if (true) {} }); +a((foo) => { if (true) {} }); +(a, b, c) => a; +(a = 10) => a; +([a, b]) => a; +({a, b}) => a; +``` + +## Further Reading + +* The `"as-needed", { "requireForBlockBody": true }` rule is directly inspired by the Airbnb + [JS Style Guide](https://github.com/airbnb/javascript#arrows--one-arg-parens). diff --git a/eslint/docs/rules/arrow-spacing.md b/eslint/docs/rules/arrow-spacing.md new file mode 100644 index 0000000..d0cea1b --- /dev/null +++ b/eslint/docs/rules/arrow-spacing.md @@ -0,0 +1,93 @@ +# Require space before/after arrow function's arrow (arrow-spacing) + +This rule normalize style of spacing before/after an arrow function's arrow(`=>`). + +```js +/*eslint-env es6*/ + +// { "before": true, "after": true } +(a) => {} + +// { "before": false, "after": false } +(a)=>{} +``` + +## Rule Details + +This rule takes an object argument with `before` and `after` properties, each with a Boolean value. + +The default configuration is `{ "before": true, "after": true }`. + +`true` means there should be **one or more spaces** and `false` means **no spaces**. + +Examples of **incorrect** code for this rule with the default `{ "before": true, "after": true }` option: + +```js +/*eslint arrow-spacing: "error"*/ +/*eslint-env es6*/ + +()=> {}; +() =>{}; +(a)=> {}; +(a) =>{}; +a =>a; +a=> a; +()=> {'\n'}; +() =>{'\n'}; +``` + +Examples of **correct** code for this rule with the default `{ "before": true, "after": true }` option: + +```js +/*eslint arrow-spacing: "error"*/ +/*eslint-env es6*/ + +() => {}; +(a) => {}; +a => a; +() => {'\n'}; +``` + +Examples of **incorrect** code for this rule with the `{ "before": false, "after": false }` option: + +```js +/*eslint arrow-spacing: ["error", { "before": false, "after": false }]*/ +/*eslint-env es6*/ + +() =>{}; +(a) => {}; +()=> {'\n'}; +``` + +Examples of **correct** code for this rule with the `{ "before": false, "after": false }` option: + +```js +/*eslint arrow-spacing: ["error", { "before": false, "after": false }]*/ +/*eslint-env es6*/ + +()=>{}; +(a)=>{}; +()=>{'\n'}; +``` + +Examples of **incorrect** code for this rule with the `{ "before": false, "after": true }` option: + +```js +/*eslint arrow-spacing: ["error", { "before": false, "after": true }]*/ +/*eslint-env es6*/ + +() =>{}; +(a) => {}; +()=>{'\n'}; +``` + +Examples of **correct** code for this rule with the `{ "before": false, "after": true }` option: + +```js +/*eslint arrow-spacing: ["error", { "before": false, "after": true }]*/ +/*eslint-env es6*/ + +()=> {}; +(a)=> {}; +()=> {'\n'}; +``` diff --git a/eslint/docs/rules/block-scoped-var.md b/eslint/docs/rules/block-scoped-var.md new file mode 100644 index 0000000..97be55b --- /dev/null +++ b/eslint/docs/rules/block-scoped-var.md @@ -0,0 +1,79 @@ +# Treat var as Block Scoped (block-scoped-var) + +The `block-scoped-var` rule generates warnings when variables are used outside of the block in which they were defined. This emulates C-style block scope. + +## Rule Details + +This rule aims to reduce the usage of variables outside of their binding context and emulate traditional block scope from other languages. This is to help newcomers to the language avoid difficult bugs with variable hoisting. + +Examples of **incorrect** code for this rule: + +```js +/*eslint block-scoped-var: "error"*/ + +function doIf() { + if (true) { + var build = true; + } + + console.log(build); +} + +function doIfElse() { + if (true) { + var build = true; + } else { + var build = false; + } +} + +function doTryCatch() { + try { + var build = 1; + } catch (e) { + var f = build; + } +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint block-scoped-var: "error"*/ + +function doIf() { + var build; + + if (true) { + build = true; + } + + console.log(build); +} + +function doIfElse() { + var build; + + if (true) { + build = true; + } else { + build = false; + } +} + +function doTryCatch() { + var build; + var f; + + try { + build = 1; + } catch (e) { + f = build; + } +} +``` + +## Further Reading + +* [JavaScript Scoping and Hoisting](http://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html) +* [var Hoisting](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var#var_hoisting) diff --git a/eslint/docs/rules/block-spacing.md b/eslint/docs/rules/block-spacing.md new file mode 100644 index 0000000..aef7c33 --- /dev/null +++ b/eslint/docs/rules/block-spacing.md @@ -0,0 +1,59 @@ +# Disallow or enforce spaces inside of blocks after opening block and before closing block (block-spacing) + +## Rule Details + +This rule enforces consistent spacing inside an open block token and the next token on the same line. This rule also enforces consistent spacing inside a close block token and previous token on the same line. + +## Options + +This rule has a string option: + +* `"always"` (default) requires one or more spaces +* `"never"` disallows spaces + +### always + +Examples of **incorrect** code for this rule with the default `"always"` option: + +```js +/*eslint block-spacing: "error"*/ + +function foo() {return true;} +if (foo) { bar = 0;} +function baz() {let i = 0; + return i; +} +``` + +Examples of **correct** code for this rule with the default `"always"` option: + +```js +/*eslint block-spacing: "error"*/ + +function foo() { return true; } +if (foo) { bar = 0; } +``` + +### never + +Examples of **incorrect** code for this rule with the `"never"` option: + +```js +/*eslint block-spacing: ["error", "never"]*/ + +function foo() { return true; } +if (foo) { bar = 0;} +``` + +Examples of **correct** code for this rule with the `"never"` option: + +```js +/*eslint block-spacing: ["error", "never"]*/ + +function foo() {return true;} +if (foo) {bar = 0;} +``` + +## When Not To Use It + +If you don't want to be notified about spacing style inside of blocks, you can safely disable this rule. diff --git a/eslint/docs/rules/brace-style.md b/eslint/docs/rules/brace-style.md new file mode 100644 index 0000000..1a5bf00 --- /dev/null +++ b/eslint/docs/rules/brace-style.md @@ -0,0 +1,325 @@ +# Require Brace Style (brace-style) + +Brace style is closely related to [indent style](https://en.wikipedia.org/wiki/Indent_style) in programming and describes the placement of braces relative to their control statement and body. There are probably a dozen, if not more, brace styles in the world. + +The *one true brace style* is one of the most common brace styles in JavaScript, in which the opening brace of a block is placed on the same line as its corresponding statement or declaration. For example: + +```js +if (foo) { + bar(); +} else { + baz(); +} +``` + +One common variant of one true brace style is called Stroustrup, in which the `else` statements in an `if-else` construct, as well as `catch` and `finally`, must be on its own line after the preceding closing brace. For example: + +```js +if (foo) { + bar(); +} +else { + baz(); +} +``` + +Another style is called [Allman](https://en.wikipedia.org/wiki/Indent_style#Allman_style), in which all the braces are expected to be on their own lines without any extra indentation. For example: + +```js +if (foo) +{ + bar(); +} +else +{ + baz(); +} +``` + +While no style is considered better than the other, most developers agree that having a consistent style throughout a project is important for its long-term maintainability. + +## Rule Details + +This rule enforces consistent brace style for blocks. + +## Options + +This rule has a string option: + +* `"1tbs"` (default) enforces one true brace style +* `"stroustrup"` enforces Stroustrup style +* `"allman"` enforces Allman style + +This rule has an object option for an exception: + +* `"allowSingleLine": true` (default `false`) allows the opening and closing braces for a block to be on the *same* line + +### 1tbs + +Examples of **incorrect** code for this rule with the default `"1tbs"` option: + +```js +/*eslint brace-style: "error"*/ + +function foo() +{ + return true; +} + +if (foo) +{ + bar(); +} + +try +{ + somethingRisky(); +} catch(e) +{ + handleError(); +} + +if (foo) { + bar(); +} +else { + baz(); +} +``` + +Examples of **correct** code for this rule with the default `"1tbs"` option: + +```js +/*eslint brace-style: "error"*/ + +function foo() { + return true; +} + +if (foo) { + bar(); +} + +if (foo) { + bar(); +} else { + baz(); +} + +try { + somethingRisky(); +} catch(e) { + handleError(); +} + +// when there are no braces, there are no problems +if (foo) bar(); +else if (baz) boom(); +``` + +Examples of **correct** code for this rule with the `"1tbs", { "allowSingleLine": true }` options: + +```js +/*eslint brace-style: ["error", "1tbs", { "allowSingleLine": true }]*/ + +function nop() { return; } + +if (foo) { bar(); } + +if (foo) { bar(); } else { baz(); } + +try { somethingRisky(); } catch(e) { handleError(); } + +if (foo) { baz(); } else { + boom(); +} + +if (foo) { baz(); } else if (bar) { + boom(); +} + +if (foo) { baz(); } else +if (bar) { + boom(); +} + +if (foo) { baz(); } else if (bar) { + boom(); +} + +try { somethingRisky(); } catch(e) { + handleError(); +} +``` + +### stroustrup + +Examples of **incorrect** code for this rule with the `"stroustrup"` option: + +```js +/*eslint brace-style: ["error", "stroustrup"]*/ + +function foo() +{ + return true; +} + +if (foo) +{ + bar(); +} + +try +{ + somethingRisky(); +} catch(e) +{ + handleError(); +} + +if (foo) { + bar(); +} else { + baz(); +} +``` + +Examples of **correct** code for this rule with the `"stroustrup"` option: + +```js +/*eslint brace-style: ["error", "stroustrup"]*/ + +function foo() { + return true; +} + +if (foo) { + bar(); +} + +if (foo) { + bar(); +} +else { + baz(); +} + +try { + somethingRisky(); +} +catch(e) { + handleError(); +} + +// when there are no braces, there are no problems +if (foo) bar(); +else if (baz) boom(); +``` + +Examples of **correct** code for this rule with the `"stroustrup", { "allowSingleLine": true }` options: + +```js +/*eslint brace-style: ["error", "stroustrup", { "allowSingleLine": true }]*/ + +function nop() { return; } + +if (foo) { bar(); } + +if (foo) { bar(); } +else { baz(); } + +try { somethingRisky(); } +catch(e) { handleError(); } +``` + +### allman + +Examples of **incorrect** code for this rule with the `"allman"` option: + +```js +/*eslint brace-style: ["error", "allman"]*/ + +function foo() { + return true; +} + +if (foo) +{ + bar(); } + +try +{ + somethingRisky(); +} catch(e) +{ + handleError(); +} + +if (foo) { + bar(); +} else { + baz(); +} +``` + +Examples of **correct** code for this rule with the `"allman"` option: + +```js +/*eslint brace-style: ["error", "allman"]*/ + +function foo() +{ + return true; +} + +if (foo) +{ + bar(); +} + +if (foo) +{ + bar(); +} +else +{ + baz(); +} + +try +{ + somethingRisky(); +} +catch(e) +{ + handleError(); +} + +// when there are no braces, there are no problems +if (foo) bar(); +else if (baz) boom(); +``` + +Examples of **correct** code for this rule with the `"allman", { "allowSingleLine": true }` options: + +```js +/*eslint brace-style: ["error", "allman", { "allowSingleLine": true }]*/ + +function nop() { return; } + +if (foo) { bar(); } + +if (foo) { bar(); } +else { baz(); } + +try { somethingRisky(); } +catch(e) { handleError(); } +``` + +## When Not To Use It + +If you don't want to enforce a particular brace style, don't enable this rule. + +## Further Reading + +* [Indent style](https://en.wikipedia.org/wiki/Indent_style) diff --git a/eslint/docs/rules/callback-return.md b/eslint/docs/rules/callback-return.md new file mode 100644 index 0000000..ba27e23 --- /dev/null +++ b/eslint/docs/rules/callback-return.md @@ -0,0 +1,174 @@ +# Enforce Return After Callback (callback-return) + +The callback pattern is at the heart of most I/O and event-driven programming + in JavaScript. + +```js +function doSomething(err, callback) { + if (err) { + return callback(err); + } + callback(); +} +``` + +To prevent calling the callback multiple times it is important to `return` anytime the callback is triggered outside + of the main function body. Neglecting this technique often leads to issues where you do something more than once. + For example, in the case of an HTTP request, you may try to send HTTP headers more than once leading Node.js to `throw` + a `Can't render headers after they are sent to the client.` error. + +## Rule Details + +This rule is aimed at ensuring that callbacks used outside of the main function block are always part-of or immediately +preceding a `return` statement. This rule decides what is a callback based on the name of the function being called. + +## Options + +The rule takes a single option - an array of possible callback names - which may include object methods. The default callback names are `callback`, `cb`, `next`. + +### Default callback names + +Examples of **incorrect** code for this rule with the default `["callback", "cb", "next"]` option: + +```js +/*eslint callback-return: "error"*/ + +function foo(err, callback) { + if (err) { + callback(err); + } + callback(); +} +``` + +Examples of **correct** code for this rule with the default `["callback", "cb", "next"]` option: + +```js +/*eslint callback-return: "error"*/ + +function foo(err, callback) { + if (err) { + return callback(err); + } + callback(); +} +``` + +### Supplied callback names + +Examples of **incorrect** code for this rule with the option `["done", "send.error", "send.success"]`: + +```js +/*eslint callback-return: ["error", ["done", "send.error", "send.success"]]*/ + +function foo(err, done) { + if (err) { + done(err); + } + done(); +} + +function bar(err, send) { + if (err) { + send.error(err); + } + send.success(); +} +``` + +Examples of **correct** code for this rule with the option `["done", "send.error", "send.success"]`: + +```js +/*eslint callback-return: ["error", ["done", "send.error", "send.success"]]*/ + +function foo(err, done) { + if (err) { + return done(err); + } + done(); +} + +function bar(err, send) { + if (err) { + return send.error(err); + } + send.success(); +} +``` + +## Known Limitations + +Because it is difficult to understand the meaning of a program through static analysis, this rule has limitations: + +* *false negatives* when this rule reports correct code, but the program calls the callback more than one time (which is incorrect behavior) +* *false positives* when this rule reports incorrect code, but the program calls the callback only one time (which is correct behavior) + +### Passing the callback by reference + +The static analysis of this rule does not detect that the program calls the callback if it is an argument of a function (for example, `setTimeout`). + +Example of a *false negative* when this rule reports correct code: + +```js +/*eslint callback-return: "error"*/ + +function foo(err, callback) { + if (err) { + setTimeout(callback, 0); // this is bad, but WILL NOT warn + } + callback(); +} +``` + +### Triggering the callback within a nested function + +The static analysis of this rule does not detect that the program calls the callback from within a nested function or an immediately-invoked function expression (IIFE). + +Example of a *false negative* when this rule reports correct code: + +```js +/*eslint callback-return: "error"*/ + +function foo(err, callback) { + if (err) { + process.nextTick(function() { + return callback(); // this is bad, but WILL NOT warn + }); + } + callback(); +} +``` + +### If/else statements + +The static analysis of this rule does not detect that the program calls the callback only one time in each branch of an `if` statement. + +Example of a *false positive* when this rule reports incorrect code: + +```js +/*eslint callback-return: "error"*/ + +function foo(err, callback) { + if (err) { + callback(err); // this is fine, but WILL warn + } else { + callback(); // this is fine, but WILL warn + } +} +``` + +## When Not To Use It + +There are some cases where you might want to call a callback function more than once. In those cases this rule + may lead to incorrect behavior. In those cases you may want to reserve a special name for those callbacks and + not include that in the list of callbacks that trigger warnings. + + +## Further Reading + +* [The Art Of Node: Callbacks](https://github.com/maxogden/art-of-node#callbacks) +* [Nodejitsu: What are the error conventions?](https://docs.nodejitsu.com/articles/errors/what-are-the-error-conventions/) + +## Related Rules + +* [handle-callback-err](handle-callback-err.md) diff --git a/eslint/docs/rules/camelcase.md b/eslint/docs/rules/camelcase.md new file mode 100644 index 0000000..96135f8 --- /dev/null +++ b/eslint/docs/rules/camelcase.md @@ -0,0 +1,246 @@ +# Require CamelCase (camelcase) + +When it comes to naming variables, style guides generally fall into one of two camps: camelcase (`variableName`) and underscores (`variable_name`). This rule focuses on using the camelcase approach. If your style guide calls for camelCasing your variable names, then this rule is for you! + +## Rule Details + +This rule looks for any underscores (`_`) located within the source code. It ignores leading and trailing underscores and only checks those in the middle of a variable name. If ESLint decides that the variable is a constant (all uppercase), then no warning will be thrown. Otherwise, a warning will be thrown. This rule only flags definitions and assignments but not function calls. In case of ES6 `import` statements, this rule only targets the name of the variable that will be imported into the local module scope. + +## Options + +This rule has an object option: + +* `"properties": "always"` (default) enforces camelcase style for property names +* `"properties": "never"` does not check property names +* `"ignoreDestructuring": false` (default) enforces camelcase style for destructured identifiers +* `"ignoreDestructuring": true` does not check destructured identifiers (but still checks any use of those identifiers later in the code) +* `"ignoreImports": false` (default) enforces camelcase style for ES2015 imports +* `"ignoreImports": true` does not check ES2015 imports (but still checks any use of the imports later in the code except function arguments) +* `allow` (`string[]`) list of properties to accept. Accept regex. + +### properties: "always" + +Examples of **incorrect** code for this rule with the default `{ "properties": "always" }` option: + +```js +/*eslint camelcase: "error"*/ + +import { no_camelcased } from "external-module" + +var my_favorite_color = "#112C85"; + +function do_something() { + // ... +} + +obj.do_something = function() { + // ... +}; + +function foo({ no_camelcased }) { + // ... +}; + +function foo({ isCamelcased: no_camelcased }) { + // ... +} + +function foo({ no_camelcased = 'default value' }) { + // ... +}; + +var obj = { + my_pref: 1 +}; + +var { category_id = 1 } = query; + +var { foo: no_camelcased } = bar; + +var { foo: bar_baz = 1 } = quz; +``` + +Examples of **correct** code for this rule with the default `{ "properties": "always" }` option: + +```js +/*eslint camelcase: "error"*/ + +import { no_camelcased as camelCased } from "external-module"; + +var myFavoriteColor = "#112C85"; +var _myFavoriteColor = "#112C85"; +var myFavoriteColor_ = "#112C85"; +var MY_FAVORITE_COLOR = "#112C85"; +var foo = bar.baz_boom; +var foo = { qux: bar.baz_boom }; + +obj.do_something(); +do_something(); +new do_something(); + +var { category_id: category } = query; + +function foo({ isCamelCased }) { + // ... +}; + +function foo({ isCamelCased: isAlsoCamelCased }) { + // ... +} + +function foo({ isCamelCased = 'default value' }) { + // ... +}; + +var { categoryId = 1 } = query; + +var { foo: isCamelCased } = bar; + +var { foo: isCamelCased = 1 } = quz; + +``` + +### properties: "never" + +Examples of **correct** code for this rule with the `{ "properties": "never" }` option: + +```js +/*eslint camelcase: ["error", {properties: "never"}]*/ + +var obj = { + my_pref: 1 +}; +``` + +### ignoreDestructuring: false + +Examples of **incorrect** code for this rule with the default `{ "ignoreDestructuring": false }` option: + +```js +/*eslint camelcase: "error"*/ + +var { category_id } = query; + +var { category_id = 1 } = query; + +var { category_id: category_id } = query; + +var { category_id: category_alias } = query; + +var { category_id: categoryId, ...other_props } = query; +``` + +### ignoreDestructuring: true + +Examples of **incorrect** code for this rule with the `{ "ignoreDestructuring": true }` option: + +```js +/*eslint camelcase: ["error", {ignoreDestructuring: true}]*/ + +var { category_id: category_alias } = query; + +var { category_id, ...other_props } = query; +``` + +Examples of **correct** code for this rule with the `{ "ignoreDestructuring": true }` option: + +```js +/*eslint camelcase: ["error", {ignoreDestructuring: true}]*/ + +var { category_id } = query; + +var { category_id = 1 } = query; + +var { category_id: category_id } = query; +``` + +Please note that this option applies only to identifiers inside destructuring patterns. It doesn't additionally allow any particular use of the created variables later in the code apart from the use that is already allowed by default or by other options. + +Examples of additional **incorrect** code for this rule with the `{ "ignoreDestructuring": true }` option: + +```js +/*eslint camelcase: ["error", {ignoreDestructuring: true}]*/ + +var { some_property } = obj; // allowed by {ignoreDestructuring: true} +var foo = some_property + 1; // error, ignoreDestructuring does not apply to this statement +``` + +A common use case for this option is to avoid useless renaming when the identifier is not intended to be used later in the code. + +Examples of additional **correct** code for this rule with the `{ "ignoreDestructuring": true }` option: + +```js +/*eslint camelcase: ["error", {ignoreDestructuring: true}]*/ + +var { some_property, ...rest } = obj; +// do something with 'rest', nothing with 'some_property' +``` + +Another common use case for this option is in combination with `{ "properties": "never" }`, when the identifier is intended to be used only as a property shorthand. + +Examples of additional **correct** code for this rule with the `{ "properties": "never", "ignoreDestructuring": true }` options: + +```js +/*eslint camelcase: ["error", {"properties": "never", ignoreDestructuring: true}]*/ + +var { some_property } = obj; +doSomething({ some_property }); +``` + +### ignoreImports: false + +Examples of **incorrect** code for this rule with the default `{ "ignoreImports": false }` option: + +```js +/*eslint camelcase: "error"*/ + +import { snake_cased } from 'mod'; +``` + +### ignoreImports: true + +Examples of **incorrect** code for this rule with the `{ "ignoreImports": true }` option: + +```js +/*eslint camelcase: ["error", {ignoreImports: true}]*/ + +import default_import from 'mod'; + +import * as namespaced_import from 'mod'; +``` + +Examples of **correct** code for this rule with the `{ "ignoreImports": true }` option: + +```js +/*eslint camelcase: ["error", {ignoreImports: true}]*/ + +import { snake_cased } from 'mod'; +``` + +## allow + +Examples of **correct** code for this rule with the `allow` option: + +```js +/*eslint camelcase: ["error", {allow: ["UNSAFE_componentWillMount"]}]*/ + +function UNSAFE_componentWillMount() { + // ... +} +``` + +```js +/*eslint camelcase: ["error", {allow: ["^UNSAFE_"]}]*/ + +function UNSAFE_componentWillMount() { + // ... +} + +function UNSAFE_componentWillMount() { + // ... +} +``` + +## When Not To Use It + +If you have established coding standards using a different naming convention (separating words with underscores), turn this rule off. diff --git a/eslint/docs/rules/capitalized-comments.md b/eslint/docs/rules/capitalized-comments.md new file mode 100644 index 0000000..ec3253b --- /dev/null +++ b/eslint/docs/rules/capitalized-comments.md @@ -0,0 +1,249 @@ +# enforce or disallow capitalization of the first letter of a comment (capitalized-comments) + +Comments are useful for leaving information for future developers. In order for that information to be useful and not distracting, it is sometimes desirable for comments to follow a particular style. One element of comment formatting styles is whether the first word of a comment should be capitalized or lowercase. + +In general, no comment style is any more or less valid than any others, but many developers would agree that a consistent style can improve a project's maintainability. + +## Rule Details + +This rule aims to enforce a consistent style of comments across your codebase, specifically by either requiring or disallowing a capitalized letter as the first word character in a comment. This rule will not issue warnings when non-cased letters are used. + +By default, this rule will require a non-lowercase letter at the beginning of comments. + +Examples of **incorrect** code for this rule: + +```js +/* eslint capitalized-comments: ["error"] */ + +// lowercase comment + +``` + +Examples of **correct** code for this rule: + +```js + +// Capitalized comment + +// 1. Non-letter at beginning of comment + +// 丈 Non-Latin character at beginning of comment + +/* eslint semi:off */ +/* eslint-env node */ +/* eslint-disable */ +/* eslint-enable */ +/* istanbul ignore next */ +/* jscs:enable */ +/* jshint asi:true */ +/* global foo */ +/* globals foo */ +/* exported myVar */ +// eslint-disable-line +// eslint-disable-next-line +// https://github.com + +``` + +### Options + +This rule has two options: a string value `"always"` or `"never"` which determines whether capitalization of the first word of a comment should be required or forbidden, and optionally an object containing more configuration parameters for the rule. + +Here are the supported object options: + +* `ignorePattern`: A string representing a regular expression pattern of words that should be ignored by this rule. If the first word of a comment matches the pattern, this rule will not report that comment. + * Note that the following words are always ignored by this rule: `["jscs", "jshint", "eslint", "istanbul", "global", "globals", "exported"]`. +* `ignoreInlineComments`: If this is `true`, the rule will not report on comments in the middle of code. By default, this is `false`. +* `ignoreConsecutiveComments`: If this is `true`, the rule will not report on a comment which violates the rule, as long as the comment immediately follows another comment. By default, this is `false`. + +Here is an example configuration: + +```json +{ + "capitalized-comments": [ + "error", + "always", + { + "ignorePattern": "pragma|ignored", + "ignoreInlineComments": true + } + ] +} +``` + +#### `"always"` + +Using the `"always"` option means that this rule will report any comments which start with a lowercase letter. This is the default configuration for this rule. + +Note that configuration comments and comments which start with URLs are never reported. + +Examples of **incorrect** code for this rule: + +```js +/* eslint capitalized-comments: ["error", "always"] */ + +// lowercase comment + +``` + +Examples of **correct** code for this rule: + +```js +/* eslint capitalized-comments: ["error", "always"] */ + +// Capitalized comment + +// 1. Non-letter at beginning of comment + +// 丈 Non-Latin character at beginning of comment + +/* eslint semi:off */ +/* eslint-env node */ +/* eslint-disable */ +/* eslint-enable */ +/* istanbul ignore next */ +/* jscs:enable */ +/* jshint asi:true */ +/* global foo */ +/* globals foo */ +/* exported myVar */ +// eslint-disable-line +// eslint-disable-next-line +// https://github.com + +``` + +#### `"never"` + +Using the `"never"` option means that this rule will report any comments which start with an uppercase letter. + +Examples of **incorrect** code with the `"never"` option: + +```js +/* eslint capitalized-comments: ["error", "never"] */ + +// Capitalized comment + +``` + +Examples of **correct** code with the `"never"` option: + +```js +/* eslint capitalized-comments: ["error", "never"] */ + +// lowercase comment + +// 1. Non-letter at beginning of comment + +// 丈 Non-Latin character at beginning of comment + +``` + +#### `ignorePattern` + +The `ignorePattern` object takes a string value, which is used as a regular expression applied to the first word of a comment. + +Examples of **correct** code with the `"ignorePattern"` option set to `"pragma"`: + +```js +/* eslint capitalized-comments: ["error", "always", { "ignorePattern": "pragma" }] */ + +function foo() { + /* pragma wrap(true) */ +} + +``` + +#### `ignoreInlineComments` + +Setting the `ignoreInlineComments` option to `true` means that comments in the middle of code (with a token on the same line as the beginning of the comment, and another token on the same line as the end of the comment) will not be reported by this rule. + +Examples of **correct** code with the `"ignoreInlineComments"` option set to `true`: + +```js +/* eslint capitalized-comments: ["error", "always", { "ignoreInlineComments": true }] */ + +function foo(/* ignored */ a) { +} + +``` + +#### `ignoreConsecutiveComments` + +If the `ignoreConsecutiveComments` option is set to `true`, then comments which otherwise violate the rule will not be reported as long as they immediately follow another comment. This can be applied more than once. + +Examples of **correct** code with `ignoreConsecutiveComments` set to `true`: + +```js +/* eslint capitalized-comments: ["error", "always", { "ignoreConsecutiveComments": true }] */ + +// This comment is valid since it has the correct capitalization. +// this comment is ignored since it follows another comment, +// and this one as well because it follows yet another comment. + +/* Here is a block comment which has the correct capitalization, */ +/* but this one is ignored due to being consecutive; */ +/* + * in fact, even if any of these are multi-line, that is fine too. + */ +``` + +Examples of **incorrect** code with `ignoreConsecutiveComments` set to `true`: + +```js +/* eslint capitalized-comments: ["error", "always", { "ignoreConsecutiveComments": true }] */ + +// this comment is invalid, but only on this line. +// this comment does NOT get reported, since it is a consecutive comment. +``` + +### Using Different Options for Line and Block Comments + +If you wish to have a different configuration for line comments and block comments, you can do so by using two different object configurations (note that the capitalization option will be enforced consistently for line and block comments): + +```json +{ + "capitalized-comments": [ + "error", + "always", + { + "line": { + "ignorePattern": "pragma|ignored", + }, + "block": { + "ignoreInlineComments": true, + "ignorePattern": "ignored" + } + } + ] +} +``` + +Examples of **incorrect** code with different line and block comment configuration: + +```js +/* eslint capitalized-comments: ["error", "always", { "block": { "ignorePattern": "blockignore" } }] */ + +// capitalized line comment, this is incorrect, blockignore does not help here +/* lowercased block comment, this is incorrect too */ + +``` + +Examples of **correct** code with different line and block comment configuration: + +```js +/* eslint capitalized-comments: ["error", "always", { "block": { "ignorePattern": "blockignore" } }] */ + +// Uppercase line comment, this is correct +/* blockignore lowercase block comment, this is correct due to ignorePattern */ + +``` + +## When Not To Use It + +This rule can be disabled if you do not care about the grammatical style of comments in your codebase. + +## Compatibility + +* **JSCS**: [requireCapitalizedComments](https://jscs-dev.github.io/rule/requireCapitalizedComments) +* **JSCS**: [disallowCapitalizedComments](https://jscs-dev.github.io/rule/disallowCapitalizedComments) diff --git a/eslint/docs/rules/class-methods-use-this.md b/eslint/docs/rules/class-methods-use-this.md new file mode 100644 index 0000000..89f6762 --- /dev/null +++ b/eslint/docs/rules/class-methods-use-this.md @@ -0,0 +1,124 @@ +# Enforce that class methods utilize `this` (class-methods-use-this) + +If a class method does not use `this`, it can *sometimes* be made into a static function. If you do convert the method into a static function, instances of the class that call that particular method have to be converted to a static call as well (`MyClass.callStaticMethod()`) + +It's possible to have a class method which doesn't use `this`, such as: + +```js +class A { + constructor() { + this.a = "hi"; + } + + print() { + console.log(this.a); + } + + sayHi() { + console.log("hi"); + } +} + +let a = new A(); +a.sayHi(); // => "hi" +``` + +In the example above, the `sayHi` method doesn't use `this`, so we can make it a static method: + +```js +class A { + constructor() { + this.a = "hi"; + } + + print() { + console.log(this.a); + } + + static sayHi() { + console.log("hi"); + } +} + +A.sayHi(); // => "hi" +``` + +Also note in the above examples that if you switch a method to a static method, *instances* of the class that call the static method (`let a = new A(); a.sayHi();`) have to be updated to being a static call (`A.sayHi();`) instead of having the instance of the *class* call the method + +## Rule Details + +This rule is aimed to flag class methods that do not use `this`. + +Examples of **incorrect** code for this rule: + +```js +/*eslint class-methods-use-this: "error"*/ +/*eslint-env es6*/ + +class A { + foo() { + console.log("Hello World"); /*error Expected 'this' to be used by class method 'foo'.*/ + } +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint class-methods-use-this: "error"*/ +/*eslint-env es6*/ +class A { + foo() { + this.bar = "Hello World"; // OK, this is used + } +} + +class A { + constructor() { + // OK. constructor is exempt + } +} + +class A { + static foo() { + // OK. static methods aren't expected to use this. + } +} +``` + +## Options + +### Exceptions + +``` +"class-methods-use-this": [, { "exceptMethods": [<...exceptions>] }] +``` + +The `exceptMethods` option allows you to pass an array of method names for which you would like to ignore warnings. For example, you might have a spec from an external library that requires you to overwrite a method as a regular function (and not as a static method) and does not use `this` inside the function body. In this case, you can add that method to ignore in the warnings. + +Examples of **incorrect** code for this rule when used without exceptMethods: + +```js +/*eslint class-methods-use-this: "error"*/ + +class A { + foo() { + } +} +``` + +Examples of **correct** code for this rule when used with exceptMethods: + +```js +/*eslint class-methods-use-this: ["error", { "exceptMethods": ["foo"] }] */ + +class A { + foo() { + } +} +``` + +## Further Reading + +* [Classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) +* [Static Methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static) diff --git a/eslint/docs/rules/comma-dangle.md b/eslint/docs/rules/comma-dangle.md new file mode 100644 index 0000000..ef92342 --- /dev/null +++ b/eslint/docs/rules/comma-dangle.md @@ -0,0 +1,315 @@ +# require or disallow trailing commas (comma-dangle) + +Trailing commas in object literals are valid according to the ECMAScript 5 (and ECMAScript 3!) spec. However, IE8 (when not in IE8 document mode) and below will throw an error when it encounters trailing commas in JavaScript. + +```js +var foo = { + bar: "baz", + qux: "quux", +}; +``` + +Trailing commas simplify adding and removing items to objects and arrays, since only the lines you are modifying must be touched. +Another argument in favor of trailing commas is that it improves the clarity of diffs when an item is added or removed from an object or array: + +Less clear: + +```diff + var foo = { +- bar: "baz", +- qux: "quux" ++ bar: "baz" + }; +``` + +More clear: + +```diff + var foo = { + bar: "baz", +- qux: "quux", + }; +``` + +## Rule Details + +This rule enforces consistent use of trailing commas in object and array literals. + +## Options + +This rule has a string option or an object option: + +```json +{ + "comma-dangle": ["error", "never"], + // or + "comma-dangle": ["error", { + "arrays": "never", + "objects": "never", + "imports": "never", + "exports": "never", + "functions": "never" + }] +} +``` + +* `"never"` (default) disallows trailing commas +* `"always"` requires trailing commas +* `"always-multiline"` requires trailing commas when the last element or property is in a *different* line than the closing `]` or `}` and disallows trailing commas when the last element or property is on the *same* line as the closing `]` or `}` +* `"only-multiline"` allows (but does not require) trailing commas when the last element or property is in a *different* line than the closing `]` or `}` and disallows trailing commas when the last element or property is on the *same* line as the closing `]` or `}` + +You can also use an object option to configure this rule for each type of syntax. +Each of the following options can be set to `"never"`, `"always"`, `"always-multiline"`, `"only-multiline"`, or `"ignore"`. +The default for each option is `"never"` unless otherwise specified. + +* `arrays` is for array literals and array patterns of destructuring. (e.g. `let [a,] = [1,];`) +* `objects` is for object literals and object patterns of destructuring. (e.g. `let {a,} = {a: 1};`) +* `imports` is for import declarations of ES Modules. (e.g. `import {a,} from "foo";`) +* `exports` is for export declarations of ES Modules. (e.g. `export {a,};`) +* `functions` is for function declarations and function calls. (e.g. `(function(a,){ })(b,);`) + * `functions` should only be enabled when linting ECMAScript 2017 or higher. + +### never + +Examples of **incorrect** code for this rule with the default `"never"` option: + +```js +/*eslint comma-dangle: ["error", "never"]*/ + +var foo = { + bar: "baz", + qux: "quux", +}; + +var arr = [1,2,]; + +foo({ + bar: "baz", + qux: "quux", +}); +``` + +Examples of **correct** code for this rule with the default `"never"` option: + +```js +/*eslint comma-dangle: ["error", "never"]*/ + +var foo = { + bar: "baz", + qux: "quux" +}; + +var arr = [1,2]; + +foo({ + bar: "baz", + qux: "quux" +}); +``` + +### always + +Examples of **incorrect** code for this rule with the `"always"` option: + +```js +/*eslint comma-dangle: ["error", "always"]*/ + +var foo = { + bar: "baz", + qux: "quux" +}; + +var arr = [1,2]; + +foo({ + bar: "baz", + qux: "quux" +}); +``` + +Examples of **correct** code for this rule with the `"always"` option: + +```js +/*eslint comma-dangle: ["error", "always"]*/ + +var foo = { + bar: "baz", + qux: "quux", +}; + +var arr = [1,2,]; + +foo({ + bar: "baz", + qux: "quux", +}); +``` + +### always-multiline + +Examples of **incorrect** code for this rule with the `"always-multiline"` option: + +```js +/*eslint comma-dangle: ["error", "always-multiline"]*/ + +var foo = { + bar: "baz", + qux: "quux" +}; + +var foo = { bar: "baz", qux: "quux", }; + +var arr = [1,2,]; + +var arr = [1, + 2,]; + +var arr = [ + 1, + 2 +]; + +foo({ + bar: "baz", + qux: "quux" +}); +``` + +Examples of **correct** code for this rule with the `"always-multiline"` option: + +```js +/*eslint comma-dangle: ["error", "always-multiline"]*/ + +var foo = { + bar: "baz", + qux: "quux", +}; + +var foo = {bar: "baz", qux: "quux"}; +var arr = [1,2]; + +var arr = [1, + 2]; + +var arr = [ + 1, + 2, +]; + +foo({ + bar: "baz", + qux: "quux", +}); +``` + +### only-multiline + +Examples of **incorrect** code for this rule with the `"only-multiline"` option: + +```js +/*eslint comma-dangle: ["error", "only-multiline"]*/ + +var foo = { bar: "baz", qux: "quux", }; + +var arr = [1,2,]; + +var arr = [1, + 2,]; + +``` + +Examples of **correct** code for this rule with the `"only-multiline"` option: + +```js +/*eslint comma-dangle: ["error", "only-multiline"]*/ + +var foo = { + bar: "baz", + qux: "quux", +}; + +var foo = { + bar: "baz", + qux: "quux" +}; + +var foo = {bar: "baz", qux: "quux"}; +var arr = [1,2]; + +var arr = [1, + 2]; + +var arr = [ + 1, + 2, +]; + +var arr = [ + 1, + 2 +]; + +foo({ + bar: "baz", + qux: "quux", +}); + +foo({ + bar: "baz", + qux: "quux" +}); +``` + +### functions + +Examples of **incorrect** code for this rule with the `{"functions": "never"}` option: + +```js +/*eslint comma-dangle: ["error", {"functions": "never"}]*/ + +function foo(a, b,) { +} + +foo(a, b,); +new foo(a, b,); +``` + +Examples of **correct** code for this rule with the `{"functions": "never"}` option: + +```js +/*eslint comma-dangle: ["error", {"functions": "never"}]*/ + +function foo(a, b) { +} + +foo(a, b); +new foo(a, b); +``` + +Examples of **incorrect** code for this rule with the `{"functions": "always"}` option: + +```js +/*eslint comma-dangle: ["error", {"functions": "always"}]*/ + +function foo(a, b) { +} + +foo(a, b); +new foo(a, b); +``` + +Examples of **correct** code for this rule with the `{"functions": "always"}` option: + +```js +/*eslint comma-dangle: ["error", {"functions": "always"}]*/ + +function foo(a, b,) { +} + +foo(a, b,); +new foo(a, b,); +``` + +## When Not To Use It + +You can turn this rule off if you are not concerned with dangling commas. diff --git a/eslint/docs/rules/comma-spacing.md b/eslint/docs/rules/comma-spacing.md new file mode 100644 index 0000000..ffa2bfd --- /dev/null +++ b/eslint/docs/rules/comma-spacing.md @@ -0,0 +1,129 @@ +# Enforces spacing around commas (comma-spacing) + +Spacing around commas improves readability of a list of items. Although most of the style guidelines for languages prescribe adding a space after a comma and not before it, it is subjective to the preferences of a project. + +```js +var foo = 1, bar = 2; +var foo = 1 ,bar = 2; +``` + +## Rule Details + +This rule enforces consistent spacing before and after commas in variable declarations, array literals, object literals, function parameters, and sequences. + +This rule does not apply in an `ArrayExpression` or `ArrayPattern` in either of the following cases: + +* adjacent null elements +* an initial null element, to avoid conflicts with the [`array-bracket-spacing`](array-bracket-spacing.md) rule + +## Options + +This rule has an object option: + +* `"before": false` (default) disallows spaces before commas +* `"before": true` requires one or more spaces before commas +* `"after": true` (default) requires one or more spaces after commas +* `"after": false` disallows spaces after commas + +### after + +Examples of **incorrect** code for this rule with the default `{ "before": false, "after": true }` options: + +```js +/*eslint comma-spacing: ["error", { "before": false, "after": true }]*/ + +var foo = 1 ,bar = 2; +var arr = [1 , 2]; +var obj = {"foo": "bar" ,"baz": "qur"}; +foo(a ,b); +new Foo(a ,b); +function foo(a ,b){} +a ,b +``` + +Examples of **correct** code for this rule with the default `{ "before": false, "after": true }` options: + +```js +/*eslint comma-spacing: ["error", { "before": false, "after": true }]*/ + +var foo = 1, bar = 2 + , baz = 3; +var arr = [1, 2]; +var arr = [1,, 3] +var obj = {"foo": "bar", "baz": "qur"}; +foo(a, b); +new Foo(a, b); +function foo(a, b){} +a, b +``` + +Example of **correct** code for this rule with initial null element for the default `{ "before": false, "after": true }` options: + +```js +/*eslint comma-spacing: ["error", { "before": false, "after": true }]*/ +/*eslint array-bracket-spacing: ["error", "always"]*/ + +var arr = [ , 2, 3 ] +``` + +### before + +Examples of **incorrect** code for this rule with the `{ "before": true, "after": false }` options: + +```js +/*eslint comma-spacing: ["error", { "before": true, "after": false }]*/ + +var foo = 1, bar = 2; +var arr = [1 , 2]; +var obj = {"foo": "bar", "baz": "qur"}; +new Foo(a,b); +function foo(a,b){} +a, b +``` + +Examples of **correct** code for this rule with the `{ "before": true, "after": false }` options: + +```js +/*eslint comma-spacing: ["error", { "before": true, "after": false }]*/ + +var foo = 1 ,bar = 2 , + baz = true; +var arr = [1 ,2]; +var arr = [1 ,,3] +var obj = {"foo": "bar" ,"baz": "qur"}; +foo(a ,b); +new Foo(a ,b); +function foo(a ,b){} +a ,b +``` + +Examples of **correct** code for this rule with initial null element for the `{ "before": true, "after": false }` options: + +```js +/*eslint comma-spacing: ["error", { "before": true, "after": false }]*/ +/*eslint array-bracket-spacing: ["error", "never"]*/ + +var arr = [,2 ,3] +``` + +## When Not To Use It + +If your project will not be following a consistent comma-spacing pattern, turn this rule off. + + +## Further Reading + +* [Javascript](http://javascript.crockford.com/code.html) +* [Dojo Style Guide](https://dojotoolkit.org/reference-guide/1.9/developer/styleguide.html) + + +## Related Rules + +* [array-bracket-spacing](array-bracket-spacing.md) +* [comma-style](comma-style.md) +* [space-in-brackets](space-in-brackets.md) (deprecated) +* [space-in-parens](space-in-parens.md) +* [space-infix-ops](space-infix-ops.md) +* [space-after-keywords](space-after-keywords.md) +* [space-unary-ops](space-unary-ops.md) +* [space-return-throw-case](space-return-throw-case.md) diff --git a/eslint/docs/rules/comma-style.md b/eslint/docs/rules/comma-style.md new file mode 100644 index 0000000..24b5515 --- /dev/null +++ b/eslint/docs/rules/comma-style.md @@ -0,0 +1,171 @@ +# Comma style (comma-style) + +The Comma Style rule enforces styles for comma-separated lists. There are two comma styles primarily used in JavaScript: + +* The standard style, in which commas are placed at the end of the current line +* Comma First style, in which commas are placed at the start of the next line + +One of the justifications for using Comma First style is that it can help track missing and trailing commas. These are problematic because missing commas in variable declarations can lead to the leakage of global variables and trailing commas can lead to errors in older versions of IE. + +## Rule Details + +This rule enforce consistent comma style in array literals, object literals, and variable declarations. + +This rule does not apply in either of the following cases: + +* comma preceded and followed by linebreak (lone comma) +* single-line array literals, object literals, and variable declarations + +## Options + +This rule has a string option: + +* `"last"` (default) requires a comma after and on the same line as an array element, object property, or variable declaration +* `"first"` requires a comma before and on the same line as an array element, object property, or variable declaration + +This rule also accepts an additional `exceptions` object: + +* `"exceptions"` has properties whose names correspond to node types in the abstract syntax tree (AST) of JavaScript code: + + * `"ArrayExpression": true` ignores comma style in array literals + * `"ArrayPattern": true` ignores comma style in array patterns of destructuring + * `"ArrowFunctionExpression": true` ignores comma style in the parameters of arrow function expressions + * `"CallExpression": true` ignores comma style in the arguments of function calls + * `"FunctionDeclaration": true` ignores comma style in the parameters of function declarations + * `"FunctionExpression": true` ignores comma style in the parameters of function expressions + * `"ImportDeclaration": true` ignores comma style in the specifiers of import declarations + * `"ObjectExpression": true` ignores comma style in object literals + * `"ObjectPattern": true` ignores comma style in object patterns of destructuring + * `"VariableDeclaration": true` ignores comma style in variable declarations + * `"NewExpression": true` ignores comma style in the parameters of constructor expressions + +A way to determine the node types as defined by [ESTree](https://github.com/estree/estree) is to use [AST Explorer](https://astexplorer.net/) with the espree parser. + +### last + +Examples of **incorrect** code for this rule with the default `"last"` option: + +```js +/*eslint comma-style: ["error", "last"]*/ + +var foo = 1 +, +bar = 2; + +var foo = 1 + , bar = 2; + +var foo = ["apples" + , "oranges"]; + +function bar() { + return { + "a": 1 + ,"b:": 2 + }; +} +``` + +Examples of **correct** code for this rule with the default `"last"` option: + +```js +/*eslint comma-style: ["error", "last"]*/ + +var foo = 1, bar = 2; + +var foo = 1, + bar = 2; + +var foo = ["apples", + "oranges"]; + +function bar() { + return { + "a": 1, + "b:": 2 + }; +} +``` + +### first + +Examples of **incorrect** code for this rule with the `"first"` option: + +```js +/*eslint comma-style: ["error", "first"]*/ + +var foo = 1, + bar = 2; + +var foo = ["apples", + "oranges"]; + +function bar() { + return { + "a": 1, + "b:": 2 + }; +} +``` + +Examples of **correct** code for this rule with the `"first"` option: + +```js +/*eslint comma-style: ["error", "first"]*/ + +var foo = 1, bar = 2; + +var foo = 1 + ,bar = 2; + +var foo = ["apples" + ,"oranges"]; + +function bar() { + return { + "a": 1 + ,"b:": 2 + }; +} +``` + +### exceptions + +An example use case is to enforce comma style *only* in var statements. + +Examples of **incorrect** code for this rule with sample `"first", { "exceptions": { … } }` options: + +```js +/*eslint comma-style: ["error", "first", { "exceptions": { "ArrayExpression": true, "ObjectExpression": true } }]*/ + +var o = {}, + a = []; +``` + +Examples of **correct** code for this rule with sample `"first", { "exceptions": { … } }` options: + +```js +/*eslint comma-style: ["error", "first", { "exceptions": { "ArrayExpression": true, "ObjectExpression": true } }]*/ + +var o = {fst:1, + snd: [1, + 2]} + , a = []; +``` + +## When Not To Use It + +This rule can safely be turned off if your project does not care about enforcing a consistent comma style. + + +## Further Reading + +For more information on the Comma First style: + +* [A better coding convention for lists and object literals in JavaScript by isaacs](https://gist.github.com/isaacs/357981) +* [npm coding style guideline](https://docs.npmjs.com/misc/coding-style) + + +## Related Rules + +* [operator-linebreak](operator-linebreak.md) diff --git a/eslint/docs/rules/complexity.md b/eslint/docs/rules/complexity.md new file mode 100644 index 0000000..3a66549 --- /dev/null +++ b/eslint/docs/rules/complexity.md @@ -0,0 +1,87 @@ +# Limit Cyclomatic Complexity (complexity) + +Cyclomatic complexity measures the number of linearly independent paths through a program's source code. This rule allows setting a cyclomatic complexity threshold. + +```js +function a(x) { + if (true) { + return x; // 1st path + } else if (false) { + return x+1; // 2nd path + } else { + return 4; // 3rd path + } +} +``` + +## Rule Details + +This rule is aimed at reducing code complexity by capping the amount of cyclomatic complexity allowed in a program. As such, it will warn when the cyclomatic complexity crosses the configured threshold (default is `20`). + +Examples of **incorrect** code for a maximum of 2: + +```js +/*eslint complexity: ["error", 2]*/ + +function a(x) { + if (true) { + return x; + } else if (false) { + return x+1; + } else { + return 4; // 3rd path + } +} +``` + +Examples of **correct** code for a maximum of 2: + +```js +/*eslint complexity: ["error", 2]*/ + +function a(x) { + if (true) { + return x; + } else { + return 4; + } +} +``` + +## Options + +Optionally, you may specify a `max` object property: + +```json +"complexity": ["error", 2] +``` + +is equivalent to + +```json +"complexity": ["error", { "max": 2 }] +``` + +**Deprecated:** the object property `maximum` is deprecated. Please use the property `max` instead. + +## When Not To Use It + +If you can't determine an appropriate complexity limit for your code, then it's best to disable this rule. + +## Further Reading + +* [Cyclomatic Complexity](https://en.wikipedia.org/wiki/Cyclomatic_complexity) +* [Complexity Analysis of JavaScript Code](https://ariya.io/2012/12/complexity-analysis-of-javascript-code) +* [More about Complexity in JavaScript](https://craftsmanshipforsoftware.com/2015/05/25/complexity-for-javascript/) +* [About Complexity](https://web.archive.org/web/20160808115119/http://jscomplexity.org/complexity) +* [Discussion about Complexity in ESLint and more links](https://github.com/eslint/eslint/issues/4808#issuecomment-167795140) + +## Related Rules + +* [max-depth](max-depth.md) +* [max-len](max-len.md) +* [max-lines](max-lines.md) +* [max-lines-per-function](max-lines-per-function.md) +* [max-nested-callbacks](max-nested-callbacks.md) +* [max-params](max-params.md) +* [max-statements](max-statements.md) diff --git a/eslint/docs/rules/computed-property-spacing.md b/eslint/docs/rules/computed-property-spacing.md new file mode 100644 index 0000000..3fdd70c --- /dev/null +++ b/eslint/docs/rules/computed-property-spacing.md @@ -0,0 +1,165 @@ +# Disallow or enforce spaces inside of computed properties (computed-property-spacing) + +While formatting preferences are very personal, a number of style guides require +or disallow spaces between computed properties in the following situations: + +```js +/*eslint-env es6*/ + +var obj = { prop: "value" }; +var a = "prop"; +var x = obj[a]; // computed property in object member expression + +var a = "prop"; +var obj = { + [a]: "value" // computed property key in object literal (ECMAScript 6) +}; +``` + +## Rule Details + +This rule enforces consistent spacing inside computed property brackets. + +It either requires or disallows spaces between the brackets and the values inside of them. +This rule does not apply to brackets that are separated from the adjacent value by a newline. + +## Options + +This rule has two options, a string option and an object option. + +String option: + +* `"never"` (default) disallows spaces inside computed property brackets +* `"always"` requires one or more spaces inside computed property brackets + +Object option: + +* `"enforceForClassMembers": true` (default) additionally applies this rule to class members. + +### never + +Examples of **incorrect** code for this rule with the default `"never"` option: + +```js +/*eslint computed-property-spacing: ["error", "never"]*/ +/*eslint-env es6*/ + +obj[foo ] +obj[ 'foo'] +var x = {[ b ]: a} +obj[foo[ bar ]] +``` + +Examples of **correct** code for this rule with the default `"never"` option: + +```js +/*eslint computed-property-spacing: ["error", "never"]*/ +/*eslint-env es6*/ + +obj[foo] +obj['foo'] +var x = {[b]: a} +obj[foo[bar]] +``` + +### always + +Examples of **incorrect** code for this rule with the `"always"` option: + +```js +/*eslint computed-property-spacing: ["error", "always"]*/ +/*eslint-env es6*/ + +obj[foo] +var x = {[b]: a} +obj[ foo] +obj['foo' ] +obj[foo[ bar ]] +var x = {[ b]: a} +``` + +Examples of **correct** code for this rule with the `"always"` option: + +```js +/*eslint computed-property-spacing: ["error", "always"]*/ +/*eslint-env es6*/ + +obj[ foo ] +obj[ 'foo' ] +var x = {[ b ]: a} +obj[ foo[ bar ] ] +``` + +#### enforceForClassMembers + +With `enforceForClassMembers` set to `true` (default), the rule also disallows/enforces spaces inside of computed keys of class methods, getters and setters. + +Examples of **incorrect** code for this rule with `"never"` and `{ "enforceForClassMembers": true }` (default): + +```js +/*eslint computed-property-spacing: ["error", "never", { "enforceForClassMembers": true }]*/ +/*eslint-env es6*/ + +class Foo { + [a ]() {} + get [b ]() {} + set [b ](value) {} +} + +const Bar = class { + [ a](){} + static [ b]() {} + static get [ c ]() {} + static set [ c ](value) {} +} +``` + +Examples of **correct** code for this rule with `"never"` and `{ "enforceForClassMembers": true }` (default): + +```js +/*eslint computed-property-spacing: ["error", "never", { "enforceForClassMembers": true }]*/ +/*eslint-env es6*/ + +class Foo { + [a]() {} + get [b]() {} + set [b](value) {} +} + +const Bar = class { + [a](){} + static [b]() {} + static get [c]() {} + static set [c](value) {} +} +``` + +Examples of **correct** code for this rule with `"never"` and `{ "enforceForClassMembers": false }`: + +```js +/*eslint computed-property-spacing: ["error", "never", { "enforceForClassMembers": false }]*/ +/*eslint-env es6*/ + +class Foo { + [a ]() {} + get [b ]() {} + set [b ](value) {} +} + +const Bar = class { + [ a](){} + static [ b]() {} + static get [ c ]() {} + static set [ c ](value) {} +} +``` + +## When Not To Use It + +You can turn this rule off if you are not concerned with the consistency of computed properties. + +## Related Rules + +* [array-bracket-spacing](array-bracket-spacing.md) +* [comma-spacing](comma-spacing.md) +* [space-in-parens](space-in-parens.md) diff --git a/eslint/docs/rules/consistent-return.md b/eslint/docs/rules/consistent-return.md new file mode 100644 index 0000000..bf4b470 --- /dev/null +++ b/eslint/docs/rules/consistent-return.md @@ -0,0 +1,145 @@ +# require `return` statements to either always or never specify values (consistent-return) + +Unlike statically-typed languages which enforce that a function returns a specified type of value, JavaScript allows different code paths in a function to return different types of values. + +A confusing aspect of JavaScript is that a function returns `undefined` if any of the following are true: + +* it does not execute a `return` statement before it exits +* it executes `return` which does not specify a value explicitly +* it executes `return undefined` +* it executes `return void` followed by an expression (for example, a function call) +* it executes `return` followed by any other expression which evaluates to `undefined` + +If any code paths in a function return a value explicitly but some code path do not return a value explicitly, it might be a typing mistake, especially in a large function. In the following example: + +* a code path through the function returns a Boolean value `true` +* another code path does not return a value explicitly, therefore returns `undefined` implicitly + +```js +function doSomething(condition) { + if (condition) { + return true; + } else { + return; + } +} +``` + +## Rule Details + +This rule requires `return` statements to either always or never specify values. This rule ignores function definitions where the name begins with an uppercase letter, because constructors (when invoked with the `new` operator) return the instantiated object implicitly if they do not return another object explicitly. + +Examples of **incorrect** code for this rule: + +```js +/*eslint consistent-return: "error"*/ + +function doSomething(condition) { + if (condition) { + return true; + } else { + return; + } +} + +function doSomething(condition) { + if (condition) { + return true; + } +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint consistent-return: "error"*/ + +function doSomething(condition) { + if (condition) { + return true; + } else { + return false; + } +} + +function Foo() { + if (!(this instanceof Foo)) { + return new Foo(); + } + + this.a = 0; +} +``` + +## Options + +This rule has an object option: + +* `"treatUndefinedAsUnspecified": false` (default) always either specify values or return `undefined` implicitly only. +* `"treatUndefinedAsUnspecified": true` always either specify values or return `undefined` explicitly or implicitly. + +### treatUndefinedAsUnspecified + +Examples of **incorrect** code for this rule with the default `{ "treatUndefinedAsUnspecified": false }` option: + +```js +/*eslint consistent-return: ["error", { "treatUndefinedAsUnspecified": false }]*/ + +function foo(callback) { + if (callback) { + return void callback(); + } + // no return statement +} + +function bar(condition) { + if (condition) { + return undefined; + } + // no return statement +} +``` + +Examples of **incorrect** code for this rule with the `{ "treatUndefinedAsUnspecified": true }` option: + +```js +/*eslint consistent-return: ["error", { "treatUndefinedAsUnspecified": true }]*/ + +function foo(callback) { + if (callback) { + return void callback(); + } + return true; +} + +function bar(condition) { + if (condition) { + return undefined; + } + return true; +} +``` + +Examples of **correct** code for this rule with the `{ "treatUndefinedAsUnspecified": true }` option: + +```js +/*eslint consistent-return: ["error", { "treatUndefinedAsUnspecified": true }]*/ + +function foo(callback) { + if (callback) { + return void callback(); + } + // no return statement +} + +function bar(condition) { + if (condition) { + return undefined; + } + // no return statement +} +``` + +## When Not To Use It + +If you want to allow functions to have different `return` behavior depending on code branching, then it is safe to disable this rule. diff --git a/eslint/docs/rules/consistent-this.md b/eslint/docs/rules/consistent-this.md new file mode 100644 index 0000000..3921232 --- /dev/null +++ b/eslint/docs/rules/consistent-this.md @@ -0,0 +1,84 @@ +# Require Consistent This (consistent-this) + +It is often necessary to capture the current execution context in order to make it available subsequently. A prominent example of this are jQuery callbacks: + +```js +var that = this; +jQuery('li').click(function (event) { + // here, "this" is the HTMLElement where the click event occurred + that.setFoo(42); +}); +``` + +There are many commonly used aliases for `this` such as `that`, `self` or `me`. It is desirable to ensure that whichever alias the team agrees upon is used consistently throughout the application. + +## Rule Details + +This rule enforces two things about variables with the designated alias names for `this`: + +* If a variable with a designated name is declared, it *must* be either initialized (in the declaration) or assigned (in the same scope as the declaration) the value `this`. +* If a variable is initialized or assigned the value `this`, the name of the variable *must* be a designated alias. + +## Options + +This rule has one or more string options: + +* designated alias names for `this` (default `"that"`) + +Examples of **incorrect** code for this rule with the default `"that"` option: + +```js +/*eslint consistent-this: ["error", "that"]*/ + +var that = 42; + +var self = this; + +that = 42; + +self = this; +``` + +Examples of **correct** code for this rule with the default `"that"` option: + +```js +/*eslint consistent-this: ["error", "that"]*/ + +var that = this; + +var self = 42; + +var self; + +that = this; + +foo.bar = this; +``` + +Examples of **incorrect** code for this rule with the default `"that"` option, if the variable is not initialized: + +```js +/*eslint consistent-this: ["error", "that"]*/ + +var that; +function f() { + that = this; +} +``` + +Examples of **correct** code for this rule with the default `"that"` option, if the variable is not initialized: + +```js +/*eslint consistent-this: ["error", "that"]*/ + +var that; +that = this; + +var foo, that; +foo = 42; +that = this; +``` + +## When Not To Use It + +If you need to capture nested context, `consistent-this` is going to be problematic. Code of that nature is usually difficult to read and maintain and you should consider refactoring it. diff --git a/eslint/docs/rules/constructor-super.md b/eslint/docs/rules/constructor-super.md new file mode 100644 index 0000000..bf43888 --- /dev/null +++ b/eslint/docs/rules/constructor-super.md @@ -0,0 +1,60 @@ +# Verify calls of `super()` in constructors (constructor-super) + +Constructors of derived classes must call `super()`. +Constructors of non derived classes must not call `super()`. +If this is not observed, the JavaScript engine will raise a runtime error. + +This rule checks whether or not there is a valid `super()` call. + +## Rule Details + +This rule is aimed to flag invalid/missing `super()` calls. + +Examples of **incorrect** code for this rule: + +```js +/*eslint constructor-super: "error"*/ +/*eslint-env es6*/ + +class A { + constructor() { + super(); // This is a SyntaxError. + } +} + +class A extends B { + constructor() { } // Would throw a ReferenceError. +} + +// Classes which inherits from a non constructor are always problems. +class A extends null { + constructor() { + super(); // Would throw a TypeError. + } +} + +class A extends null { + constructor() { } // Would throw a ReferenceError. +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint constructor-super: "error"*/ +/*eslint-env es6*/ + +class A { + constructor() { } +} + +class A extends B { + constructor() { + super(); + } +} +``` + +## When Not To Use It + +If you don't want to be notified about invalid/missing `super()` callings in constructors, you can safely disable this rule. diff --git a/eslint/docs/rules/curly.md b/eslint/docs/rules/curly.md new file mode 100644 index 0000000..9f4df27 --- /dev/null +++ b/eslint/docs/rules/curly.md @@ -0,0 +1,293 @@ +# Require Following Curly Brace Conventions (curly) + +JavaScript allows the omission of curly braces when a block contains only one statement. However, it is considered by many to be best practice to _never_ omit curly braces around blocks, even when they are optional, because it can lead to bugs and reduces code clarity. So the following: + +```js +if (foo) foo++; +``` + +Can be rewritten as: + +```js +if (foo) { + foo++; +} +``` + +There are, however, some who prefer to only use braces when there is more than one statement to be executed. + +## Rule Details + +This rule is aimed at preventing bugs and increasing code clarity by ensuring that block statements are wrapped in curly braces. It will warn when it encounters blocks that omit curly braces. + +## Options + +### all + +Examples of **incorrect** code for the default `"all"` option: + +```js +/*eslint curly: "error"*/ + +if (foo) foo++; + +while (bar) + baz(); + +if (foo) { + baz(); +} else qux(); +``` + +Examples of **correct** code for the default `"all"` option: + +```js +/*eslint curly: "error"*/ + +if (foo) { + foo++; +} + +while (bar) { + baz(); +} + +if (foo) { + baz(); +} else { + qux(); +} +``` + +### multi + +By default, this rule warns whenever `if`, `else`, `for`, `while`, or `do` are used without block statements as their body. However, you can specify that block statements should be used only when there are multiple statements in the block and warn when there is only one statement in the block. + +Examples of **incorrect** code for the `"multi"` option: + +```js +/*eslint curly: ["error", "multi"]*/ + +if (foo) { + foo++; +} + +if (foo) bar(); +else { + foo++; +} + +while (true) { + doSomething(); +} + +for (var i=0; i < items.length; i++) { + doSomething(); +} +``` + +Examples of **correct** code for the `"multi"` option: + +```js +/*eslint curly: ["error", "multi"]*/ + +if (foo) foo++; + +else foo(); + +while (true) { + doSomething(); + doSomethingElse(); +} +``` + +### multi-line + +Alternatively, you can relax the rule to allow brace-less single-line `if`, `else if`, `else`, `for`, `while`, or `do`, while still enforcing the use of curly braces for other instances. + +Examples of **incorrect** code for the `"multi-line"` option: + +```js +/*eslint curly: ["error", "multi-line"]*/ + +if (foo) + doSomething(); +else + doSomethingElse(); + +if (foo) foo( + bar, + baz); +``` + +Examples of **correct** code for the `"multi-line"` option: + +```js +/*eslint curly: ["error", "multi-line"]*/ + +if (foo) foo++; else doSomething(); + +if (foo) foo++; +else if (bar) baz() +else doSomething(); + +do something(); +while (foo); + +while (foo + && bar) baz(); + +if (foo) { + foo++; +} + +if (foo) { foo++; } + +while (true) { + doSomething(); + doSomethingElse(); +} +``` + +### multi-or-nest + +You can use another configuration that forces brace-less `if`, `else if`, `else`, `for`, `while`, or `do` if their body contains only one single-line statement. And forces braces in all other cases. + +Examples of **incorrect** code for the `"multi-or-nest"` option: + +```js +/*eslint curly: ["error", "multi-or-nest"]*/ + +if (!foo) + foo = { + bar: baz, + qux: foo + }; + +while (true) + if(foo) + doSomething(); + else + doSomethingElse(); + +if (foo) { + foo++; +} + +while (true) { + doSomething(); +} + +for (var i = 0; foo; i++) { + doSomething(); +} + +if (foo) + // some comment + bar(); +``` + +Examples of **correct** code for the `"multi-or-nest"` option: + +```js +/*eslint curly: ["error", "multi-or-nest"]*/ + +if (!foo) { + foo = { + bar: baz, + qux: foo + }; +} + +while (true) { + if(foo) + doSomething(); + else + doSomethingElse(); +} + +if (foo) + foo++; + +while (true) + doSomething(); + +for (var i = 0; foo; i++) + doSomething(); + +if (foo) { + // some comment + bar(); +} +``` + +### consistent + +When using any of the `multi*` options, you can add an option to enforce all bodies of a `if`, +`else if` and `else` chain to be with or without braces. + +Examples of **incorrect** code for the `"multi", "consistent"` options: + +```js +/*eslint curly: ["error", "multi", "consistent"]*/ + +if (foo) { + bar(); + baz(); +} else + buz(); + +if (foo) + bar(); +else if (faa) + bor(); +else { + other(); + things(); +} + +if (true) + foo(); +else { + baz(); +} + +if (foo) { + foo++; +} +``` + +Examples of **correct** code for the `"multi", "consistent"` options: + +```js +/*eslint curly: ["error", "multi", "consistent"]*/ + +if (foo) { + bar(); + baz(); +} else { + buz(); +} + +if (foo) { + bar(); +} else if (faa) { + bor(); +} else { + other(); + things(); +} + +if (true) + foo(); +else + baz(); + +if (foo) + foo++; + +``` + +## When Not To Use It + +If you have no strict conventions about when to use block statements and when not to, you can safely disable this rule. diff --git a/eslint/docs/rules/default-case-last.md b/eslint/docs/rules/default-case-last.md new file mode 100644 index 0000000..c9eedbe --- /dev/null +++ b/eslint/docs/rules/default-case-last.md @@ -0,0 +1,125 @@ +# Enforce default clauses in switch statements to be last (default-case-last) + +A `switch` statement can optionally have a `default` clause. + +If present, it's usually the last clause, but it doesn't need to be. It is also allowed to put the `default` clause before all `case` clauses, or anywhere between. The behavior is mostly the same as if it was the last clause. The `default` block will be still executed only if there is no match in the `case` clauses (including those defined after the `default`), but there is also the ability to "fall through" from the `default` clause to the following clause in the list. However, such flow is not common and it would be confusing to the readers. + +Even if there is no "fall through" logic, it's still unexpected to see the `default` clause before or between the `case` clauses. By convention, it is expected to be the last clause. + +If a `switch` statement should have a `default` clause, it's considered a best practice to define it as the last clause. + +## Rule Details + +This rule enforces `default` clauses in `switch` statements to be last. + +It applies only to `switch` statements that already have a `default` clause. + +This rule does not enforce the existence of `default` clauses. See [default-case](default-case.md) if you also want to enforce the existence of `default` clauses in `switch` statements. + +Examples of **incorrect** code for this rule: + +```js +/*eslint default-case-last: "error"*/ + +switch (foo) { + default: + bar(); + break; + case "a": + baz(); + break; +} + +switch (foo) { + case 1: + bar(); + break; + default: + baz(); + break; + case 2: + quux(); + break; +} + +switch (foo) { + case "x": + bar(); + break; + default: + case "y": + baz(); + break; +} + +switch (foo) { + default: + break; + case -1: + bar(); + break; +} + +switch (foo) { + default: + doSomethingIfNotZero(); + case 0: + doSomethingAnyway(); +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint default-case-last: "error"*/ + +switch (foo) { + case "a": + baz(); + break; + default: + bar(); + break; +} + +switch (foo) { + case 1: + bar(); + break; + case 2: + quux(); + break; + default: + baz(); + break; +} + +switch (foo) { + case "x": + bar(); + break; + case "y": + default: + baz(); + break; +} + +switch (foo) { + case -1: + bar(); + break; +} + +if (foo !== 0) { + doSomethingIfNotZero(); +} +doSomethingAnyway(); +``` + +## Further Reading + +* [MDN switch statement](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch) + +## Related Rules + +* [default-case](default-case.md) diff --git a/eslint/docs/rules/default-case.md b/eslint/docs/rules/default-case.md new file mode 100644 index 0000000..ba13407 --- /dev/null +++ b/eslint/docs/rules/default-case.md @@ -0,0 +1,126 @@ +# Require Default Case in Switch Statements (default-case) + +Some code conventions require that all `switch` statements have a `default` case, even if the default case is empty, such as: + +```js +switch (foo) { + case 1: + doSomething(); + break; + + case 2: + doSomething(); + break; + + default: + // do nothing +} +``` + +The thinking is that it's better to always explicitly state what the default behavior should be so that it's clear whether or not the developer forgot to include the default behavior by mistake. + +Other code conventions allow you to skip the `default` case so long as there is a comment indicating the omission is intentional, such as: + +```js +switch (foo) { + case 1: + doSomething(); + break; + + case 2: + doSomething(); + break; + + // no default +} +``` + +Once again, the intent here is to show that the developer intended for there to be no default behavior. + +## Rule Details + +This rule aims to require `default` case in `switch` statements. You may optionally include a `// no default` after the last `case` if there is no `default` case. The comment may be in any desired case, such as `// No Default`. + +Examples of **incorrect** code for this rule: + +```js +/*eslint default-case: "error"*/ + +switch (a) { + case 1: + /* code */ + break; +} + +``` + +Examples of **correct** code for this rule: + +```js +/*eslint default-case: "error"*/ + +switch (a) { + case 1: + /* code */ + break; + + default: + /* code */ + break; +} + + +switch (a) { + case 1: + /* code */ + break; + + // no default +} + +switch (a) { + case 1: + /* code */ + break; + + // No Default +} +``` + +## Options + +This rule accepts a single options argument: + +* Set the `commentPattern` option to a regular expression string to change the default `/^no default$/i` comment test pattern + +### commentPattern + +Examples of **correct** code for the `{ "commentPattern": "^skip\\sdefault" }` option: + +```js +/*eslint default-case: ["error", { "commentPattern": "^skip\\sdefault" }]*/ + +switch(a) { + case 1: + /* code */ + break; + + // skip default +} + +switch(a) { + case 1: + /* code */ + break; + + // skip default case +} +``` + +## When Not To Use It + +If you don't want to enforce a `default` case for `switch` statements, you can safely disable this rule. + +## Related Rules + +* [no-fallthrough](no-fallthrough.md) diff --git a/eslint/docs/rules/default-param-last.md b/eslint/docs/rules/default-param-last.md new file mode 100644 index 0000000..97bba70 --- /dev/null +++ b/eslint/docs/rules/default-param-last.md @@ -0,0 +1,35 @@ +# enforce default parameters to be last (default-param-last) + +Putting default parameter at last allows function calls to omit optional tail arguments. + +```js +// Correct: optional argument can be omitted +function createUser(id, isAdmin = false) {} +createUser("tabby") + +// Incorrect: optional argument can **not** be omitted +function createUser(isAdmin = false, id) {} +createUser(undefined, "tabby") +``` + +## Rule Details + +This rule enforces default parameters to be the last of parameters. + +Examples of **incorrect** code for this rule: + +```js +/* eslint default-param-last: ["error"] */ + +function f(a = 0, b) {} + +function f(a, b = 0, c) {} +``` + +Examples of **correct** code for this rule: + +```js +/* eslint default-param-last: ["error"] */ + +function f(a, b = 0) {} +``` diff --git a/eslint/docs/rules/dot-location.md b/eslint/docs/rules/dot-location.md new file mode 100644 index 0000000..82f0d94 --- /dev/null +++ b/eslint/docs/rules/dot-location.md @@ -0,0 +1,85 @@ +# Enforce newline before and after dot (dot-location) + +JavaScript allows you to place newlines before or after a dot in a member expression. + +Consistency in placing a newline before or after the dot can greatly increase readability. + +```js +var a = universe. + galaxy; + +var b = universe + .galaxy; +``` + +## Rule Details + +This rule aims to enforce newline consistency in member expressions. This rule prevents the use of mixed newlines around the dot in a member expression. + +## Options + +The rule takes one option, a string: + +* If it is `"object"` (default), the dot in a member expression should be on the same line as the object portion. +* If it is `"property"`, the dot in a member expression should be on the same line as the property portion. + +### object + +The default `"object"` option requires the dot to be on the same line as the object. + +Examples of **incorrect** code for the default `"object"` option: + +```js +/*eslint dot-location: ["error", "object"]*/ + +var foo = object +.property; +``` + +Examples of **correct** code for the default `"object"` option: + +```js +/*eslint dot-location: ["error", "object"]*/ + +var foo = object. +property; + +var bar = ( + object +). +property; + +var baz = object.property; +``` + +### property + +The `"property"` option requires the dot to be on the same line as the property. + +Examples of **incorrect** code for the `"property"` option: + +```js +/*eslint dot-location: ["error", "property"]*/ + +var foo = object. +property; +``` + +Examples of **correct** code for the `"property"` option: + +```js +/*eslint dot-location: ["error", "property"]*/ + +var foo = object +.property; +var bar = object.property; +``` + +## When Not To Use It + +You can turn this rule off if you are not concerned with the consistency of newlines before or after dots in member expressions. + +## Related Rules + +* [newline-after-var](newline-after-var.md) +* [dot-notation](dot-notation.md) diff --git a/eslint/docs/rules/dot-notation.md b/eslint/docs/rules/dot-notation.md new file mode 100644 index 0000000..a13049a --- /dev/null +++ b/eslint/docs/rules/dot-notation.md @@ -0,0 +1,67 @@ +# Require Dot Notation (dot-notation) + +In JavaScript, one can access properties using the dot notation (`foo.bar`) or square-bracket notation (`foo["bar"]`). However, the dot notation is often preferred because it is easier to read, less verbose, and works better with aggressive JavaScript minimizers. + +```js +foo["bar"]; +``` + +## Rule Details + +This rule is aimed at maintaining code consistency and improving code readability by encouraging use of the dot notation style whenever possible. As such, it will warn when it encounters an unnecessary use of square-bracket notation. + +Examples of **incorrect** code for this rule: + +```js +/*eslint dot-notation: "error"*/ + +var x = foo["bar"]; +``` + +Examples of **correct** code for this rule: + +```js +/*eslint dot-notation: "error"*/ + +var x = foo.bar; + +var x = foo[bar]; // Property name is a variable, square-bracket notation required +``` + +## Options + +This rule accepts a single options argument: + +* Set the `allowKeywords` option to `false` (default is `true`) to follow ECMAScript version 3 compatible style, avoiding dot notation for reserved word properties. +* Set the `allowPattern` option to a regular expression string to allow bracket notation for property names that match a pattern (by default, no pattern is tested). + +### allowKeywords + +Examples of **correct** code for the `{ "allowKeywords": false }` option: + +```js +/*eslint dot-notation: ["error", { "allowKeywords": false }]*/ + +var foo = { "class": "CS 101" } +var x = foo["class"]; // Property name is a reserved word, square-bracket notation required +``` + +### allowPattern + +For example, when preparing data to be sent to an external API, it is often required to use property names that include underscores. If the `camelcase` rule is in effect, these [snake case](https://en.wikipedia.org/wiki/Snake_case) properties would not be allowed. By providing an `allowPattern` to the `dot-notation` rule, these snake case properties can be accessed with bracket notation. + +Examples of **correct** code for the sample `{ "allowPattern": "^[a-z]+(_[a-z]+)+$" }` option: + +```js +/*eslint camelcase: "error"*/ +/*eslint dot-notation: ["error", { "allowPattern": "^[a-z]+(_[a-z]+)+$" }]*/ + +var data = {}; +data.foo_bar = 42; + +var data = {}; +data["fooBar"] = 42; + +var data = {}; +data["foo_bar"] = 42; // no warning +``` diff --git a/eslint/docs/rules/eol-last.md b/eslint/docs/rules/eol-last.md new file mode 100644 index 0000000..ce6203b --- /dev/null +++ b/eslint/docs/rules/eol-last.md @@ -0,0 +1,46 @@ +# require or disallow newline at the end of files (eol-last) + +Trailing newlines in non-empty files are a common UNIX idiom. Benefits of +trailing newlines include the ability to concatenate or append to files as well +as output files to the terminal without interfering with shell prompts. + +## Rule Details + +This rule enforces at least one newline (or absence thereof) at the end +of non-empty files. + +Prior to v0.16.0 this rule also enforced that there was only a single line at +the end of the file. If you still want this behavior, consider enabling +[no-multiple-empty-lines](no-multiple-empty-lines.md) with `maxEOF` and/or +[no-trailing-spaces](no-trailing-spaces.md). + +Examples of **incorrect** code for this rule: + +```js +/*eslint eol-last: ["error", "always"]*/ + +function doSmth() { + var foo = 2; +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint eol-last: ["error", "always"]*/ + +function doSmth() { + var foo = 2; +}\n +``` + +## Options + +This rule has a string option: + +* `"always"` (default) enforces that files end with a newline (LF) +* `"never"` enforces that files do not end with a newline +* `"unix"` (deprecated) is identical to "always" +* `"windows"` (deprecated) is identical to "always", but will use a CRLF character when autofixing + +**Deprecated:** The options `"unix"` and `"windows"` are deprecated. If you need to enforce a specific linebreak style, use this rule in conjunction with `linebreak-style`. diff --git a/eslint/docs/rules/eqeqeq.md b/eslint/docs/rules/eqeqeq.md new file mode 100644 index 0000000..4849f99 --- /dev/null +++ b/eslint/docs/rules/eqeqeq.md @@ -0,0 +1,125 @@ +# Require === and !== (eqeqeq) + +It is considered good practice to use the type-safe equality operators `===` and `!==` instead of their regular counterparts `==` and `!=`. + +The reason for this is that `==` and `!=` do type coercion which follows the rather obscure [Abstract Equality Comparison Algorithm](https://www.ecma-international.org/ecma-262/5.1/#sec-11.9.3). +For instance, the following statements are all considered `true`: + +* `[] == false` +* `[] == ![]` +* `3 == "03"` + +If one of those occurs in an innocent-looking statement such as `a == b` the actual problem is very difficult to spot. + +## Rule Details + +This rule is aimed at eliminating the type-unsafe equality operators. + +Examples of **incorrect** code for this rule: + +```js +/*eslint eqeqeq: "error"*/ + +if (x == 42) { } + +if ("" == text) { } + +if (obj.getStuff() != undefined) { } +``` + +The `--fix` option on the command line automatically fixes some problems reported by this rule. A problem is only fixed if one of the operands is a `typeof` expression, or if both operands are literals with the same type. + +## Options + +### always + +The `"always"` option (default) enforces the use of `===` and `!==` in every situation (except when you opt-in to more specific handling of `null` [see below]). + +Examples of **incorrect** code for the `"always"` option: + +```js +/*eslint eqeqeq: ["error", "always"]*/ + +a == b +foo == true +bananas != 1 +value == undefined +typeof foo == 'undefined' +'hello' != 'world' +0 == 0 +true == true +foo == null + +``` + +Examples of **correct** code for the `"always"` option: + +```js +/*eslint eqeqeq: ["error", "always"]*/ + +a === b +foo === true +bananas !== 1 +value === undefined +typeof foo === 'undefined' +'hello' !== 'world' +0 === 0 +true === true +foo === null + +``` + +This rule optionally takes a second argument, which should be an object with the following supported properties: + +* `"null"`: Customize how this rule treats `null` literals. Possible values: + * `always` (default) - Always use === or !==. + * `never` - Never use === or !== with `null`. + * `ignore` - Do not apply this rule to `null`. + +### smart + +The `"smart"` option enforces the use of `===` and `!==` except for these cases: + +* Comparing two literal values +* Evaluating the value of `typeof` +* Comparing against `null` + +Examples of **incorrect** code for the `"smart"` option: + +```js +/*eslint eqeqeq: ["error", "smart"]*/ + +// comparing two variables requires === +a == b + +// only one side is a literal +foo == true +bananas != 1 + +// comparing to undefined requires === +value == undefined +``` + +Examples of **correct** code for the `"smart"` option: + +```js +/*eslint eqeqeq: ["error", "smart"]*/ + +typeof foo == 'undefined' +'hello' != 'world' +0 == 0 +true == true +foo == null +``` + +### allow-null + +**Deprecated:** Instead of using this option use "always" and pass a "null" option property with value "ignore". This will tell ESLint to always enforce strict equality except when comparing with the `null` literal. + +```js +["error", "always", {"null": "ignore"}] +``` + +## When Not To Use It + +If you don't want to enforce a style for using equality operators, then it's safe to disable this rule. diff --git a/eslint/docs/rules/for-direction.md b/eslint/docs/rules/for-direction.md new file mode 100644 index 0000000..be24f7f --- /dev/null +++ b/eslint/docs/rules/for-direction.md @@ -0,0 +1,24 @@ +# Enforce "for" loop update clause moving the counter in the right direction. (for-direction) + +## Rule Details + +A `for` loop with a stop condition that can never be reached, such as one with a counter that moves in the wrong direction, will run infinitely. While there are occasions when an infinite loop is intended, the convention is to construct such loops as `while` loops. More typically, an infinite for loop is a bug. + +Examples of **incorrect** code for this rule: + +```js +/*eslint for-direction: "error"*/ +for (var i = 0; i < 10; i--) { +} + +for (var i = 10; i >= 0; i++) { +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint for-direction: "error"*/ +for (var i = 0; i < 10; i++) { +} +``` diff --git a/eslint/docs/rules/func-call-spacing.md b/eslint/docs/rules/func-call-spacing.md new file mode 100644 index 0000000..0982915 --- /dev/null +++ b/eslint/docs/rules/func-call-spacing.md @@ -0,0 +1,105 @@ +# require or disallow spacing between function identifiers and their invocations (func-call-spacing) + +When calling a function, developers may insert optional whitespace between the function's name and the parentheses that invoke it. The following pairs of function calls are equivalent: + +```js +alert('Hello'); +alert ('Hello'); + +console.log(42); +console.log (42); + +new Date(); +new Date (); +``` + +## Rule Details + +This rule requires or disallows spaces between the function name and the opening parenthesis that calls it. + +## options + +This rule has a string option: + +- `"never"` (default) disallows space between the function name and the opening parenthesis. +- `"always"` requires space between the function name and the opening parenthesis. + +Further, in `"always"` mode, a second object option is available that contains a single boolean `allowNewlines` property. + +### never + +Examples of **incorrect** code for this rule with the default `"never"` option: + +```js +/*eslint func-call-spacing: ["error", "never"]*/ + +fn (); + +fn +(); +``` + +Examples of **correct** code for this rule with the default `"never"` option: + +```js +/*eslint func-call-spacing: ["error", "never"]*/ + +fn(); +``` + +### always + +Examples of **incorrect** code for this rule with the `"always"` option: + +```js +/*eslint func-call-spacing: ["error", "always"]*/ + +fn(); + +fn +(); +``` + +Examples of **correct** code for this rule with the `"always"` option: + +```js +/*eslint func-call-spacing: ["error", "always"]*/ + +fn (); +``` + +#### allowNewlines + +By default, `"always"` does not allow newlines. To permit newlines when in `"always"` mode, set the `allowNewlines` option to `true`. Newlines are never required. + +Examples of **incorrect** code for this rule with `allowNewlines` option enabled: + +```js +/*eslint func-call-spacing: ["error", "always", { "allowNewlines": true }]*/ + +fn(); +``` + +Examples of **correct** code for this rule with the `allowNewlines` option enabled: + +```js +/*eslint func-call-spacing: ["error", "always", { "allowNewlines": true }]*/ + +fn (); // Newlines are never required. + +fn +(); +``` + +## When Not To Use It + +This rule can safely be turned off if your project does not care about enforcing a consistent style for spacing within function calls. + +## Related Rules + +- [no-spaced-func](no-spaced-func.md) (deprecated) + +## Compatibility + +- **JSCS**: [disallowSpacesInCallExpression](https://jscs-dev.github.io/rule/disallowSpacesInCallExpression) +- **JSCS**: [requireSpacesInCallExpression](https://jscs-dev.github.io/rule/requireSpacesInCallExpression) diff --git a/eslint/docs/rules/func-name-matching.md b/eslint/docs/rules/func-name-matching.md new file mode 100644 index 0000000..53be2a0 --- /dev/null +++ b/eslint/docs/rules/func-name-matching.md @@ -0,0 +1,140 @@ +# require function names to match the name of the variable or property to which they are assigned (func-name-matching) + +## Rule Details + +This rule requires function names to match the name of the variable or property to which they are assigned. The rule will ignore property assignments where the property name is a literal that is not a valid identifier in the ECMAScript version specified in your configuration (default ES5). + +Examples of **incorrect** code for this rule: + +```js +/*eslint func-name-matching: "error"*/ + +var foo = function bar() {}; +foo = function bar() {}; +obj.foo = function bar() {}; +obj['foo'] = function bar() {}; +var obj = {foo: function bar() {}}; +({['foo']: function bar() {}}); +``` + +```js +/*eslint func-name-matching: ["error", "never"] */ + +var foo = function foo() {}; +foo = function foo() {}; +obj.foo = function foo() {}; +obj['foo'] = function foo() {}; +var obj = {foo: function foo() {}}; +({['foo']: function foo() {}}); +``` + +Examples of **correct** code for this rule: + +```js +/*eslint func-name-matching: "error"*/ +/*eslint func-name-matching: ["error", "always"]*/ // these are equivalent +/*eslint-env es6*/ + +var foo = function foo() {}; +var foo = function() {}; +var foo = () => {}; +foo = function foo() {}; + +obj.foo = function foo() {}; +obj['foo'] = function foo() {}; +obj['foo//bar'] = function foo() {}; +obj[foo] = function bar() {}; + +var obj = {foo: function foo() {}}; +var obj = {[foo]: function bar() {}}; +var obj = {'foo//bar': function foo() {}}; +var obj = {foo: function() {}}; + +obj['x' + 2] = function bar(){}; +var [ bar ] = [ function bar(){} ]; +({[foo]: function bar() {}}) + +module.exports = function foo(name) {}; +module['exports'] = function foo(name) {}; +``` + +```js +/*eslint func-name-matching: ["error", "never"] */ +/*eslint-env es6*/ + +var foo = function bar() {}; +var foo = function() {}; +var foo = () => {}; +foo = function bar() {}; + +obj.foo = function bar() {}; +obj['foo'] = function bar() {}; +obj['foo//bar'] = function foo() {}; +obj[foo] = function foo() {}; + +var obj = {foo: function bar() {}}; +var obj = {[foo]: function foo() {}}; +var obj = {'foo//bar': function foo() {}}; +var obj = {foo: function() {}}; + +obj['x' + 2] = function bar(){}; +var [ bar ] = [ function bar(){} ]; +({[foo]: function bar() {}}) + +module.exports = function foo(name) {}; +module['exports'] = function foo(name) {}; +``` + +## Options + +This rule takes an optional string of "always" or "never" (when omitted, it defaults to "always"), and an optional options object with two properties `considerPropertyDescriptor` and `includeCommonJSModuleExports`. + +### considerPropertyDescriptor + +A boolean value that defaults to `false`. If `considerPropertyDescriptor` is set to true, the check will take into account the use of `Object.create`, `Object.defineProperty`, `Object.defineProperties`, and `Reflect.defineProperty`. + +Examples of **correct** code for the `{ considerPropertyDescriptor: true }` option: + +```js +/*eslint func-name-matching: ["error", { "considerPropertyDescriptor": true }]*/ +/*eslint func-name-matching: ["error", "always", { "considerPropertyDescriptor": true }]*/ // these are equivalent +var obj = {}; +Object.create(obj, {foo:{value: function foo() {}}}); +Object.defineProperty(obj, 'bar', {value: function bar() {}}); +Object.defineProperties(obj, {baz:{value: function baz() {} }}); +Reflect.defineProperty(obj, 'foo', {value: function foo() {}}); +``` + +Examples of **incorrect** code for the `{ considerPropertyDescriptor: true }` option: + +```js +/*eslint func-name-matching: ["error", { "considerPropertyDescriptor": true }]*/ +/*eslint func-name-matching: ["error", "always", { "considerPropertyDescriptor": true }]*/ // these are equivalent +var obj = {}; +Object.create(obj, {foo:{value: function bar() {}}}); +Object.defineProperty(obj, 'bar', {value: function baz() {}}); +Object.defineProperties(obj, {baz:{value: function foo() {} }}); +Reflect.defineProperty(obj, 'foo', {value: function value() {}}); +``` + +### includeCommonJSModuleExports + +A boolean value that defaults to `false`. If `includeCommonJSModuleExports` is set to true, `module.exports` and `module["exports"]` will be checked by this rule. + +Examples of **incorrect** code for the `{ includeCommonJSModuleExports: true }` option: + +```js +/*eslint func-name-matching: ["error", { "includeCommonJSModuleExports": true }]*/ +/*eslint func-name-matching: ["error", "always", { "includeCommonJSModuleExports": true }]*/ // these are equivalent + +module.exports = function foo(name) {}; +module['exports'] = function foo(name) {}; +``` + +## When Not To Use It + +Do not use this rule if you want to allow named functions to have different names from the variable or property to which they are assigned. + +## Compatibility + +* **JSCS**: [requireMatchingFunctionName](https://jscs-dev.github.io/rule/requireMatchingFunctionName) diff --git a/eslint/docs/rules/func-names.md b/eslint/docs/rules/func-names.md new file mode 100644 index 0000000..27b9cc2 --- /dev/null +++ b/eslint/docs/rules/func-names.md @@ -0,0 +1,210 @@ +# Require or disallow named `function` expressions (func-names) + +A pattern that's becoming more common is to give function expressions names to aid in debugging. For example: + +```js +Foo.prototype.bar = function bar() {}; +``` + +Adding the second `bar` in the above example is optional. If you leave off the function name then when the function throws an exception you are likely to get something similar to `anonymous function` in the stack trace. If you provide the optional name for a function expression then you will get the name of the function expression in the stack trace. + +## Rule Details + +This rule can enforce or disallow the use of named function expressions. + +## Options + +This rule has a string option: + +* `"always"` (default) requires function expressions to have a name +* `"as-needed"` requires function expressions to have a name, if the name cannot be assigned automatically in an ES6 environment +* `"never"` disallows named function expressions, except in recursive functions, where a name is needed + +This rule has an object option: + +* `"generators": "always" | "as-needed" | "never"` + * `"always"` require named generators + * `"as-needed"` require named generators if the name cannot be assigned automatically in an ES6 environment. + * `"never"` disallow named generators where possible. + +When a value for `generators` is not provided the behavior for generator functions falls back to the base option. + +Please note that `"always"` and `"as-needed"` require function expressions and function declarations in `export default` declarations to have a name. + +### always + +Examples of **incorrect** code for this rule with the default `"always"` option: + +```js +/*eslint func-names: ["error", "always"]*/ + +Foo.prototype.bar = function() {}; + +const cat = { + meow: function() {} +} + +(function() { + // ... +}()) + +export default function() {} +``` + +Examples of **correct** code for this rule with the default `"always"` option: + +```js +/*eslint func-names: ["error", "always"]*/ + +Foo.prototype.bar = function bar() {}; + +const cat = { + meow() {} +} + +(function bar() { + // ... +}()) + +export default function foo() {} +``` + +### as-needed + +ECMAScript 6 introduced a `name` property on all functions. The value of `name` is determined by evaluating the code around the function to see if a name can be inferred. For example, a function assigned to a variable will automatically have a `name` property equal to the name of the variable. The value of `name` is then used in stack traces for easier debugging. + +Examples of **incorrect** code for this rule with the `"as-needed"` option: + +```js +/*eslint func-names: ["error", "as-needed"]*/ + +Foo.prototype.bar = function() {}; + +(function() { + // ... +}()) + +export default function() {} +``` + +Examples of **correct** code for this rule with the `"as-needed"` option: + +```js +/*eslint func-names: ["error", "as-needed"]*/ + +var bar = function() {}; + +const cat = { + meow: function() {} +} + +(function bar() { + // ... +}()) + +export default function foo() {} +``` + +### never + +Examples of **incorrect** code for this rule with the `"never"` option: + +```js +/*eslint func-names: ["error", "never"]*/ + +Foo.prototype.bar = function bar() {}; + +(function bar() { + // ... +}()) +``` + +Examples of **correct** code for this rule with the `"never"` option: + +```js +/*eslint func-names: ["error", "never"]*/ + +Foo.prototype.bar = function() {}; + +(function() { + // ... +}()) +``` + +### generators + +Examples of **incorrect** code for this rule with the `"always", { "generators": "as-needed" }` options: + +```js +/*eslint func-names: ["error", "always", { "generators": "as-needed" }]*/ + +(function*() { + // ... +}()) +``` + +Examples of **correct** code for this rule with the `"always", { "generators": "as-needed" }` options: + +```js +/*eslint func-names: ["error", "always", { "generators": "as-needed" }]*/ + +var foo = function*() {}; +``` + +Examples of **incorrect** code for this rule with the `"always", { "generators": "never" }` options: + +```js +/*eslint func-names: ["error", "always", { "generators": "never" }]*/ + +var foo = bar(function *baz() {}); +``` + +Examples of **correct** code for this rule with the `"always", { "generators": "never" }` options: + +```js +/*eslint func-names: ["error", "always", { "generators": "never" }]*/ + +var foo = bar(function *() {}); +``` + +Examples of **incorrect** code for this rule with the `"as-needed", { "generators": "never" }` options: + +```js +/*eslint func-names: ["error", "as-needed", { "generators": "never" }]*/ + +var foo = bar(function *baz() {}); +``` + +Examples of **correct** code for this rule with the `"as-needed", { "generators": "never" }` options: + +```js +/*eslint func-names: ["error", "as-needed", { "generators": "never" }]*/ + +var foo = bar(function *() {}); +``` + +Examples of **incorrect** code for this rule with the `"never", { "generators": "always" }` options: + +```js +/*eslint func-names: ["error", "never", { "generators": "always" }]*/ + +var foo = bar(function *() {}); +``` + +Examples of **correct** code for this rule with the `"never", { "generators": "always" }` options: + +```js +/*eslint func-names: ["error", "never", { "generators": "always" }]*/ + +var foo = bar(function *baz() {}); +``` + +## Further Reading + +* [Functions Explained](http://markdaggett.com/blog/2013/02/15/functions-explained/) +* [Function Names in ES6](http://2ality.com/2015/09/function-names-es6.html) + +## Compatibility + +* **JSCS**: [requireAnonymousFunctions](https://jscs-dev.github.io/rule/requireAnonymousFunctions) +* **JSCS**: [disallowAnonymousFunctions](https://jscs-dev.github.io/rule/disallowAnonymousFunctions) diff --git a/eslint/docs/rules/func-style.md b/eslint/docs/rules/func-style.md new file mode 100644 index 0000000..c197403 --- /dev/null +++ b/eslint/docs/rules/func-style.md @@ -0,0 +1,131 @@ +# enforce the consistent use of either `function` declarations or expressions (func-style) + +There are two ways of defining functions in JavaScript: `function` declarations and `function` expressions. Declarations contain the `function` keyword first, followed by a name and then its arguments and the function body, for example: + +```js +function doSomething() { + // ... +} +``` + +Equivalent function expressions begin with the `var` keyword, followed by a name and then the function itself, such as: + +```js +var doSomething = function() { + // ... +}; +``` + +The primary difference between `function` declarations and `function expressions` is that declarations are *hoisted* to the top of the scope in which they are defined, which allows you to write code that uses the function before its declaration. For example: + +```js +doSomething(); + +function doSomething() { + // ... +} +``` + +Although this code might seem like an error, it actually works fine because JavaScript engines hoist the `function` declarations to the top of the scope. That means this code is treated as if the declaration came before the invocation. + +For `function` expressions, you must define the function before it is used, otherwise it causes an error. Example: + +```js +doSomething(); // error! + +var doSomething = function() { + // ... +}; +``` + +In this case, `doSomething()` is undefined at the time of invocation and so causes a runtime error. + +Due to these different behaviors, it is common to have guidelines as to which style of function should be used. There is really no correct or incorrect choice here, it is just a preference. + +## Rule Details + +This rule enforces a particular type of `function` style throughout a JavaScript file, either declarations or expressions. You can specify which you prefer in the configuration. + +## Options + +This rule has a string option: + +* `"expression"` (default) requires the use of function expressions instead of function declarations +* `"declaration"` requires the use of function declarations instead of function expressions + +This rule has an object option for an exception: + +* `"allowArrowFunctions"`: `true` (default `false`) allows the use of arrow functions. This option applies only when the string option is set to `"declaration"` (arrow functions are always allowed when the string option is set to `"expression"`, regardless of this option) + +### expression + +Examples of **incorrect** code for this rule with the default `"expression"` option: + +```js +/*eslint func-style: ["error", "expression"]*/ + +function foo() { + // ... +} +``` + +Examples of **correct** code for this rule with the default `"expression"` option: + +```js +/*eslint func-style: ["error", "expression"]*/ + +var foo = function() { + // ... +}; + +var foo = () => {}; + +// allowed as allowArrowFunctions : false is applied only for declaration +``` + +### declaration + +Examples of **incorrect** code for this rule with the `"declaration"` option: + +```js +/*eslint func-style: ["error", "declaration"]*/ + +var foo = function() { + // ... +}; + +var foo = () => {}; +``` + +Examples of **correct** code for this rule with the `"declaration"` option: + +```js +/*eslint func-style: ["error", "declaration"]*/ + +function foo() { + // ... +} + +// Methods (functions assigned to objects) are not checked by this rule +SomeObject.foo = function() { + // ... +}; +``` + +### allowArrowFunctions + +Examples of additional **correct** code for this rule with the `"declaration", { "allowArrowFunctions": true }` options: + +```js +/*eslint func-style: ["error", "declaration", { "allowArrowFunctions": true }]*/ + +var foo = () => {}; +``` + +## When Not To Use It + +If you want to allow developers to each decide how they want to write functions on their own, then you can disable this rule. + +## Further Reading + +* [JavaScript Scoping and Hoisting](http://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html) diff --git a/eslint/docs/rules/function-call-argument-newline.md b/eslint/docs/rules/function-call-argument-newline.md new file mode 100644 index 0000000..bdb9880 --- /dev/null +++ b/eslint/docs/rules/function-call-argument-newline.md @@ -0,0 +1,202 @@ +# enforce line breaks between arguments of a function call (function-call-argument-newline) + +A number of style guides require or disallow line breaks between arguments of a function call. + +## Rule Details + +This rule enforces line breaks between arguments of a function call. + +## Options + +This rule has a string option: + +* `"always"` (default) requires line breaks between arguments +* `"never"` disallows line breaks between arguments +* `"consistent"` requires consistent usage of line breaks between arguments + + +### always + +Examples of **incorrect** code for this rule with the default `"always"` option: + +```js +/*eslint function-call-argument-newline: ["error", "always"]*/ + +foo("one", "two", "three"); + +bar("one", "two", { + one: 1, + two: 2 +}); + +baz("one", "two", (x) => { + console.log(x); +}); +``` + +Examples of **correct** code for this rule with the default `"always"` option: + +```js +/*eslint function-call-argument-newline: ["error", "always"]*/ + +foo( + "one", + "two", + "three" +); + +bar( + "one", + "two", + { one: 1, two: 2 } +); +// or +bar( + "one", + "two", + { + one: 1, + two: 2 + } +); + +baz( + "one", + "two", + (x) => { + console.log(x); + } +); +``` + + +### never + +Examples of **incorrect** code for this rule with the default `"never"` option: + +```js +/*eslint function-call-argument-newline: ["error", "never"]*/ + +foo( + "one", + "two", "three" +); + +bar( + "one", + "two", { + one: 1, + two: 2 + } +); + +baz( + "one", + "two", (x) => { + console.log(x); + } +); +``` + +Examples of **correct** code for this rule with the `"never"` option: + +```js +/*eslint function-call-argument-newline: ["error", "never"]*/ + +foo("one", "two", "three"); +// or +foo( + "one", "two", "three" +); + +bar("one", "two", { one: 1, two: 2 }); +// or +bar("one", "two", { + one: 1, + two: 2 +}); + +baz("one", "two", (x) => { + console.log(x); +}); +``` + +### consistent + +Examples of **incorrect** code for this rule with the default `"consistent"` option: + +```js +/*eslint function-call-argument-newline: ["error", "consistent"]*/ + +foo("one", "two", + "three"); +//or +foo("one", + "two", "three"); + +bar("one", "two", + { one: 1, two: 2} +); + +baz("one", "two", + (x) => { console.log(x); } +); +``` + +Examples of **correct** code for this rule with the default `"consistent"` option: + +```js +/*eslint function-call-argument-newline: ["error", "consistent"]*/ + +foo("one", "two", "three"); +// or +foo( + "one", + "two", + "three" +); + +bar("one", "two", { + one: 1, + two: 2 +}); +// or +bar( + "one", + "two", + { one: 1, two: 2 } +); +// or +bar( + "one", + "two", + { + one: 1, + two: 2 + } +); + +baz("one", "two", (x) => { + console.log(x); +}); +// or +baz( + "one", + "two", + (x) => { + console.log(x); + } +); +``` + + +## When Not To Use It + +If you don't want to enforce line breaks between arguments, don't enable this rule. + +## Related Rules + +* [function-paren-newline](function-paren-newline.md) +* [func-call-spacing](func-call-spacing.md) +* [object-property-newline](object-property-newline.md) +* [array-element-newline](array-element-newline.md) diff --git a/eslint/docs/rules/function-paren-newline.md b/eslint/docs/rules/function-paren-newline.md new file mode 100644 index 0000000..ab86680 --- /dev/null +++ b/eslint/docs/rules/function-paren-newline.md @@ -0,0 +1,328 @@ +# enforce consistent line breaks inside function parentheses (function-paren-newline) + +Many style guides require or disallow newlines inside of function parentheses. + +## Rule Details + +This rule enforces consistent line breaks inside parentheses of function parameters or arguments. + +### Options + +This rule has a single option, which can either be a string or an object. + +* `"always"` requires line breaks inside all function parentheses. +* `"never"` disallows line breaks inside all function parentheses. +* `"multiline"` (default) requires linebreaks inside function parentheses if any of the parameters/arguments have a line break between them. Otherwise, it disallows linebreaks. +* `"multiline-arguments"` works like `multiline` but allows linebreaks inside function parentheses if there is only one parameter/argument. +* `"consistent"` requires consistent usage of linebreaks for each pair of parentheses. It reports an error if one parenthesis in the pair has a linebreak inside it and the other parenthesis does not. +* `{ "minItems": value }` requires linebreaks inside function parentheses if the number of parameters/arguments is at least `value`. Otherwise, it disallows linebreaks. + +Example configurations: + +```json +{ + "rules": { + "function-paren-newline": ["error", "never"] + } +} +``` + +```json +{ + "rules": { + "function-paren-newline": ["error", { "minItems": 3 }] + } +} +``` + +Examples of **incorrect** code for this rule with the `"always"` option: + +```js +/* eslint function-paren-newline: ["error", "always"] */ + +function foo(bar, baz) {} + +var foo = function(bar, baz) {}; + +var foo = (bar, baz) => {}; + +foo(bar, baz); +``` + +Examples of **correct** code for this rule with the `"always"` option: + +```js +/* eslint function-paren-newline: ["error", "always"] */ + +function foo( + bar, + baz +) {} + +var foo = function( + bar, baz +) {}; + +var foo = ( + bar, + baz +) => {}; + +foo( + bar, + baz +); +``` + +Examples of **incorrect** code for this rule with the `"never"` option: + +```js +/* eslint function-paren-newline: ["error", "never"] */ + +function foo( + bar, + baz +) {} + +var foo = function( + bar, baz +) {}; + +var foo = ( + bar, + baz +) => {}; + +foo( + bar, + baz +); +``` + +Examples of **correct** code for this rule with the `"never"` option: + +```js +/* eslint function-paren-newline: ["error", "never"] */ + +function foo(bar, baz) {} + +function foo(bar, + baz) {} + +var foo = function(bar, baz) {}; + +var foo = (bar, baz) => {}; + +foo(bar, baz); + +foo(bar, + baz); +``` + +Examples of **incorrect** code for this rule with the default `"multiline"` option: + +```js +/* eslint function-paren-newline: ["error", "multiline"] */ + +function foo(bar, + baz +) {} + +var foo = function( + bar, baz +) {}; + +var foo = ( + bar, + baz) => {}; + +foo(bar, + baz); + +foo( + function() { + return baz; + } +); +``` + +Examples of **correct** code for this rule with the default `"multiline"` option: + +```js +/* eslint function-paren-newline: ["error", "multiline"] */ + +function foo(bar, baz) {} + +var foo = function( + bar, + baz +) {}; + +var foo = (bar, baz) => {}; + +foo(bar, baz, qux); + +foo( + bar, + baz, + qux +); + +foo(function() { + return baz; +}); +``` + +Examples of **incorrect** code for this rule with the `"consistent"` option: + +```js +/* eslint function-paren-newline: ["error", "consistent"] */ + +function foo(bar, + baz +) {} + +var foo = function(bar, + baz +) {}; + +var foo = ( + bar, + baz) => {}; + +foo( + bar, + baz); + +foo( + function() { + return baz; + }); +``` + +Examples of **correct** code for this rule with the `"consistent"` option: + +```js +/* eslint function-paren-newline: ["error", "consistent"] */ + +function foo(bar, + baz) {} + +var foo = function(bar, baz) {}; + +var foo = ( + bar, + baz +) => {}; + +foo( + bar, baz +); + +foo( + function() { + return baz; + } +); +``` + +Examples of **incorrect** code for this rule with the `"multiline-arguments"` option: + +```js +/* eslint function-paren-newline: ["error", "multiline-arguments"] */ + +function foo(bar, + baz +) {} + +var foo = function(bar, + baz +) {}; + +var foo = ( + bar, + baz) => {}; + +foo( + bar, + baz); + +foo( + bar, qux, + baz +); +``` + +Examples of **correct** code for this rule with the consistent `"multiline-arguments"` option: + +```js +/* eslint function-paren-newline: ["error", "multiline-arguments"] */ + +function foo( + bar, + baz +) {} + +var foo = function(bar, baz) {}; + +var foo = ( + bar +) => {}; + +foo( + function() { + return baz; + } +); +``` + +Examples of **incorrect** code for this rule with the `{ "minItems": 3 }` option: + +```js +/* eslint function-paren-newline: ["error", { "minItems": 3 }] */ + +function foo( + bar, + baz +) {} + +function foo(bar, baz, qux) {} + +var foo = function( + bar, baz +) {}; + +var foo = (bar, + baz) => {}; + +foo(bar, + baz); +``` + +Examples of **correct** code for this rule with the `{ "minItems": 3 }` option: + +```js +/* eslint function-paren-newline: ["error", { "minItems": 3 }] */ + +function foo(bar, baz) {} + +var foo = function( + bar, + baz, + qux +) {}; + +var foo = ( + bar, baz, qux +) => {}; + +foo(bar, baz); + +foo( + bar, baz, qux +); +``` + +## When Not To Use It + +If don't want to enforce consistent linebreaks inside function parentheses, do not turn on this rule. diff --git a/eslint/docs/rules/generator-star-spacing.md b/eslint/docs/rules/generator-star-spacing.md new file mode 100644 index 0000000..00a9ada --- /dev/null +++ b/eslint/docs/rules/generator-star-spacing.md @@ -0,0 +1,207 @@ +# Enforce spacing around the * in generator functions (generator-star-spacing) + +Generators are a new type of function in ECMAScript 6 that can return multiple values over time. +These special functions are indicated by placing an `*` after the `function` keyword. + +Here is an example of a generator function: + +```js +/*eslint-env es6*/ + +function* generator() { + yield "44"; + yield "55"; +} +``` + +This is also valid: + +```js +/*eslint-env es6*/ + +function *generator() { + yield "44"; + yield "55"; +} +``` + +This is valid as well: + +```js +/*eslint-env es6*/ + +function * generator() { + yield "44"; + yield "55"; +} +``` + +To keep a sense of consistency when using generators this rule enforces a single position for the `*`. + +## Rule Details + +This rule aims to enforce spacing around the `*` of generator functions. + +## Options + +The rule takes one option, an object, which has two keys `before` and `after` having boolean values `true` or `false`. + +* `before` enforces spacing between the `*` and the `function` keyword. + If it is `true`, a space is required, otherwise spaces are disallowed. + + In object literal shorthand methods, spacing before the `*` is not checked, as they lack a `function` keyword. + +* `after` enforces spacing between the `*` and the function name (or the opening parenthesis for anonymous generator functions). + If it is `true`, a space is required, otherwise spaces are disallowed. + +The default is `{"before": true, "after": false}`. + +An example configuration: + +```json +"generator-star-spacing": ["error", {"before": true, "after": false}] +``` + +And the option has shorthand as a string keyword: + +* `{"before": true, "after": false}` → `"before"` +* `{"before": false, "after": true}` → `"after"` +* `{"before": true, "after": true}` → `"both"` +* `{"before": false, "after": false}` → `"neither"` + +An example of shorthand configuration: + +```json +"generator-star-spacing": ["error", "after"] +``` + +Additionally, this rule allows further configurability via overrides per function type. + +* `named` provides overrides for named functions +* `anonymous` provides overrides for anonymous functions +* `method` provides overrides for class methods or property function shorthand + +An example of a configuration with overrides: + +```json +"generator-star-spacing": ["error", { + "before": false, + "after": true, + "anonymous": "neither", + "method": {"before": true, "after": true} +}] +``` + +In the example configuration above, the top level "before" and "after" options define the default behavior of +the rule, while the "anonymous" and "method" options override the default behavior. +Overrides can be either an object with "before" and "after", or a shorthand string as above. + +## Examples + +### before + +Examples of **correct** code for this rule with the `"before"` option: + +```js +/*eslint generator-star-spacing: ["error", {"before": true, "after": false}]*/ +/*eslint-env es6*/ + +function *generator() {} + +var anonymous = function *() {}; + +var shorthand = { *generator() {} }; +``` + +### after + +Examples of **correct** code for this rule with the `"after"` option: + +```js +/*eslint generator-star-spacing: ["error", {"before": false, "after": true}]*/ +/*eslint-env es6*/ + +function* generator() {} + +var anonymous = function* () {}; + +var shorthand = { * generator() {} }; +``` + +### both + +Examples of **correct** code for this rule with the `"both"` option: + +```js +/*eslint generator-star-spacing: ["error", {"before": true, "after": true}]*/ +/*eslint-env es6*/ + +function * generator() {} + +var anonymous = function * () {}; + +var shorthand = { * generator() {} }; +``` + +### neither + +Examples of **correct** code for this rule with the `"neither"` option: + +```js +/*eslint generator-star-spacing: ["error", {"before": false, "after": false}]*/ +/*eslint-env es6*/ + +function*generator() {} + +var anonymous = function*() {}; + +var shorthand = { *generator() {} }; +``` + +Examples of **incorrect** code for this rule with overrides present: + +```js +/*eslint generator-star-spacing: ["error", { + "before": false, + "after": true, + "anonymous": "neither", + "method": {"before": true, "after": true} +}]*/ +/*eslint-env es6*/ + +function * generator() {} + +var anonymous = function* () {}; + +var shorthand = { *generator() {} }; + +class Class { static* method() {} } +``` + +Examples of **correct** code for this rule with overrides present: + +```js +/*eslint generator-star-spacing: ["error", { + "before": false, + "after": true, + "anonymous": "neither", + "method": {"before": true, "after": true} +}]*/ +/*eslint-env es6*/ + +function* generator() {} + +var anonymous = function*() {}; + +var shorthand = { * generator() {} }; + +class Class { static * method() {} } +``` + +## When Not To Use It + +If your project will not be using generators or you are not concerned with spacing consistency, you do not need this rule. + +## Further Reading + +* [Understanding ES6: Generators](https://leanpub.com/understandinges6/read/#leanpub-auto-generators) diff --git a/eslint/docs/rules/generator-star.md b/eslint/docs/rules/generator-star.md new file mode 100644 index 0000000..f344cc4 --- /dev/null +++ b/eslint/docs/rules/generator-star.md @@ -0,0 +1,126 @@ +# generator-star: enforce consistent spacing around the asterisk in generator functions + +(removed) This rule was **removed** in ESLint v1.0 and **replaced** by the [generator-star-spacing](generator-star-spacing.md) rule. + +Generators are a new type of function in ECMAScript 6 that can return multiple values over time. +These special functions are indicated by placing an `*` after the `function` keyword. + +Here is an example of a generator function: + +```js +/*eslint-env es6*/ + +function* generator() { + yield "44"; + yield "55"; +} +``` + +This is also valid: + +```js +/*eslint-env es6*/ + +function *generator() { + yield "44"; + yield "55"; +} +``` + +This is valid as well: + +```js +/*eslint-env es6*/ + +function * generator() { + yield "44"; + yield "55"; +} +``` + +To keep a sense of consistency when using generators this rule enforces a single position for the `*`. + +## Rule Details + +This rule enforces that the `*` is either placed next to the `function` keyword or the name of the function. The single +option for this rule is a string specifying the placement of the asterisk. For this option you may pass +`"start"`, `"middle"` or `"end"`. The default is `"end"`. + +You can set the style in configuration like this: + +```json +"generator-star": ["error", "start"] +``` + +When using `"start"` this placement will be enforced: + +```js +/*eslint-env es6*/ + +function* generator() { +} +``` + +When using `"middle"` this placement will be enforced: + +```js +/*eslint-env es6*/ + +function * generator() { +} +``` + +When using `"end"` this placement will be enforced: + +```js +/*eslint-env es6*/ + +function *generator() { +} +``` + +When using the expression syntax `"start"` will be enforced here: + +```js +/*eslint-env es6*/ + +var generator = function* () { +} +``` + +When using the expression syntax `"middle"` will be enforced here: + +```js +/*eslint-env es6*/ + +var generator = function * () { +} +``` + +When using the expression syntax `"end"` will be enforced here: + +```js +/*eslint-env es6*/ + +var generator = function *() { +} +``` + +When using the expression syntax this is valid for both `"start"` and `"end"`: + +```js +/*eslint-env es6*/ + +var generator = function*() { +} +``` + +The shortened object literal syntax for generators is not affected by this rule. + +## When Not To Use It + +If your project will not be using generators you do not need this rule. + +## Further Reading + +* [Understanding ES6: Generators](https://leanpub.com/understandinges6/read/#leanpub-auto-generators) diff --git a/eslint/docs/rules/getter-return.md b/eslint/docs/rules/getter-return.md new file mode 100644 index 0000000..c49f39d --- /dev/null +++ b/eslint/docs/rules/getter-return.md @@ -0,0 +1,97 @@ +# Enforces that a return statement is present in property getters (getter-return) + +The get syntax binds an object property to a function that will be called when that property is looked up. It was first introduced in ECMAScript 5: + +```js + var p = { + get name(){ + return "nicholas"; + } + }; + + Object.defineProperty(p, "age", { + get: function (){ + return 17; + } + }); +``` + +Note that every `getter` is expected to return a value. + +## Rule Details + +This rule enforces that a return statement is present in property getters. + +Examples of **incorrect** code for this rule: + +```js +/*eslint getter-return: "error"*/ + +p = { + get name(){ + // no returns. + } +}; + +Object.defineProperty(p, "age", { + get: function (){ + // no returns. + } +}); + +class P{ + get name(){ + // no returns. + } +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint getter-return: "error"*/ + +p = { + get name(){ + return "nicholas"; + } +}; + +Object.defineProperty(p, "age", { + get: function (){ + return 18; + } +}); + +class P{ + get name(){ + return "nicholas"; + } +} +``` + +## Options + +This rule has an object option: + +* `"allowImplicit": false` (default) disallows implicitly returning `undefined` with a `return` statement. + +Examples of **correct** code for the `{ "allowImplicit": true }` option: + +```js +/*eslint getter-return: ["error", { allowImplicit: true }]*/ +p = { + get name(){ + return; // return undefined implicitly. + } +}; +``` + +## When Not To Use It + +If your project will not be using ES5 property getters you do not need this rule. + +## Further Reading + +* [MDN: Functions getter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get) +* [Understanding ES6: Accessor Properties](https://leanpub.com/understandinges6/read/#leanpub-auto-accessor-properties) diff --git a/eslint/docs/rules/global-require.md b/eslint/docs/rules/global-require.md new file mode 100644 index 0000000..c31134d --- /dev/null +++ b/eslint/docs/rules/global-require.md @@ -0,0 +1,89 @@ +# Enforce require() on the top-level module scope (global-require) + +In Node.js, module dependencies are included using the `require()` function, such as: + +```js +var fs = require("fs"); +``` + +While `require()` may be called anywhere in code, some style guides prescribe that it should be called only in the top level of a module to make it easier to identify dependencies. For instance, it's arguably harder to identify dependencies when they are deeply nested inside of functions and other statements: + +```js +function foo() { + + if (condition) { + var fs = require("fs"); + } +} +``` + +Since `require()` does a synchronous load, it can cause performance problems when used in other locations. + +Further, ES6 modules mandate that `import` and `export` statements can only occur in the top level of the module's body. + +## Rule Details + +This rule requires all calls to `require()` to be at the top level of the module, similar to ES6 `import` and `export` statements, which also can occur only at the top level. + +Examples of **incorrect** code for this rule: + +```js +/*eslint global-require: "error"*/ +/*eslint-env es6*/ + +// calling require() inside of a function is not allowed +function readFile(filename, callback) { + var fs = require('fs'); + fs.readFile(filename, callback) +} + +// conditional requires like this are also not allowed +if (DEBUG) { require('debug'); } + +// a require() in a switch statement is also flagged +switch(x) { case '1': require('1'); break; } + +// you may not require() inside an arrow function body +var getModule = (name) => require(name); + +// you may not require() inside of a function body as well +function getModule(name) { return require(name); } + +// you may not require() inside of a try/catch block +try { + require(unsafeModule); +} catch(e) { + console.log(e); +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint global-require: "error"*/ + +// all these variations of require() are ok +require('x'); +var y = require('y'); +var z; +z = require('z').initialize(); + +// requiring a module and using it in a function is ok +var fs = require('fs'); +function readFile(filename, callback) { + fs.readFile(filename, callback) +} + +// you can use a ternary to determine which module to require +var logger = DEBUG ? require('dev-logger') : require('logger'); + +// if you want you can require() at the end of your module +function doSomethingA() {} +function doSomethingB() {} +var x = require("x"), + z = require("z"); +``` + +## When Not To Use It + +If you have a module that must be initialized with information that comes from the file-system or if a module is only used in very rare situations and will cause significant overhead to load it may make sense to disable the rule. If you need to `require()` an optional dependency inside of a `try`/`catch`, you can disable this rule for just that dependency using the `// eslint-disable-line global-require` comment. diff --git a/eslint/docs/rules/global-strict.md b/eslint/docs/rules/global-strict.md new file mode 100644 index 0000000..5fbba7b --- /dev/null +++ b/eslint/docs/rules/global-strict.md @@ -0,0 +1,61 @@ +# global-strict: require or disallow strict mode directives in the global scope + +(removed) This rule was **removed** in ESLint v1.0 and **replaced** by the [strict](strict.md) rule. The `"global"` option in the new rule is most similar to the removed rule. + +Strict mode is enabled by using the following pragma in your code: + +```js +"use strict"; +``` + +When used globally, as in this example, the strict mode pragma applies to all code within a single file. This can be dangerous if you concatenate scripts together before serving them to a browser. For instance, if you have a file running in strict mode and you concatenate that file with jQuery, the strict mode now also applies to jQuery and may cause errors. + +However, if you're using Node.js, you may want to turn strict mode on globally. Files are typically not concatenated together in Node.js projects and therefore the risk of applying strict mode accidentally is minimal. Further, since every file in Node.js has its own scope, global strict mode only effects the single file in which it is placed. + +## Rule Details + +This rule requires or disallows global strict mode invoked by a `"use strict"` pragma in the global scope. + +The following pattern is under strict mode globally and is considered valid with the `"always"` option and a warning with the `"never"` option. + +```js +"use strict"; + +function foo() { + return true; +} +``` + +The following patterns apply strict mode only to functions so are valid with the `"never"` option but are problems with the `"always"` option. + +```js +function foo() { + "use strict"; + + return true; +} + +(function() { + "use strict"; + + // other code +}()); +``` + +## Options + +```json +"global-strict": ["error", "always"] +``` + +Requires that every file have a top-level `"use strict"` statement. + +```json +"global-strict": ["error", "never"] +``` + +Warns whenever `"use strict"` is used in the global scope such that it could contaminate concatenated files. + +## When Not To Use It + +When a project may use non-strict-mode code side by side with strict-mode code and the files are not concatenated, the decision to use global strict mode can be made on an individual basis, rendering this rule unnecessary. diff --git a/eslint/docs/rules/grouped-accessor-pairs.md b/eslint/docs/rules/grouped-accessor-pairs.md new file mode 100644 index 0000000..60848d1 --- /dev/null +++ b/eslint/docs/rules/grouped-accessor-pairs.md @@ -0,0 +1,326 @@ +# Require grouped accessor pairs in object literals and classes (grouped-accessor-pairs) + +A getter and setter for the same property don't necessarily have to be defined adjacent to each other. + +For example, the following statements would create the same object: + +```js +var o = { + get a() { + return this.val; + }, + set a(value) { + this.val = value; + }, + b: 1 +}; + +var o = { + get a() { + return this.val; + }, + b: 1, + set a(value) { + this.val = value; + } +}; +``` + +While it is allowed to define the pair for a getter or a setter anywhere in an object or class definition, it's considered a best practice to group accessor functions for the same property. + +In other words, if a property has a getter and a setter, the setter should be defined right after the getter, or vice versa. + +## Rule Details + +This rule requires grouped definitions of accessor functions for the same property in object literals, class declarations and class expressions. + +Optionally, this rule can also enforce consistent order (`getBeforeSet` or `setBeforeGet`). + +This rule does not enforce the existence of the pair for a getter or a setter. See [accessor-pairs](accessor-pairs.md) if you also want to enforce getter/setter pairs. + +Examples of **incorrect** code for this rule: + +```js +/*eslint grouped-accessor-pairs: "error"*/ + +var foo = { + get a() { + return this.val; + }, + b: 1, + set a(value) { + this.val = value; + } +}; + +var bar = { + set b(value) { + this.val = value; + }, + a: 1, + get b() { + return this.val; + } +} + +class Foo { + set a(value) { + this.val = value; + } + b(){} + get a() { + return this.val; + } +} + +const Bar = class { + static get a() { + return this.val; + } + b(){} + static set a(value) { + this.val = value; + } +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint grouped-accessor-pairs: "error"*/ + +var foo = { + get a() { + return this.val; + }, + set a(value) { + this.val = value; + }, + b: 1 +}; + +var bar = { + set b(value) { + this.val = value; + }, + get b() { + return this.val; + }, + a: 1 +} + +class Foo { + set a(value) { + this.val = value; + } + get a() { + return this.val; + } + b(){} +} + +const Bar = class { + static get a() { + return this.val; + } + static set a(value) { + this.val = value; + } + b(){} +} +``` + +## Options + +This rule has a string option: + +* `"anyOrder"` (default) does not enforce order. +* `"getBeforeSet"` if a property has both getter and setter, requires the getter to be defined before the setter. +* `"setBeforeGet"` if a property has both getter and setter, requires the setter to be defined before the getter. + +### getBeforeSet + +Examples of **incorrect** code for this rule with the `"getBeforeSet"` option: + +```js +/*eslint grouped-accessor-pairs: ["error", "getBeforeSet"]*/ + +var foo = { + set a(value) { + this.val = value; + }, + get a() { + return this.val; + } +}; + +class Foo { + set a(value) { + this.val = value; + } + get a() { + return this.val; + } +} + +const Bar = class { + static set a(value) { + this.val = value; + } + static get a() { + return this.val; + } +} +``` + +Examples of **correct** code for this rule with the `"getBeforeSet"` option: + +```js +/*eslint grouped-accessor-pairs: ["error", "getBeforeSet"]*/ + +var foo = { + get a() { + return this.val; + }, + set a(value) { + this.val = value; + } +}; + +class Foo { + get a() { + return this.val; + } + set a(value) { + this.val = value; + } +} + +const Bar = class { + static get a() { + return this.val; + } + static set a(value) { + this.val = value; + } +} +``` + +### setBeforeGet + +Examples of **incorrect** code for this rule with the `"setBeforeGet"` option: + +```js +/*eslint grouped-accessor-pairs: ["error", "setBeforeGet"]*/ + +var foo = { + get a() { + return this.val; + }, + set a(value) { + this.val = value; + } +}; + +class Foo { + get a() { + return this.val; + } + set a(value) { + this.val = value; + } +} + +const Bar = class { + static get a() { + return this.val; + } + static set a(value) { + this.val = value; + } +} +``` + +Examples of **correct** code for this rule with the `"setBeforeGet"` option: + +```js +/*eslint grouped-accessor-pairs: ["error", "setBeforeGet"]*/ + +var foo = { + set a(value) { + this.val = value; + }, + get a() { + return this.val; + } +}; + +class Foo { + set a(value) { + this.val = value; + } + get a() { + return this.val; + } +} + +const Bar = class { + static set a(value) { + this.val = value; + } + static get a() { + return this.val; + } +} +``` + +## Known Limitations + +Due to the limits of static analysis, this rule does not account for possible side effects and in certain cases +might require or miss to require grouping or order for getters/setters that have a computed key, like in the following example: + +```js +/*eslint grouped-accessor-pairs: "error"*/ + +var a = 1; + +// false warning (false positive) +var foo = { + get [a++]() { + return this.val; + }, + b: 1, + set [a++](value) { + this.val = value; + } +}; + +// missed warning (false negative) +var bar = { + get [++a]() { + return this.val; + }, + b: 1, + set [a](value) { + this.val = value; + } +}; +``` + +Also, this rule does not report any warnings for properties that have duplicate getters or setters. + +See [no-dupe-keys](no-dupe-keys.md) if you also want to disallow duplicate keys in object literals. + +See [no-dupe-class-members](no-dupe-class-members.md) if you also want to disallow duplicate names in class definitions. + +## Further Reading + +* [Object Setters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set) +* [Object Getters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get) +* [Classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) + +## Related Rules + +* [accessor-pairs](accessor-pairs.md) +* [no-dupe-keys](no-dupe-keys.md) +* [no-dupe-class-members](no-dupe-class-members.md) diff --git a/eslint/docs/rules/guard-for-in.md b/eslint/docs/rules/guard-for-in.md new file mode 100644 index 0000000..28dc163 --- /dev/null +++ b/eslint/docs/rules/guard-for-in.md @@ -0,0 +1,52 @@ +# Require Guarding for-in (guard-for-in) + +Looping over objects with a `for in` loop will include properties that are inherited through the prototype chain. This behavior can lead to unexpected items in your for loop. + +```js +for (key in foo) { + doSomething(key); +} +``` + +Note that simply checking `foo.hasOwnProperty(key)` is likely to cause an error in some cases; see [no-prototype-builtins](no-prototype-builtins.md). + +## Rule Details + +This rule is aimed at preventing unexpected behavior that could arise from using a `for in` loop without filtering the results in the loop. As such, it will warn when `for in` loops do not filter their results with an `if` statement. + +Examples of **incorrect** code for this rule: + +```js +/*eslint guard-for-in: "error"*/ + +for (key in foo) { + doSomething(key); +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint guard-for-in: "error"*/ + +for (key in foo) { + if (Object.prototype.hasOwnProperty.call(foo, key)) { + doSomething(key); + } +} + +for (key in foo) { + if ({}.hasOwnProperty.call(foo, key)) { + doSomething(key); + } +} +``` + +## Related Rules + +* [no-prototype-builtins](no-prototype-builtins.md) + +## Further Reading + +* [Exploring JavaScript for-in loops](https://javascriptweblog.wordpress.com/2011/01/04/exploring-javascript-for-in-loops/) +* [The pitfalls of using objects as maps in JavaScript](http://2ality.com/2012/01/objects-as-maps.html) diff --git a/eslint/docs/rules/handle-callback-err.md b/eslint/docs/rules/handle-callback-err.md new file mode 100644 index 0000000..5733e42 --- /dev/null +++ b/eslint/docs/rules/handle-callback-err.md @@ -0,0 +1,81 @@ +# Enforce Callback Error Handling (handle-callback-err) + +In Node.js, a common pattern for dealing with asynchronous behavior is called the callback pattern. +This pattern expects an `Error` object or `null` as the first argument of the callback. +Forgetting to handle these errors can lead to some really strange behavior in your application. + +```js +function loadData (err, data) { + doSomething(); // forgot to handle error +} +``` + +## Rule Details + +This rule expects that when you're using the callback pattern in Node.js you'll handle the error. + +## Options + +The rule takes a single string option: the name of the error parameter. The default is `"err"`. + +Examples of **incorrect** code for this rule with the default `"err"` parameter name: + +```js +/*eslint handle-callback-err: "error"*/ + +function loadData (err, data) { + doSomething(); +} + +``` + +Examples of **correct** code for this rule with the default `"err"` parameter name: + +```js +/*eslint handle-callback-err: "error"*/ + +function loadData (err, data) { + if (err) { + console.log(err.stack); + } + doSomething(); +} + +function generateError (err) { + if (err) {} +} +``` + +Examples of **correct** code for this rule with a sample `"error"` parameter name: + +```js +/*eslint handle-callback-err: ["error", "error"]*/ + +function loadData (error, data) { + if (error) { + console.log(error.stack); + } + doSomething(); +} +``` + +### regular expression + +Sometimes (especially in big projects) the name of the error variable is not consistent across the project, +so you need a more flexible configuration to ensure that the rule reports all unhandled errors. + +If the configured name of the error variable begins with a `^` it is considered to be a regexp pattern. + +* If the option is `"^(err|error|anySpecificError)$"`, the rule reports unhandled errors where the parameter name can be `err`, `error` or `anySpecificError`. +* If the option is `"^.+Error$"`, the rule reports unhandled errors where the parameter name ends with `Error` (for example, `connectionError` or `validationError` will match). +* If the option is `"^.*(e|E)rr"`, the rule reports unhandled errors where the parameter name matches any string that contains `err` or `Err` (for example, `err`, `error`, `anyError`, `some_err` will match). + +## When Not To Use It + +There are cases where it may be safe for your application to ignore errors, however only ignore errors if you are +confident that some other form of monitoring will help you catch the problem. + +## Further Reading + +* [The Art Of Node: Callbacks](https://github.com/maxogden/art-of-node#callbacks) +* [Nodejitsu: What are the error conventions?](https://docs.nodejitsu.com/articles/errors/what-are-the-error-conventions/) diff --git a/eslint/docs/rules/id-blacklist.md b/eslint/docs/rules/id-blacklist.md new file mode 100644 index 0000000..a7685c4 --- /dev/null +++ b/eslint/docs/rules/id-blacklist.md @@ -0,0 +1,82 @@ +# disallow specified identifiers (id-blacklist) + +> "There are only two hard things in Computer Science: cache invalidation and naming things." — Phil Karlton + +Bad names can lead to hard-to-decipher code. Generic names, such as `data`, don't infer much about the code and the values it receives. This rule allows you to configure a blacklist of bad identifier names, that you don't want to see in your code. + +## Rule Details + +This rule disallows specified identifiers in assignments and `function` definitions. + +This rule will catch blacklisted identifiers that are: + +- variable declarations +- function declarations +- object properties assigned to during object creation + +It will not catch blacklisted identifiers that are: + +- function calls (so you can still use functions you do not have control over) +- object properties (so you can still use objects you do not have control over) + +## Options + +The rule takes one or more strings as options: the names of restricted identifiers. + +For example, to restrict the use of common generic identifiers: + +```json +{ + "id-blacklist": ["error", "data", "err", "e", "cb", "callback"] +} +``` + +Examples of **incorrect** code for this rule with sample `"data", "callback"` restricted identifiers: + +```js +/*eslint id-blacklist: ["error", "data", "callback"] */ + +var data = {...}; + +function callback() { + // ... +} + +element.callback = function() { + // ... +}; + +var itemSet = { + data: [...] +}; +``` + +Examples of **correct** code for this rule with sample `"data", "callback"` restricted identifiers: + +```js +/*eslint id-blacklist: ["error", "data", "callback"] */ + +var encodingOptions = {...}; + +function processFileResult() { + // ... +} + +element.successHandler = function() { + // ... +}; + +var itemSet = { + entities: [...] +}; + +callback(); // all function calls are ignored + +foo.callback(); // all function calls are ignored + +foo.data; // all property names that are not assignments are ignored +``` + +## When Not To Use It + +You can turn this rule off if you are happy for identifiers to be named freely. diff --git a/eslint/docs/rules/id-length.md b/eslint/docs/rules/id-length.md new file mode 100644 index 0000000..f9f6de1 --- /dev/null +++ b/eslint/docs/rules/id-length.md @@ -0,0 +1,225 @@ +# enforce minimum and maximum identifier lengths (id-length) + +Very short identifier names like `e`, `x`, `_t` or very long ones like `hashGeneratorResultOutputContainerObject` can make code harder to read and potentially less maintainable. To prevent this, one may enforce a minimum and/or maximum identifier length. + +```js +var x = 5; // too short; difficult to understand its purpose without context +``` + +## Rule Details + +This rule enforces a minimum and/or maximum identifier length convention. + +## Options + +Examples of **incorrect** code for this rule with the default options: + +```js +/*eslint id-length: "error"*/ // default is minimum 2-chars ({ "min": 2 }) +/*eslint-env es6*/ + +var x = 5; +obj.e = document.body; +var foo = function (e) { }; +try { + dangerousStuff(); +} catch (e) { + // ignore as many do +} +var myObj = { a: 1 }; +(a) => { a * a }; +class x { } +class Foo { x() {} } +function foo(...x) { } +function foo([x]) { } +var [x] = arr; +var { prop: [x]} = {}; +function foo({x}) { } +var { x } = {}; +var { prop: a} = {}; +({ prop: obj.x } = {}); +``` + +Examples of **correct** code for this rule with the default options: + +```js +/*eslint id-length: "error"*/ // default is minimum 2-chars ({ "min": 2 }) +/*eslint-env es6*/ + +var num = 5; +function _f() { return 42; } +function _func() { return 42; } +obj.el = document.body; +var foo = function (evt) { /* do stuff */ }; +try { + dangerousStuff(); +} catch (error) { + // ignore as many do +} +var myObj = { apple: 1 }; +(num) => { num * num }; +function foo(num = 0) { } +class MyClass { } +class Foo { method() {} } +function foo(...args) { } +function foo([longName]) { } +var { prop } = {}; +var { prop: [longName] } = {}; +var [longName] = arr; +function foo({ prop }) { } +function foo({ a: prop }) { } +var { prop } = {}; +var { a: prop } = {}; +({ prop: obj.longName } = {}); +var data = { "x": 1 }; // excused because of quotes +data["y"] = 3; // excused because of calculated property access +``` + +This rule has an object option: + +* `"min"` (default: 2) enforces a minimum identifier length +* `"max"` (default: Infinity) enforces a maximum identifier length +* `"properties": always` (default) enforces identifier length convention for property names +* `"properties": never` ignores identifier length convention for property names +* `"exceptions"` allows an array of specified identifier names + +### min + +Examples of **incorrect** code for this rule with the `{ "min": 4 }` option: + +```js +/*eslint id-length: ["error", { "min": 4 }]*/ +/*eslint-env es6*/ + +var val = 5; +obj.e = document.body; +function foo (e) { }; +try { + dangerousStuff(); +} catch (e) { + // ignore as many do +} +var myObj = { a: 1 }; +(val) => { val * val }; +class x { } +class Foo { x() {} } +function foo(...x) { } +var { x } = {}; +var { prop: a} = {}; +var [x] = arr; +var { prop: [x]} = {}; +({ prop: obj.x } = {}); +``` + +Examples of **correct** code for this rule with the `{ "min": 4 }` option: + +```js +/*eslint id-length: ["error", { "min": 4 }]*/ +/*eslint-env es6*/ + +var value = 5; +function func() { return 42; } +obj.element = document.body; +var foobar = function (event) { /* do stuff */ }; +try { + dangerousStuff(); +} catch (error) { + // ignore as many do +} +var myObj = { apple: 1 }; +(value) => { value * value }; +function foobar(value = 0) { } +class MyClass { } +class Foobar { method() {} } +function foobar(...args) { } +var { prop } = {}; +var [longName] = foo; +var { a: [prop] } = {}; +var { a: longName } = {}; +({ prop: obj.name } = {}); +var data = { "x": 1 }; // excused because of quotes +data["y"] = 3; // excused because of calculated property access +``` + +### max + +Examples of **incorrect** code for this rule with the `{ "max": 10 }` option: + +```js +/*eslint id-length: ["error", { "max": 10 }]*/ +/*eslint-env es6*/ + +var reallyLongVarName = 5; +function reallyLongFuncName() { return 42; } +obj.reallyLongPropName = document.body; +var foo = function (reallyLongArgName) { /* do stuff */ }; +try { + dangerousStuff(); +} catch (reallyLongErrorName) { + // ignore as many do +} +(reallyLongArgName) => { return !reallyLongArgName; }; +var [reallyLongFirstElementName] = arr; +``` + +Examples of **correct** code for this rule with the `{ "max": 10 }` option: + +```js +/*eslint id-length: ["error", { "max": 10 }]*/ +/*eslint-env es6*/ + +var varName = 5; +function funcName() { return 42; } +obj.propName = document.body; +var foo = function (arg) { /* do stuff */ }; +try { + dangerousStuff(); +} catch (error) { + // ignore as many do +} +(arg) => { return !arg; }; +var [first] = arr; +``` + +### properties + +Examples of **correct** code for this rule with the `{ "properties": "never" }` option: + +```js +/*eslint id-length: ["error", { "properties": "never" }]*/ +/*eslint-env es6*/ + +var myObj = { a: 1 }; +({ a: obj.x.y.z } = {}); +({ prop: obj.i } = {}); +``` + +### exceptions + +Examples of additional **correct** code for this rule with the `{ "exceptions": ["x"] }` option: + +```js +/*eslint id-length: ["error", { "exceptions": ["x"] }]*/ +/*eslint-env es6*/ + +var x = 5; +function x() { return 42; } +obj.x = document.body; +var foo = function (x) { /* do stuff */ }; +try { + dangerousStuff(); +} catch (x) { + // ignore as many do +} +(x) => { return x * x; }; +var [x] = arr; +const { x } = foo; +const { a: x } = foo; +``` + +## Related Rules + +* [max-len](max-len.md) +* [new-cap](new-cap.md) +* [func-names](func-names.md) +* [camelcase](camelcase.md) diff --git a/eslint/docs/rules/id-match.md b/eslint/docs/rules/id-match.md new file mode 100644 index 0000000..0d9deeb --- /dev/null +++ b/eslint/docs/rules/id-match.md @@ -0,0 +1,131 @@ +# require identifiers to match a specified regular expression (id-match) + +> "There are only two hard things in Computer Science: cache invalidation and naming things." — Phil Karlton + +Naming things consistently in a project is an often underestimated aspect of code creation. +When done correctly, it can save your team hours of unnecessary head scratching and misdirections. +This rule allows you to precisely define and enforce the variables and function names on your team should use. +No more limiting yourself to camelCase, snake_case, PascalCase or oHungarianNotation. Id-match has all your needs covered! + +## Rule Details + +This rule requires identifiers in assignments and `function` definitions to match a specified regular expression. + +## Options + +This rule has a string option for the specified regular expression. + +For example, to enforce a camelcase naming convention: + +```json +{ + "id-match": ["error", "^[a-z]+([A-Z][a-z]+)*$"] +} +``` + +Examples of **incorrect** code for this rule with the `"^[a-z]+([A-Z][a-z]+)*$"` option: + +```js +/*eslint id-match: ["error", "^[a-z]+([A-Z][a-z]+)*$"]*/ + +var my_favorite_color = "#112C85"; +var _myFavoriteColor = "#112C85"; +var myFavoriteColor_ = "#112C85"; +var MY_FAVORITE_COLOR = "#112C85"; +function do_something() { + // ... +} +obj.do_something = function() { + // ... +}; +``` + +Examples of **correct** code for this rule with the `"^[a-z]+([A-Z][a-z]+)*$"` option: + +```js +/*eslint id-match: ["error", "^[a-z]+([A-Z][a-z]+)*$"]*/ + +var myFavoriteColor = "#112C85"; +var foo = bar.baz_boom; +var foo = { qux: bar.baz_boom }; +do_something(); +var obj = { + my_pref: 1 +}; +``` + +This rule has an object option: + +* `"properties": true` requires object properties to match the specified regular expression +* `"onlyDeclarations": true` requires only `var`, `function`, and `class` declarations to match the specified regular expression +* `"onlyDeclarations": false` requires all variable names to match the specified regular expression +* `"ignoreDestructuring": false` (default) enforces `id-match` for destructured identifiers +* `"ignoreDestructuring": true` does not check destructured identifiers + +### properties + +Examples of **incorrect** code for this rule with the `"^[a-z]+([A-Z][a-z]+)*$", { "properties": true }` options: + +```js +/*eslint id-match: ["error", "^[a-z]+([A-Z][a-z]+)*$", { "properties": true }]*/ + +var obj = { + my_pref: 1 +}; +``` + +### onlyDeclarations + +Examples of **correct** code for this rule with the `"^[a-z]+([A-Z][a-z]+)*$", { "onlyDeclarations": true }` options: + +```js +/*eslint id-match: [2, "^[a-z]+([A-Z][a-z]+)*$", { "onlyDeclarations": true }]*/ + +do_something(__dirname); +``` + +### ignoreDestructuring: false + +Examples of **incorrect** code for this rule with the default `"^[^_]+$", { "ignoreDestructuring": false }` option: + +```js +/*eslint id-match: [2, "^[^_]+$", { "ignoreDestructuring": false }]*/ + +var { category_id } = query; + +var { category_id = 1 } = query; + +var { category_id: category_id } = query; + +var { category_id: category_alias } = query; + +var { category_id: categoryId, ...other_props } = query; +``` + +### ignoreDestructuring: true + +Examples of **incorrect** code for this rule with the `"^[^_]+$", { "ignoreDestructuring": true }` option: + +```js +/*eslint id-match: [2, "^[^_]+$", { "ignoreDestructuring": true }]*/ + +var { category_id: category_alias } = query; + +var { category_id, ...other_props } = query; +``` + +Examples of **correct** code for this rule with the `"^[^_]+$", { "ignoreDestructuring": true }` option: + +```js +/*eslint id-match: [2, "^[^_]+$", { "ignoreDestructuring": true }]*/ + +var { category_id } = query; + +var { category_id = 1 } = query; + +var { category_id: category_id } = query; +``` + +## When Not To Use It + +If you don't want to enforce any particular naming convention for all identifiers, or your naming convention is too complex to be enforced by configuring this rule, then you should not enable this rule. diff --git a/eslint/docs/rules/implicit-arrow-linebreak.md b/eslint/docs/rules/implicit-arrow-linebreak.md new file mode 100644 index 0000000..a1a2355 --- /dev/null +++ b/eslint/docs/rules/implicit-arrow-linebreak.md @@ -0,0 +1,101 @@ +# Enforce the location of arrow function bodies with implicit returns (implicit-arrow-linebreak) + +An arrow function body can contain an implicit return as an expression instead of a block body. It can be useful to enforce a consistent location for the implicitly returned expression. + +## Rule Details + +This rule aims to enforce a consistent location for an arrow function containing an implicit return. + +See Also: + +- [`brace-style`](https://eslint.org/docs/rules/brace-style) which enforces this behavior for arrow functions with block bodies. + +### Options + +This rule accepts a string option: + +- `"beside"` (default) disallows a newline before an arrow function body. +- `"below"` requires a newline before an arrow function body. + +Examples of **incorrect** code for this rule with the default `"beside"` option: + +```js +/* eslint implicit-arrow-linebreak: ["error", "beside"] */ + +(foo) => + bar; + +(foo) => + (bar); + +(foo) => + bar => + baz; + +(foo) => +( + bar() +); +``` + +Examples of **correct** code for this rule with the default `"beside"` option: + +```js +/* eslint implicit-arrow-linebreak: ["error", "beside"] */ + +(foo) => bar; + +(foo) => (bar); + +(foo) => bar => baz; + +(foo) => ( + bar() +); + +// functions with block bodies allowed with this rule using any style +// to enforce a consistent location for this case, see the rule: `brace-style` +(foo) => { + return bar(); +} + +(foo) => +{ + return bar(); +} +``` + +Examples of **incorrect** code for this rule with the `"below"` option: + +```js +/* eslint implicit-arrow-linebreak: ["error", "below"] */ + +(foo) => bar; + +(foo) => (bar); + +(foo) => bar => baz; +``` + +Examples of **correct** code for this rule with the `"below"` option: + +```js +/* eslint implicit-arrow-linebreak: ["error", "below"] */ + + +(foo) => + bar; + +(foo) => + (bar); + +(foo) => + bar => + baz; +``` + +## When Not To Use It + +If you're not concerned about consistent locations of implicitly returned arrow function expressions, you should not turn on this rule. + +You can also disable this rule if you are using the `"always"` option for the [`arrow-body-style`](https://eslint.org/docs/rules/arrow-body-style), since this will disable the use of implicit returns in arrow functions. diff --git a/eslint/docs/rules/indent-legacy.md b/eslint/docs/rules/indent-legacy.md new file mode 100644 index 0000000..45ef1a6 --- /dev/null +++ b/eslint/docs/rules/indent-legacy.md @@ -0,0 +1,532 @@ +# enforce consistent indentation (indent-legacy) + +ESLint 4.0.0 introduced a rewrite of the [`indent`](/docs/rules/indent) rule, which now reports more errors than it did in previous versions. To ease the process of migrating to 4.0.0, the `indent-legacy` rule was introduced as a snapshot of the `indent` rule from ESLint 3.x. If your build is failing after the upgrade to 4.0.0, you can disable `indent` and enable `indent-legacy` as a quick fix. Eventually, you should switch back to the `indent` rule to get bugfixes and improvements in future versions. + +--- + +There are several common guidelines which require specific indentation of nested blocks and statements, like: + +```js +function hello(indentSize, type) { + if (indentSize === 4 && type !== 'tab') { + console.log('Each next indentation will increase on 4 spaces'); + } +} +``` + +These are the most common scenarios recommended in different style guides: + +* Two spaces, not longer and no tabs: Google, npm, Node.js, Idiomatic, Felix +* Tabs: jQuery +* Four spaces: Crockford + +## Rule Details + +This rule enforces a consistent indentation style. The default style is `4 spaces`. + +## Options + +This rule has a mixed option: + +For example, for 2-space indentation: + +```json +{ + "indent": ["error", 2] +} +``` + +Or for tabbed indentation: + +```json +{ + "indent": ["error", "tab"] +} +``` + +Examples of **incorrect** code for this rule with the default options: + +```js +/*eslint indent: "error"*/ + +if (a) { + b=c; + function foo(d) { + e=f; + } +} +``` + +Examples of **correct** code for this rule with the default options: + +```js +/*eslint indent: "error"*/ + +if (a) { + b=c; + function foo(d) { + e=f; + } +} +``` + +This rule has an object option: + +* `"SwitchCase"` (default: 0) enforces indentation level for `case` clauses in `switch` statements +* `"VariableDeclarator"` (default: 1) enforces indentation level for `var` declarators; can also take an object to define separate rules for `var`, `let` and `const` declarations. +* `"outerIIFEBody"` (default: 1) enforces indentation level for file-level IIFEs. +* `"MemberExpression"` (off by default) enforces indentation level for multi-line property chains (except in variable declarations and assignments) +* `"FunctionDeclaration"` takes an object to define rules for function declarations. + * `parameters` (off by default) 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. + * `body` (default: 1) enforces indentation level for the body of a function declaration. +* `"FunctionExpression"` takes an object to define rules for function expressions. + * `parameters` (off by default) 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. + * `body` (default: 1) enforces indentation level for the body of a function expression. +* `"CallExpression"` takes an object to define rules for function call expressions. + * `arguments` (off by default) 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. +* `"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. +* `"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. + +Level of indentation denotes the multiple of the indent specified. Example: + +* Indent of 4 spaces with `VariableDeclarator` set to `2` will indent the multi-line variable declarations with 8 spaces. +* Indent of 2 spaces with `VariableDeclarator` set to `2` will indent the multi-line variable declarations with 4 spaces. +* Indent of 2 spaces with `VariableDeclarator` set to `{"var": 2, "let": 2, "const": 3}` will indent the multi-line variable declarations with 4 spaces for `var` and `let`, 6 spaces for `const` statements. +* Indent of tab with `VariableDeclarator` set to `2` will indent the multi-line variable declarations with 2 tabs. +* Indent of 2 spaces with `SwitchCase` set to `0` will not indent `case` clauses with respect to `switch` statements. +* Indent of 2 spaces with `SwitchCase` set to `1` will indent `case` clauses with 2 spaces with respect to `switch` statements. +* Indent of 2 spaces with `SwitchCase` set to `2` will indent `case` clauses with 4 spaces with respect to `switch` statements. +* Indent of tab with `SwitchCase` set to `2` will indent `case` clauses with 2 tabs with respect to `switch` statements. +* Indent of 2 spaces with `MemberExpression` set to `0` will indent the multi-line property chains with 0 spaces. +* Indent of 2 spaces with `MemberExpression` set to `1` will indent the multi-line property chains with 2 spaces. +* Indent of 2 spaces with `MemberExpression` set to `2` will indent the multi-line property chains with 4 spaces. +* Indent of 4 spaces with `MemberExpression` set to `0` will indent the multi-line property chains with 0 spaces. +* Indent of 4 spaces with `MemberExpression` set to `1` will indent the multi-line property chains with 4 spaces. +* Indent of 4 spaces with `MemberExpression` set to `2` will indent the multi-line property chains with 8 spaces. + +### tab + +Examples of **incorrect** code for this rule with the `"tab"` option: + +```js +/*eslint indent: ["error", "tab"]*/ + +if (a) { + b=c; +function foo(d) { + e=f; + } +} +``` + +Examples of **correct** code for this rule with the `"tab"` option: + +```js +/*eslint indent: ["error", "tab"]*/ + +if (a) { +/*tab*/b=c; +/*tab*/function foo(d) { +/*tab*//*tab*/e=f; +/*tab*/} +} +``` + +### SwitchCase + +Examples of **incorrect** code for this rule with the `2, { "SwitchCase": 1 }` options: + +```js +/*eslint indent: ["error", 2, { "SwitchCase": 1 }]*/ + +switch(a){ +case "a": + break; +case "b": + break; +} +``` + +Examples of **correct** code for this rule with the `2, { "SwitchCase": 1 }` option: + +```js +/*eslint indent: ["error", 2, { "SwitchCase": 1 }]*/ + +switch(a){ + case "a": + break; + case "b": + break; +} +``` + +### VariableDeclarator + +Examples of **incorrect** code for this rule with the `2, { "VariableDeclarator": 1 }` options: + +```js +/*eslint indent: ["error", 2, { "VariableDeclarator": 1 }]*/ +/*eslint-env es6*/ + +var a, + b, + c; +let a, + b, + c; +const a = 1, + b = 2, + c = 3; +``` + +Examples of **correct** code for this rule with the `2, { "VariableDeclarator": 1 }` options: + +```js +/*eslint indent: ["error", 2, { "VariableDeclarator": 1 }]*/ +/*eslint-env es6*/ + +var a, + b, + c; +let a, + b, + c; +const a = 1, + b = 2, + c = 3; +``` + +Examples of **correct** code for this rule with the `2, { "VariableDeclarator": 2 }` options: + +```js +/*eslint indent: ["error", 2, { "VariableDeclarator": 2 }]*/ +/*eslint-env es6*/ + +var a, + b, + c; +let a, + b, + c; +const a = 1, + b = 2, + c = 3; +``` + +Examples of **correct** code for this rule with the `2, { "VariableDeclarator": { "var": 2, "let": 2, "const": 3 } }` options: + +```js +/*eslint indent: ["error", 2, { "VariableDeclarator": { "var": 2, "let": 2, "const": 3 } }]*/ +/*eslint-env es6*/ + +var a, + b, + c; +let a, + b, + c; +const a = 1, + b = 2, + c = 3; +``` + +### outerIIFEBody + +Examples of **incorrect** code for this rule with the options `2, { "outerIIFEBody": 0 }`: + +```js +/*eslint indent: ["error", 2, { "outerIIFEBody": 0 }]*/ + +(function() { + + function foo(x) { + return x + 1; + } + +})(); + + +if(y) { +console.log('foo'); +} +``` + +Examples of **correct** code for this rule with the options `2, {"outerIIFEBody": 0}`: + +```js +/*eslint indent: ["error", 2, { "outerIIFEBody": 0 }]*/ + +(function() { + +function foo(x) { + return x + 1; +} + +})(); + + +if(y) { + console.log('foo'); +} +``` + +### MemberExpression + +Examples of **incorrect** code for this rule with the `2, { "MemberExpression": 1 }` options: + +```js +/*eslint indent: ["error", 2, { "MemberExpression": 1 }]*/ + +foo +.bar +.baz() +``` + +Examples of **correct** code for this rule with the `2, { "MemberExpression": 1 }` option: + +```js +/*eslint indent: ["error", 2, { "MemberExpression": 1 }]*/ + +foo + .bar + .baz(); + +// Any indentation is permitted in variable declarations and assignments. +var bip = aardvark.badger + .coyote; +``` + +### FunctionDeclaration + +Examples of **incorrect** code for this rule with the `2, { "FunctionDeclaration": {"body": 1, "parameters": 2} }` option: + +```js +/*eslint indent: ["error", 2, { "FunctionDeclaration": {"body": 1, "parameters": 2} }]*/ + +function foo(bar, + baz, + qux) { + qux(); +} +``` + +Examples of **correct** code for this rule with the `2, { "FunctionDeclaration": {"body": 1, "parameters": 2} }` option: + +```js +/*eslint indent: ["error", 2, { "FunctionDeclaration": {"body": 1, "parameters": 2} }]*/ + +function foo(bar, + baz, + qux) { + qux(); +} +``` + +Examples of **incorrect** code for this rule with the `2, { "FunctionDeclaration": {"parameters": "first"} }` option: + +```js +/*eslint indent: ["error", 2, {"FunctionDeclaration": {"parameters": "first"}}]*/ + +function foo(bar, baz, + qux, boop) { + qux(); +} +``` + +Examples of **correct** code for this rule with the `2, { "FunctionDeclaration": {"parameters": "first"} }` option: + +```js +/*eslint indent: ["error", 2, {"FunctionDeclaration": {"parameters": "first"}}]*/ + +function foo(bar, baz, + qux, boop) { + qux(); +} +``` + +### FunctionExpression + +Examples of **incorrect** code for this rule with the `2, { "FunctionExpression": {"body": 1, "parameters": 2} }` option: + +```js +/*eslint indent: ["error", 2, { "FunctionExpression": {"body": 1, "parameters": 2} }]*/ + +var foo = function(bar, + baz, + qux) { + qux(); +} +``` + +Examples of **correct** code for this rule with the `2, { "FunctionExpression": {"body": 1, "parameters": 2} }` option: + +```js +/*eslint indent: ["error", 2, { "FunctionExpression": {"body": 1, "parameters": 2} }]*/ + +var foo = function(bar, + baz, + qux) { + qux(); +} +``` + +Examples of **incorrect** code for this rule with the `2, { "FunctionExpression": {"parameters": "first"} }` option: + +```js +/*eslint indent: ["error", 2, {"FunctionExpression": {"parameters": "first"}}]*/ + +var foo = function(bar, baz, + qux, boop) { + qux(); +} +``` + +Examples of **correct** code for this rule with the `2, { "FunctionExpression": {"parameters": "first"} }` option: + +```js +/*eslint indent: ["error", 2, {"FunctionExpression": {"parameters": "first"}}]*/ + +var foo = function(bar, baz, + qux, boop) { + qux(); +} +``` + +### CallExpression + +Examples of **incorrect** code for this rule with the `2, { "CallExpression": {"arguments": 1} }` option: + +```js +/*eslint indent: ["error", 2, { "CallExpression": {"arguments": 1} }]*/ + +foo(bar, + baz, + qux +); +``` + +Examples of **correct** code for this rule with the `2, { "CallExpression": {"arguments": 1} }` option: + +```js +/*eslint indent: ["error", 2, { "CallExpression": {"arguments": 1} }]*/ + +foo(bar, + baz, + qux +); +``` + +Examples of **incorrect** code for this rule with the `2, { "CallExpression": {"arguments": "first"} }` option: + +```js +/*eslint indent: ["error", 2, {"CallExpression": {"arguments": "first"}}]*/ + +foo(bar, baz, + baz, boop, beep); +``` + +Examples of **correct** code for this rule with the `2, { "CallExpression": {"arguments": "first"} }` option: + +```js +/*eslint indent: ["error", 2, {"CallExpression": {"arguments": "first"}}]*/ + +foo(bar, baz, + baz, boop, beep); +``` + +### ArrayExpression + +Examples of **incorrect** code for this rule with the `2, { "ArrayExpression": 1 }` option: + +```js +/*eslint indent: ["error", 2, { "ArrayExpression": 1 }]*/ + +var foo = [ + bar, +baz, + qux +]; +``` + +Examples of **correct** code for this rule with the `2, { "ArrayExpression": 1 }` option: + +```js +/*eslint indent: ["error", 2, { "ArrayExpression": 1 }]*/ + +var foo = [ + bar, + baz, + qux +]; +``` + +Examples of **incorrect** code for this rule with the `2, { "ArrayExpression": "first" }` option: + +```js +/*eslint indent: ["error", 2, {"ArrayExpression": "first"}]*/ + +var foo = [bar, + baz, + qux +]; +``` + +Examples of **correct** code for this rule with the `2, { "ArrayExpression": "first" }` option: + +```js +/*eslint indent: ["error", 2, {"ArrayExpression": "first"}]*/ + +var foo = [bar, + baz, + qux +]; +``` + +### ObjectExpression + +Examples of **incorrect** code for this rule with the `2, { "ObjectExpression": 1 }` option: + +```js +/*eslint indent: ["error", 2, { "ObjectExpression": 1 }]*/ + +var foo = { + bar: 1, +baz: 2, + qux: 3 +}; +``` + +Examples of **correct** code for this rule with the `2, { "ObjectExpression": 1 }` option: + +```js +/*eslint indent: ["error", 2, { "ObjectExpression": 1 }]*/ + +var foo = { + bar: 1, + baz: 2, + qux: 3 +}; +``` + +Examples of **incorrect** code for this rule with the `2, { "ObjectExpression": "first" }` option: + +```js +/*eslint indent: ["error", 2, {"ObjectExpression": "first"}]*/ + +var foo = { bar: 1, + baz: 2 }; +``` + +Examples of **correct** code for this rule with the `2, { "ObjectExpression": "first" }` option: + +```js +/*eslint indent: ["error", 2, {"ObjectExpression": "first"}]*/ + +var foo = { bar: 1, + baz: 2 }; +``` + +## Compatibility + +* **JSHint**: `indent` +* **JSCS**: [validateIndentation](https://jscs-dev.github.io/rule/validateIndentation) diff --git a/eslint/docs/rules/indent.md b/eslint/docs/rules/indent.md new file mode 100644 index 0000000..7c2e6b7 --- /dev/null +++ b/eslint/docs/rules/indent.md @@ -0,0 +1,795 @@ +# enforce consistent indentation (indent) + +There are several common guidelines which require specific indentation of nested blocks and statements, like: + +```js +function hello(indentSize, type) { + if (indentSize === 4 && type !== 'tab') { + console.log('Each next indentation will increase on 4 spaces'); + } +} +``` + +These are the most common scenarios recommended in different style guides: + +* Two spaces, not longer and no tabs: Google, npm, Node.js, Idiomatic, Felix +* Tabs: jQuery +* Four spaces: Crockford + +## Rule Details + +This rule enforces a consistent indentation style. The default style is `4 spaces`. + +## Options + +This rule has a mixed option: + +For example, for 2-space indentation: + +```json +{ + "indent": ["error", 2] +} +``` + +Or for tabbed indentation: + +```json +{ + "indent": ["error", "tab"] +} +``` + +Examples of **incorrect** code for this rule with the default options: + +```js +/*eslint indent: "error"*/ + +if (a) { + b=c; + function foo(d) { + e=f; + } +} +``` + +Examples of **correct** code for this rule with the default options: + +```js +/*eslint indent: "error"*/ + +if (a) { + b=c; + function foo(d) { + e=f; + } +} +``` + +This rule has an object option: + +* `"SwitchCase"` (default: 0) enforces indentation level for `case` clauses in `switch` statements +* `"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. +* `"outerIIFEBody"` (default: 1) enforces indentation level for file-level IIFEs. This can also be set to `"off"` to disable checking for file-level IIFEs. +* `"MemberExpression"` (default: 1) enforces indentation level for multi-line property chains. This can also be set to `"off"` to disable checking for MemberExpression indentation. +* `"FunctionDeclaration"` takes an object to define rules for function declarations. + * `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. + * `body` (default: 1) enforces indentation level for the body of a function declaration. +* `"FunctionExpression"` takes an object to define rules for function expressions. + * `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. + * `body` (default: 1) enforces indentation level for the body of a function expression. +* `"CallExpression"` takes an object to define rules for function call expressions. + * `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. +* `"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. +* `"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. +* `"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. +* `"flatTernaryExpressions": true` (`false` by default) requires no indentation for ternary expressions which are nested in other ternary expressions. +* `"offsetTernaryExpressions": true` (`false` by default) requires indentation for values of ternary expressions. +* `"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. +* `"ignoreComments"` (default: false) can be used when comments do not need to be aligned with nodes on the previous or next line. + +Level of indentation denotes the multiple of the indent specified. Example: + +* Indent of 4 spaces with `VariableDeclarator` set to `2` will indent the multi-line variable declarations with 8 spaces. +* Indent of 2 spaces with `VariableDeclarator` set to `2` will indent the multi-line variable declarations with 4 spaces. +* Indent of 2 spaces with `VariableDeclarator` set to `{"var": 2, "let": 2, "const": 3}` will indent the multi-line variable declarations with 4 spaces for `var` and `let`, 6 spaces for `const` statements. +* Indent of tab with `VariableDeclarator` set to `2` will indent the multi-line variable declarations with 2 tabs. +* Indent of 2 spaces with `SwitchCase` set to `0` will not indent `case` clauses with respect to `switch` statements. +* Indent of 2 spaces with `SwitchCase` set to `1` will indent `case` clauses with 2 spaces with respect to `switch` statements. +* Indent of 2 spaces with `SwitchCase` set to `2` will indent `case` clauses with 4 spaces with respect to `switch` statements. +* Indent of tab with `SwitchCase` set to `2` will indent `case` clauses with 2 tabs with respect to `switch` statements. +* Indent of 2 spaces with `MemberExpression` set to `0` will indent the multi-line property chains with 0 spaces. +* Indent of 2 spaces with `MemberExpression` set to `1` will indent the multi-line property chains with 2 spaces. +* Indent of 2 spaces with `MemberExpression` set to `2` will indent the multi-line property chains with 4 spaces. +* Indent of 4 spaces with `MemberExpression` set to `0` will indent the multi-line property chains with 0 spaces. +* Indent of 4 spaces with `MemberExpression` set to `1` will indent the multi-line property chains with 4 spaces. +* Indent of 4 spaces with `MemberExpression` set to `2` will indent the multi-line property chains with 8 spaces. + +### tab + +Examples of **incorrect** code for this rule with the `"tab"` option: + +```js +/*eslint indent: ["error", "tab"]*/ + +if (a) { + b=c; +function foo(d) { + e=f; + } +} +``` + +Examples of **correct** code for this rule with the `"tab"` option: + +```js +/*eslint indent: ["error", "tab"]*/ + +if (a) { +/*tab*/b=c; +/*tab*/function foo(d) { +/*tab*//*tab*/e=f; +/*tab*/} +} +``` + +### SwitchCase + +Examples of **incorrect** code for this rule with the `2, { "SwitchCase": 1 }` options: + +```js +/*eslint indent: ["error", 2, { "SwitchCase": 1 }]*/ + +switch(a){ +case "a": + break; +case "b": + break; +} +``` + +Examples of **correct** code for this rule with the `2, { "SwitchCase": 1 }` option: + +```js +/*eslint indent: ["error", 2, { "SwitchCase": 1 }]*/ + +switch(a){ + case "a": + break; + case "b": + break; +} +``` + +### VariableDeclarator + +Examples of **incorrect** code for this rule with the `2, { "VariableDeclarator": 1 }` options: + +```js +/*eslint indent: ["error", 2, { "VariableDeclarator": 1 }]*/ +/*eslint-env es6*/ + +var a, + b, + c; +let a, + b, + c; +const a = 1, + b = 2, + c = 3; +``` + +Examples of **correct** code for this rule with the `2, { "VariableDeclarator": 1 }` options: + +```js +/*eslint indent: ["error", 2, { "VariableDeclarator": 1 }]*/ +/*eslint-env es6*/ + +var a, + b, + c; +let a, + b, + c; +const a = 1, + b = 2, + c = 3; +``` + +Examples of **correct** code for this rule with the `2, { "VariableDeclarator": 2 }` options: + +```js +/*eslint indent: ["error", 2, { "VariableDeclarator": 2 }]*/ +/*eslint-env es6*/ + +var a, + b, + c; +let a, + b, + c; +const a = 1, + b = 2, + c = 3; +``` + +Examples of **incorrect** code for this rule with the `2, { "VariableDeclarator": "first" }` options: + +```js +/*eslint indent: ["error", 2, { "VariableDeclarator": "first" }]*/ +/*eslint-env es6*/ + +var a, + b, + c; +let a, + b, + c; +const a = 1, + b = 2, + c = 3; +``` + +Examples of **correct** code for this rule with the `2, { "VariableDeclarator": "first" }` options: + +```js +/*eslint indent: ["error", 2, { "VariableDeclarator": "first" }]*/ +/*eslint-env es6*/ + +var a, + b, + c; +let a, + b, + c; +const a = 1, + b = 2, + c = 3; +``` + +Examples of **correct** code for this rule with the `2, { "VariableDeclarator": { "var": 2, "let": 2, "const": 3 } }` options: + +```js +/*eslint indent: ["error", 2, { "VariableDeclarator": { "var": 2, "let": 2, "const": 3 } }]*/ +/*eslint-env es6*/ + +var a, + b, + c; +let a, + b, + c; +const a = 1, + b = 2, + c = 3; +``` + +### outerIIFEBody + +Examples of **incorrect** code for this rule with the options `2, { "outerIIFEBody": 0 }`: + +```js +/*eslint indent: ["error", 2, { "outerIIFEBody": 0 }]*/ + +(function() { + + function foo(x) { + return x + 1; + } + +})(); + + +if (y) { +console.log('foo'); +} +``` + +Examples of **correct** code for this rule with the options `2, { "outerIIFEBody": 0 }`: + +```js +/*eslint indent: ["error", 2, { "outerIIFEBody": 0 }]*/ + +(function() { + +function foo(x) { + return x + 1; +} + +})(); + + +if (y) { + console.log('foo'); +} +``` + +Examples of **correct** code for this rule with the options `2, { "outerIIFEBody": "off" }`: + +```js +/*eslint indent: ["error", 2, { "outerIIFEBody": "off" }]*/ + +(function() { + +function foo(x) { + return x + 1; +} + +})(); + +(function() { + + function foo(x) { + return x + 1; + } + +})(); + +if (y) { + console.log('foo'); +} +``` + +### MemberExpression + +Examples of **incorrect** code for this rule with the `2, { "MemberExpression": 1 }` options: + +```js +/*eslint indent: ["error", 2, { "MemberExpression": 1 }]*/ + +foo +.bar +.baz() +``` + +Examples of **correct** code for this rule with the `2, { "MemberExpression": 1 }` option: + +```js +/*eslint indent: ["error", 2, { "MemberExpression": 1 }]*/ + +foo + .bar + .baz(); +``` + +### FunctionDeclaration + +Examples of **incorrect** code for this rule with the `2, { "FunctionDeclaration": {"body": 1, "parameters": 2} }` option: + +```js +/*eslint indent: ["error", 2, { "FunctionDeclaration": {"body": 1, "parameters": 2} }]*/ + +function foo(bar, + baz, + qux) { + qux(); +} +``` + +Examples of **correct** code for this rule with the `2, { "FunctionDeclaration": {"body": 1, "parameters": 2} }` option: + +```js +/*eslint indent: ["error", 2, { "FunctionDeclaration": {"body": 1, "parameters": 2} }]*/ + +function foo(bar, + baz, + qux) { + qux(); +} +``` + +Examples of **incorrect** code for this rule with the `2, { "FunctionDeclaration": {"parameters": "first"} }` option: + +```js +/*eslint indent: ["error", 2, {"FunctionDeclaration": {"parameters": "first"}}]*/ + +function foo(bar, baz, + qux, boop) { + qux(); +} +``` + +Examples of **correct** code for this rule with the `2, { "FunctionDeclaration": {"parameters": "first"} }` option: + +```js +/*eslint indent: ["error", 2, {"FunctionDeclaration": {"parameters": "first"}}]*/ + +function foo(bar, baz, + qux, boop) { + qux(); +} +``` + +### FunctionExpression + +Examples of **incorrect** code for this rule with the `2, { "FunctionExpression": {"body": 1, "parameters": 2} }` option: + +```js +/*eslint indent: ["error", 2, { "FunctionExpression": {"body": 1, "parameters": 2} }]*/ + +var foo = function(bar, + baz, + qux) { + qux(); +} +``` + +Examples of **correct** code for this rule with the `2, { "FunctionExpression": {"body": 1, "parameters": 2} }` option: + +```js +/*eslint indent: ["error", 2, { "FunctionExpression": {"body": 1, "parameters": 2} }]*/ + +var foo = function(bar, + baz, + qux) { + qux(); +} +``` + +Examples of **incorrect** code for this rule with the `2, { "FunctionExpression": {"parameters": "first"} }` option: + +```js +/*eslint indent: ["error", 2, {"FunctionExpression": {"parameters": "first"}}]*/ + +var foo = function(bar, baz, + qux, boop) { + qux(); +} +``` + +Examples of **correct** code for this rule with the `2, { "FunctionExpression": {"parameters": "first"} }` option: + +```js +/*eslint indent: ["error", 2, {"FunctionExpression": {"parameters": "first"}}]*/ + +var foo = function(bar, baz, + qux, boop) { + qux(); +} +``` + +### CallExpression + +Examples of **incorrect** code for this rule with the `2, { "CallExpression": {"arguments": 1} }` option: + +```js +/*eslint indent: ["error", 2, { "CallExpression": {"arguments": 1} }]*/ + +foo(bar, + baz, + qux +); +``` + +Examples of **correct** code for this rule with the `2, { "CallExpression": {"arguments": 1} }` option: + +```js +/*eslint indent: ["error", 2, { "CallExpression": {"arguments": 1} }]*/ + +foo(bar, + baz, + qux +); +``` + +Examples of **incorrect** code for this rule with the `2, { "CallExpression": {"arguments": "first"} }` option: + +```js +/*eslint indent: ["error", 2, {"CallExpression": {"arguments": "first"}}]*/ + +foo(bar, baz, + baz, boop, beep); +``` + +Examples of **correct** code for this rule with the `2, { "CallExpression": {"arguments": "first"} }` option: + +```js +/*eslint indent: ["error", 2, {"CallExpression": {"arguments": "first"}}]*/ + +foo(bar, baz, + baz, boop, beep); +``` + +### ArrayExpression + +Examples of **incorrect** code for this rule with the `2, { "ArrayExpression": 1 }` option: + +```js +/*eslint indent: ["error", 2, { "ArrayExpression": 1 }]*/ + +var foo = [ + bar, +baz, + qux +]; +``` + +Examples of **correct** code for this rule with the `2, { "ArrayExpression": 1 }` option: + +```js +/*eslint indent: ["error", 2, { "ArrayExpression": 1 }]*/ + +var foo = [ + bar, + baz, + qux +]; +``` + +Examples of **incorrect** code for this rule with the `2, { "ArrayExpression": "first" }` option: + +```js +/*eslint indent: ["error", 2, {"ArrayExpression": "first"}]*/ + +var foo = [bar, + baz, + qux +]; +``` + +Examples of **correct** code for this rule with the `2, { "ArrayExpression": "first" }` option: + +```js +/*eslint indent: ["error", 2, {"ArrayExpression": "first"}]*/ + +var foo = [bar, + baz, + qux +]; +``` + +### ObjectExpression + +Examples of **incorrect** code for this rule with the `2, { "ObjectExpression": 1 }` option: + +```js +/*eslint indent: ["error", 2, { "ObjectExpression": 1 }]*/ + +var foo = { + bar: 1, +baz: 2, + qux: 3 +}; +``` + +Examples of **correct** code for this rule with the `2, { "ObjectExpression": 1 }` option: + +```js +/*eslint indent: ["error", 2, { "ObjectExpression": 1 }]*/ + +var foo = { + bar: 1, + baz: 2, + qux: 3 +}; +``` + +Examples of **incorrect** code for this rule with the `2, { "ObjectExpression": "first" }` option: + +```js +/*eslint indent: ["error", 2, {"ObjectExpression": "first"}]*/ + +var foo = { bar: 1, + baz: 2 }; +``` + +Examples of **correct** code for this rule with the `2, { "ObjectExpression": "first" }` option: + +```js +/*eslint indent: ["error", 2, {"ObjectExpression": "first"}]*/ + +var foo = { bar: 1, + baz: 2 }; +``` + +### ImportDeclaration + +Examples of **correct** code for this rule with the `4, { "ImportDeclaration": 1 }` option (the default): + +```js +/*eslint indent: ["error", 4, { "ImportDeclaration": 1 }]*/ + +import { foo, + bar, + baz, +} from 'qux'; + +import { + foo, + bar, + baz, +} from 'qux'; +``` + +Examples of **incorrect** code for this rule with the `4, { "ImportDeclaration": "first" }` option: + +```js +/*eslint indent: ["error", 4, { "ImportDeclaration": "first" }]*/ + +import { foo, + bar, + baz, +} from 'qux'; +``` + +Examples of **correct** code for this rule with the `4, { "ImportDeclaration": "first" }` option: + +```js +/*eslint indent: ["error", 4, { "ImportDeclaration": "first" }]*/ + +import { foo, + bar, + baz, +} from 'qux'; +``` + +### flatTernaryExpressions + +Examples of **incorrect** code for this rule with the default `4, { "flatTernaryExpressions": false }` option: + +```js +/*eslint indent: ["error", 4, { "flatTernaryExpressions": false }]*/ + +var a = + foo ? bar : + baz ? qux : + boop; +``` + +Examples of **correct** code for this rule with the default `4, { "flatTernaryExpressions": false }` option: + +```js +/*eslint indent: ["error", 4, { "flatTernaryExpressions": false }]*/ + +var a = + foo ? bar : + baz ? qux : + boop; +``` + +Examples of **incorrect** code for this rule with the `4, { "flatTernaryExpressions": true }` option: + +```js +/*eslint indent: ["error", 4, { "flatTernaryExpressions": true }]*/ + +var a = + foo ? bar : + baz ? qux : + boop; +``` + +Examples of **correct** code for this rule with the `4, { "flatTernaryExpressions": true }` option: + +```js +/*eslint indent: ["error", 4, { "flatTernaryExpressions": true }]*/ + +var a = + foo ? bar : + baz ? qux : + boop; +``` + +### offsetTernaryExpressions + +Examples of **incorrect** code for this rule with the default `2, { "offsetTernaryExpressions": false }` option: + +```js +/*eslint indent: ["error", 2, { "offsetTernaryExpressions": false }]*/ + +condition + ? () => { + return true + } + : () => { + false + } +``` + +Examples of **correct** code for this rule with the default `2, { "offsetTernaryExpressions": false }` option: + +```js +/*eslint indent: ["error", 2, { "offsetTernaryExpressions": false }]*/ + +condition + ? () => { + return true + } + : condition2 + ? () => { + return true + } + : () => { + return false + } +``` + +Examples of **incorrect** code for this rule with the `2, { "offsetTernaryExpressions": true }` option: + +```js +/*eslint indent: ["error", 2, { "offsetTernaryExpressions": true }]*/ + +condition + ? () => { + return true + } + : condition2 + ? () => { + return true + } + : () => { + return false + } +``` + +Examples of **correct** code for this rule with the `2, { "offsetTernaryExpressions": true }` option: + +```js +/*eslint indent: ["error", 2, { "offsetTernaryExpressions": true }]*/ + +condition + ? () => { + return true + } + : condition2 + ? () => { + return true + } + : () => { + return false + } +``` + +### ignoredNodes + +The following configuration ignores the indentation of `ConditionalExpression` ("ternary expression") nodes: + +Examples of **correct** code for this rule with the `4, { "ignoredNodes": ["ConditionalExpression"] }` option: + +```js +/*eslint indent: ["error", 4, { "ignoredNodes": ["ConditionalExpression"] }]*/ + +var a = foo + ? bar + : baz; + +var a = foo + ? bar +: baz; +``` + +The following configuration ignores indentation in the body of IIFEs. + +Examples of **correct** code for this rule with the `4, { "ignoredNodes": ["CallExpression > FunctionExpression.callee > BlockStatement.body"] }` option: + +```js +/*eslint indent: ["error", 4, { "ignoredNodes": ["CallExpression > FunctionExpression.callee > BlockStatement.body"] }]*/ + +(function() { + +foo(); +bar(); + +}) +``` + +### ignoreComments + +Examples of additional **correct** code for this rule with the `4, { "ignoreComments": true }` option: + +```js +/*eslint indent: ["error", 4, { "ignoreComments": true }] */ + +if (foo) { + doSomething(); + +// comment intentionally de-indented + doSomethingElse(); +} +``` + + +## Compatibility + +* **JSHint**: `indent` +* **JSCS**: [validateIndentation](https://jscs-dev.github.io/rule/validateIndentation) diff --git a/eslint/docs/rules/init-declarations.md b/eslint/docs/rules/init-declarations.md new file mode 100644 index 0000000..97c831a --- /dev/null +++ b/eslint/docs/rules/init-declarations.md @@ -0,0 +1,131 @@ +# require or disallow initialization in variable declarations (init-declarations) + +In JavaScript, variables can be assigned during declaration, or at any point afterwards using an assignment statement. For example, in the following code, `foo` is initialized during declaration, while `bar` is initialized later. + +```js +var foo = 1; +var bar; + +if (foo) { + bar = 1; +} else { + bar = 2; +} +``` + +## Rule Details + +This rule is aimed at enforcing or eliminating variable initializations during declaration. For example, in the following code, `foo` is initialized during declaration, while `bar` is not. + +```js +var foo = 1; +var bar; + +bar = 2; +``` + +This rule aims to bring consistency to variable initializations and declarations. + +## Options + +The rule takes two options: + +1. A string which must be either `"always"` (the default), to enforce initialization at declaration, or `"never"` to disallow initialization during declaration. This rule applies to `var`, `let`, and `const` variables, however `"never"` is ignored for `const` variables, as unassigned `const`s generate a parse error. +2. An object that further controls the behavior of this rule. Currently, the only available parameter is `ignoreForLoopInit`, which indicates if initialization at declaration is allowed in `for` loops when `"never"` is set, since it is a very typical use case. + +You can configure the rule as follows: + +Variables must be initialized at declaration (default) + +```json +{ + "init-declarations": ["error", "always"], +} +``` + +Variables must not be initialized at declaration + +```json +{ + "init-declarations": ["error", "never"] +} +``` + +Variables must not be initialized at declaration, except in for loops, where it is allowed + +```json +{ + "init-declarations": ["error", "never", { "ignoreForLoopInit": true }] +} +``` + +### always + +Examples of **incorrect** code for the default `"always"` option: + +```js +/*eslint init-declarations: ["error", "always"]*/ +/*eslint-env es6*/ + +function foo() { + var bar; + let baz; +} +``` + +Examples of **correct** code for the default `"always"` option: + +```js +/*eslint init-declarations: ["error", "always"]*/ +/*eslint-env es6*/ + +function foo() { + var bar = 1; + let baz = 2; + const qux = 3; +} +``` + +### never + +Examples of **incorrect** code for the `"never"` option: + +```js +/*eslint init-declarations: ["error", "never"]*/ +/*eslint-env es6*/ + +function foo() { + var bar = 1; + let baz = 2; + + for (var i = 0; i < 1; i++) {} +} +``` + +Examples of **correct** code for the `"never"` option: + +```js +/*eslint init-declarations: ["error", "never"]*/ +/*eslint-env es6*/ + +function foo() { + var bar; + let baz; + const buzz = 1; +} +``` + +The `"never"` option ignores `const` variable initializations. + +### ignoreForLoopInit + +Examples of **correct** code for the `"never", { "ignoreForLoopInit": true }` options: + +```js +/*eslint init-declarations: ["error", "never", { "ignoreForLoopInit": true }]*/ +for (var i = 0; i < 1; i++) {} +``` + +## When Not To Use It + +When you are indifferent as to how your variables are initialized. diff --git a/eslint/docs/rules/jsx-quotes.md b/eslint/docs/rules/jsx-quotes.md new file mode 100644 index 0000000..ba0b876 --- /dev/null +++ b/eslint/docs/rules/jsx-quotes.md @@ -0,0 +1,73 @@ +# enforce the consistent use of either double or single quotes in JSX attributes (jsx-quotes) + +JSX attribute values can contain string literals, which are delimited with single or double quotes. + +```xml + + +``` + +Unlike string literals in JavaScript, string literals within JSX attributes can’t contain escaped quotes. +If you want to have e.g. a double quote within a JSX attribute value, you have to use single quotes as string delimiter. + +```xml + + +``` + +## Rule Details + +This rule enforces the consistent use of either double or single quotes in JSX attributes. + +## Options + +This rule has a string option: + +* `"prefer-double"` (default) enforces the use of double quotes for all JSX attribute values that don't contain a double quote. +* `"prefer-single"` enforces the use of single quotes for all JSX attribute values that don’t contain a single quote. + +### prefer-double + +Examples of **incorrect** code for this rule with the default `"prefer-double"` option: + +```xml +/*eslint jsx-quotes: ["error", "prefer-double"]*/ + + +``` + +Examples of **correct** code for this rule with the default `"prefer-double"` option: + +```xml +/*eslint jsx-quotes: ["error", "prefer-double"]*/ + + + +``` + +### prefer-single + +Examples of **incorrect** code for this rule with the `"prefer-single"` option: + +```xml +/*eslint jsx-quotes: ["error", "prefer-single"]*/ + + +``` + +Examples of **correct** code for this rule with the `"prefer-single"` option: + +```xml +/*eslint jsx-quotes: ["error", "prefer-single"]*/ + + + +``` + +## When Not To Use It + +You can turn this rule off if you don’t use JSX or if you aren’t concerned with a consistent usage of quotes within JSX attributes. + +## Related Rules + +* [quotes](quotes.md) diff --git a/eslint/docs/rules/key-spacing.md b/eslint/docs/rules/key-spacing.md new file mode 100644 index 0000000..c94b11f --- /dev/null +++ b/eslint/docs/rules/key-spacing.md @@ -0,0 +1,330 @@ +# enforce consistent spacing between keys and values in object literal properties (key-spacing) + +This rule enforces spacing around the colon in object literal properties. It can verify each property individually, or it can ensure horizontal alignment of adjacent properties in an object literal. + +## Rule Details + +This rule enforces consistent spacing between keys and values in object literal properties. In the case of long lines, it is acceptable to add a new line wherever whitespace is allowed. + +## Options + +This rule has an object option: + +* `"beforeColon": false (default) | true` + * `false`: disallows spaces between the key and the colon in object literals. + * `true`: requires at least one space between the key and the colon in object literals. +* `"afterColon": true (default) | false` + * `true`: requires at least one space between the colon and the value in object literals. + * `false`: disallows spaces between the colon and the value in object literals. +* `"mode": "strict" (default) | "minimum"` + * `"strict"`: enforces exactly one space before or after colons in object literals. + * `"minimum"`: enforces one or more spaces before or after colons in object literals. +* `"align": "value" | "colon"` + * `"value"`: enforces horizontal alignment of values in object literals. + * `"colon"` enforces horizontal alignment of both colons and values in object literals. +* `"align"` with an object value allows for fine-grained spacing when values are being aligned in object literals. +* `"singleLine"` specifies a spacing style for single-line object literals. +* `"multiLine"` specifies a spacing style for multi-line object literals. + +Please note that you can either use the top-level options or the grouped options (`singleLine` and `multiLine`) but not both. + +### beforeColon + +Examples of **incorrect** code for this rule with the default `{ "beforeColon": false }` option: + +```js +/*eslint key-spacing: ["error", { "beforeColon": false }]*/ + +var obj = { "foo" : 42 }; +``` + +Examples of **correct** code for this rule with the default `{ "beforeColon": false }` option: + +```js +/*eslint key-spacing: ["error", { "beforeColon": false }]*/ + +var obj = { "foo": 42 }; +``` + +Examples of **incorrect** code for this rule with the `{ "beforeColon": true }` option: + +```js +/*eslint key-spacing: ["error", { "beforeColon": true }]*/ + +var obj = { "foo": 42 }; +``` + +Examples of **correct** code for this rule with the `{ "beforeColon": true }` option: + +```js +/*eslint key-spacing: ["error", { "beforeColon": true }]*/ + +var obj = { "foo" : 42 }; +``` + +### afterColon + +Examples of **incorrect** code for this rule with the default `{ "afterColon": true }` option: + +```js +/*eslint key-spacing: ["error", { "afterColon": true }]*/ + +var obj = { "foo":42 }; +``` + +Examples of **correct** code for this rule with the default `{ "afterColon": true }` option: + +```js +/*eslint key-spacing: ["error", { "afterColon": true }]*/ + +var obj = { "foo": 42 }; +``` + +Examples of **incorrect** code for this rule with the `{ "afterColon": false }` option: + +```js +/*eslint key-spacing: ["error", { "afterColon": false }]*/ + +var obj = { "foo": 42 }; +``` + +Examples of **correct** code for this rule with the `{ "afterColon": false }` option: + +```js +/*eslint key-spacing: ["error", { "afterColon": false }]*/ + +var obj = { "foo":42 }; +``` + +### mode + +Examples of **incorrect** code for this rule with the default `{ "mode": "strict" }` option: + +```js +/*eslint key-spacing: ["error", { "mode": "strict" }]*/ + +call({ + foobar: 42, + bat: 2 * 2 +}); +``` + +Examples of **correct** code for this rule with the default `{ "mode": "strict" }` option: + +```js +/*eslint key-spacing: ["error", { "mode": "strict" }]*/ + +call({ + foobar: 42, + bat: 2 * 2 +}); +``` + +Examples of **correct** code for this rule with the `{ "mode": "minimum" }` option: + +```js +/*eslint key-spacing: ["error", { "mode": "minimum" }]*/ + +call({ + foobar: 42, + bat: 2 * 2 +}); +``` + +### align + +Examples of **incorrect** code for this rule with the `{ "align": "value" }` option: + +```js +/*eslint key-spacing: ["error", { "align": "value" }]*/ + +var obj = { + a: value, + bcde: 42, + fg : foo() +}; +``` + +Examples of **correct** code for this rule with the `{ "align": "value" }` option: + +```js +/*eslint key-spacing: ["error", { "align": "value" }]*/ + +var obj = { + a: value, + bcde: 42, + + fg: foo(), + h: function() { + return this.a; + }, + ijkl: 'Non-consecutive lines form a new group' +}; + +var obj = { a: "foo", longPropertyName: "bar" }; +``` + +Examples of **incorrect** code for this rule with the `{ "align": "colon" }` option: + +```js +/*eslint key-spacing: ["error", { "align": "colon" }]*/ + +call({ + foobar: 42, + bat: 2 * 2 +}); +``` + +Examples of **correct** code for this rule with the `{ "align": "colon" }` option: + +```js +/*eslint key-spacing: ["error", { "align": "colon" }]*/ + +call({ + foobar: 42, + bat : 2 * 2 +}); +``` + +### align + +The `align` option can take additional configuration through the `beforeColon`, `afterColon`, `mode`, and `on` options. + +If `align` is defined as an object, but not all of the parameters are provided, undefined parameters will default to the following: + +```js +// Defaults +align: { + "beforeColon": false, + "afterColon": true, + "on": "colon", + "mode": "strict" +} +``` + +Examples of **correct** code for this rule with sample `{ "align": { } }` options: + +```js +/*eslint key-spacing: ["error", { + "align": { + "beforeColon": true, + "afterColon": true, + "on": "colon" + } +}]*/ + +var obj = { + "one" : 1, + "seven" : 7 +} +``` + +```js +/*eslint key-spacing: ["error", { + "align": { + "beforeColon": false, + "afterColon": false, + "on": "value" + } +}]*/ + +var obj = { + "one": 1, + "seven":7 +} +``` + +### align and multiLine + +The `multiLine` and `align` options can differ, which allows for fine-tuned control over the `key-spacing` of your files. `align` will **not** inherit from `multiLine` if `align` is configured as an object. + +`multiLine` is used any time an object literal spans multiple lines. The `align` configuration is used when there is a group of properties in the same object. For example: + +```javascript +var myObj = { + key1: 1, // uses multiLine + + key2: 2, // uses align (when defined) + key3: 3, // uses align (when defined) + + key4: 4 // uses multiLine +} + +``` + +Examples of **incorrect** code for this rule with sample `{ "align": { }, "multiLine": { } }` options: + +```js +/*eslint key-spacing: ["error", { + "multiLine": { + "beforeColon": false, + "afterColon":true + }, + "align": { + "beforeColon": true, + "afterColon": true, + "on": "colon" + } +}]*/ + +var obj = { + "myObjectFunction": function() { + // Do something + }, + "one" : 1, + "seven" : 7 +} +``` + +Examples of **correct** code for this rule with sample `{ "align": { }, "multiLine": { } }` options: + +```js +/*eslint key-spacing: ["error", { + "multiLine": { + "beforeColon": false, + "afterColon": true + + }, + "align": { + "beforeColon": true, + "afterColon": true, + "on": "colon" + } +}]*/ + +var obj = { + "myObjectFunction": function() { + // Do something + // + }, // These are two separate groups, so no alignment between `myObjectFuction` and `one` + "one" : 1, + "seven" : 7 // `one` and `seven` are in their own group, and therefore aligned +} +``` + +### singleLine and multiLine + +Examples of **correct** code for this rule with sample `{ "singleLine": { }, "multiLine": { } }` options: + +```js +/*eslint "key-spacing": [2, { + "singleLine": { + "beforeColon": false, + "afterColon": true + }, + "multiLine": { + "beforeColon": true, + "afterColon": true, + "align": "colon" + } +}]*/ +var obj = { one: 1, "two": 2, three: 3 }; +var obj2 = { + "two" : 2, + three : 3 +}; +``` + +## When Not To Use It + +If you have another convention for property spacing that might not be consistent with the available options, or if you want to permit multiple styles concurrently you can safely disable this rule. diff --git a/eslint/docs/rules/keyword-spacing.md b/eslint/docs/rules/keyword-spacing.md new file mode 100644 index 0000000..ecc6bb0 --- /dev/null +++ b/eslint/docs/rules/keyword-spacing.md @@ -0,0 +1,272 @@ +# enforce consistent spacing before and after keywords (keyword-spacing) + +Keywords are syntax elements of JavaScript, such as `try` and `if`. +These keywords have special meaning to the language and so often appear in a different color in code editors. +As an important part of the language, style guides often refer to the spacing that should be used around keywords. +For example, you might have a style guide that says keywords should be always surrounded by spaces, which would mean `if-else` statements must look like this: + +```js +if (foo) { + // ... +} else { + // ... +} +``` + +Of course, you could also have a style guide that disallows spaces around keywords. + +However, if you want to enforce the style of spacing between the `function` keyword and the following opening parenthesis, please refer to [space-before-function-paren](space-before-function-paren.md). + +## Rule Details + +This rule enforces consistent spacing around keywords and keyword-like tokens: `as` (in module declarations), `async` (of async functions), `await` (of await expressions), `break`, `case`, `catch`, `class`, `const`, `continue`, `debugger`, `default`, `delete`, `do`, `else`, `export`, `extends`, `finally`, `for`, `from` (in module declarations), `function`, `get` (of getters), `if`, `import`, `in`, `instanceof`, `let`, `new`, `of` (in for-of statements), `return`, `set` (of setters), `static`, `super`, `switch`, `this`, `throw`, `try`, `typeof`, `var`, `void`, `while`, `with`, and `yield`. This rule is designed carefully not to conflict with other spacing rules: it does not apply to spacing where other rules report problems. + +## Options + +This rule has an object option: + +* `"before": true` (default) requires at least one space before keywords +* `"before": false` disallows spaces before keywords +* `"after": true` (default) requires at least one space after keywords +* `"after": false` disallows spaces after keywords +* `"overrides"` allows overriding spacing style for specified keywords + +### before + +Examples of **incorrect** code for this rule with the default `{ "before": true }` option: + +```js +/*eslint keyword-spacing: ["error", { "before": true }]*/ + +if (foo) { + //... +}else if (bar) { + //... +}else { + //... +} +``` + +Examples of **correct** code for this rule with the default `{ "before": true }` option: + +```js +/*eslint keyword-spacing: ["error", { "before": true }]*/ +/*eslint-env es6*/ + +if (foo) { + //... +} else if (bar) { + //... +} else { + //... +} + +// Avoid conflict with `array-bracket-spacing` +let a = [this]; +let b = [function() {}]; + +// Avoid conflict with `arrow-spacing` +let a = ()=> this.foo; + +// Avoid conflict with `block-spacing` +{function foo() {}} + +// Avoid conflict with `comma-spacing` +let a = [100,this.foo, this.bar]; + +// Avoid conflict with `computed-property-spacing` +obj[this.foo] = 0; + +// Avoid conflict with `generator-star-spacing` +function *foo() {} + +// Avoid conflict with `key-spacing` +let obj = { + foo:function() {} +}; + +// Avoid conflict with `object-curly-spacing` +let obj = {foo: this}; + +// Avoid conflict with `semi-spacing` +let a = this;function foo() {} + +// Avoid conflict with `space-in-parens` +(function () {})(); + +// Avoid conflict with `space-infix-ops` +if ("foo"in {foo: 0}) {} +if (10+this.foo<= this.bar) {} + +// Avoid conflict with `jsx-curly-spacing` +let a = +``` + +Examples of **incorrect** code for this rule with the `{ "before": false }` option: + +```js +/*eslint keyword-spacing: ["error", { "before": false }]*/ + +if (foo) { + //... +} else if (bar) { + //... +} else { + //... +} +``` + +Examples of **correct** code for this rule with the `{ "before": false }` option: + +```js +/*eslint keyword-spacing: ["error", { "before": false }]*/ + +if (foo) { + //... +}else if (bar) { + //... +}else { + //... +} +``` + +### after + +Examples of **incorrect** code for this rule with the default `{ "after": true }` option: + +```js +/*eslint keyword-spacing: ["error", { "after": true }]*/ + +if(foo) { + //... +} else if(bar) { + //... +} else{ + //... +} +``` + +Examples of **correct** code for this rule with the default `{ "after": true }` option: + +```js +/*eslint keyword-spacing: ["error", { "after": true }]*/ + +if (foo) { + //... +} else if (bar) { + //... +} else { + //... +} + +// Avoid conflict with `array-bracket-spacing` +let a = [this]; + +// Avoid conflict with `arrow-spacing` +let a = ()=> this.foo; + +// Avoid conflict with `comma-spacing` +let a = [100, this.foo, this.bar]; + +// Avoid conflict with `computed-property-spacing` +obj[this.foo] = 0; + +// Avoid conflict with `generator-star-spacing` +function* foo() {} + +// Avoid conflict with `key-spacing` +let obj = { + foo:function() {} +}; + +// Avoid conflict with `func-call-spacing` +class A { + constructor() { + super(); + } +} + +// Avoid conflict with `object-curly-spacing` +let obj = {foo: this}; + +// Avoid conflict with `semi-spacing` +let a = this;function foo() {} + +// Avoid conflict with `space-before-function-paren` +function() {} + +// Avoid conflict with `space-infix-ops` +if ("foo"in{foo: 0}) {} +if (10+this.foo<= this.bar) {} + +// Avoid conflict with `space-unary-ops` +function* foo(a) { + return yield+a; +} + +// Avoid conflict with `yield-star-spacing` +function* foo(a) { + return yield* a; +} + +// Avoid conflict with `jsx-curly-spacing` +let a = +``` + +Examples of **incorrect** code for this rule with the `{ "after": false }` option: + +```js +/*eslint keyword-spacing: ["error", { "after": false }]*/ + +if (foo) { + //... +} else if (bar) { + //... +} else { + //... +} +``` + +Examples of **correct** code for this rule with the `{ "after": false }` option: + +```js +/*eslint keyword-spacing: ["error", { "after": false }]*/ + +if(foo) { + //... +} else if(bar) { + //... +} else{ + //... +} +``` + +### overrides + +Examples of **correct** code for this rule with the `{ "overrides": { "if": { "after": false }, "for": { "after": false }, "while": { "after": false } } }` option: + +```js +/*eslint keyword-spacing: ["error", { "overrides": { + "if": { "after": false }, + "for": { "after": false }, + "while": { "after": false } +} }]*/ + +if(foo) { + //... +} else if(bar) { + //... +} else { + //... +} + +for(;;); + +while(true) { + //... +} +``` + +## When Not To Use It + +If you don't want to enforce consistency on keyword spacing, then it's safe to disable this rule. diff --git a/eslint/docs/rules/line-comment-position.md b/eslint/docs/rules/line-comment-position.md new file mode 100644 index 0000000..7344534 --- /dev/null +++ b/eslint/docs/rules/line-comment-position.md @@ -0,0 +1,106 @@ +# enforce position of line comments (line-comment-position) + +Line comments can be positioned above or beside code. This rule helps teams maintain a consistent style. + +```js +// above comment +var foo = "bar"; // beside comment +``` + +## Rule Details + +This rule enforces consistent position of line comments. Block comments are not affected by this rule. By default, this rule ignores comments starting with the following words: `eslint`, `jshint`, `jslint`, `istanbul`, `global`, `exported`, `jscs`, `falls through`. + + +## Options + +This rule takes one argument, which can be a string or an object. The string settings are the same as those of the `position` property (explained below). The object option has the following properties: + +### position + +The `position` option has two settings: + +* `above` (default) enforces line comments only above code, in its own line. +* `beside` enforces line comments only at the end of code lines. + +#### position: above + +Examples of **correct** code for the `{ "position": "above" }` option: + +```js +/*eslint line-comment-position: ["error", { "position": "above" }]*/ +// valid comment +1 + 1; +``` + + +Examples of **incorrect** code for the `{ "position": "above" }` option: + +```js +/*eslint line-comment-position: ["error", { "position": "above" }]*/ +1 + 1; // invalid comment +``` + +#### position: beside + +Examples of **correct** code for the `{ "position": "beside" }` option: + +```js +/*eslint line-comment-position: ["error", { "position": "beside" }]*/ +1 + 1; // valid comment +``` + + +Examples of **incorrect** code for the `{ "position": "beside" }` option: + +```js +/*eslint line-comment-position: ["error", { "position": "beside" }]*/ +// invalid comment +1 + 1; +``` + +### ignorePattern + +By default this rule ignores comments starting with the following words: `eslint`, `jshint`, `jslint`, `istanbul`, `global`, `exported`, `jscs`, `falls through`. An alternative regular expression can be provided. + +Examples of **correct** code for the `ignorePattern` option: + +```js +/*eslint line-comment-position: ["error", { "ignorePattern": "pragma" }]*/ +1 + 1; // pragma valid comment +``` + +Examples of **incorrect** code for the `ignorePattern` option: + +```js +/*eslint line-comment-position: ["error", { "ignorePattern": "pragma" }]*/ +1 + 1; // invalid comment +``` + +### applyDefaultIgnorePatterns + +Default ignore patterns are applied even when `ignorePattern` is provided. If you want to omit default patterns, set this option to `false`. + +Examples of **correct** code for the `{ "applyDefaultIgnorePatterns": false }` option: + +```js +/*eslint line-comment-position: ["error", { "ignorePattern": "pragma", "applyDefaultIgnorePatterns": false }]*/ +1 + 1; // pragma valid comment +``` + +Examples of **incorrect** code for the `{ "applyDefaultIgnorePatterns": false }` option: + +```js +/*eslint line-comment-position: ["error", { "ignorePattern": "pragma", "applyDefaultIgnorePatterns": false }]*/ +1 + 1; // falls through +``` + +**Deprecated:** the object property `applyDefaultPatterns` is deprecated. Please use the property `applyDefaultIgnorePatterns` instead. + +## When Not To Use It + +If you aren't concerned about having different line comment styles, then you can turn off this rule. + +## Compatibility + +**JSCS**: [validateCommentPosition](https://jscs-dev.github.io/rule/validateCommentPosition) diff --git a/eslint/docs/rules/linebreak-style.md b/eslint/docs/rules/linebreak-style.md new file mode 100644 index 0000000..d2aa5a1 --- /dev/null +++ b/eslint/docs/rules/linebreak-style.md @@ -0,0 +1,86 @@ +# enforce consistent linebreak style (linebreak-style) + +When developing with a lot of people all having different editors, VCS applications and operating systems it may occur that +different line endings are written by either of the mentioned (might especially happen when using the windows and mac versions of SourceTree together). + +The linebreaks (new lines) used in windows operating system are usually _carriage returns_ (CR) followed by a _line feed_ (LF) making it a _carriage return line feed_ (CRLF) +whereas Linux and Unix use a simple _line feed_ (LF). The corresponding _control sequences_ are `"\n"` (for LF) and `"\r\n"` for (CRLF). + +Many versioning systems (like git and subversion) can automatically ensure the correct ending. However to cover all contingencies, you can activate this rule. + +## Rule Details + +This rule enforces consistent line endings independent of operating system, VCS, or editor used across your codebase. + +### Options + +This rule has a string option: + +* `"unix"` (default) enforces the usage of Unix line endings: `\n` for LF. +* `"windows"` enforces the usage of Windows line endings: `\r\n` for CRLF. + + +### unix + +Examples of **incorrect** code for this rule with the default `"unix"` option: + +```js +/*eslint linebreak-style: ["error", "unix"]*/ + +var a = 'a'; // \r\n + +``` + +Examples of **correct** code for this rule with the default `"unix"` option: + +```js +/*eslint linebreak-style: ["error", "unix"]*/ + +var a = 'a', // \n + b = 'b'; // \n +// \n +function foo(params) { // \n + // do stuff \n +}// \n +``` + +### windows + +Examples of **incorrect** code for this rule with the `"windows"` option: + +```js +/*eslint linebreak-style: ["error", "windows"]*/ + +var a = 'a'; // \n +``` + +Examples of **correct** code for this rule with the `"windows"` option: + +```js +/*eslint linebreak-style: ["error", "windows"]*/ + +var a = 'a', // \r\n + b = 'b'; // \r\n +// \r\n +function foo(params) { // \r\n + // do stuff \r\n +} // \r\n +``` + +## Using this rule with version control systems + +Version control systems sometimes have special behavior for linebreaks. To make it easy for developers to contribute to your codebase from different platforms, you may want to configure your VCS to handle linebreaks appropriately. + +For example, the default behavior of [git](https://git-scm.com/) on Windows systems is to convert LF linebreaks to CRLF when checking out files, but to store the linebreaks as LF when committing a change. This will cause the `linebreak-style` rule to report errors if configured with the `"unix"` setting, because the files that ESLint sees will have CRLF linebreaks. If you use git, you may want to add a line to your [`.gitattributes` file](https://git-scm.com/docs/gitattributes) to prevent git from converting linebreaks in `.js` files: + +``` +*.js text eol=lf +``` + +## When Not To Use It + +If you aren't concerned about having different line endings within your code, then you can safely turn this rule off. + +## Compatibility + +* **JSCS**: [validateLineBreaks](https://jscs-dev.github.io/rule/validateLineBreaks) diff --git a/eslint/docs/rules/lines-around-comment.md b/eslint/docs/rules/lines-around-comment.md new file mode 100644 index 0000000..61af596 --- /dev/null +++ b/eslint/docs/rules/lines-around-comment.md @@ -0,0 +1,498 @@ +# require empty lines around comments (lines-around-comment) + +Many style guides require empty lines before or after comments. The primary goal +of these rules is to make the comments easier to read and improve readability of the code. + +## Rule Details + +This rule requires empty lines before and/or after comments. It can be enabled separately for both block (`/*`) and line (`//`) comments. This rule does not apply to comments that appear on the same line as code and does not require empty lines at the beginning or end of a file. + +## Options + +This rule has an object option: + +* `"beforeBlockComment": true` (default) requires an empty line before block comments +* `"afterBlockComment": true` requires an empty line after block comments +* `"beforeLineComment": true` requires an empty line before line comments +* `"afterLineComment": true` requires an empty line after line comments +* `"allowBlockStart": true` allows comments to appear at the start of block statements +* `"allowBlockEnd": true` allows comments to appear at the end of block statements +* `"allowObjectStart": true` allows comments to appear at the start of object literals +* `"allowObjectEnd": true` allows comments to appear at the end of object literals +* `"allowArrayStart": true` allows comments to appear at the start of array literals +* `"allowArrayEnd": true` allows comments to appear at the end of array literals +* `"allowClassStart": true` allows comments to appear at the start of classes +* `"allowClassEnd": true` allows comments to appear at the end of classes +* `"applyDefaultIgnorePatterns"` enables or disables the default comment patterns to be ignored by the rule +* `"ignorePattern"` custom patterns to be ignored by the rule + + +### beforeBlockComment + +Examples of **incorrect** code for this rule with the default `{ "beforeBlockComment": true }` option: + +```js +/*eslint lines-around-comment: ["error", { "beforeBlockComment": true }]*/ + +var night = "long"; +/* what a great and wonderful day */ +var day = "great" +``` + +Examples of **correct** code for this rule with the default `{ "beforeBlockComment": true }` option: + +```js +/*eslint lines-around-comment: ["error", { "beforeBlockComment": true }]*/ + +var night = "long"; + +/* what a great and wonderful day */ +var day = "great" +``` + +### afterBlockComment + +Examples of **incorrect** code for this rule with the `{ "afterBlockComment": true }` option: + +```js +/*eslint lines-around-comment: ["error", { "afterBlockComment": true }]*/ + +var night = "long"; + +/* what a great and wonderful day */ +var day = "great" +``` + +Examples of **correct** code for this rule with the `{ "afterBlockComment": true }` option: + +```js +/*eslint lines-around-comment: ["error", { "afterBlockComment": true }]*/ + +var night = "long"; + +/* what a great and wonderful day */ + +var day = "great" +``` + +### beforeLineComment + +Examples of **incorrect** code for this rule with the `{ "beforeLineComment": true }` option: + +```js +/*eslint lines-around-comment: ["error", { "beforeLineComment": true }]*/ + +var night = "long"; +// what a great and wonderful day +var day = "great" +``` + +Examples of **correct** code for this rule with the `{ "beforeLineComment": true }` option: + +```js +/*eslint lines-around-comment: ["error", { "beforeLineComment": true }]*/ + +var night = "long"; + +// what a great and wonderful day +var day = "great" +``` + +### afterLineComment + +Examples of **incorrect** code for this rule with the `{ "afterLineComment": true }` option: + +```js +/*eslint lines-around-comment: ["error", { "afterLineComment": true }]*/ + +var night = "long"; +// what a great and wonderful day +var day = "great" +``` + +Examples of **correct** code for this rule with the `{ "afterLineComment": true }` option: + +```js +/*eslint lines-around-comment: ["error", { "afterLineComment": true }]*/ + +var night = "long"; +// what a great and wonderful day + +var day = "great" +``` + +### allowBlockStart + +Examples of **correct** code for this rule with the `{ "beforeLineComment": true, "allowBlockStart": true }` options: + +```js +/*eslint lines-around-comment: ["error", { "beforeLineComment": true, "allowBlockStart": true }]*/ + +function foo(){ + // what a great and wonderful day + var day = "great" + return day; +} +``` + +Examples of **correct** code for this rule with the `{ "beforeBlockComment": true, "allowBlockStart": true }` options: + +```js +/*eslint lines-around-comment: ["error", { "beforeBlockComment": true, "allowBlockStart": true }]*/ + +function foo(){ + /* what a great and wonderful day */ + var day = "great" + return day; +} +``` + +### allowBlockEnd + +Examples of **correct** code for this rule with the `{ "afterLineComment": true, "allowBlockEnd": true }` option: + +```js +/*eslint lines-around-comment: ["error", { "afterLineComment": true, "allowBlockEnd": true }]*/ + +function foo(){ + var day = "great" + return day; + // what a great and wonderful day +} +``` + +Examples of **correct** code for this rule with the `{ "afterBlockComment": true, "allowBlockEnd": true }` option: + +```js +/*eslint lines-around-comment: ["error", { "afterBlockComment": true, "allowBlockEnd": true }]*/ + +function foo(){ + var day = "great" + return day; + + /* what a great and wonderful day */ +} +``` + +### allowClassStart + +Examples of **incorrect** code for this rule with the `{ "beforeLineComment": true, "allowClassStart": false }` option: + +```js +/*eslint lines-around-comment: ["error", { "beforeLineComment": true, "allowClassStart": false }]*/ + +class foo { + // what a great and wonderful day + day() {} +}; +``` + +Examples of **correct** code for this rule with the `{ "beforeLineComment": true, "allowClassStart": false }` option: + +```js +/*eslint lines-around-comment: ["error", { "beforeLineComment": true, "allowClassStart": false }]*/ + +class foo { + + // what a great and wonderful day + day() {} +}; +``` + +Examples of **correct** code for this rule with the `{ "beforeLineComment": true, "allowClassStart": true }` option: + +```js +/*eslint lines-around-comment: ["error", { "beforeLineComment": true, "allowClassStart": true }]*/ + +class foo { + // what a great and wonderful day + day() {} +}; +``` + +Examples of **incorrect** code for this rule with the `{ "beforeBlockComment": true, "allowClassStart": false }` option: + +```js +/*eslint lines-around-comment: ["error", { "beforeBlockComment": true, "allowClassStart": false }]*/ + +class foo { + /* what a great and wonderful day */ + day() {} +}; +``` + +Examples of **correct** code for this rule with the `{ "beforeBlockComment": true, "allowClassStart": false }` option: + +```js +/*eslint lines-around-comment: ["error", { "beforeBlockComment": true, "allowClassStart": false }]*/ + +class foo { + + /* what a great and wonderful day */ + day() {} +}; +``` + +Examples of **correct** code for this rule with the `{ "beforeBlockComment": true, "allowClassStart": true }` option: + +```js +/*eslint lines-around-comment: ["error", { "beforeBlockComment": true, "allowClassStart": true }]*/ + +class foo { + /* what a great and wonderful day */ + day() {} +}; +``` + +### allowClassEnd + +Examples of **correct** code for this rule with the `{ "afterLineComment": true, "allowClassEnd": true }` option: + +```js +/*eslint lines-around-comment: ["error", { "afterLineComment": true, "allowClassEnd": true }]*/ + +class foo { + day() {} + // what a great and wonderful day +}; +``` + +Examples of **correct** code for this rule with the `{ "afterBlockComment": true, "allowClassEnd": true }` option: + +```js +/*eslint lines-around-comment: ["error", { "afterBlockComment": true, "allowClassEnd": true }]*/ + +class foo { + day() {} + + /* what a great and wonderful day */ +}; +``` + +### allowObjectStart + +Examples of **correct** code for this rule with the `{ "beforeLineComment": true, "allowObjectStart": true }` option: + +```js +/*eslint lines-around-comment: ["error", { "beforeLineComment": true, "allowObjectStart": true }]*/ + +var foo = { + // what a great and wonderful day + day: "great" +}; + +const { + // what a great and wonderful day + foo: someDay +} = {foo: "great"}; + +const { + // what a great and wonderful day + day +} = {day: "great"}; +``` + +Examples of **correct** code for this rule with the `{ "beforeBlockComment": true, "allowObjectStart": true }` option: + +```js +/*eslint lines-around-comment: ["error", { "beforeBlockComment": true, "allowObjectStart": true }]*/ + +var foo = { + /* what a great and wonderful day */ + day: "great" +}; + +const { + /* what a great and wonderful day */ + foo: someDay +} = {foo: "great"}; + +const { + /* what a great and wonderful day */ + day +} = {day: "great"}; +``` + +### allowObjectEnd + +Examples of **correct** code for this rule with the `{ "afterLineComment": true, "allowObjectEnd": true }` option: + +```js +/*eslint lines-around-comment: ["error", { "afterLineComment": true, "allowObjectEnd": true }]*/ + +var foo = { + day: "great" + // what a great and wonderful day +}; + +const { + foo: someDay + // what a great and wonderful day +} = {foo: "great"}; + +const { + day + // what a great and wonderful day +} = {day: "great"}; +``` + +Examples of **correct** code for this rule with the `{ "afterBlockComment": true, "allowObjectEnd": true }` option: + +```js +/*eslint lines-around-comment: ["error", { "afterBlockComment": true, "allowObjectEnd": true }]*/ + +var foo = { + day: "great" + + /* what a great and wonderful day */ +}; + +const { + foo: someDay + + /* what a great and wonderful day */ +} = {foo: "great"}; + +const { + day + + /* what a great and wonderful day */ +} = {day: "great"}; +``` + +### allowArrayStart + +Examples of **correct** code for this rule with the `{ "beforeLineComment": true, "allowArrayStart": true }` option: + +```js +/*eslint lines-around-comment: ["error", { "beforeLineComment": true, "allowArrayStart": true }]*/ + +var day = [ + // what a great and wonderful day + "great", + "wonderful" +]; + +const [ + // what a great and wonderful day + someDay +] = ["great", "not great"]; +``` + +Examples of **correct** code for this rule with the `{ "beforeBlockComment": true, "allowArrayStart": true }` option: + +```js +/*eslint lines-around-comment: ["error", { "beforeBlockComment": true, "allowArrayStart": true }]*/ + +var day = [ + /* what a great and wonderful day */ + "great", + "wonderful" +]; + +const [ + /* what a great and wonderful day */ + someDay +] = ["great", "not great"]; +``` + +### allowArrayEnd + +Examples of **correct** code for this rule with the `{ "afterLineComment": true, "allowArrayEnd": true }` option: + +```js +/*eslint lines-around-comment: ["error", { "afterLineComment": true, "allowArrayEnd": true }]*/ + +var day = [ + "great", + "wonderful" + // what a great and wonderful day +]; + +const [ + someDay + // what a great and wonderful day +] = ["great", "not great"]; +``` + +Examples of **correct** code for this rule with the `{ "afterBlockComment": true, "allowArrayEnd": true }` option: + +```js +/*eslint lines-around-comment: ["error", { "afterBlockComment": true, "allowArrayEnd": true }]*/ + +var day = [ + "great", + "wonderful" + + /* what a great and wonderful day */ +]; + +const [ + someDay + + /* what a great and wonderful day */ +] = ["great", "not great"]; +``` + + +### ignorePattern + +By default this rule ignores comments starting with the following words: `eslint`, `jshint`, `jslint`, `istanbul`, `global`, `exported`, `jscs`. An alternative regular expression can be provided. + +Examples of **correct** code for the `ignorePattern` option: + +```js +/*eslint lines-around-comment: ["error"]*/ + +foo(); +/* eslint mentioned in this comment */, +bar(); + + +/*eslint lines-around-comment: ["error", { "ignorePattern": "pragma" }] */ + +foo(); +/* a valid comment using pragma in it */ +``` + +Examples of **incorrect** code for the `ignorePattern` option: + +```js +/*eslint lines-around-comment: ["error", { "ignorePattern": "pragma" }] */ + +1 + 1; +/* something else */ +``` + +### applyDefaultIgnorePatterns + +Default ignore patterns are applied even when `ignorePattern` is provided. If you want to omit default patterns, set this option to `false`. + +Examples of **correct** code for the `{ "applyDefaultIgnorePatterns": false }` option: + +```js +/*eslint lines-around-comment: ["error", { "ignorePattern": "pragma", applyDefaultIgnorePatterns: false }] */ + +foo(); +/* a valid comment using pragma in it */ +``` + +Examples of **incorrect** code for the `{ "applyDefaultIgnorePatterns": false }` option: + +```js +/*eslint lines-around-comment: ["error", { "applyDefaultIgnorePatterns": false }] */ + +foo(); +/* eslint mentioned in comment */ + +``` + + +## When Not To Use It + +Many people enjoy a terser code style and don't mind comments bumping up against code. If you fall into that category this rule is not for you. + +## Related Rules + +* [space-before-blocks](space-before-blocks.md) +* [spaced-comment](spaced-comment.md) diff --git a/eslint/docs/rules/lines-around-directive.md b/eslint/docs/rules/lines-around-directive.md new file mode 100644 index 0000000..6aa9f1d --- /dev/null +++ b/eslint/docs/rules/lines-around-directive.md @@ -0,0 +1,322 @@ +# require or disallow newlines around directives (lines-around-directive) + +This rule was **deprecated** in ESLint v4.0.0 and replaced by the [padding-line-between-statements](padding-line-between-statements.md) rule. + +Directives are used in JavaScript to indicate to the execution environment that a script would like to opt into a feature such as `"strict mode"`. Directives are grouped together in a [directive prologue](https://www.ecma-international.org/ecma-262/7.0/#directive-prologue) at the top of either a file or function block and are applied to the scope in which they occur. + +```js +// Strict mode is invoked for the entire script +"use strict"; + +var foo; + +function bar() { + var baz; +} +``` + +```js +var foo; + +function bar() { + // Strict mode is only invoked within this function + "use strict"; + + var baz; +} +``` + +## Rule Details + +This rule requires or disallows blank newlines around directive prologues. This rule does not enforce any conventions about blank newlines between the individual directives. In addition, it does not require blank newlines before directive prologues unless they are preceded by a comment. Please use the [padded-blocks](padded-blocks.md) rule if this is a style you would like to enforce. + +## Options + +This rule has one option. It can either be a string or an object: + +* `"always"` (default) enforces blank newlines around directives. +* `"never"` disallows blank newlines around directives. + +or + +```js +{ + "before": "always" or "never" + "after": "always" or "never", +} +``` + +### always + +This is the default option. + +Examples of **incorrect** code for this rule with the `"always"` option: + +```js +/* eslint lines-around-directive: ["error", "always"] */ + +/* Top of file */ +"use strict"; +var foo; + +/* Top of file */ +// comment +"use strict"; +"use asm"; +var foo; + +function foo() { + "use strict"; + "use asm"; + var bar; +} + +function foo() { + // comment + "use strict"; + var bar; +} +``` + +Examples of **correct** code for this rule with the `"always"` option: + +```js +/* eslint lines-around-directive: ["error", "always"] */ + +/* Top of file */ +"use strict"; + +var foo; + +/* Top of file */ +// comment + +"use strict"; +"use asm"; + +var foo; + +function foo() { + "use strict"; + "use asm"; + + var bar; +} + +function foo() { + // comment + + "use strict"; + + var bar; +} +``` + +### never + +Examples of **incorrect** code for this rule with the `"never"` option: + +```js +/* eslint lines-around-directive: ["error", "never"] */ + +/* Top of file */ + +"use strict"; + +var foo; + + +/* Top of file */ +// comment + +"use strict"; +"use asm"; + +var foo; + + +function foo() { + "use strict"; + "use asm"; + + var bar; +} + + +function foo() { + // comment + + "use strict"; + + var bar; +} +``` + +Examples of **correct** code for this rule with the `"never"` option: + +```js +/* eslint lines-around-directive: ["error", "never"] */ + +/* Top of file */ +"use strict"; +var foo; + +/* Top of file */ +// comment +"use strict"; +"use asm"; +var foo; + +function foo() { + "use strict"; + "use asm"; + var bar; +} + +function foo() { + // comment + "use strict"; + var bar; +} +``` + +### before & after + +Examples of **incorrect** code for this rule with the `{ "before": "never", "after": "always" }` option: + +```js +/* eslint lines-around-directive: ["error", { "before": "never", "after": "always" }] */ + +/* Top of file */ + +"use strict"; +var foo; + +/* Top of file */ +// comment + +"use strict"; +"use asm"; +var foo; + +function foo() { + "use strict"; + "use asm"; + var bar; +} + +function foo() { + // comment + + "use strict"; + var bar; +} +``` + +Examples of **correct** code for this rule with the `{ "before": "never", "after": "always" }` option: + +```js +/* eslint lines-around-directive: ["error", { "before": "never", "after": "always" }] */ + +/* Top of file */ +"use strict"; + +var foo; + +/* Top of file */ +// comment +"use strict"; +"use asm"; + +var foo; + +function foo() { + "use strict"; + "use asm"; + + var bar; +} + +function foo() { + // comment + "use strict"; + + var bar; +} +``` + +Examples of **incorrect** code for this rule with the `{ "before": "always", "after": "never" }` option: + +```js +/* eslint lines-around-directive: ["error", { "before": "always", "after": "never" }] */ + +/* Top of file */ +"use strict"; + +var foo; + +/* Top of file */ +// comment +"use strict"; +"use asm"; + +var foo; + +function foo() { + "use strict"; + "use asm"; + + var bar; +} + +function foo() { + // comment + "use strict"; + + var bar; +} +``` + +Examples of **correct** code for this rule with the `{ "before": "always", "after": "never" }` option: + +```js +/* eslint lines-around-directive: ["error", { "before": "always", "after": "never" }] */ + +/* Top of file */ +"use strict"; +var foo; + +/* Top of file */ +// comment + +"use strict"; +"use asm"; +var foo; + +function foo() { + "use strict"; + "use asm"; + var bar; +} + +function foo() { + // comment + + "use strict"; + var bar; +} +``` + +## When Not To Use It + +You can safely disable this rule if you do not have any strict conventions about whether or not directive prologues should have blank newlines before or after them. + +## Related Rules + +* [lines-around-comment](lines-around-comment.md) +* [padded-blocks](padded-blocks.md) + +## Compatibility + +* **JSCS**: [requirePaddingNewLinesAfterUseStrict](https://jscs-dev.github.io/rule/requirePaddingNewLinesAfterUseStrict) +* **JSCS**: [disallowPaddingNewLinesAfterUseStrict](https://jscs-dev.github.io/rule/disallowPaddingNewLinesAfterUseStrict) diff --git a/eslint/docs/rules/lines-between-class-members.md b/eslint/docs/rules/lines-between-class-members.md new file mode 100644 index 0000000..c3b1449 --- /dev/null +++ b/eslint/docs/rules/lines-between-class-members.md @@ -0,0 +1,110 @@ +# require or disallow an empty line between class members (lines-between-class-members) + +This rule improves readability by enforcing lines between class members. It will not check empty lines before the first member and after the last member, since that is already taken care of by padded-blocks. + +## Rule Details + +Examples of **incorrect** code for this rule: + +```js +/* eslint lines-between-class-members: ["error", "always"]*/ +class MyClass { + foo() { + //... + } + bar() { + //... + } +} +``` + +Examples of **correct** code for this rule: + +```js +/* eslint lines-between-class-members: ["error", "always"]*/ +class MyClass { + foo() { + //... + } + + bar() { + //... + } +} +``` + +### Options + +This rule has a string option and an object option. + +String option: + +* `"always"`(default) require an empty line after class members +* `"never"` disallows an empty line after class members + +Object option: + +* `"exceptAfterSingleLine": false`(default) **do not** skip checking empty lines after single-line class members +* `"exceptAfterSingleLine": true` skip checking empty lines after single-line class members + +Examples of **incorrect** code for this rule with the string option: + +```js +/* eslint lines-between-class-members: ["error", "always"]*/ +class Foo{ + bar(){} + baz(){} +} + +/* eslint lines-between-class-members: ["error", "never"]*/ +class Foo{ + bar(){} + + baz(){} +} +``` + +Examples of **correct** code for this rule with the string option: + +```js +/* eslint lines-between-class-members: ["error", "always"]*/ +class Foo{ + bar(){} + + baz(){} +} + +/* eslint lines-between-class-members: ["error", "never"]*/ +class Foo{ + bar(){} + baz(){} +} +``` + +Examples of **correct** code for this rule with the object option: + +```js +/* eslint lines-between-class-members: ["error", "always", { exceptAfterSingleLine: true }]*/ +class Foo{ + bar(){} // single line class member + baz(){ + // multi line class member + } + + qux(){} +} +``` + +## When Not To Use It + +If you don't want to enforce empty lines between class members, you can disable this rule. + +## Related Rules + +* [padded-blocks](padded-blocks.md) +* [padding-line-between-statements](padding-line-between-statements.md) + +## Compatibility + +* [requirePaddingNewLinesAfterBlocks](https://jscs-dev.github.io/rule/requirePaddingNewLinesAfterBlocks) +* [disallowPaddingNewLinesAfterBlocks](https://jscs-dev.github.io/rule/disallowPaddingNewLinesAfterBlocks) diff --git a/eslint/docs/rules/max-classes-per-file.md b/eslint/docs/rules/max-classes-per-file.md new file mode 100644 index 0000000..98ca1d1 --- /dev/null +++ b/eslint/docs/rules/max-classes-per-file.md @@ -0,0 +1,49 @@ +# enforce a maximum number of classes per file (max-classes-per-file) + +Files containing multiple classes can often result in a less navigable +and poorly structured codebase. Best practice is to keep each file +limited to a single responsibility. + +## Rule Details + +This rule enforces that each file may contain only a particular number +of classes and no more. + +Examples of **incorrect** code for this rule: + +```js +/*eslint max-classes-per-file: "error"*/ + +class Foo {} +class Bar {} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint max-classes-per-file: "error"*/ + +class Foo {} +``` + +## Options + +This rule has a numeric option (defaulted to 1) to specify the +maximum number of classes. + +For example: + +```json +{ + "max-classes-per-file": ["error", 1] +} +``` + +Examples of **correct** code for this rule with the numeric option set to `2`: + +```js +/* eslint max-classes-per-file: ["error", 2] */ + +class Foo {} +class Bar {} +``` diff --git a/eslint/docs/rules/max-depth.md b/eslint/docs/rules/max-depth.md new file mode 100644 index 0000000..b853d02 --- /dev/null +++ b/eslint/docs/rules/max-depth.md @@ -0,0 +1,65 @@ +# enforce a maximum depth that blocks can be nested (max-depth) + +Many developers consider code difficult to read if blocks are nested beyond a certain depth. + +## Rule Details + +This rule enforces a maximum depth that blocks can be nested to reduce code complexity. + +## Options + +This rule has a number or object option: + +* `"max"` (default `4`) enforces a maximum depth that blocks can be nested + +**Deprecated:** The object property `maximum` is deprecated; please use the object property `max` instead. + +### max + +Examples of **incorrect** code for this rule with the default `{ "max": 4 }` option: + +```js +/*eslint max-depth: ["error", 4]*/ +/*eslint-env es6*/ + +function foo() { + for (;;) { // Nested 1 deep + while (true) { // Nested 2 deep + if (true) { // Nested 3 deep + if (true) { // Nested 4 deep + if (true) { // Nested 5 deep + } + } + } + } + } +} +``` + +Examples of **correct** code for this rule with the default `{ "max": 4 }` option: + +```js +/*eslint max-depth: ["error", 4]*/ +/*eslint-env es6*/ + +function foo() { + for (;;) { // Nested 1 deep + while (true) { // Nested 2 deep + if (true) { // Nested 3 deep + if (true) { // Nested 4 deep + } + } + } + } +} +``` + +## Related Rules + +* [complexity](complexity.md) +* [max-len](max-len.md) +* [max-lines](max-lines.md) +* [max-lines-per-function](max-lines-per-function.md) +* [max-nested-callbacks](max-nested-callbacks.md) +* [max-params](max-params.md) +* [max-statements](max-statements.md) diff --git a/eslint/docs/rules/max-len.md b/eslint/docs/rules/max-len.md new file mode 100644 index 0000000..7aa755a --- /dev/null +++ b/eslint/docs/rules/max-len.md @@ -0,0 +1,161 @@ +# enforce a maximum line length (max-len) + +Very long lines of code in any language can be difficult to read. In order to aid in readability and maintainability many coders have developed a convention to limit lines of code to X number of characters (traditionally 80 characters). + +```js +var foo = { "bar": "This is a bar.", "baz": { "qux": "This is a qux" }, "difficult": "to read" }; // very long +``` + +## Rule Details + +This rule enforces a maximum line length to increase code readability and maintainability. The length of a line is defined as the number of Unicode characters in the line. + +## Options + +This rule has a number or object option: + +* `"code"` (default `80`) enforces a maximum line length +* `"tabWidth"` (default `4`) specifies the character width for tab characters +* `"comments"` enforces a maximum line length for comments; defaults to value of `code` +* `"ignorePattern"` ignores lines matching a regular expression; can only match a single line and need to be double escaped when written in YAML or JSON +* `"ignoreComments": true` ignores all trailing comments and comments on their own line +* `"ignoreTrailingComments": true` ignores only trailing comments +* `"ignoreUrls": true` ignores lines that contain a URL +* `"ignoreStrings": true` ignores lines that contain a double-quoted or single-quoted string +* `"ignoreTemplateLiterals": true` ignores lines that contain a template literal +* `"ignoreRegExpLiterals": true` ignores lines that contain a RegExp literal + +### code + +Examples of **incorrect** code for this rule with the default `{ "code": 80 }` option: + +```js +/*eslint max-len: ["error", { "code": 80 }]*/ + +var foo = { "bar": "This is a bar.", "baz": { "qux": "This is a qux" }, "difficult": "to read" }; +``` + +Examples of **correct** code for this rule with the default `{ "code": 80 }` option: + +```js +/*eslint max-len: ["error", { "code": 80 }]*/ + +var foo = { + "bar": "This is a bar.", + "baz": { "qux": "This is a qux" }, + "easier": "to read" +}; +``` + +### tabWidth + +Examples of **incorrect** code for this rule with the default `{ "tabWidth": 4 }` option: + +```js +/*eslint max-len: ["error", { "code": 80, "tabWidth": 4 }]*/ + +\t \t var foo = { "bar": "This is a bar.", "baz": { "qux": "This is a qux" } }; +``` + +Examples of **correct** code for this rule with the default `{ "tabWidth": 4 }` option: + +```js +/*eslint max-len: ["error", { "code": 80, "tabWidth": 4 }]*/ + +\t \t var foo = { +\t \t \t \t "bar": "This is a bar.", +\t \t \t \t "baz": { "qux": "This is a qux" } +\t \t }; +``` + +### comments + +Examples of **incorrect** code for this rule with the `{ "comments": 65 }` option: + +```js +/*eslint max-len: ["error", { "comments": 65 }]*/ + +/** + * This is a comment that violates the maximum line length we have specified +**/ +``` + +### ignoreComments + +Examples of **correct** code for this rule with the `{ "ignoreComments": true }` option: + +```js +/*eslint max-len: ["error", { "ignoreComments": true }]*/ + +/** + * This is a really really really really really really really really really long comment +**/ +``` + +### ignoreTrailingComments + +Examples of **correct** code for this rule with the `{ "ignoreTrailingComments": true }` option: + +```js +/*eslint max-len: ["error", { "ignoreTrailingComments": true }]*/ + +var foo = 'bar'; // This is a really really really really really really really long comment +``` + +### ignoreUrls + +Examples of **correct** code for this rule with the `{ "ignoreUrls": true }` option: + +```js +/*eslint max-len: ["error", { "ignoreUrls": true }]*/ + +var url = 'https://www.example.com/really/really/really/really/really/really/really/long'; +``` + +### ignoreStrings + +Examples of **correct** code for this rule with the `{ "ignoreStrings": true }` option: + +```js +/*eslint max-len: ["error", { "ignoreStrings": true }]*/ + +var longString = 'this is a really really really really really long string!'; +``` + +### ignoreTemplateLiterals + +Examples of **correct** code for this rule with the `{ "ignoreTemplateLiterals": true }` option: + +```js +/*eslint max-len: ["error", { "ignoreTemplateLiterals": true }]*/ + +var longTemplateLiteral = `this is a really really really really really long template literal!`; +``` + +### ignoreRegExpLiterals + +Examples of **correct** code for this rule with the `{ "ignoreRegExpLiterals": true }` option: + +```js +/*eslint max-len: ["error", { "ignoreRegExpLiterals": true }]*/ + +var longRegExpLiteral = /this is a really really really really really long regular expression!/; +``` + +### ignorePattern + +Examples of **correct** code for this rule with the `ignorePattern` option: + +```js +/*eslint max-len: ["error", { "ignorePattern": "^\\s*var\\s.+=\\s*require\\s*\\(" }]*/ + +var dep = require('really/really/really/really/really/really/really/really/long/module'); +``` + +## Related Rules + +* [complexity](complexity.md) +* [max-depth](max-depth.md) +* [max-nested-callbacks](max-nested-callbacks.md) +* [max-params](max-params.md) +* [max-statements](max-statements.md) diff --git a/eslint/docs/rules/max-lines-per-function.md b/eslint/docs/rules/max-lines-per-function.md new file mode 100644 index 0000000..22ffea7 --- /dev/null +++ b/eslint/docs/rules/max-lines-per-function.md @@ -0,0 +1,191 @@ +# enforce a maximum function length (max-lines-per-function) + +Some people consider large functions a code smell. Large functions tend to do a lot of things and can make it hard following what's going on. Many coding style guides dictate a limit of the number of lines that a function can comprise of. This rule can help enforce that style. + +## Rule Details + +This rule enforces a maximum number of lines per function, in order to aid in maintainability and reduce complexity. + +## Why not use `max-statements` or other complexity measurement rules instead? + +Nested long method chains like the below example are often broken onto separate lines for readability: + +``` +function() { + return m("div", [ + m("table", {className: "table table-striped latest-data"}, [ + m("tbody", + data.map(function(db) { + return m("tr", {key: db.dbname}, [ + m("td", {className: "dbname"}, db.dbname), + m("td", {className: "query-count"}, [ + m("span", {className: db.lastSample.countClassName}, db.lastSample.nbQueries) + ]) + ]) + }) + ) + ]) + ]) +} +``` + +* `max-statements` will only report this as 1 statement, despite being 16 lines of code. +* `complexity` will only report a complexity of 1 +* `max-nested-callbacks` will only report 1 +* `max-depth` will report a depth of 0 + +## Options + +This rule has the following options that can be specified using an object: + +* `"max"` (default `50`) enforces a maximum number of lines in a function. + +* `"skipBlankLines"` (default `false`) ignore lines made up purely of whitespace. + +* `"skipComments"` (default `false`) ignore lines containing just comments. + +* `"IIFEs"` (default `false`) include any code included in IIFEs. + +Alternatively, you may specify a single integer for the `max` option: + +```json +"max-lines-per-function": ["error", 20] +``` + +is equivalent to + +```json +"max-lines-per-function": ["error", { "max": 20 }] +``` + +### code + +Examples of **incorrect** code for this rule with a max value of `2`: + +```js +/*eslint max-lines-per-function: ["error", 2]*/ +function foo() { + var x = 0; +} +``` + +```js +/*eslint max-lines-per-function: ["error", 2]*/ +function foo() { + // a comment + var x = 0; +} +``` + +```js +/*eslint max-lines-per-function: ["error", 2]*/ +function foo() { + // a comment followed by a blank line + + var x = 0; +} +``` + +Examples of **correct** code for this rule with a max value of `3`: + +```js +/*eslint max-lines-per-function: ["error", 3]*/ +function foo() { + var x = 0; +} +``` + +```js +/*eslint max-lines-per-function: ["error", 3]*/ +function foo() { + // a comment + var x = 0; +} +``` + +```js +/*eslint max-lines-per-function: ["error", 3]*/ +function foo() { + // a comment followed by a blank line + + var x = 0; +} +``` + +### skipBlankLines + +Examples of **incorrect** code for this rule with the `{ "skipBlankLines": true }` option: + +```js +/*eslint max-lines-per-function: ["error", {"max": 2, "skipBlankLines": true}]*/ +function foo() { + + var x = 0; +} +``` + +Examples of **correct** code for this rule with the `{ "skipBlankLines": true }` option: + +```js +/*eslint max-lines-per-function: ["error", {"max": 3, "skipBlankLines": true}]*/ +function foo() { + + var x = 0; +} +``` + +### skipComments + +Examples of **incorrect** code for this rule with the `{ "skipComments": true }` option: + +```js +/*eslint max-lines-per-function: ["error", {"max": 2, "skipComments": true}]*/ +function foo() { + // a comment + var x = 0; +} +``` + +Examples of **correct** code for this rule with the `{ "skipComments": true }` option: + +```js +/*eslint max-lines-per-function: ["error", {"max": 3, "skipComments": true}]*/ +function foo() { + // a comment + var x = 0; +} +``` + +### IIFEs + +Examples of **incorrect** code for this rule with the `{ "IIFEs": true }` option: + +```js +/*eslint max-lines-per-function: ["error", {"max": 2, "IIFEs": true}]*/ +(function(){ + var x = 0; +}()); +``` + +Examples of **correct** code for this rule with the `{ "IIFEs": true }` option: + +```js +/*eslint max-lines-per-function: ["error", {"max": 3, "IIFEs": true}]*/ +(function(){ + var x = 0; +}()); +``` + +## When Not To Use It + +You can turn this rule off if you are not concerned with the number of lines in your functions. + +## Related Rules + +* [complexity](complexity.md) +* [max-depth](max-depth.md) +* [max-lines](max-lines.md) +* [max-nested-callbacks](max-nested-callbacks.md) +* [max-params](max-params.md) +* [max-statements](max-statements.md) +* [max-statements-per-line](max-statements-per-line.md) diff --git a/eslint/docs/rules/max-lines.md b/eslint/docs/rules/max-lines.md new file mode 100644 index 0000000..0019e98 --- /dev/null +++ b/eslint/docs/rules/max-lines.md @@ -0,0 +1,126 @@ +# enforce a maximum file length (max-lines) + +Some people consider large files a code smell. Large files tend to do a lot of things and can make it hard following what's going. While there is not an objective maximum number of lines considered acceptable in a file, most people would agree it should not be in the thousands. Recommendations usually range from 100 to 500 lines. + +## Rule Details + +This rule enforces a maximum number of lines per file, in order to aid in maintainability and reduce complexity. + + +## Options + +This rule has a number or object option: + +* `"max"` (default `300`) enforces a maximum number of lines in a file + +* `"skipBlankLines": true` ignore lines made up purely of whitespace. + +* `"skipComments": true` ignore lines containing just comments + +### code + +Examples of **incorrect** code for this rule with a max value of `2`: + +```js +/*eslint max-lines: ["error", 2]*/ +var a, + b, + c; +``` + +```js +/*eslint max-lines: ["error", 2]*/ + +var a, + b,c; +``` + +```js +/*eslint max-lines: ["error", 2]*/ +// a comment +var a, + b,c; +``` + +Examples of **correct** code for this rule with a max value of `2`: + +```js +/*eslint max-lines: ["error", 2]*/ +var a, + b, c; +``` + +```js +/*eslint max-lines: ["error", 2]*/ + +var a, b, c; +``` + +```js +/*eslint max-lines: ["error", 2]*/ +// a comment +var a, b, c; +``` + +### skipBlankLines + +Examples of **incorrect** code for this rule with the `{ "skipBlankLines": true }` option: + +```js +/*eslint max-lines: ["error", {"max": 2, "skipBlankLines": true}]*/ + +var a, + b, + c; +``` + +Examples of **correct** code for this rule with the `{ "skipBlankLines": true }` option: + +```js +/*eslint max-lines: ["error", {"max": 2, "skipBlankLines": true}]*/ + +var a, + b, c; +``` + +### skipComments + +Examples of **incorrect** code for this rule with the `{ "skipComments": true }` option: + +```js +/*eslint max-lines: ["error", {"max": 2, "skipComments": true}]*/ +// a comment +var a, + b, + c; +``` + +Examples of **correct** code for this rule with the `{ "skipComments": true }` option: + +```js +/*eslint max-lines: ["error", {"max": 2, "skipComments": true}]*/ +// a comment +var a, + b, c; +``` + +## When Not To Use It + +You can turn this rule off if you are not concerned with the number of lines in your files. + +## Further reading + +* [Software Module size and file size](https://web.archive.org/web/20160725154648/http://www.mind2b.com/component/content/article/24-software-module-size-and-file-size) + +## Related Rules + +* [complexity](complexity.md) +* [max-depth](max-depth.md) +* [max-lines-per-function](max-lines-per-function.md) +* [max-nested-callbacks](max-nested-callbacks.md) +* [max-params](max-params.md) +* [max-statements](max-statements.md) + +## Compatibility + +* **JSCS**: [maximumNumberOfLines](https://jscs-dev.github.io/rule/maximumNumberOfLines) diff --git a/eslint/docs/rules/max-nested-callbacks.md b/eslint/docs/rules/max-nested-callbacks.md new file mode 100644 index 0000000..24e4b90 --- /dev/null +++ b/eslint/docs/rules/max-nested-callbacks.md @@ -0,0 +1,85 @@ +# enforce a maximum depth that callbacks can be nested (max-nested-callbacks) + +Many JavaScript libraries use the callback pattern to manage asynchronous operations. A program of any complexity will most likely need to manage several asynchronous operations at various levels of concurrency. A common pitfall that is easy to fall into is nesting callbacks, which makes code more difficult to read the deeper the callbacks are nested. + +```js +foo(function () { + bar(function () { + baz(function() { + qux(function () { + + }); + }); + }); +}); +``` + +## Rule Details + +This rule enforces a maximum depth that callbacks can be nested to increase code clarity. + +## Options + +This rule has a number or object option: + +* `"max"` (default `10`) enforces a maximum depth that callbacks can be nested + +**Deprecated:** The object property `maximum` is deprecated; please use the object property `max` instead. + +### max + +Examples of **incorrect** code for this rule with the `{ "max": 3 }` option: + +```js +/*eslint max-nested-callbacks: ["error", 3]*/ + +foo1(function() { + foo2(function() { + foo3(function() { + foo4(function() { + // Do something + }); + }); + }); +}); +``` + +Examples of **correct** code for this rule with the `{ "max": 3 }` option: + +```js +/*eslint max-nested-callbacks: ["error", 3]*/ + +foo1(handleFoo1); + +function handleFoo1() { + foo2(handleFoo2); +} + +function handleFoo2() { + foo3(handleFoo3); +} + +function handleFoo3() { + foo4(handleFoo4); +} + +function handleFoo4() { + foo5(); +} +``` + +## Further Reading + +* [Control flow in Node.js](http://book.mixu.net/node/ch7.html) +* [Control Flow in Node](https://howtonode.org/control-flow) +* [Control Flow in Node Part II](https://howtonode.org/control-flow-part-ii) + +## Related Rules + +* [complexity](complexity.md) +* [max-depth](max-depth.md) +* [max-len](max-len.md) +* [max-lines](max-lines.md) +* [max-lines-per-function](max-lines-per-function.md) +* [max-params](max-params.md) +* [max-statements](max-statements.md) diff --git a/eslint/docs/rules/max-params.md b/eslint/docs/rules/max-params.md new file mode 100644 index 0000000..050d2a3 --- /dev/null +++ b/eslint/docs/rules/max-params.md @@ -0,0 +1,63 @@ +# enforce a maximum number of parameters in function definitions (max-params) + +Functions that take numerous parameters can be difficult to read and write because it requires the memorization of what each parameter is, its type, and the order they should appear in. As a result, many coders adhere to a convention that caps the number of parameters a function can take. + +```js +function foo (bar, baz, qux, qxx) { // four parameters, may be too many + doSomething(); +} +``` + +## Rule Details + +This rule enforces a maximum number of parameters allowed in function definitions. + +## Options + +This rule has a number or object option: + +* `"max"` (default `3`) enforces a maximum number of parameters in function definitions + +**Deprecated:** The object property `maximum` is deprecated; please use the object property `max` instead. + +### max + +Examples of **incorrect** code for this rule with the default `{ "max": 3 }` option: + +```js +/*eslint max-params: ["error", 3]*/ +/*eslint-env es6*/ + +function foo (bar, baz, qux, qxx) { + doSomething(); +} + +let foo = (bar, baz, qux, qxx) => { + doSomething(); +}; +``` + +Examples of **correct** code for this rule with the default `{ "max": 3 }` option: + +```js +/*eslint max-params: ["error", 3]*/ +/*eslint-env es6*/ + +function foo (bar, baz, qux) { + doSomething(); +} + +let foo = (bar, baz, qux) => { + doSomething(); +}; +``` + +## Related Rules + +* [complexity](complexity.md) +* [max-depth](max-depth.md) +* [max-len](max-len.md) +* [max-lines](max-lines.md) +* [max-lines-per-function](max-lines-per-function.md) +* [max-nested-callbacks](max-nested-callbacks.md) +* [max-statements](max-statements.md) diff --git a/eslint/docs/rules/max-statements-per-line.md b/eslint/docs/rules/max-statements-per-line.md new file mode 100644 index 0000000..e8462b6 --- /dev/null +++ b/eslint/docs/rules/max-statements-per-line.md @@ -0,0 +1,87 @@ +# enforce a maximum number of statements allowed per line (max-statements-per-line) + +A line of code containing too many statements can be difficult to read. Code is generally read from the top down, especially when scanning, so limiting the number of statements allowed on a single line can be very beneficial for readability and maintainability. + +```js +function foo () { var bar; if (condition) { bar = 1; } else { bar = 2; } return true; } // too many statements +``` + +## Rule Details + +This rule enforces a maximum number of statements allowed per line. + +## Options + +### max + +The "max" object property is optional (default: 1). + +Examples of **incorrect** code for this rule with the default `{ "max": 1 }` option: + +```js +/*eslint max-statements-per-line: ["error", { "max": 1 }]*/ + +var bar; var baz; +if (condition) { bar = 1; } +for (var i = 0; i < length; ++i) { bar = 1; } +switch (discriminant) { default: break; } +function foo() { bar = 1; } +var foo = function foo() { bar = 1; }; +(function foo() { bar = 1; })(); +``` + +Examples of **correct** code for this rule with the default `{ "max": 1 }` option: + +```js +/*eslint max-statements-per-line: ["error", { "max": 1 }]*/ + +var bar, baz; +if (condition) bar = 1; +for (var i = 0; i < length; ++i); +switch (discriminant) { default: } +function foo() { } +var foo = function foo() { }; +(function foo() { })(); +``` + +Examples of **incorrect** code for this rule with the `{ "max": 2 }` option: + +```js +/*eslint max-statements-per-line: ["error", { "max": 2 }]*/ + +var bar; var baz; var qux; +if (condition) { bar = 1; } else { baz = 2; } +for (var i = 0; i < length; ++i) { bar = 1; baz = 2; } +switch (discriminant) { case 'test': break; default: break; } +function foo() { bar = 1; baz = 2; } +var foo = function foo() { bar = 1; }; +(function foo() { bar = 1; baz = 2; })(); +``` + +Examples of **correct** code for this rule with the `{ "max": 2 }` option: + +```js +/*eslint max-statements-per-line: ["error", { "max": 2 }]*/ + +var bar; var baz; +if (condition) bar = 1; if (condition) baz = 2; +for (var i = 0; i < length; ++i) { bar = 1; } +switch (discriminant) { default: break; } +function foo() { bar = 1; } +var foo = function foo() { bar = 1; }; +(function foo() { var bar = 1; })(); +``` + +## When Not To Use It + +You can turn this rule off if you are not concerned with the number of statements on each line. + +## Related Rules + +* [max-depth](max-depth.md) +* [max-len](max-len.md) +* [max-lines](max-lines.md) +* [max-lines-per-function](max-lines-per-function.md) +* [max-nested-callbacks](max-nested-callbacks.md) +* [max-params](max-params.md) +* [max-statements](max-statements.md) diff --git a/eslint/docs/rules/max-statements.md b/eslint/docs/rules/max-statements.md new file mode 100644 index 0000000..6ef87c2 --- /dev/null +++ b/eslint/docs/rules/max-statements.md @@ -0,0 +1,145 @@ +# enforce a maximum number of statements allowed in function blocks (max-statements) + +The `max-statements` rule allows you to specify the maximum number of statements allowed in a function. + +```js +function foo() { + var bar = 1; // one statement + var baz = 2; // two statements + var qux = 3; // three statements +} +``` + +## Rule Details + +This rule enforces a maximum number of statements allowed in function blocks. + +## Options + +This rule has a number or object option: + +* `"max"` (default `10`) enforces a maximum number of statements allows in function blocks + +**Deprecated:** The object property `maximum` is deprecated; please use the object property `max` instead. + +This rule has an object option: + +* `"ignoreTopLevelFunctions": true` ignores top-level functions + +### max + +Examples of **incorrect** code for this rule with the default `{ "max": 10 }` option: + +```js +/*eslint max-statements: ["error", 10]*/ +/*eslint-env es6*/ + +function foo() { + var foo1 = 1; + var foo2 = 2; + var foo3 = 3; + var foo4 = 4; + var foo5 = 5; + var foo6 = 6; + var foo7 = 7; + var foo8 = 8; + var foo9 = 9; + var foo10 = 10; + + var foo11 = 11; // Too many. +} + +let foo = () => { + var foo1 = 1; + var foo2 = 2; + var foo3 = 3; + var foo4 = 4; + var foo5 = 5; + var foo6 = 6; + var foo7 = 7; + var foo8 = 8; + var foo9 = 9; + var foo10 = 10; + + var foo11 = 11; // Too many. +}; +``` + +Examples of **correct** code for this rule with the default `{ "max": 10 }` option: + +```js +/*eslint max-statements: ["error", 10]*/ +/*eslint-env es6*/ + +function foo() { + var foo1 = 1; + var foo2 = 2; + var foo3 = 3; + var foo4 = 4; + var foo5 = 5; + var foo6 = 6; + var foo7 = 7; + var foo8 = 8; + var foo9 = 9; + var foo10 = 10; + return function () { + + // The number of statements in the inner function does not count toward the + // statement maximum. + + return 42; + }; +} + +let foo = () => { + var foo1 = 1; + var foo2 = 2; + var foo3 = 3; + var foo4 = 4; + var foo5 = 5; + var foo6 = 6; + var foo7 = 7; + var foo8 = 8; + var foo9 = 9; + var foo10 = 10; + return function () { + + // The number of statements in the inner function does not count toward the + // statement maximum. + + return 42; + }; +} +``` + +### ignoreTopLevelFunctions + +Examples of additional **correct** code for this rule with the `{ "max": 10 }, { "ignoreTopLevelFunctions": true }` options: + +```js +/*eslint max-statements: ["error", 10, { "ignoreTopLevelFunctions": true }]*/ + +function foo() { + var foo1 = 1; + var foo2 = 2; + var foo3 = 3; + var foo4 = 4; + var foo5 = 5; + var foo6 = 6; + var foo7 = 7; + var foo8 = 8; + var foo9 = 9; + var foo10 = 10; + var foo11 = 11; +} +``` + +## Related Rules + +* [complexity](complexity.md) +* [max-depth](max-depth.md) +* [max-len](max-len.md) +* [max-lines](max-lines.md) +* [max-lines-per-function](max-lines-per-function.md) +* [max-nested-callbacks](max-nested-callbacks.md) +* [max-params](max-params.md) diff --git a/eslint/docs/rules/multiline-comment-style.md b/eslint/docs/rules/multiline-comment-style.md new file mode 100644 index 0000000..32beddd --- /dev/null +++ b/eslint/docs/rules/multiline-comment-style.md @@ -0,0 +1,123 @@ +# enforce a particular style for multiline comments (multiline-comment-style) + +Many style guides require a particular style for comments that span multiple lines. For example, some style guides prefer the use of a single block comment for multiline comments, whereas other style guides prefer consecutive line comments. + +## Rule Details + +This rule aims to enforce a particular style for multiline comments. + +### Options + +This rule has a string option, which can have one of the following values: + +* `"starred-block"` (default): Disallows consecutive line comments in favor of block comments. Additionally, requires block comments to have an aligned `*` character before each line. +* `"bare-block"`: Disallows consecutive line comments in favor of block comments, and disallows block comments from having a `"*"` character before each line. +* `"separate-lines"`: Disallows block comments in favor of consecutive line comments + +The rule always ignores directive comments such as `/* eslint-disable */`. Additionally, unless the mode is `"starred-block"`, the rule ignores JSDoc comments. + +Examples of **incorrect** code for this rule with the default `"starred-block"` option: + +```js + +/* eslint multiline-comment-style: ["error", "starred-block"] */ + +// this line +// calls foo() +foo(); + +/* this line +calls foo() */ +foo(); + +/* this comment + * is missing a newline after /* + */ + +/* + * this comment + * is missing a newline at the end */ + +/* +* the star in this line should have a space before it + */ + +/* + * the star on the following line should have a space before it +*/ + +``` + +Examples of **correct** code for this rule with the default `"starred-block"` option: + +```js +/* eslint multiline-comment-style: ["error", "starred-block"] */ + +/* + * this line + * calls foo() + */ +foo(); + +// single-line comment +``` + +Examples of **incorrect** code for this rule with the `"bare-block"` option: + +```js +/* eslint multiline-comment-style: ["error", "bare-block"] */ + +// this line +// calls foo() +foo(); + +/* + * this line + * calls foo() + */ +foo(); +``` + +Examples of **correct** code for this rule with the `"bare-block"` option: + +```js +/* eslint multiline-comment-style: ["error", "bare-block"] */ + +/* this line + calls foo() */ +foo(); +``` + +Examples of **incorrect** code for this rule with the `"separate-lines"` option: + +```js + +/* eslint multiline-comment-style: ["error", "separate-lines"] */ + +/* This line +calls foo() */ +foo(); + +/* + * This line + * calls foo() + */ +foo(); + +``` + +Examples of **correct** code for this rule with the `"separate-lines"` option: + +```js +/* eslint multiline-comment-style: ["error", "separate-lines"] */ + +// This line +// calls foo() +foo(); + + +``` + +## When Not To Use It + +If you don't want to enforce a particular style for multiline comments, you can disable the rule. diff --git a/eslint/docs/rules/multiline-ternary.md b/eslint/docs/rules/multiline-ternary.md new file mode 100644 index 0000000..e9461a1 --- /dev/null +++ b/eslint/docs/rules/multiline-ternary.md @@ -0,0 +1,149 @@ +# Enforce or disallow newlines between operands of ternary expressions (multiline-ternary) + +JavaScript allows operands of ternary expressions to be separated by newlines, which can improve the readability of your program. + +For example: + +```js +var foo = bar > baz ? value1 : value2; +``` + +The above can be rewritten as the following to improve readability and more clearly delineate the operands: + +```js +var foo = bar > baz ? + value1 : + value2; +``` + +## Rule Details + +This rule enforces or disallows newlines between operands of a ternary expression. +Note: The location of the operators is not enforced by this rule. Please see the [operator-linebreak](operator-linebreak.md) rule if you are interested in enforcing the location of the operators themselves. + +## Options + +This rule has a string option: + +* `"always"` (default) enforces newlines between the operands of a ternary expression. +* `"always-multiline"` enforces newlines between the operands of a ternary expression if the expression spans multiple lines. +* `"never"` disallows newlines between the operands of a ternary expression (enforcing that the entire ternary expression is on one line). + +### always + +This is the default option. + +Examples of **incorrect** code for this rule with the `"always"` option: + +```js +/*eslint multiline-ternary: ["error", "always"]*/ + +foo > bar ? value1 : value2; + +foo > bar ? value : + value2; + +foo > bar ? + value : value2; +``` + +Examples of **correct** code for this rule with the `"always"` option: + +```js +/*eslint multiline-ternary: ["error", "always"]*/ + +foo > bar ? + value1 : + value2; + +foo > bar ? + (baz > qux ? + value1 : + value2) : + value3; +``` + +### always-multiline + +Examples of **incorrect** code for this rule with the `"always-multiline"` option: + +```js +/*eslint multiline-ternary: ["error", "always-multiline"]*/ + +foo > bar ? value1 : + value2; + +foo > bar ? + value1 : value2; + +foo > bar && + bar > baz ? value1 : value2; +``` + +Examples of **correct** code for this rule with the `"always-multiline"` option: + +```js +/*eslint multiline-ternary: ["error", "always-multiline"]*/ + +foo > bar ? value1 : value2; + +foo > bar ? + value1 : + value2; + +foo > bar ? + (baz > qux ? value1 : value2) : + value3; + +foo > bar ? + (baz > qux ? + value1 : + value2) : + value3; + +foo > bar && + bar > baz ? + value1 : + value2; +``` + +### never + +Examples of **incorrect** code for this rule with the `"never"` option: + +```js +/*eslint multiline-ternary: ["error", "never"]*/ + +foo > bar ? value : + value2; + +foo > bar ? + value : value2; + +foo > + bar ? + value1 : + value2; +``` + +Examples of **correct** code for this rule with the `"never"` option: + +```js +/*eslint multiline-ternary: ["error", "never"]*/ + +foo > bar ? value1 : value2; + +foo > bar ? (baz > qux ? value1 : value2) : value3; +``` + +## When Not To Use It + +You can safely disable this rule if you do not have any strict conventions about whether the operands of a ternary expression should be separated by newlines. + +## Related Rules + +* [operator-linebreak](operator-linebreak.md) + +## Compatibility + +* **JSCS**: [requireMultiLineTernary](https://jscs-dev.github.io/rule/requireMultiLineTernary) diff --git a/eslint/docs/rules/new-cap.md b/eslint/docs/rules/new-cap.md new file mode 100644 index 0000000..22ac577 --- /dev/null +++ b/eslint/docs/rules/new-cap.md @@ -0,0 +1,175 @@ +# require constructor names to begin with a capital letter (new-cap) + +The `new` operator in JavaScript creates a new instance of a particular type of object. That type of object is represented by a constructor function. Since constructor functions are just regular functions, the only defining characteristic is that `new` is being used as part of the call. Native JavaScript functions begin with an uppercase letter to distinguish those functions that are to be used as constructors from functions that are not. Many style guides recommend following this pattern to more easily determine which functions are to be used as constructors. + +```js +var friend = new Person(); +``` + +## Rule Details + +This rule requires constructor names to begin with a capital letter. Certain built-in identifiers are exempt from this rule. These identifiers are: + +* `Array` +* `Boolean` +* `Date` +* `Error` +* `Function` +* `Number` +* `Object` +* `RegExp` +* `String` +* `Symbol` + +Examples of **correct** code for this rule: + +```js +/*eslint new-cap: "error"*/ + +function foo(arg) { + return Boolean(arg); +} +``` + +## Options + +This rule has an object option: + +* `"newIsCap": true` (default) requires all `new` operators to be called with uppercase-started functions. +* `"newIsCap": false` allows `new` operators to be called with lowercase-started or uppercase-started functions. +* `"capIsNew": true` (default) requires all uppercase-started functions to be called with `new` operators. +* `"capIsNew": false` allows uppercase-started functions to be called without `new` operators. +* `"newIsCapExceptions"` allows specified lowercase-started function names to be called with the `new` operator. +* `"newIsCapExceptionPattern"` allows any lowercase-started function names that match the specified regex pattern to be called with the `new` operator. +* `"capIsNewExceptions"` allows specified uppercase-started function names to be called without the `new` operator. +* `"capIsNewExceptionPattern"` allows any uppercase-started function names that match the specified regex pattern to be called without the `new` operator. +* `"properties": true` (default) enables checks on object properties +* `"properties": false` disables checks on object properties + +### newIsCap + +Examples of **incorrect** code for this rule with the default `{ "newIsCap": true }` option: + +```js +/*eslint new-cap: ["error", { "newIsCap": true }]*/ + +var friend = new person(); +``` + +Examples of **correct** code for this rule with the default `{ "newIsCap": true }` option: + +```js +/*eslint new-cap: ["error", { "newIsCap": true }]*/ + +var friend = new Person(); +``` + +Examples of **correct** code for this rule with the `{ "newIsCap": false }` option: + +```js +/*eslint new-cap: ["error", { "newIsCap": false }]*/ + +var friend = new person(); +``` + +### capIsNew + +Examples of **incorrect** code for this rule with the default `{ "capIsNew": true }` option: + +```js +/*eslint new-cap: ["error", { "capIsNew": true }]*/ + +var colleague = Person(); +``` + +Examples of **correct** code for this rule with the default `{ "capIsNew": true }` option: + +```js +/*eslint new-cap: ["error", { "capIsNew": true }]*/ + +var colleague = new Person(); +``` + +Examples of **correct** code for this rule with the `{ "capIsNew": false }` option: + +```js +/*eslint new-cap: ["error", { "capIsNew": false }]*/ + +var colleague = Person(); +``` + +### newIsCapExceptions + +Examples of additional **correct** code for this rule with the `{ "newIsCapExceptions": ["events"] }` option: + +```js +/*eslint new-cap: ["error", { "newIsCapExceptions": ["events"] }]*/ + +var events = require('events'); + +var emitter = new events(); +``` + +### newIsCapExceptionPattern + +Examples of additional **correct** code for this rule with the `{ "newIsCapExceptionPattern": "^person\.." }` option: + +```js +/*eslint new-cap: ["error", { "newIsCapExceptionPattern": "^person\.." }]*/ + +var friend = new person.acquaintance(); +var bestFriend = new person.friend(); +``` + +### capIsNewExceptions + +Examples of additional **correct** code for this rule with the `{ "capIsNewExceptions": ["Person"] }` option: + +```js +/*eslint new-cap: ["error", { "capIsNewExceptions": ["Person"] }]*/ + +function foo(arg) { + return Person(arg); +} +``` + +### capIsNewExceptionPattern + +Examples of additional **correct** code for this rule with the `{ "capIsNewExceptionPattern": "^Person\.." }` option: + +```js +/*eslint new-cap: ["error", { "capIsNewExceptionPattern": "^Person\.." }]*/ + +var friend = person.Acquaintance(); +var bestFriend = person.Friend(); +``` + +### properties + +Examples of **incorrect** code for this rule with the default `{ "properties": true }` option: + +```js +/*eslint new-cap: ["error", { "properties": true }]*/ + +var friend = new person.acquaintance(); +``` + +Examples of **correct** code for this rule with the default `{ "properties": true }` option: + +```js +/*eslint new-cap: ["error", { "properties": true }]*/ + +var friend = new person.Acquaintance(); +``` + +Examples of **correct** code for this rule with the `{ "properties": false }` option: + +```js +/*eslint new-cap: ["error", { "properties": false }]*/ + +var friend = new person.acquaintance(); +``` + +## When Not To Use It + +If you have conventions that don't require an uppercase letter for constructors, or don't require capitalized functions be only used as constructors, turn this rule off. diff --git a/eslint/docs/rules/new-parens.md b/eslint/docs/rules/new-parens.md new file mode 100644 index 0000000..0b2a49b --- /dev/null +++ b/eslint/docs/rules/new-parens.md @@ -0,0 +1,59 @@ +# require parentheses when invoking a constructor with no arguments (new-parens) + +JavaScript allows the omission of parentheses when invoking a function via the `new` keyword and the constructor has no arguments. However, some coders believe that omitting the parentheses is inconsistent with the rest of the language and thus makes code less clear. + +```js +var person = new Person; +``` + +## Rule Details + +This rule can enforce or disallow parentheses when invoking a constructor with no arguments using the `new` keyword. + +## Options + +This rule takes one option. + +- `"always"` enforces parenthesis after a new constructor with no arguments (default) +- `"never"` enforces no parenthesis after a new constructor with no arguments + +### always + +Examples of **incorrect** code for this rule with the `"always"` option: + +```js +/*eslint new-parens: "error"*/ + +var person = new Person; +var person = new (Person); +``` + +Examples of **correct** code for this rule with the `"always"` option: + +```js +/*eslint new-parens: "error"*/ + +var person = new Person(); +var person = new (Person)(); +``` + +### never + +Examples of **incorrect** code for this rule with the `"never"` option: + +```js +/*eslint new-parens: ["error", "never"]*/ + +var person = new Person(); +var person = new (Person)(); +``` + +Examples of **correct** code for this rule with the `"never"` option: + +```js +/*eslint new-parens: ["error", "never"]*/ + +var person = new Person; +var person = (new Person); +var person = new Person("Name"); +``` diff --git a/eslint/docs/rules/newline-after-var.md b/eslint/docs/rules/newline-after-var.md new file mode 100644 index 0000000..43ec9ba --- /dev/null +++ b/eslint/docs/rules/newline-after-var.md @@ -0,0 +1,142 @@ +# require or disallow an empty line after variable declarations (newline-after-var) + +This rule was **deprecated** in ESLint v4.0.0 and replaced by the [padding-line-between-statements](padding-line-between-statements.md) rule. + +As of today there is no consistency in separating variable declarations from the rest of the code. Some developers leave an empty line between var statements and the rest of the code like: + +```js +var foo; + +// do something with foo +``` + +Whereas others don't leave any empty newlines at all. + +```js +var foo; +// do something with foo +``` + +The problem is when these developers work together in a project. This rule enforces a coding style where empty newlines are allowed or disallowed after `var`, `let`, or `const` statements. It helps the code to look consistent across the entire project. + +## Rule Details + +This rule enforces a coding style where empty lines are required or disallowed after `var`, `let`, or `const` statements to achieve a consistent coding style across the project. + +## Options + +This rule has a string option: + +* `"always"` (default) requires an empty line after `var`, `let`, or `const` + + Comments on a line directly after var statements are treated like additional var statements. + +* `"never"` disallows empty lines after `var`, `let`, or `const` + +### always + +Examples of **incorrect** code for this rule with the default `"always"` option: + +```js +/*eslint newline-after-var: ["error", "always"]*/ +/*eslint-env es6*/ + +var greet = "hello,", + name = "world"; +console.log(greet, name); + +let greet = "hello,", + name = "world"; +console.log(greet, name); + +var greet = "hello,"; +const NAME = "world"; +console.log(greet, NAME); + +var greet = "hello,"; +var name = "world"; +// var name = require("world"); +console.log(greet, name); +``` + +Examples of **correct** code for this rule with the default `"always"` option: + +```js +/*eslint newline-after-var: ["error", "always"]*/ +/*eslint-env es6*/ + +var greet = "hello,", + name = "world"; + +console.log(greet, name); + +let greet = "hello,", + name = "world"; + +console.log(greet, name); + +var greet = "hello,"; +const NAME = "world"; + +console.log(greet, NAME); + +var greet = "hello,"; +var name = "world"; +// var name = require("world"); + +console.log(greet, name); +``` + +### never + +Examples of **incorrect** code for this rule with the `"never"` option: + +```js +/*eslint newline-after-var: ["error", "never"]*/ +/*eslint-env es6*/ + +var greet = "hello,", + name = "world"; + +console.log(greet, name); + +let greet = "hello,", + name = "world"; + +console.log(greet, name); + +var greet = "hello,"; +const NAME = "world"; + +console.log(greet, NAME); + +var greet = "hello,"; +var name = "world"; +// var name = require("world"); + +console.log(greet, name); +``` + +Examples of **correct** code for this rule with the `"never"` option: + +```js +/*eslint newline-after-var: ["error", "never"]*/ +/*eslint-env es6*/ + +var greet = "hello,", + name = "world"; +console.log(greet, name); + +let greet = "hello,", + name = "world"; +console.log(greet, name); + +var greet = "hello,"; +const NAME = "world"; +console.log(greet, NAME); + +var greet = "hello,"; +var name = "world"; +// var name = require("world"); +console.log(greet, name); +``` diff --git a/eslint/docs/rules/newline-before-return.md b/eslint/docs/rules/newline-before-return.md new file mode 100644 index 0000000..6709534 --- /dev/null +++ b/eslint/docs/rules/newline-before-return.md @@ -0,0 +1,116 @@ +# require an empty line before `return` statements (newline-before-return) + +This rule was **deprecated** in ESLint v4.0.0 and replaced by the [padding-line-between-statements](padding-line-between-statements.md) rule. + +There is no hard and fast rule about whether empty lines should precede `return` statements in JavaScript. However, clearly delineating where a function is returning can greatly increase the readability and clarity of the code. For example: + +```js +function foo(bar) { + var baz = 'baz'; + if (!bar) { + bar = baz; + return bar; + } + return bar; +} +``` + +Adding newlines visibly separates the return statements from the previous lines, making it clear where the function exits and what value it returns: + +```js +function foo(bar) { + var baz = 'baz'; + + if (!bar) { + bar = baz; + + return bar; + } + + return bar; +} +``` + +## Rule Details + +This rule requires an empty line before `return` statements to increase code clarity, except when the `return` is alone inside a statement group (such as an if statement). In the latter case, the `return` statement does not need to be delineated by virtue of it being alone. Comments are ignored and do not count as empty lines. + +Examples of **incorrect** code for this rule: + +```js +/*eslint newline-before-return: "error"*/ + +function foo(bar) { + if (!bar) { + return; + } + return bar; +} + +function foo(bar) { + if (!bar) { + return; + } + /* multi-line + comment */ + return bar; +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint newline-before-return: "error"*/ + +function foo() { + return; +} + +function foo() { + + return; +} + +function foo(bar) { + if (!bar) return; +} + +function foo(bar) { + if (!bar) { return }; +} + +function foo(bar) { + if (!bar) { + return; + } +} + +function foo(bar) { + if (!bar) { + return; + } + + return bar; +} + +function foo(bar) { + if (!bar) { + + return; + } +} + +function foo() { + + // comment + return; +} +``` + +## When Not To Use It + +You can safely disable this rule if you do not have any strict conventions about whitespace before `return` statements. + +## Related Rules + +* [newline-after-var](newline-after-var.md) diff --git a/eslint/docs/rules/newline-per-chained-call.md b/eslint/docs/rules/newline-per-chained-call.md new file mode 100644 index 0000000..c4227c0 --- /dev/null +++ b/eslint/docs/rules/newline-per-chained-call.md @@ -0,0 +1,118 @@ +# require a newline after each call in a method chain (newline-per-chained-call) + +Chained method calls on a single line without line breaks are harder to read, so some developers place a newline character after each method call in the chain to make it more readable and easy to maintain. + +Let's look at the following perfectly valid (but single line) code. + +```js +d3.select("body").selectAll("p").data([4, 8, 15, 16, 23, 42 ]).enter().append("p").text(function(d) { return "I'm number " + d + "!"; }); +``` + +However, with appropriate new lines, it becomes easy to read and understand. Look at the same code written below with line breaks after each call. + +```js +d3 + .select("body") + .selectAll("p") + .data([ + 4, + 8, + 15, + 16, + 23, + 42 + ]) + .enter() + .append("p") + .text(function (d) { + return "I'm number " + d + "!"; + }); +``` + +Another argument in favor of this style is that it improves the clarity of diffs when something in the method chain is changed: + +Less clear: + +```diff +-d3.select("body").selectAll("p").style("color", "white"); ++d3.select("body").selectAll("p").style("color", "blue"); +``` + +More clear: + +```diff +d3 + .select("body") + .selectAll("p") +- .style("color", "white"); ++ .style("color", "blue"); +``` + +## Rule Details + +This rule requires a newline after each call in a method chain or deep member access. Computed property accesses such as `instance[something]` are excluded. + +## Options + +This rule has an object option: + +* `"ignoreChainWithDepth"` (default: `2`) allows chains up to a specified depth. + +### ignoreChainWithDepth + +Examples of **incorrect** code for this rule with the default `{ "ignoreChainWithDepth": 2 }` option: + +```js +/*eslint newline-per-chained-call: ["error", { "ignoreChainWithDepth": 2 }]*/ + +_.chain({}).map(foo).filter(bar).value(); + +// Or +_.chain({}).map(foo).filter(bar); + +// Or +_ + .chain({}).map(foo) + .filter(bar); + +// Or +obj.method().method2().method3(); +``` + +Examples of **correct** code for this rule with the default `{ "ignoreChainWithDepth": 2 }` option: + +```js +/*eslint newline-per-chained-call: ["error", { "ignoreChainWithDepth": 2 }]*/ + +_ + .chain({}) + .map(foo) + .filter(bar) + .value(); + +// Or +_ + .chain({}) + .map(foo) + .filter(bar); + +// Or +_.chain({}) + .map(foo) + .filter(bar); + +// Or +obj + .prop + .method().prop; + +// Or +obj + .prop.method() + .method2() + .method3().prop; +``` + +## When Not To Use It + +If you have conflicting rules or when you are fine with chained calls on one line, you can safely turn this rule off. diff --git a/eslint/docs/rules/no-alert.md b/eslint/docs/rules/no-alert.md new file mode 100644 index 0000000..6b7e664 --- /dev/null +++ b/eslint/docs/rules/no-alert.md @@ -0,0 +1,45 @@ +# Disallow Use of Alert (no-alert) + +JavaScript's `alert`, `confirm`, and `prompt` functions are widely considered to be obtrusive as UI elements and should be replaced by a more appropriate custom UI implementation. Furthermore, `alert` is often used while debugging code, which should be removed before deployment to production. + +```js +alert("here!"); +``` + +## Rule Details + +This rule is aimed at catching debugging code that should be removed and popup UI elements that should be replaced with less obtrusive, custom UIs. As such, it will warn when it encounters `alert`, `prompt`, and `confirm` function calls which are not shadowed. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-alert: "error"*/ + +alert("here!"); + +confirm("Are you sure?"); + +prompt("What's your name?", "John Doe"); +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-alert: "error"*/ + +customAlert("Something happened!"); + +customConfirm("Are you sure?"); + +customPrompt("Who are you?"); + +function foo() { + var alert = myCustomLib.customAlert; + alert(); +} +``` + +## Related Rules + +* [no-console](no-console.md) +* [no-debugger](no-debugger.md) diff --git a/eslint/docs/rules/no-array-constructor.md b/eslint/docs/rules/no-array-constructor.md new file mode 100644 index 0000000..863f56d --- /dev/null +++ b/eslint/docs/rules/no-array-constructor.md @@ -0,0 +1,49 @@ +# disallow `Array` constructors (no-array-constructor) + +Use of the `Array` constructor to construct a new array is generally +discouraged in favor of array literal notation because of the single-argument +pitfall and because the `Array` global may be redefined. The exception is when +the Array constructor is used to intentionally create sparse arrays of a +specified size by giving the constructor a single numeric argument. + +## Rule Details + +This rule disallows `Array` constructors. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-array-constructor: "error"*/ + +Array(0, 1, 2) +``` + +```js +/*eslint no-array-constructor: "error"*/ + +new Array(0, 1, 2) +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-array-constructor: "error"*/ + +Array(500) +``` + +```js +/*eslint no-array-constructor: "error"*/ + +new Array(someOtherArray.length) +``` + +## When Not To Use It + +This rule enforces a nearly universal stylistic concern. That being said, this +rule may be disabled if the constructor style is preferred. + +## Related Rules + +* [no-new-object](no-new-object.md) +* [no-new-wrappers](no-new-wrappers.md) diff --git a/eslint/docs/rules/no-arrow-condition.md b/eslint/docs/rules/no-arrow-condition.md new file mode 100644 index 0000000..faa5566 --- /dev/null +++ b/eslint/docs/rules/no-arrow-condition.md @@ -0,0 +1,48 @@ +# no-arrow-condition: disallow arrow functions where test conditions are expected + +(removed) This rule was **removed** in ESLint v2.0 and **replaced** by a combination of the [no-confusing-arrow](no-confusing-arrow.md) and [no-constant-condition](no-constant-condition.md) rules. + +Arrow functions (`=>`) are similar in syntax to some comparison operators (`>`, `<`, `<=`, and `>=`). This rule warns against using the arrow function syntax in places where a condition is expected. Even if the arguments of the arrow function are wrapped with parens, this rule still warns about it. + +Here's an example where the usage of `=>` is most likely a typo: + +```js +// This is probably a typo +if (a => 1) {} +// And should instead be +if (a >= 1) {} +``` + +There are also cases where the usage of `=>` can be ambiguous and should be rewritten to more clearly show the author's intent: + +```js +// The intent is not clear +var x = a => 1 ? 2 : 3 +// Did the author mean this +var x = function (a) { return a >= 1 ? 2 : 3 } +// Or this +var x = a <= 1 ? 2 : 3 +``` + +## Rule Details + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-arrow-condition: "error"*/ +/*eslint-env es6*/ + +if (a => 1) {} +while (a => 1) {} +for (var a = 1; a => 10; a++) {} +a => 1 ? 2 : 3 +(a => 1) ? 2 : 3 +var x = a => 1 ? 2 : 3 +var x = (a) => 1 ? 2 : 3 +``` + +## Related Rules + +* [arrow-parens](arrow-parens.md) +* [no-confusing-arrow](no-confusing-arrow.md) +* [no-constant-condition](no-constant-condition.md) diff --git a/eslint/docs/rules/no-async-promise-executor.md b/eslint/docs/rules/no-async-promise-executor.md new file mode 100644 index 0000000..58655b7 --- /dev/null +++ b/eslint/docs/rules/no-async-promise-executor.md @@ -0,0 +1,62 @@ +# disallow using an async function as a Promise executor (no-async-promise-executor) + +The `new Promise` constructor accepts an *executor* function as an argument, which has `resolve` and `reject` parameters that can be used to control the state of the created Promise. For example: + +```js +const result = new Promise(function executor(resolve, reject) { + readFile('foo.txt', function(err, result) { + if (err) { + reject(err); + } else { + resolve(result); + } + }); +}); +``` + +The executor function can also be an `async function`. However, this is usually a mistake, for a few reasons: + +* If an async executor function throws an error, the error will be lost and won't cause the newly-constructed `Promise` to reject. This could make it difficult to debug and handle some errors. +* If a Promise executor function is using `await`, this is usually a sign that it is not actually necessary to use the `new Promise` constructor, or the scope of the `new Promise` constructor can be reduced. + +## Rule Details + +This rule aims to disallow async Promise executor functions. + +Examples of **incorrect** code for this rule: + +```js +const foo = new Promise(async (resolve, reject) => { + readFile('foo.txt', function(err, result) { + if (err) { + reject(err); + } else { + resolve(result); + } + }); +}); + +const result = new Promise(async (resolve, reject) => { + resolve(await foo); +}); +``` + +Examples of **correct** code for this rule: + +```js +const foo = new Promise((resolve, reject) => { + readFile('foo.txt', function(err, result) { + if (err) { + reject(err); + } else { + resolve(result); + } + }); +}); + +const result = Promise.resolve(foo); +``` + +## When Not To Use It + +If your codebase doesn't support async function syntax, there's no need to enable this rule. diff --git a/eslint/docs/rules/no-await-in-loop.md b/eslint/docs/rules/no-await-in-loop.md new file mode 100644 index 0000000..9edf073 --- /dev/null +++ b/eslint/docs/rules/no-await-in-loop.md @@ -0,0 +1,75 @@ +# Disallow `await` inside of loops (no-await-in-loop) + +Performing an operation on each element of an iterable is a common task. However, performing an +`await` as part of each operation is an indication that the program is not taking full advantage of +the parallelization benefits of `async`/`await`. + +Usually, the code should be refactored to create all the promises at once, then get access to the +results using `Promise.all()`. Otherwise, each successive operation will not start until the +previous one has completed. + +Concretely, the following function should be refactored as shown: + +```js +async function foo(things) { + const results = []; + for (const thing of things) { + // Bad: each loop iteration is delayed until the entire asynchronous operation completes + results.push(await bar(thing)); + } + return baz(results); +} +``` + +```js +async function foo(things) { + const results = []; + for (const thing of things) { + // Good: all asynchronous operations are immediately started. + results.push(bar(thing)); + } + // Now that all the asynchronous operations are running, here we wait until they all complete. + return baz(await Promise.all(results)); +} +``` + +## Rule Details + +This rule disallows the use of `await` within loop bodies. + +## Examples + +Examples of **correct** code for this rule: + +```js +async function foo(things) { + const results = []; + for (const thing of things) { + // Good: all asynchronous operations are immediately started. + results.push(bar(thing)); + } + // Now that all the asynchronous operations are running, here we wait until they all complete. + return baz(await Promise.all(results)); +} +``` + +Examples of **incorrect** code for this rule: + +```js +async function foo(things) { + const results = []; + for (const thing of things) { + // Bad: each loop iteration is delayed until the entire asynchronous operation completes + results.push(await bar(thing)); + } + return baz(results); +} +``` + +## When Not To Use It + +In many cases the iterations of a loop are not actually independent of each-other. For example, the +output of one iteration might be used as the input to another. Or, loops may be used to retry +asynchronous operations that were unsuccessful. Or, loops may be used to prevent your code from sending +an excessive amount of requests in parallel. In such cases it makes sense to use `await` within a +loop and it is recommended to disable the rule via a standard ESLint disable comment. diff --git a/eslint/docs/rules/no-bitwise.md b/eslint/docs/rules/no-bitwise.md new file mode 100644 index 0000000..252f0cf --- /dev/null +++ b/eslint/docs/rules/no-bitwise.md @@ -0,0 +1,86 @@ +# disallow bitwise operators (no-bitwise) + +The use of bitwise operators in JavaScript is very rare and often `&` or `|` is simply a mistyped `&&` or `||`, which will lead to unexpected behavior. + +```js +var x = y | z; +``` + +## Rule Details + +This rule disallows bitwise operators. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-bitwise: "error"*/ + +var x = y | z; + +var x = y & z; + +var x = y ^ z; + +var x = ~ z; + +var x = y << z; + +var x = y >> z; + +var x = y >>> z; + +x |= y; + +x &= y; + +x ^= y; + +x <<= y; + +x >>= y; + +x >>>= y; +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-bitwise: "error"*/ + +var x = y || z; + +var x = y && z; + +var x = y > z; + +var x = y < z; + +x += y; +``` + +## Options + +This rule has an object option: + +* `"allow"`: Allows a list of bitwise operators to be used as exceptions. +* `"int32Hint"`: Allows the use of bitwise OR in `|0` pattern for type casting. + +### allow + +Examples of **correct** code for this rule with the `{ "allow": ["~"] }` option: + +```js +/*eslint no-bitwise: ["error", { "allow": ["~"] }] */ + +~[1,2,3].indexOf(1) === -1; +``` + +### int32Hint + +Examples of **correct** code for this rule with the `{ "int32Hint": true }` option: + +```js +/*eslint no-bitwise: ["error", { "int32Hint": true }] */ + +var b = a|0; +``` diff --git a/eslint/docs/rules/no-buffer-constructor.md b/eslint/docs/rules/no-buffer-constructor.md new file mode 100644 index 0000000..f287ce5 --- /dev/null +++ b/eslint/docs/rules/no-buffer-constructor.md @@ -0,0 +1,41 @@ +# disallow use of the Buffer() constructor (no-buffer-constructor) + +In Node.js, the behavior of the `Buffer` constructor is different depending on the type of its argument. Passing an argument from user input to `Buffer()` without validating its type can lead to security vulnerabilities such as remote memory disclosure and denial of service. As a result, the `Buffer` constructor has been deprecated and should not be used. Use the producer methods `Buffer.from`, `Buffer.alloc`, and `Buffer.allocUnsafe` instead. + +## Rule Details + +This rule disallows calling and constructing the `Buffer()` constructor. + +Examples of **incorrect** code for this rule: + +```js +new Buffer(5); +new Buffer([1, 2, 3]); + +Buffer(5); +Buffer([1, 2, 3]); + +new Buffer(res.body.amount); +new Buffer(res.body.values); +``` + +Examples of **correct** code for this rule: + +```js +Buffer.alloc(5); +Buffer.allocUnsafe(5); +Buffer.from([1, 2, 3]); + +Buffer.alloc(res.body.amount); +Buffer.from(res.body.values); +``` + +## When Not To Use It + +If you don't use Node.js, or you still need to support versions of Node.js that lack methods like `Buffer.from`, then you should not enable this rule. + +## Further Reading + +* [Buffer API documentation](https://nodejs.org/api/buffer.html) +* [Let's fix Node.js Buffer API](https://github.com/ChALkeR/notes/blob/master/Lets-fix-Buffer-API.md) +* [Buffer(number) is unsafe](https://github.com/nodejs/node/issues/4660) diff --git a/eslint/docs/rules/no-caller.md b/eslint/docs/rules/no-caller.md new file mode 100644 index 0000000..f5688df --- /dev/null +++ b/eslint/docs/rules/no-caller.md @@ -0,0 +1,49 @@ +# Disallow Use of caller/callee (no-caller) + +The use of `arguments.caller` and `arguments.callee` make several code optimizations impossible. They have been deprecated in future versions of JavaScript and their use is forbidden in ECMAScript 5 while in strict mode. + +```js +function foo() { + var callee = arguments.callee; +} +``` + +## Rule Details + +This rule is aimed at discouraging the use of deprecated and sub-optimal code by disallowing the use of `arguments.caller` and `arguments.callee`. As such, it will warn when `arguments.caller` and `arguments.callee` are used. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-caller: "error"*/ + +function foo(n) { + if (n <= 0) { + return; + } + + arguments.callee(n - 1); +} + +[1,2,3,4,5].map(function(n) { + return !(n > 1) ? 1 : arguments.callee(n - 1) * n; +}); +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-caller: "error"*/ + +function foo(n) { + if (n <= 0) { + return; + } + + foo(n - 1); +} + +[1,2,3,4,5].map(function factorial(n) { + return !(n > 1) ? 1 : factorial(n - 1) * n; +}); +``` diff --git a/eslint/docs/rules/no-case-declarations.md b/eslint/docs/rules/no-case-declarations.md new file mode 100644 index 0000000..3652c74 --- /dev/null +++ b/eslint/docs/rules/no-case-declarations.md @@ -0,0 +1,75 @@ +# Disallow lexical declarations in case/default clauses (no-case-declarations) + +This rule disallows lexical declarations (`let`, `const`, `function` and `class`) +in `case`/`default` clauses. The reason is that the lexical declaration is visible +in the entire switch block but it only gets initialized when it is assigned, which +will only happen if the case where it is defined is reached. + +To ensure that the lexical declaration only applies to the current case clause +wrap your clauses in blocks. + +## Rule Details + +This rule aims to prevent access to uninitialized lexical bindings as well as accessing hoisted functions across case clauses. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-case-declarations: "error"*/ +/*eslint-env es6*/ + +switch (foo) { + case 1: + let x = 1; + break; + case 2: + const y = 2; + break; + case 3: + function f() {} + break; + default: + class C {} +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-case-declarations: "error"*/ +/*eslint-env es6*/ + +// Declarations outside switch-statements are valid +const a = 0; + +switch (foo) { + // The following case clauses are wrapped into blocks using brackets + case 1: { + let x = 1; + break; + } + case 2: { + const y = 2; + break; + } + case 3: { + function f() {} + break; + } + case 4: + // Declarations using var without brackets are valid due to function-scope hoisting + var z = 4; + break; + default: { + class C {} + } +} +``` + +## When Not To Use It + +If you depend on fall through behavior and want access to bindings introduced in the case block. + +## Related Rules + +* [no-fallthrough](no-fallthrough.md) diff --git a/eslint/docs/rules/no-catch-shadow.md b/eslint/docs/rules/no-catch-shadow.md new file mode 100644 index 0000000..cd57ac8 --- /dev/null +++ b/eslint/docs/rules/no-catch-shadow.md @@ -0,0 +1,73 @@ +# Disallow Shadowing of Variables Inside of catch (no-catch-shadow) + +This rule was **deprecated** in ESLint v5.1.0. + +In IE 8 and earlier, the catch clause parameter can overwrite the value of a variable in the outer scope, if that variable has the same name as the catch clause parameter. + +```js +var err = "x"; + +try { + throw "problem"; +} catch (err) { + +} + +console.log(err) // err is 'problem', not 'x' +``` + +## Rule Details + +This rule is aimed at preventing unexpected behavior in your program that may arise from a bug in IE 8 and earlier, in which the catch clause parameter can leak into outer scopes. This rule will warn whenever it encounters a catch clause parameter that has the same name as a variable in an outer scope. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-catch-shadow: "error"*/ + +var err = "x"; + +try { + throw "problem"; +} catch (err) { + +} + +function err() { + // ... +}; + +try { + throw "problem"; +} catch (err) { + +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-catch-shadow: "error"*/ + +var err = "x"; + +try { + throw "problem"; +} catch (e) { + +} + +function err() { + // ... +}; + +try { + throw "problem"; +} catch (e) { + +} +``` + +## When Not To Use It + +If you do not need to support IE 8 and earlier, you should turn this rule off. diff --git a/eslint/docs/rules/no-class-assign.md b/eslint/docs/rules/no-class-assign.md new file mode 100644 index 0000000..62c8a71 --- /dev/null +++ b/eslint/docs/rules/no-class-assign.md @@ -0,0 +1,93 @@ +# Disallow modifying variables of class declarations (no-class-assign) + +`ClassDeclaration` creates a variable, and we can modify the variable. + +```js +/*eslint-env es6*/ + +class A { } +A = 0; +``` + +But the modification is a mistake in most cases. + +## Rule Details + +This rule is aimed to flag modifying variables of class declarations. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-class-assign: "error"*/ +/*eslint-env es6*/ + +class A { } +A = 0; +``` + +```js +/*eslint no-class-assign: "error"*/ +/*eslint-env es6*/ + +A = 0; +class A { } +``` + +```js +/*eslint no-class-assign: "error"*/ +/*eslint-env es6*/ + +class A { + b() { + A = 0; + } +} +``` + +```js +/*eslint no-class-assign: "error"*/ +/*eslint-env es6*/ + +let A = class A { + b() { + A = 0; + // `let A` is shadowed by the class name. + } +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-class-assign: "error"*/ +/*eslint-env es6*/ + +let A = class A { } +A = 0; // A is a variable. +``` + +```js +/*eslint no-class-assign: "error"*/ +/*eslint-env es6*/ + +let A = class { + b() { + A = 0; // A is a variable. + } +} +``` + +```js +/*eslint no-class-assign: 2*/ +/*eslint-env es6*/ + +class A { + b(A) { + A = 0; // A is a parameter. + } +} +``` + +## When Not To Use It + +If you don't want to be notified about modifying variables of class declarations, you can safely disable this rule. diff --git a/eslint/docs/rules/no-comma-dangle.md b/eslint/docs/rules/no-comma-dangle.md new file mode 100644 index 0000000..d8396ba --- /dev/null +++ b/eslint/docs/rules/no-comma-dangle.md @@ -0,0 +1,52 @@ +# no-comma-dangle: disallow trailing commas in object and array literals + +(removed) This rule was **removed** in ESLint v1.0 and **replaced** by the [comma-dangle](comma-dangle.md) rule. + +Trailing commas in object literals are valid according to the ECMAScript 5 (and ECMAScript 3!) spec, however IE8 (when not in IE8 document mode) and below will throw an error when it encounters trailing commas in JavaScript. + +```js +var foo = { + bar: "baz", + qux: "quux", +}; +``` + +## Rule Details + +This rule is aimed at detecting trailing commas in object literals. As such, it will warn whenever it encounters a trailing comma in an object literal. + +Examples of **incorrect** code for this rule: + +```js +var foo = { + bar: "baz", + qux: "quux", +}; + +var arr = [1,2,]; + +foo({ + bar: "baz", + qux: "quux", +}); +``` + +Examples of **correct** code for this rule: + +```js +var foo = { + bar: "baz", + qux: "quux" +}; + +var arr = [1,2]; + +foo({ + bar: "baz", + qux: "quux" +}); +``` + +## When Not To Use It + +If your code will not be run in IE8 or below (a Node.js application, for example) and you'd prefer to allow trailing commas, turn this rule off. diff --git a/eslint/docs/rules/no-compare-neg-zero.md b/eslint/docs/rules/no-compare-neg-zero.md new file mode 100644 index 0000000..e9de786 --- /dev/null +++ b/eslint/docs/rules/no-compare-neg-zero.md @@ -0,0 +1,35 @@ +# disallow comparing against -0 (no-compare-neg-zero) + +## Rule Details + +The rule should warn against code that tries to compare against -0, since that will not work as intended. That is, code like x === -0 will pass for both +0 and -0. The author probably intended Object.is(x, -0). + +Examples of **incorrect** code for this rule: + +```js +/* eslint no-compare-neg-zero: "error" */ + +if (x === -0) { + // doSomething()... +} +``` + +Examples of **correct** code for this rule: + +```js +/* eslint no-compare-neg-zero: "error" */ + +if (x === 0) { + // doSomething()... +} +``` + +```js +/* eslint no-compare-neg-zero: "error" */ + +if (Object.is(x, -0)) { + // doSomething()... +} +``` + + diff --git a/eslint/docs/rules/no-cond-assign.md b/eslint/docs/rules/no-cond-assign.md new file mode 100644 index 0000000..f1738fa --- /dev/null +++ b/eslint/docs/rules/no-cond-assign.md @@ -0,0 +1,127 @@ +# disallow assignment operators in conditional statements (no-cond-assign) + +In conditional statements, it is very easy to mistype a comparison operator (such as `==`) as an assignment operator (such as `=`). For example: + +```js +// Check the user's job title +if (user.jobTitle = "manager") { + // user.jobTitle is now incorrect +} +``` + +There are valid reasons to use assignment operators in conditional statements. However, it can be difficult to tell whether a specific assignment was intentional. + +## Rule Details + +This rule disallows ambiguous assignment operators in test conditions of `if`, `for`, `while`, and `do...while` statements. + +## Options + +This rule has a string option: + +* `"except-parens"` (default) allows assignments in test conditions *only if* they are enclosed in parentheses (for example, to allow reassigning a variable in the test of a `while` or `do...while` loop) +* `"always"` disallows all assignments in test conditions + +### except-parens + +Examples of **incorrect** code for this rule with the default `"except-parens"` option: + +```js +/*eslint no-cond-assign: "error"*/ + +// Unintentional assignment +var x; +if (x = 0) { + var b = 1; +} + +// Practical example that is similar to an error +function setHeight(someNode) { + "use strict"; + do { + someNode.height = "100px"; + } while (someNode = someNode.parentNode); +} +``` + +Examples of **correct** code for this rule with the default `"except-parens"` option: + +```js +/*eslint no-cond-assign: "error"*/ + +// Assignment replaced by comparison +var x; +if (x === 0) { + var b = 1; +} + +// Practical example that wraps the assignment in parentheses +function setHeight(someNode) { + "use strict"; + do { + someNode.height = "100px"; + } while ((someNode = someNode.parentNode)); +} + +// Practical example that wraps the assignment and tests for 'null' +function setHeight(someNode) { + "use strict"; + do { + someNode.height = "100px"; + } while ((someNode = someNode.parentNode) !== null); +} +``` + +### always + +Examples of **incorrect** code for this rule with the `"always"` option: + +```js +/*eslint no-cond-assign: ["error", "always"]*/ + +// Unintentional assignment +var x; +if (x = 0) { + var b = 1; +} + +// Practical example that is similar to an error +function setHeight(someNode) { + "use strict"; + do { + someNode.height = "100px"; + } while (someNode = someNode.parentNode); +} + +// Practical example that wraps the assignment in parentheses +function setHeight(someNode) { + "use strict"; + do { + someNode.height = "100px"; + } while ((someNode = someNode.parentNode)); +} + +// Practical example that wraps the assignment and tests for 'null' +function setHeight(someNode) { + "use strict"; + do { + someNode.height = "100px"; + } while ((someNode = someNode.parentNode) !== null); +} +``` + +Examples of **correct** code for this rule with the `"always"` option: + +```js +/*eslint no-cond-assign: ["error", "always"]*/ + +// Assignment replaced by comparison +var x; +if (x === 0) { + var b = 1; +} +``` + +## Related Rules + +* [no-extra-parens](no-extra-parens.md) diff --git a/eslint/docs/rules/no-confusing-arrow.md b/eslint/docs/rules/no-confusing-arrow.md new file mode 100644 index 0000000..dccf11e --- /dev/null +++ b/eslint/docs/rules/no-confusing-arrow.md @@ -0,0 +1,69 @@ +# Disallow arrow functions where they could be confused with comparisons (no-confusing-arrow) + +Arrow functions (`=>`) are similar in syntax to some comparison operators (`>`, `<`, `<=`, and `>=`). This rule warns against using the arrow function syntax in places where it could be confused with a comparison operator. + +Here's an example where the usage of `=>` could be confusing: + +```js +// The intent is not clear +var x = a => 1 ? 2 : 3; +// Did the author mean this +var x = function (a) { return 1 ? 2 : 3 }; +// Or this +var x = a <= 1 ? 2 : 3; +``` + +## Rule Details + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-confusing-arrow: "error"*/ +/*eslint-env es6*/ + +var x = a => 1 ? 2 : 3; +var x = (a) => 1 ? 2 : 3; +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-confusing-arrow: "error"*/ +/*eslint-env es6*/ + +var x = a => (1 ? 2 : 3); +var x = (a) => (1 ? 2 : 3); +var x = a => { return 1 ? 2 : 3; }; +var x = (a) => { return 1 ? 2 : 3; }; +``` + +## Options + +This rule accepts a single options argument with the following defaults: + +```json +{ + "rules": { + "no-confusing-arrow": ["error", {"allowParens": true}] + } +} +``` + +`allowParens` is a boolean setting that can be `true`(default) or `false`: + +1. `true` relaxes the rule and accepts parenthesis as a valid "confusion-preventing" syntax. +2. `false` warns even if the expression is wrapped in parenthesis + +Examples of **incorrect** code for this rule with the `{"allowParens": false}` option: + +```js +/*eslint no-confusing-arrow: ["error", {"allowParens": false}]*/ +/*eslint-env es6*/ +var x = a => (1 ? 2 : 3); +var x = (a) => (1 ? 2 : 3); +``` + +## Related Rules + +* [no-constant-condition](no-constant-condition.md) +* [arrow-parens](arrow-parens.md) diff --git a/eslint/docs/rules/no-console.md b/eslint/docs/rules/no-console.md new file mode 100644 index 0000000..f2fb79e --- /dev/null +++ b/eslint/docs/rules/no-console.md @@ -0,0 +1,96 @@ +# disallow the use of `console` (no-console) + +In JavaScript that is designed to be executed in the browser, it's considered a best practice to avoid using methods on `console`. Such messages are considered to be for debugging purposes and therefore not suitable to ship to the client. In general, calls using `console` should be stripped before being pushed to production. + +```js +console.log("Made it here."); +console.error("That shouldn't have happened."); +``` + +## Rule Details + +This rule disallows calls to methods of the `console` object. + +Examples of **incorrect** code for this rule: + +```js +/* eslint no-console: "error" */ + +console.log("Log a debug level message."); +console.warn("Log a warn level message."); +console.error("Log an error level message."); +``` + +Examples of **correct** code for this rule: + +```js +/* eslint no-console: "error" */ + +// custom console +Console.log("Hello world!"); +``` + +## Options + +This rule has an object option for exceptions: + +* `"allow"` has an array of strings which are allowed methods of the `console` object + +Examples of additional **correct** code for this rule with a sample `{ "allow": ["warn", "error"] }` option: + +```js +/* eslint no-console: ["error", { allow: ["warn", "error"] }] */ + +console.warn("Log a warn level message."); +console.error("Log an error level message."); +``` + +## When Not To Use It + +If you're using Node.js, however, `console` is used to output information to the user and so is not strictly used for debugging purposes. If you are developing for Node.js then you most likely do not want this rule enabled. + +Another case where you might not use this rule is if you want to enforce console calls and not console overwrites. For example: + +```js +/* eslint no-console: ["error", { allow: ["warn"] }] */ +console.error = function (message) { + throw new Error(message); +}; +``` + +With the `no-console` rule in the above example, ESLint will report an error. For the above example, you can disable the rule: + +```js +// eslint-disable-next-line no-console +console.error = function (message) { + throw new Error(message); +}; + +// or + +console.error = function (message) { // eslint-disable-line no-console + throw new Error(message); +}; +``` + +However, you might not want to manually add `eslint-disable-next-line` or `eslint-disable-line`. You can achieve the effect of only receiving errors for console calls with the `no-restricted-syntax` rule: + +```json +{ + "rules": { + "no-console": "off", + "no-restricted-syntax": [ + "error", + { + "selector": "CallExpression[callee.object.name='console'][callee.property.name!=/^(log|warn|error|info|trace)$/]", + "message": "Unexpected property on console object was called" + } + ] + } +} +``` + +## Related Rules + +* [no-alert](no-alert.md) +* [no-debugger](no-debugger.md) diff --git a/eslint/docs/rules/no-const-assign.md b/eslint/docs/rules/no-const-assign.md new file mode 100644 index 0000000..3969767 --- /dev/null +++ b/eslint/docs/rules/no-const-assign.md @@ -0,0 +1,68 @@ +# Disallow modifying variables that are declared using `const` (no-const-assign) + +We cannot modify variables that are declared using `const` keyword. +It will raise a runtime error. + +Under non ES2015 environment, it might be ignored merely. + +## Rule Details + +This rule is aimed to flag modifying variables that are declared using `const` keyword. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-const-assign: "error"*/ +/*eslint-env es6*/ + +const a = 0; +a = 1; +``` + +```js +/*eslint no-const-assign: "error"*/ +/*eslint-env es6*/ + +const a = 0; +a += 1; +``` + +```js +/*eslint no-const-assign: "error"*/ +/*eslint-env es6*/ + +const a = 0; +++a; +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-const-assign: "error"*/ +/*eslint-env es6*/ + +const a = 0; +console.log(a); +``` + +```js +/*eslint no-const-assign: "error"*/ +/*eslint-env es6*/ + +for (const a in [1, 2, 3]) { // `a` is re-defined (not modified) on each loop step. + console.log(a); +} +``` + +```js +/*eslint no-const-assign: "error"*/ +/*eslint-env es6*/ + +for (const a of [1, 2, 3]) { // `a` is re-defined (not modified) on each loop step. + console.log(a); +} +``` + +## When Not To Use It + +If you don't want to be notified about modifying variables that are declared using `const` keyword, you can safely disable this rule. diff --git a/eslint/docs/rules/no-constant-condition.md b/eslint/docs/rules/no-constant-condition.md new file mode 100644 index 0000000..8566d8c --- /dev/null +++ b/eslint/docs/rules/no-constant-condition.md @@ -0,0 +1,101 @@ +# disallow constant expressions in conditions (no-constant-condition) + +A constant expression (for example, a literal) as a test condition might be a typo or development trigger for a specific behavior. For example, the following code looks as if it is not ready for production. + +```js +if (false) { + doSomethingUnfinished(); +} +``` + +## Rule Details + +This rule disallows constant expressions in the test condition of: + +* `if`, `for`, `while`, or `do...while` statement +* `?:` ternary expression + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-constant-condition: "error"*/ + +if (false) { + doSomethingUnfinished(); +} + +if (void x) { + doSomethingUnfinished(); +} + +for (;-2;) { + doSomethingForever(); +} + +while (typeof x) { + doSomethingForever(); +} + +do { + doSomethingForever(); +} while (x = -1); + +var result = 0 ? a : b; +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-constant-condition: "error"*/ + +if (x === 0) { + doSomething(); +} + +for (;;) { + doSomethingForever(); +} + +while (typeof x === "undefined") { + doSomething(); +} + +do { + doSomething(); +} while (x); + +var result = x !== 0 ? a : b; +``` + +## Options + +### checkLoops + +Set to `true` by default. Setting this option to `false` allows constant expressions in loops. + +Examples of **correct** code for when `checkLoops` is `false`: + +```js +/*eslint no-constant-condition: ["error", { "checkLoops": false }]*/ + +while (true) { + doSomething(); + if (condition()) { + break; + } +}; + +for (;true;) { + doSomething(); + if (condition()) { + break; + } +}; + +do { + doSomething(); + if (condition()) { + break; + } +} while (true) +``` diff --git a/eslint/docs/rules/no-constructor-return.md b/eslint/docs/rules/no-constructor-return.md new file mode 100644 index 0000000..284b757 --- /dev/null +++ b/eslint/docs/rules/no-constructor-return.md @@ -0,0 +1,50 @@ +# Disallow returning value in constructor (no-constructor-return) + +In JavaScript, returning a value in the constructor of a class may be a mistake. Forbidding this pattern prevents mistakes resulting from unfamiliarity with the language or a copy-paste error. + +## Rule Details + +This rule disallows return statements in the constructor of a class. Note that returning nothing with flow control is allowed. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-constructor-return: "error"*/ + +class A { + constructor(a) { + this.a = a; + return a; + } +} + +class B { + constructor(f) { + if (!f) { + return 'falsy'; + } + } +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-constructor-return: "error"*/ + +class C { + constructor(c) { + this.c = c; + } +} + +class D { + constructor(f) { + if (!f) { + return; // Flow control. + } + + f(); + } +} +``` diff --git a/eslint/docs/rules/no-continue.md b/eslint/docs/rules/no-continue.md new file mode 100644 index 0000000..37c1188 --- /dev/null +++ b/eslint/docs/rules/no-continue.md @@ -0,0 +1,71 @@ +# disallow `continue` statements (no-continue) + +The `continue` statement terminates execution of the statements in the current iteration of the current or labeled loop, and continues execution of the loop with the next iteration. When used incorrectly it makes code less testable, less readable and less maintainable. Structured control flow statements such as `if` should be used instead. + +```js +var sum = 0, + i; + +for(i = 0; i < 10; i++) { + if(i >= 5) { + continue; + } + + a += i; +} +``` + +## Rule Details + +This rule disallows `continue` statements. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-continue: "error"*/ + +var sum = 0, + i; + +for(i = 0; i < 10; i++) { + if(i >= 5) { + continue; + } + + a += i; +} +``` + +```js +/*eslint no-continue: "error"*/ + +var sum = 0, + i; + +labeledLoop: for(i = 0; i < 10; i++) { + if(i >= 5) { + continue labeledLoop; + } + + a += i; +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-continue: "error"*/ + +var sum = 0, + i; + +for(i = 0; i < 10; i++) { + if(i < 5) { + a += i; + } +} +``` + +## Compatibility + +* **JSLint**: `continue` diff --git a/eslint/docs/rules/no-control-regex.md b/eslint/docs/rules/no-control-regex.md new file mode 100644 index 0000000..01a55fb --- /dev/null +++ b/eslint/docs/rules/no-control-regex.md @@ -0,0 +1,34 @@ +# disallow control characters in regular expressions (no-control-regex) + +Control characters are special, invisible characters in the ASCII range 0-31. These characters are rarely used in JavaScript strings so a regular expression containing these characters is most likely a mistake. + +## Rule Details + +This rule disallows control characters in regular expressions. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-control-regex: "error"*/ + +var pattern1 = /\x1f/; +var pattern2 = new RegExp("\x1f"); +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-control-regex: "error"*/ + +var pattern1 = /\x20/; +var pattern2 = new RegExp("\x20"); +``` + +## When Not To Use It + +If you need to use control character pattern matching, then you should turn this rule off. + +## Related Rules + +* [no-div-regex](no-div-regex.md) +* [no-regex-spaces](no-regex-spaces.md) diff --git a/eslint/docs/rules/no-debugger.md b/eslint/docs/rules/no-debugger.md new file mode 100644 index 0000000..f8220bd --- /dev/null +++ b/eslint/docs/rules/no-debugger.md @@ -0,0 +1,41 @@ +# disallow the use of `debugger` (no-debugger) + +The `debugger` statement is used to tell the executing JavaScript environment to stop execution and start up a debugger at the current point in the code. This has fallen out of favor as a good practice with the advent of modern debugging and development tools. Production code should definitely not contain `debugger`, as it will cause the browser to stop executing code and open an appropriate debugger. + +## Rule Details + +This rule disallows `debugger` statements. + +Example of **incorrect** code for this rule: + +```js +/*eslint no-debugger: "error"*/ + +function isTruthy(x) { + debugger; + return Boolean(x); +} +``` + +Example of **correct** code for this rule: + +```js +/*eslint no-debugger: "error"*/ + +function isTruthy(x) { + return Boolean(x); // set a breakpoint at this line +} +``` + +## When Not To Use It + +If your code is still very much in development and don't want to worry about stripping `debugger` statements, then turn this rule off. You'll generally want to turn it back on when testing code prior to deployment. + +## Further Reading + +* [Debugger](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/debugger) + +## Related Rules + +* [no-alert](no-alert.md) +* [no-console](no-console.md) diff --git a/eslint/docs/rules/no-delete-var.md b/eslint/docs/rules/no-delete-var.md new file mode 100644 index 0000000..ab90d11 --- /dev/null +++ b/eslint/docs/rules/no-delete-var.md @@ -0,0 +1,18 @@ +# disallow deleting variables (no-delete-var) + +The purpose of the `delete` operator is to remove a property from an object. Using the `delete` operator on a variable might lead to unexpected behavior. + +## Rule Details + +This rule disallows the use of the `delete` operator on variables. + +If ESLint parses code in strict mode, the parser (instead of this rule) reports the error. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-delete-var: "error"*/ + +var x; +delete x; +``` diff --git a/eslint/docs/rules/no-div-regex.md b/eslint/docs/rules/no-div-regex.md new file mode 100644 index 0000000..211525b --- /dev/null +++ b/eslint/docs/rules/no-div-regex.md @@ -0,0 +1,32 @@ +# Disallow Regular Expressions That Look Like Division (no-div-regex) + +Require regex literals to escape division operators. + +```js +function bar() { return /=foo/; } +``` + +## Rule Details + +This is used to disambiguate the division operator to not confuse users. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-div-regex: "error"*/ + +function bar() { return /=foo/; } +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-div-regex: "error"*/ + +function bar() { return /[=]foo/; } +``` + +## Related Rules + +* [no-control-regex](no-control-regex.md) +* [no-regex-spaces](no-regex-spaces.md) diff --git a/eslint/docs/rules/no-dupe-args.md b/eslint/docs/rules/no-dupe-args.md new file mode 100644 index 0000000..80a3d85 --- /dev/null +++ b/eslint/docs/rules/no-dupe-args.md @@ -0,0 +1,37 @@ +# disallow duplicate arguments in `function` definitions (no-dupe-args) + +If more than one parameter has the same name in a function definition, the last occurrence "shadows" the preceding occurrences. A duplicated name might be a typing error. + +## Rule Details + +This rule disallows duplicate parameter names in function declarations or expressions. It does not apply to arrow functions or class methods, because the parser reports the error. + +If ESLint parses code in strict mode, the parser (instead of this rule) reports the error. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-dupe-args: "error"*/ + +function foo(a, b, a) { + console.log("value of the second a:", a); +} + +var bar = function (a, b, a) { + console.log("value of the second a:", a); +}; +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-dupe-args: "error"*/ + +function foo(a, b, c) { + console.log(a, b, c); +} + +var bar = function (a, b, c) { + console.log(a, b, c); +}; +``` diff --git a/eslint/docs/rules/no-dupe-class-members.md b/eslint/docs/rules/no-dupe-class-members.md new file mode 100644 index 0000000..9c1cc4f --- /dev/null +++ b/eslint/docs/rules/no-dupe-class-members.md @@ -0,0 +1,74 @@ +# Disallow duplicate name in class members (no-dupe-class-members) + +If there are declarations of the same name in class members, the last declaration overwrites other declarations silently. +It can cause unexpected behaviors. + +```js +/*eslint-env es6*/ + +class Foo { + bar() { console.log("hello"); } + bar() { console.log("goodbye"); } +} + +var foo = new Foo(); +foo.bar(); // goodbye +``` + +## Rule Details + +This rule is aimed to flag the use of duplicate names in class members. + +## Examples + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-dupe-class-members: "error"*/ +/*eslint-env es6*/ + +class Foo { + bar() { } + bar() { } +} + +class Foo { + bar() { } + get bar() { } +} + +class Foo { + static bar() { } + static bar() { } +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-dupe-class-members: "error"*/ +/*eslint-env es6*/ + +class Foo { + bar() { } + qux() { } +} + +class Foo { + get bar() { } + set bar(value) { } +} + +class Foo { + static bar() { } + bar() { } +} +``` + +## When Not To Use It + +This rule should not be used in ES3/5 environments. + +In ES2015 (ES6) or later, if you don't want to be notified about duplicate names in class members, you can safely disable this rule. + +It's also safe to disable this rule when using TypeScript because TypeScript's compiler already checks for duplicate function implementations. diff --git a/eslint/docs/rules/no-dupe-else-if.md b/eslint/docs/rules/no-dupe-else-if.md new file mode 100644 index 0000000..8b91c83 --- /dev/null +++ b/eslint/docs/rules/no-dupe-else-if.md @@ -0,0 +1,178 @@ +# Disallow duplicate conditions in `if-else-if` chains (no-dupe-else-if) + +`if-else-if` chains are commonly used when there is a need to execute only one branch (or at most one branch) out of several possible branches, based on certain conditions. + +```js +if (a) { + foo(); +} else if (b) { + bar(); +} else if (c) { + baz(); +} +``` + +Two identical test conditions in the same chain are almost always a mistake in the code. Unless there are side effects in the expressions, a duplicate will evaluate to the same `true` or `false` value as the identical expression earlier in the chain, meaning that its branch can never execute. + +```js +if (a) { + foo(); +} else if (b) { + bar(); +} else if (b) { + baz(); +} +``` + +In the above example, `baz()` can never execute. Obviously, `baz()` could be executed only when `b` evaluates to `true`, but in that case `bar()` would be executed instead, since it's earlier in the chain. + +## Rule Details + +This rule disallows duplicate conditions in the same `if-else-if` chain. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-dupe-else-if: "error"*/ + +if (isSomething(x)) { + foo(); +} else if (isSomething(x)) { + bar(); +} + +if (a) { + foo(); +} else if (b) { + bar(); +} else if (c && d) { + baz(); +} else if (c && d) { + quux(); +} else { + quuux(); +} + +if (n === 1) { + foo(); +} else if (n === 2) { + bar(); +} else if (n === 3) { + baz(); +} else if (n === 2) { + quux(); +} else if (n === 5) { + quuux(); +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-dupe-else-if: "error"*/ + +if (isSomething(x)) { + foo(); +} else if (isSomethingElse(x)) { + bar(); +} + +if (a) { + foo(); +} else if (b) { + bar(); +} else if (c && d) { + baz(); +} else if (c && e) { + quux(); +} else { + quuux(); +} + +if (n === 1) { + foo(); +} else if (n === 2) { + bar(); +} else if (n === 3) { + baz(); +} else if (n === 4) { + quux(); +} else if (n === 5) { + quuux(); +} +``` + +This rule can also detect some cases where the conditions are not identical, but the branch can never execute due to the logic of `||` and `&&` operators. + +Examples of additional **incorrect** code for this rule: + +```js +/*eslint no-dupe-else-if: "error"*/ + +if (a || b) { + foo(); +} else if (a) { + bar(); +} + +if (a) { + foo(); +} else if (b) { + bar(); +} else if (a || b) { + baz(); +} + +if (a) { + foo(); +} else if (a && b) { + bar(); +} + +if (a && b) { + foo(); +} else if (a && b && c) { + bar(); +} + +if (a || b) { + foo(); +} else if (b && c) { + bar(); +} + +if (a) { + foo(); +} else if (b && c) { + bar(); +} else if (d && (c && e && b || a)) { + baz(); +} +``` + +Please note that this rule does not compare conditions from the chain with conditions inside statements, and will not warn in the cases such as follows: + +```js +if (a) { + if (a) { + foo(); + } +} + +if (a) { + foo(); +} else { + if (a) { + bar(); + } +} +``` + +## When Not To Use It + +In rare cases where you really need identical test conditions in the same chain, which necessarily means that the expressions in the chain are causing and relying on side effects, you will have to turn this rule off. + +## Related Rules + +* [no-duplicate-case](no-duplicate-case.md) +* [no-lonely-if](no-lonely-if.md) diff --git a/eslint/docs/rules/no-dupe-keys.md b/eslint/docs/rules/no-dupe-keys.md new file mode 100644 index 0000000..0d892ef --- /dev/null +++ b/eslint/docs/rules/no-dupe-keys.md @@ -0,0 +1,46 @@ +# disallow duplicate keys in object literals (no-dupe-keys) + +Multiple properties with the same key in object literals can cause unexpected behavior in your application. + +```js +var foo = { + bar: "baz", + bar: "qux" +}; +``` + +## Rule Details + +This rule disallows duplicate keys in object literals. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-dupe-keys: "error"*/ + +var foo = { + bar: "baz", + bar: "qux" +}; + +var foo = { + "bar": "baz", + bar: "qux" +}; + +var foo = { + 0x1: "baz", + 1: "qux" +}; +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-dupe-keys: "error"*/ + +var foo = { + bar: "baz", + quxx: "qux" +}; +``` diff --git a/eslint/docs/rules/no-duplicate-case.md b/eslint/docs/rules/no-duplicate-case.md new file mode 100644 index 0000000..c728bdf --- /dev/null +++ b/eslint/docs/rules/no-duplicate-case.md @@ -0,0 +1,91 @@ +# Rule to disallow a duplicate case label (no-duplicate-case) + +If a `switch` statement has duplicate test expressions in `case` clauses, it is likely that a programmer copied a `case` clause but forgot to change the test expression. + +## Rule Details + +This rule disallows duplicate test expressions in `case` clauses of `switch` statements. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-duplicate-case: "error"*/ + +var a = 1, + one = 1; + +switch (a) { + case 1: + break; + case 2: + break; + case 1: // duplicate test expression + break; + default: + break; +} + +switch (a) { + case one: + break; + case 2: + break; + case one: // duplicate test expression + break; + default: + break; +} + +switch (a) { + case "1": + break; + case "2": + break; + case "1": // duplicate test expression + break; + default: + break; +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-duplicate-case: "error"*/ + +var a = 1, + one = 1; + +switch (a) { + case 1: + break; + case 2: + break; + case 3: + break; + default: + break; +} + +switch (a) { + case one: + break; + case 2: + break; + case 3: + break; + default: + break; +} + +switch (a) { + case "1": + break; + case "2": + break; + case "3": + break; + default: + break; +} +``` diff --git a/eslint/docs/rules/no-duplicate-imports.md b/eslint/docs/rules/no-duplicate-imports.md new file mode 100644 index 0000000..683f315 --- /dev/null +++ b/eslint/docs/rules/no-duplicate-imports.md @@ -0,0 +1,60 @@ +# Disallow duplicate imports (no-duplicate-imports) + +Using a single `import` statement per module will make the code clearer because you can see everything being imported from that module on one line. + +In the following example the `module` import on line 1 is repeated on line 3. These can be combined to make the list of imports more succinct. + +```js +import { merge } from 'module'; +import something from 'another-module'; +import { find } from 'module'; +``` + +## Rule Details + +This rule requires that all imports from a single module exists in a single `import` statement. + +Example of **incorrect** code for this rule: + +```js +/*eslint no-duplicate-imports: "error"*/ + +import { merge } from 'module'; +import something from 'another-module'; +import { find } from 'module'; +``` + +Example of **correct** code for this rule: + +```js +/*eslint no-duplicate-imports: "error"*/ + +import { merge, find } from 'module'; +import something from 'another-module'; +``` + +## Options + +This rule takes one optional argument, an object with a single key, `includeExports` which is a `boolean`. It defaults to `false`. + +If re-exporting from an imported module, you should add the imports to the `import`-statement, and export that directly, not use `export ... from`. + +Example of **incorrect** code for this rule with the `{ "includeExports": true }` option: + +```js +/*eslint no-duplicate-imports: ["error", { "includeExports": true }]*/ + +import { merge } from 'module'; + +export { find } from 'module'; +``` + +Example of **correct** code for this rule with the `{ "includeExports": true }` option: + +```js +/*eslint no-duplicate-imports: ["error", { "includeExports": true }]*/ + +import { merge, find } from 'module'; + +export { find }; +``` diff --git a/eslint/docs/rules/no-else-return.md b/eslint/docs/rules/no-else-return.md new file mode 100644 index 0000000..2f3f83d --- /dev/null +++ b/eslint/docs/rules/no-else-return.md @@ -0,0 +1,157 @@ +# Disallow return before else (no-else-return) + +If an `if` block contains a `return` statement, the `else` block becomes unnecessary. Its contents can be placed outside of the block. + +```js +function foo() { + if (x) { + return y; + } else { + return z; + } +} +``` + +## Rule Details + +This rule is aimed at highlighting an unnecessary block of code following an `if` containing a return statement. As such, it will warn when it encounters an `else` following a chain of `if`s, all of them containing a `return` statement. + +## Options + +This rule has an object option: + +* `allowElseIf: true` (default) allows `else if` blocks after a return +* `allowElseIf: false` disallows `else if` blocks after a return + +### `allowElseIf: true` + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-else-return: "error"*/ + +function foo() { + if (x) { + return y; + } else { + return z; + } +} + +function foo() { + if (x) { + return y; + } else if (z) { + return w; + } else { + return t; + } +} + +function foo() { + if (x) { + return y; + } else { + var t = "foo"; + } + + return t; +} + +function foo() { + if (error) { + return 'It failed'; + } else { + if (loading) { + return "It's still loading"; + } + } +} + +// Two warnings for nested occurrences +function foo() { + if (x) { + if (y) { + return y; + } else { + return x; + } + } else { + return z; + } +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-else-return: "error"*/ + +function foo() { + if (x) { + return y; + } + + return z; +} + +function foo() { + if (x) { + return y; + } else if (z) { + var t = "foo"; + } else { + return w; + } +} + +function foo() { + if (x) { + if (z) { + return y; + } + } else { + return z; + } +} + +function foo() { + if (error) { + return 'It failed'; + } else if (loading) { + return "It's still loading"; + } +} +``` + +### `allowElseIf: false` + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-else-return: ["error", {allowElseIf: false}]*/ + +function foo() { + if (error) { + return 'It failed'; + } else if (loading) { + return "It's still loading"; + } +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-else-return: ["error", {allowElseIf: false}]*/ + +function foo() { + if (error) { + return 'It failed'; + } + + if (loading) { + return "It's still loading"; + } +} +``` diff --git a/eslint/docs/rules/no-empty-character-class.md b/eslint/docs/rules/no-empty-character-class.md new file mode 100644 index 0000000..c587e9d --- /dev/null +++ b/eslint/docs/rules/no-empty-character-class.md @@ -0,0 +1,44 @@ +# disallow empty character classes in regular expressions (no-empty-character-class) + +Because empty character classes in regular expressions do not match anything, they might be typing mistakes. + +```js +var foo = /^abc[]/; +``` + +## Rule Details + +This rule disallows empty character classes in regular expressions. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-empty-character-class: "error"*/ + +/^abc[]/.test("abcdefg"); // false +"abcdefg".match(/^abc[]/); // null +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-empty-character-class: "error"*/ + +/^abc/.test("abcdefg"); // true +"abcdefg".match(/^abc/); // ["abc"] + +/^abc[a-z]/.test("abcdefg"); // true +"abcdefg".match(/^abc[a-z]/); // ["abcd"] +``` + +## Known Limitations + +This rule does not report empty character classes in the string argument of calls to the `RegExp` constructor. + +Example of a *false negative* when this rule reports correct code: + +```js +/*eslint no-empty-character-class: "error"*/ + +var abcNeverMatches = new RegExp("^abc[]"); +``` diff --git a/eslint/docs/rules/no-empty-class.md b/eslint/docs/rules/no-empty-class.md new file mode 100644 index 0000000..6ed0267 --- /dev/null +++ b/eslint/docs/rules/no-empty-class.md @@ -0,0 +1,33 @@ +# no-empty-class: disallow empty character classes in regular expressions + +(removed) This rule was **removed** in ESLint v1.0 and **replaced** by the [no-empty-character-class](no-empty-character-class.md) rule. + +Empty character classes in regular expressions do not match anything and can result in code that may not work as intended. + +```js +var foo = /^abc[]/; +``` + +## Rule Details + +This rule is aimed at highlighting possible typos and unexpected behavior in regular expressions which may arise from the use of empty character classes. + +Examples of **incorrect** code for this rule: + +```js +var foo = /^abc[]/; + +/^abc[]/.test(foo); + +bar.match(/^abc[]/); +``` + +Examples of **correct** code for this rule: + +```js +var foo = /^abc/; + +var foo = /^abc[a-z]/; + +var bar = new RegExp("^abc[]"); +``` diff --git a/eslint/docs/rules/no-empty-function.md b/eslint/docs/rules/no-empty-function.md new file mode 100644 index 0000000..0f8691c --- /dev/null +++ b/eslint/docs/rules/no-empty-function.md @@ -0,0 +1,347 @@ +# Disallow empty functions (no-empty-function) + +Empty functions can reduce readability because readers need to guess whether it's intentional or not. +So writing a clear comment for empty functions is a good practice. + +```js +function foo() { + // do nothing. +} +``` + +Especially, the empty block of arrow functions might be confusing developers. +It's very similar to an empty object literal. + +```js +list.map(() => {}); // This is a block, would return undefined. +list.map(() => ({})); // This is an empty object. +``` + +## Rule Details + +This rule is aimed at eliminating empty functions. +A function will not be considered a problem if it contains a comment. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-empty-function: "error"*/ +/*eslint-env es6*/ + +function foo() {} + +var foo = function() {}; + +var foo = () => {}; + +function* foo() {} + +var foo = function*() {}; + +var obj = { + foo: function() {}, + + foo: function*() {}, + + foo() {}, + + *foo() {}, + + get foo() {}, + + set foo(value) {} +}; + +class A { + constructor() {} + + foo() {} + + *foo() {} + + get foo() {} + + set foo(value) {} + + static foo() {} + + static *foo() {} + + static get foo() {} + + static set foo(value) {} +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-empty-function: "error"*/ +/*eslint-env es6*/ + +function foo() { + // do nothing. +} + +var foo = function() { + // any clear comments. +}; + +var foo = () => { + bar(); +}; + +function* foo() { + // do nothing. +} + +var foo = function*() { + // do nothing. +}; + +var obj = { + foo: function() { + // do nothing. + }, + + foo: function*() { + // do nothing. + }, + + foo() { + // do nothing. + }, + + *foo() { + // do nothing. + }, + + get foo() { + // do nothing. + }, + + set foo(value) { + // do nothing. + } +}; + +class A { + constructor() { + // do nothing. + } + + foo() { + // do nothing. + } + + *foo() { + // do nothing. + } + + get foo() { + // do nothing. + } + + set foo(value) { + // do nothing. + } + + static foo() { + // do nothing. + } + + static *foo() { + // do nothing. + } + + static get foo() { + // do nothing. + } + + static set foo(value) { + // do nothing. + } +} +``` + +## Options + +This rule has an option to allow specific kinds of functions to be empty. + +* `allow` (`string[]`) - A list of kind to allow empty functions. List items are some of the following strings. An empty array (`[]`) by default. + * `"functions"` - Normal functions. + * `"arrowFunctions"` - Arrow functions. + * `"generatorFunctions"` - Generator functions. + * `"methods"` - Class methods and method shorthands of object literals. + * `"generatorMethods"` - Class methods and method shorthands of object literals with generator. + * `"getters"` - Getters. + * `"setters"` - Setters. + * `"constructors"` - Class constructors. + * `"asyncFunctions"` - Async functions. + * `"asyncMethods"` - Async class methods and method shorthands of object literals. + +#### allow: functions + +Examples of **correct** code for the `{ "allow": ["functions"] }` option: + +```js +/*eslint no-empty-function: ["error", { "allow": ["functions"] }]*/ + +function foo() {} + +var foo = function() {}; + +var obj = { + foo: function() {} +}; +``` + +#### allow: arrowFunctions + +Examples of **correct** code for the `{ "allow": ["arrowFunctions"] }` option: + +```js +/*eslint no-empty-function: ["error", { "allow": ["arrowFunctions"] }]*/ +/*eslint-env es6*/ + +var foo = () => {}; +``` + +#### allow: generatorFunctions + +Examples of **correct** code for the `{ "allow": ["generatorFunctions"] }` option: + +```js +/*eslint no-empty-function: ["error", { "allow": ["generatorFunctions"] }]*/ +/*eslint-env es6*/ + +function* foo() {} + +var foo = function*() {}; + +var obj = { + foo: function*() {} +}; +``` + +#### allow: methods + +Examples of **correct** code for the `{ "allow": ["methods"] }` option: + +```js +/*eslint no-empty-function: ["error", { "allow": ["methods"] }]*/ +/*eslint-env es6*/ + +var obj = { + foo() {} +}; + +class A { + foo() {} + static foo() {} +} +``` + +#### allow: generatorMethods + +Examples of **correct** code for the `{ "allow": ["generatorMethods"] }` option: + +```js +/*eslint no-empty-function: ["error", { "allow": ["generatorMethods"] }]*/ +/*eslint-env es6*/ + +var obj = { + *foo() {} +}; + +class A { + *foo() {} + static *foo() {} +} +``` + +#### allow: getters + +Examples of **correct** code for the `{ "allow": ["getters"] }` option: + +```js +/*eslint no-empty-function: ["error", { "allow": ["getters"] }]*/ +/*eslint-env es6*/ + +var obj = { + get foo() {} +}; + +class A { + get foo() {} + static get foo() {} +} +``` + +#### allow: setters + +Examples of **correct** code for the `{ "allow": ["setters"] }` option: + +```js +/*eslint no-empty-function: ["error", { "allow": ["setters"] }]*/ +/*eslint-env es6*/ + +var obj = { + set foo(value) {} +}; + +class A { + set foo(value) {} + static set foo(value) {} +} +``` + +#### allow: constructors + +Examples of **correct** code for the `{ "allow": ["constructors"] }` option: + +```js +/*eslint no-empty-function: ["error", { "allow": ["constructors"] }]*/ +/*eslint-env es6*/ + +class A { + constructor() {} +} +``` + +#### allow: asyncFunctions + +Examples of **correct** code for the `{ "allow": ["asyncFunctions"] }` options: + +```js +/*eslint no-empty-function: ["error", { "allow": ["asyncFunctions"] }]*/ +/*eslint-env es2017*/ + +async function a(){} +``` + +#### allow: asyncMethods + +Examples of **correct** code for the `{ "allow": ["asyncMethods"] }` options: + +```js +/*eslint no-empty-function: ["error", { "allow": ["asyncMethods"] }]*/ +/*eslint-env es2017*/ + +var obj = { + async foo() {} +}; + +class A { + async foo() {} + static async foo() {} +} +``` + +## When Not To Use It + +If you don't want to be notified about empty functions, then it's safe to disable this rule. + +## Related Rules + +* [no-empty](./no-empty.md) diff --git a/eslint/docs/rules/no-empty-label.md b/eslint/docs/rules/no-empty-label.md new file mode 100644 index 0000000..3f88501 --- /dev/null +++ b/eslint/docs/rules/no-empty-label.md @@ -0,0 +1,40 @@ +# no-empty-label: disallow labels for anything other than loops and switches + +(removed) This rule was **removed** in ESLint v2.0 and **replaced** by the [no-labels](no-labels.md) rule. + +Labeled statements are only used in conjunction with labeled break and continue statements. ECMAScript has no goto statement. + + +## Rule Details + +This error occurs when a label is used to mark a statement that is not an iteration or switch + +Example of **incorrect** code for this rule: + +```js +/*eslint no-empty-label: "error"*/ + +labeled: +var x = 10; +``` + +Example of **correct** code for this rule: + +```js +/*eslint no-empty-label: "error"*/ + +labeled: +for (var i=10; i; i--) { + // ... +} +``` + +## When Not To Use It + +If you don't want to be notified about usage of labels, then it's safe to disable this rule. + +## Related Rules + +* [no-labels](./no-labels.md) +* [no-label-var](./no-label-var.md) +* [no-unused-labels](./no-unused-labels.md) diff --git a/eslint/docs/rules/no-empty-pattern.md b/eslint/docs/rules/no-empty-pattern.md new file mode 100644 index 0000000..9459915 --- /dev/null +++ b/eslint/docs/rules/no-empty-pattern.md @@ -0,0 +1,54 @@ +# Disallow empty destructuring patterns (no-empty-pattern) + +When using destructuring, it's possible to create a pattern that has no effect. This happens when empty curly braces are used to the right of an embedded object destructuring pattern, such as: + +```js +// doesn't create any variables +var {a: {}} = foo; +``` + +In this code, no new variables are created because `a` is just a location helper while the `{}` is expected to contain the variables to create, such as: + +```js +// creates variable b +var {a: { b }} = foo; +``` + +In many cases, the empty object pattern is a mistake where the author intended to use a default value instead, such as: + +```js +// creates variable a +var {a = {}} = foo; +``` + +The difference between these two patterns is subtle, especially because the problematic empty pattern looks just like an object literal. + +## Rule Details + +This rule aims to flag any empty patterns in destructured objects and arrays, and as such, will report a problem whenever one is encountered. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-empty-pattern: "error"*/ + +var {} = foo; +var [] = foo; +var {a: {}} = foo; +var {a: []} = foo; +function foo({}) {} +function foo([]) {} +function foo({a: {}}) {} +function foo({a: []}) {} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-empty-pattern: "error"*/ + +var {a = {}} = foo; +var {a = []} = foo; +function foo({a = {}}) {} +function foo({a = []}) {} +``` diff --git a/eslint/docs/rules/no-empty.md b/eslint/docs/rules/no-empty.md new file mode 100644 index 0000000..a088708 --- /dev/null +++ b/eslint/docs/rules/no-empty.md @@ -0,0 +1,89 @@ +# disallow empty block statements (no-empty) + +Empty block statements, while not technically errors, usually occur due to refactoring that wasn't completed. They can cause confusion when reading code. + +## Rule Details + +This rule disallows empty block statements. This rule ignores block statements which contain a comment (for example, in an empty `catch` or `finally` block of a `try` statement to indicate that execution should continue regardless of errors). + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-empty: "error"*/ + +if (foo) { +} + +while (foo) { +} + +switch(foo) { +} + +try { + doSomething(); +} catch(ex) { + +} finally { + +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-empty: "error"*/ + +if (foo) { + // empty +} + +while (foo) { + /* empty */ +} + +try { + doSomething(); +} catch (ex) { + // continue regardless of error +} + +try { + doSomething(); +} finally { + /* continue regardless of error */ +} +``` + +## Options + +This rule has an object option for exceptions: + +* `"allowEmptyCatch": true` allows empty `catch` clauses (that is, which do not contain a comment) + +### allowEmptyCatch + +Examples of additional **correct** code for this rule with the `{ "allowEmptyCatch": true }` option: + +```js +/* eslint no-empty: ["error", { "allowEmptyCatch": true }] */ +try { + doSomething(); +} catch (ex) {} + +try { + doSomething(); +} +catch (ex) {} +finally { + /* continue regardless of error */ +} +``` + +## When Not To Use It + +If you intentionally use empty block statements then you can disable this rule. + +## Related Rules + +* [no-empty-function](./no-empty-function.md) diff --git a/eslint/docs/rules/no-eq-null.md b/eslint/docs/rules/no-eq-null.md new file mode 100644 index 0000000..df8f97a --- /dev/null +++ b/eslint/docs/rules/no-eq-null.md @@ -0,0 +1,41 @@ +# Disallow Null Comparisons (no-eq-null) + +Comparing to `null` without a type-checking operator (`==` or `!=`), can have unintended results as the comparison will evaluate to true when comparing to not just a `null`, but also an `undefined` value. + +```js +if (foo == null) { + bar(); +} +``` + +## Rule Details + +The `no-eq-null` rule aims reduce potential bug and unwanted behavior by ensuring that comparisons to `null` only match `null`, and not also `undefined`. As such it will flag comparisons to null when using `==` and `!=`. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-eq-null: "error"*/ + +if (foo == null) { + bar(); +} + +while (qux != null) { + baz(); +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-eq-null: "error"*/ + +if (foo === null) { + bar(); +} + +while (qux !== null) { + baz(); +} +``` diff --git a/eslint/docs/rules/no-eval.md b/eslint/docs/rules/no-eval.md new file mode 100644 index 0000000..9f7b71a --- /dev/null +++ b/eslint/docs/rules/no-eval.md @@ -0,0 +1,147 @@ +# Disallow eval() (no-eval) + +JavaScript's `eval()` function is potentially dangerous and is often misused. Using `eval()` on untrusted code can open a program up to several different injection attacks. The use of `eval()` in most contexts can be substituted for a better, alternative approach to a problem. + +```js +var obj = { x: "foo" }, + key = "x", + value = eval("obj." + key); +``` + +## Rule Details + +This rule is aimed at preventing potentially dangerous, unnecessary, and slow code by disallowing the use of the `eval()` function. As such, it will warn whenever the `eval()` function is used. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-eval: "error"*/ + +var obj = { x: "foo" }, + key = "x", + value = eval("obj." + key); + +(0, eval)("var a = 0"); + +var foo = eval; +foo("var a = 0"); + +// This `this` is the global object. +this.eval("var a = 0"); +``` + +Example of additional **incorrect** code for this rule when `browser` environment is set to `true`: + +```js +/*eslint no-eval: "error"*/ +/*eslint-env browser*/ + +window.eval("var a = 0"); +``` + +Example of additional **incorrect** code for this rule when `node` environment is set to `true`: + +```js +/*eslint no-eval: "error"*/ +/*eslint-env node*/ + +global.eval("var a = 0"); +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-eval: "error"*/ +/*eslint-env es6*/ + +var obj = { x: "foo" }, + key = "x", + value = obj[key]; + +class A { + foo() { + // This is a user-defined method. + this.eval("var a = 0"); + } + + eval() { + } +} +``` + +## Options + +This rule has an option to allow indirect calls to `eval`. +Indirect calls to `eval` are less dangerous than direct calls to `eval` because they cannot dynamically change the scope. Because of this, they also will not negatively impact performance to the degree of direct `eval`. + +```js +{ + "no-eval": ["error", {"allowIndirect": true}] // default is false +} +``` + +Example of **incorrect** code for this rule with the `{"allowIndirect": true}` option: + +```js +/*eslint no-eval: "error"*/ + +var obj = { x: "foo" }, + key = "x", + value = eval("obj." + key); +``` + +Examples of **correct** code for this rule with the `{"allowIndirect": true}` option: + +```js +/*eslint no-eval: "error"*/ + +(0, eval)("var a = 0"); + +var foo = eval; +foo("var a = 0"); + +this.eval("var a = 0"); +``` + +```js +/*eslint no-eval: "error"*/ +/*eslint-env browser*/ + +window.eval("var a = 0"); +``` + +```js +/*eslint no-eval: "error"*/ +/*eslint-env node*/ + +global.eval("var a = 0"); +``` + +## Known Limitations + +* This rule is warning every `eval()` even if the `eval` is not global's. + This behavior is in order to detect calls of direct `eval`. Such as: + + ```js + module.exports = function(eval) { + // If the value of this `eval` is built-in `eval` function, this is a + // call of direct `eval`. + eval("var a = 0"); + }; + ``` + +* This rule cannot catch renaming the global object. Such as: + + ```js + var foo = window; + foo.eval("var a = 0"); + ``` + +## Further Reading + +* [Eval is Evil, Part One](https://blogs.msdn.com/b/ericlippert/archive/2003/11/01/53329.aspx) +* [How evil is eval](https://javascriptweblog.wordpress.com/2010/04/19/how-evil-is-eval/) + +## Related Rules + +* [no-implied-eval](no-implied-eval.md) diff --git a/eslint/docs/rules/no-ex-assign.md b/eslint/docs/rules/no-ex-assign.md new file mode 100644 index 0000000..11745da --- /dev/null +++ b/eslint/docs/rules/no-ex-assign.md @@ -0,0 +1,36 @@ +# disallow reassigning exceptions in `catch` clauses (no-ex-assign) + +If a `catch` clause in a `try` statement accidentally (or purposely) assigns another value to the exception parameter, it impossible to refer to the error from that point on. +Since there is no `arguments` object to offer alternative access to this data, assignment of the parameter is absolutely destructive. + +## Rule Details + +This rule disallows reassigning exceptions in `catch` clauses. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-ex-assign: "error"*/ + +try { + // code +} catch (e) { + e = 10; +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-ex-assign: "error"*/ + +try { + // code +} catch (e) { + var foo = 10; +} +``` + +## Further Reading + +* [The "catch" with try...catch](https://bocoup.com/blog/the-catch-with-try-catch) by Ben Alman explains how the exception identifier can leak into the outer scope in IE 6-8 diff --git a/eslint/docs/rules/no-extend-native.md b/eslint/docs/rules/no-extend-native.md new file mode 100644 index 0000000..c3ccaeb --- /dev/null +++ b/eslint/docs/rules/no-extend-native.md @@ -0,0 +1,75 @@ +# Disallow Extending of Native Objects (no-extend-native) + +In JavaScript, you can extend any object, including builtin or "native" objects. Sometimes people change the behavior of these native objects in ways that break the assumptions made about them in other parts of the code. + +For example here we are overriding a builtin method that will then affect all Objects, even other builtins. + +```js +// seems harmless +Object.prototype.extra = 55; + +// loop through some userIds +var users = { + "123": "Stan", + "456": "David" +}; + +// not what you'd expect +for (var id in users) { + console.log(id); // "123", "456", "extra" +} +``` + +A common suggestion to avoid this problem would be to wrap the inside of the `for` loop with `users.hasOwnProperty(id)`. However, if this rule is strictly enforced throughout your codebase you won't need to take that step. + +## Rule Details + +Disallows directly modifying the prototype of builtin objects. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-extend-native: "error"*/ + +Object.prototype.a = "a"; +Object.defineProperty(Array.prototype, "times", { value: 999 }); +``` + +## Options + +This rule accepts an `exceptions` option, which can be used to specify a list of builtins for which extensions will be allowed. + +### exceptions + +Examples of **correct** code for the sample `{ "exceptions": ["Object"] }` option: + +```js +/*eslint no-extend-native: ["error", { "exceptions": ["Object"] }]*/ + +Object.prototype.a = "a"; +``` + +## Known Limitations + +This rule *does not* report any of the following less obvious approaches to modify the prototype of builtin objects: + +```js +var x = Object; +x.prototype.thing = a; + +eval("Array.prototype.forEach = 'muhahaha'"); + +with(Array) { + prototype.thing = 'thing'; +}; + +window.Function.prototype.bind = 'tight'; +``` + +## When Not To Use It + +You may want to disable this rule when working with polyfills that try to patch older versions of JavaScript with the latest spec, such as those that might `Function.prototype.bind` or `Array.prototype.forEach` in a future-friendly way. + +## Related Rules + +* [no-global-assign](no-global-assign.md) diff --git a/eslint/docs/rules/no-extra-bind.md b/eslint/docs/rules/no-extra-bind.md new file mode 100644 index 0000000..6d6bf13 --- /dev/null +++ b/eslint/docs/rules/no-extra-bind.md @@ -0,0 +1,86 @@ +# Disallow unnecessary function binding (no-extra-bind) + +The `bind()` method is used to create functions with specific `this` values and, optionally, binds arguments to specific values. When used to specify the value of `this`, it's important that the function actually uses `this` in its function body. For example: + +```js +var boundGetName = (function getName() { + return this.name; +}).bind({ name: "ESLint" }); + +console.log(boundGetName()); // "ESLint" +``` + +This code is an example of a good use of `bind()` for setting the value of `this`. + +Sometimes during the course of code maintenance, the `this` value is removed from the function body. In that case, you can end up with a call to `bind()` that doesn't accomplish anything: + +```js +// useless bind +var boundGetName = (function getName() { + return "ESLint"; +}).bind({ name: "ESLint" }); + +console.log(boundGetName()); // "ESLint" +``` + +In this code, the reference to `this` has been removed but `bind()` is still used. In this case, the `bind()` is unnecessary overhead (and a performance hit) and can be safely removed. + +## Rule Details + +This rule is aimed at avoiding the unnecessary use of `bind()` and as such will warn whenever an immediately-invoked function expression (IIFE) is using `bind()` and doesn't have an appropriate `this` value. This rule won't flag usage of `bind()` that includes function argument binding. + +**Note:** Arrow functions can never have their `this` value set using `bind()`. This rule flags all uses of `bind()` with arrow functions as a problem + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-extra-bind: "error"*/ +/*eslint-env es6*/ + +var x = function () { + foo(); +}.bind(bar); + +var x = (() => { + foo(); +}).bind(bar); + +var x = (() => { + this.foo(); +}).bind(bar); + +var x = function () { + (function () { + this.foo(); + }()); +}.bind(bar); + +var x = function () { + function foo() { + this.bar(); + } +}.bind(baz); +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-extra-bind: "error"*/ + +var x = function () { + this.foo(); +}.bind(bar); + +var x = function (a) { + return a + 1; +}.bind(foo, bar); +``` + +## When Not To Use It + +If you are not concerned about unnecessary calls to `bind()`, you can safely disable this rule. + +## Further Reading + +* [Function.prototype.bind](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) +* [Understanding JavaScript's Function.prototype.bind](https://www.smashingmagazine.com/2014/01/understanding-javascript-function-prototype-bind/) diff --git a/eslint/docs/rules/no-extra-boolean-cast.md b/eslint/docs/rules/no-extra-boolean-cast.md new file mode 100644 index 0000000..5c7d200 --- /dev/null +++ b/eslint/docs/rules/no-extra-boolean-cast.md @@ -0,0 +1,124 @@ +# disallow unnecessary boolean casts (no-extra-boolean-cast) + +In contexts such as an `if` statement's test where the result of the expression will already be coerced to a Boolean, casting to a Boolean via double negation (`!!`) or a `Boolean` call is unnecessary. For example, these `if` statements are equivalent: + +```js +if (!!foo) { + // ... +} + +if (Boolean(foo)) { + // ... +} + +if (foo) { + // ... +} +``` + +## Rule Details + +This rule disallows unnecessary boolean casts. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-extra-boolean-cast: "error"*/ + +var foo = !!!bar; + +var foo = !!bar ? baz : bat; + +var foo = Boolean(!!bar); + +var foo = new Boolean(!!bar); + +if (!!foo) { + // ... +} + +if (Boolean(foo)) { + // ... +} + +while (!!foo) { + // ... +} + +do { + // ... +} while (Boolean(foo)); + +for (; !!foo; ) { + // ... +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-extra-boolean-cast: "error"*/ + +var foo = !!bar; +var foo = Boolean(bar); + +function foo() { + return !!bar; +} + +var foo = bar ? !!baz : !!bat; +``` + +## Options + +This rule has an object option: + +* `"enforceForLogicalOperands"` when set to `true`, in addition to checking default contexts, checks whether the extra boolean cast is contained within a logical expression. Default is `false`, meaning that this rule by default does not warn about extra booleans cast inside logical expression. + +### enforceForLogicalOperands + +Examples of **incorrect** code for this rule with `"enforceForLogicalOperands"` option set to `true`: + +```js +/*eslint no-extra-boolean-cast: ["error", {"enforceForLogicalOperands": true}]*/ + +if (!!foo || bar) { + //... +} + +while (!!foo && bar) { + //... +} + +if ((!!foo || bar) && baz) { + //... +} + +foo && Boolean(bar) ? baz : bat + +var foo = new Boolean(!!bar || baz) +``` + +Examples of **correct** code for this rule with `"enforceForLogicalOperands"` option set to `true`: + +```js +/*eslint no-extra-boolean-cast: ["error", {"enforceForLogicalOperands": true}]*/ + +if (foo || bar) { + //... +} + +while (foo && bar) { + //... +} + +if ((foo || bar) && baz) { + //... +} + +foo && bar ? baz : bat + +var foo = new Boolean(bar || baz) + +var foo = !!bar || baz; +``` diff --git a/eslint/docs/rules/no-extra-label.md b/eslint/docs/rules/no-extra-label.md new file mode 100644 index 0000000..5c7c114 --- /dev/null +++ b/eslint/docs/rules/no-extra-label.md @@ -0,0 +1,82 @@ +# Disallow Unnecessary Labels (no-extra-label) + +If a loop contains no nested loops or switches, labeling the loop is unnecessary. + +```js +A: while (a) { + break A; +} +``` + +You can achieve the same result by removing the label and using `break` or `continue` without a label. +Probably those labels would confuse developers because they expect labels to jump to further. + +## Rule Details + +This rule is aimed at eliminating unnecessary labels. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-extra-label: "error"*/ + +A: while (a) { + break A; +} + +B: for (let i = 0; i < 10; ++i) { + break B; +} + +C: switch (a) { + case 0: + break C; +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-extra-label: "error"*/ + +while (a) { + break; +} + +for (let i = 0; i < 10; ++i) { + break; +} + +switch (a) { + case 0: + break; +} + +A: { + break A; +} + +B: while (a) { + while (b) { + break B; + } +} + +C: switch (a) { + case 0: + while (b) { + break C; + } + break; +} +``` + +## When Not To Use It + +If you don't want to be notified about usage of labels, then it's safe to disable this rule. + +## Related Rules + +* [no-labels](./no-labels.md) +* [no-label-var](./no-label-var.md) +* [no-unused-labels](./no-unused-labels.md) diff --git a/eslint/docs/rules/no-extra-parens.md b/eslint/docs/rules/no-extra-parens.md new file mode 100644 index 0000000..4cadaa7 --- /dev/null +++ b/eslint/docs/rules/no-extra-parens.md @@ -0,0 +1,267 @@ +# disallow unnecessary parentheses (no-extra-parens) + +This rule restricts the use of parentheses to only where they are necessary. + +## Rule Details + +This rule always ignores extra parentheses around the following: + +* RegExp literals such as `(/abc/).test(var)` to avoid conflicts with the [wrap-regex](wrap-regex.md) rule +* immediately-invoked function expressions (also known as IIFEs) such as `var x = (function () {})();` and `((function foo() {return 1;})())` to avoid conflicts with the [wrap-iife](wrap-iife.md) rule +* arrow function arguments to avoid conflicts with the [arrow-parens](arrow-parens.md) rule + +## Options + +This rule has a string option: + +* `"all"` (default) disallows unnecessary parentheses around *any* expression +* `"functions"` disallows unnecessary parentheses *only* around function expressions + +This rule has an object option for exceptions to the `"all"` option: + +* `"conditionalAssign": false` allows extra parentheses around assignments in conditional test expressions +* `"returnAssign": false` allows extra parentheses around assignments in `return` statements +* `"nestedBinaryExpressions": false` allows extra parentheses in nested binary expressions +* `"ignoreJSX": "none|all|multi-line|single-line"` allows extra parentheses around no/all/multi-line/single-line JSX components. Defaults to `none`. +* `"enforceForArrowConditionals": false` allows extra parentheses around ternary expressions which are the body of an arrow function +* `"enforceForSequenceExpressions": false` allows extra parentheses around sequence expressions +* `"enforceForNewInMemberExpressions": false` allows extra parentheses around `new` expressions in member expressions + +### all + +Examples of **incorrect** code for this rule with the default `"all"` option: + +```js +/* eslint no-extra-parens: "error" */ + +a = (b * c); + +(a * b) + c; + +for (a in (b, c)); + +for (a in (b)); + +for (a of (b)); + +typeof (a); + +(function(){} ? a() : b()); +``` + +Examples of **correct** code for this rule with the default `"all"` option: + +```js +/* eslint no-extra-parens: "error" */ + +(0).toString(); + +(Object.prototype.toString.call()); + +({}.toString.call()); + +(function(){}) ? a() : b(); + +(/^a$/).test(x); + +for (a of (b, c)); + +for (a of b); + +for (a in b, c); + +for (a in b); +``` + +### conditionalAssign + +Examples of **correct** code for this rule with the `"all"` and `{ "conditionalAssign": false }` options: + +```js +/* eslint no-extra-parens: ["error", "all", { "conditionalAssign": false }] */ + +while ((foo = bar())) {} + +if ((foo = bar())) {} + +do; while ((foo = bar())) + +for (;(a = b);); +``` + +### returnAssign + +Examples of **correct** code for this rule with the `"all"` and `{ "returnAssign": false }` options: + +```js +/* eslint no-extra-parens: ["error", "all", { "returnAssign": false }] */ + +function a(b) { + return (b = 1); +} + +function a(b) { + return b ? (c = d) : (c = e); +} + +b => (b = 1); + +b => b ? (c = d) : (c = e); +``` + +### nestedBinaryExpressions + +Examples of **correct** code for this rule with the `"all"` and `{ "nestedBinaryExpressions": false }` options: + +```js +/* eslint no-extra-parens: ["error", "all", { "nestedBinaryExpressions": false }] */ + +x = a || (b && c); +x = a + (b * c); +x = (a * b) / c; +``` + +### ignoreJSX + +Examples of **correct** code for this rule with the `all` and `{ "ignoreJSX": "all" }` options: + +```js +/* eslint no-extra-parens: ["error", "all", { ignoreJSX: "all" }] */ +const Component = (
) +const Component = ( +
+) +``` + +Examples of **incorrect** code for this rule with the `all` and `{ "ignoreJSX": "multi-line" }` options: + +```js +/* eslint no-extra-parens: ["error", "all", { ignoreJSX: "multi-line" }] */ +const Component = (
) +const Component = (

) +``` + +Examples of **correct** code for this rule with the `all` and `{ "ignoreJSX": "multi-line" }` options: + +```js +/* eslint no-extra-parens: ["error", "all", { ignoreJSX: "multi-line" }] */ +const Component = ( +
+

+

+) +const Component = ( +
+) +``` + +Examples of **incorrect** code for this rule with the `all` and `{ "ignoreJSX": "single-line" }` options: + +```js +/* eslint no-extra-parens: ["error", "all", { ignoreJSX: "single-line" }] */ +const Component = ( +
+

+

+) +const Component = ( +
+) +``` + +Examples of **correct** code for this rule with the `all` and `{ "ignoreJSX": "single-line" }` options: + +```js +/* eslint no-extra-parens: ["error", "all", { ignoreJSX: "single-line" }] */ +const Component = (
) +const Component = (

) +``` + +### enforceForArrowConditionals + +Examples of **correct** code for this rule with the `"all"` and `{ "enforceForArrowConditionals": false }` options: + +```js +/* eslint no-extra-parens: ["error", "all", { "enforceForArrowConditionals": false }] */ + +const b = a => 1 ? 2 : 3; +const d = c => (1 ? 2 : 3); +``` + +### enforceForSequenceExpressions + +Examples of **correct** code for this rule with the `"all"` and `{ "enforceForSequenceExpressions": false }` options: + +```js +/* eslint no-extra-parens: ["error", "all", { "enforceForSequenceExpressions": false }] */ + +(a, b); + +if ((val = foo(), val < 10)) {} + +while ((val = foo(), val < 10)); +``` + +### enforceForNewInMemberExpressions + +Examples of **correct** code for this rule with the `"all"` and `{ "enforceForNewInMemberExpressions": false }` options: + +```js +/* eslint no-extra-parens: ["error", "all", { "enforceForNewInMemberExpressions": false }] */ + +const foo = (new Bar()).baz; + +const quux = (new Bar())[baz]; + +(new Bar()).doSomething(); +``` + +### functions + +Examples of **incorrect** code for this rule with the `"functions"` option: + +```js +/* eslint no-extra-parens: ["error", "functions"] */ + +((function foo() {}))(); + +var y = (function () {return 1;}); +``` + +Examples of **correct** code for this rule with the `"functions"` option: + +```js +/* eslint no-extra-parens: ["error", "functions"] */ + +(0).toString(); + +(Object.prototype.toString.call()); + +({}.toString.call()); + +(function(){} ? a() : b()); + +(/^a$/).test(x); + +a = (b * c); + +(a * b) + c; + +typeof (a); +``` + +## Further Reading + +* [MDN: Operator Precedence](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence) + +## Related Rules + +* [arrow-parens](arrow-parens.md) +* [no-cond-assign](no-cond-assign.md) +* [no-return-assign](no-return-assign.md) diff --git a/eslint/docs/rules/no-extra-semi.md b/eslint/docs/rules/no-extra-semi.md new file mode 100644 index 0000000..f4dd8e0 --- /dev/null +++ b/eslint/docs/rules/no-extra-semi.md @@ -0,0 +1,42 @@ +# disallow unnecessary semicolons (no-extra-semi) + +Typing mistakes and misunderstandings about where semicolons are required can lead to semicolons that are unnecessary. While not technically an error, extra semicolons can cause confusion when reading code. + +## Rule Details + +This rule disallows unnecessary semicolons. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-extra-semi: "error"*/ + +var x = 5;; + +function foo() { + // code +}; + +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-extra-semi: "error"*/ + +var x = 5; + +var foo = function() { + // code +}; + +``` + +## When Not To Use It + +If you intentionally use extra semicolons then you can disable this rule. + +## Related Rules + +* [semi](semi.md) +* [semi-spacing](semi-spacing.md) diff --git a/eslint/docs/rules/no-extra-strict.md b/eslint/docs/rules/no-extra-strict.md new file mode 100644 index 0000000..a350f77 --- /dev/null +++ b/eslint/docs/rules/no-extra-strict.md @@ -0,0 +1,50 @@ +# no-extra-strict: disallow strict mode directives when already in strict mode + +(removed) This rule was **removed** in ESLint v1.0 and **replaced** by the [strict](strict.md) rule. The `"global"` or `"function"` options in the new rule are similar to the removed rule. + +The `"use strict";` directive applies to the scope in which it appears and all inner scopes contained within that scope. Therefore, using the `"use strict";` directive in one of these inner scopes is unnecessary. + +```js +"use strict"; + +(function () { + "use strict"; + var foo = true; +}()); +``` + +## Rule Details + +This rule is aimed at preventing unnecessary `"use strict";` directives. As such, it will warn when it encounters a `"use strict";` directive when already in strict mode. + +Example of **incorrect** code for this rule: + +```js +"use strict"; + +(function () { + "use strict"; + var foo = true; +}()); +``` + +Examples of **correct** code for this rule: + +```js +"use strict"; + +(function () { + var foo = true; +}()); +``` + +```js +(function () { + "use strict"; + var foo = true; +}()); +``` + +## Further Reading + +* [The ECMAScript 5 Annotated Specification - Strict Mode](https://es5.github.io/#C) diff --git a/eslint/docs/rules/no-fallthrough.md b/eslint/docs/rules/no-fallthrough.md new file mode 100644 index 0000000..15ca77f --- /dev/null +++ b/eslint/docs/rules/no-fallthrough.md @@ -0,0 +1,169 @@ +# Disallow Case Statement Fallthrough (no-fallthrough) + +The `switch` statement in JavaScript is one of the more error-prone constructs of the language thanks in part to the ability to "fall through" from one `case` to the next. For example: + +```js +switch(foo) { + case 1: + doSomething(); + + case 2: + doSomethingElse(); +} +``` + +In this example, if `foo` is `1`, then execution will flow through both cases, as the first falls through to the second. You can prevent this by using `break`, as in this example: + +```js +switch(foo) { + case 1: + doSomething(); + break; + + case 2: + doSomethingElse(); +} +``` + +That works fine when you don't want a fallthrough, but what if the fallthrough is intentional, there is no way to indicate that in the language. It's considered a best practice to always indicate when a fallthrough is intentional using a comment which matches the `/falls?\s?through/i` regular expression: + +```js +switch(foo) { + case 1: + doSomething(); + // falls through + + case 2: + doSomethingElse(); +} + +switch(foo) { + case 1: + doSomething(); + // fall through + + case 2: + doSomethingElse(); +} + +switch(foo) { + case 1: + doSomething(); + // fallsthrough + + case 2: + doSomethingElse(); +} +``` + +In this example, there is no confusion as to the expected behavior. It is clear that the first case is meant to fall through to the second case. + +## Rule Details + +This rule is aimed at eliminating unintentional fallthrough of one case to the other. As such, it flags any fallthrough scenarios that are not marked by a comment. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-fallthrough: "error"*/ + +switch(foo) { + case 1: + doSomething(); + + case 2: + doSomething(); +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-fallthrough: "error"*/ + +switch(foo) { + case 1: + doSomething(); + break; + + case 2: + doSomething(); +} + +function bar(foo) { + switch(foo) { + case 1: + doSomething(); + return; + + case 2: + doSomething(); + } +} + +switch(foo) { + case 1: + doSomething(); + throw new Error("Boo!"); + + case 2: + doSomething(); +} + +switch(foo) { + case 1: + case 2: + doSomething(); +} + +switch(foo) { + case 1: + doSomething(); + // falls through + + case 2: + doSomething(); +} +``` + +Note that the last `case` statement in these examples does not cause a warning because there is nothing to fall through into. + +## Options + +This rule accepts a single options argument: + +* Set the `commentPattern` option to a regular expression string to change the test for intentional fallthrough comment + +### commentPattern + +Examples of **correct** code for the `{ "commentPattern": "break[\\s\\w]*omitted" }` option: + +```js +/*eslint no-fallthrough: ["error", { "commentPattern": "break[\\s\\w]*omitted" }]*/ + +switch(foo) { + case 1: + doSomething(); + // break omitted + + case 2: + doSomething(); +} + +switch(foo) { + case 1: + doSomething(); + // caution: break is omitted intentionally + + default: + doSomething(); +} +``` + +## When Not To Use It + +If you don't want to enforce that each `case` statement should end with a `throw`, `return`, `break`, or comment, then you can safely turn this rule off. + +## Related Rules + +* [default-case](default-case.md) diff --git a/eslint/docs/rules/no-floating-decimal.md b/eslint/docs/rules/no-floating-decimal.md new file mode 100644 index 0000000..880bc07 --- /dev/null +++ b/eslint/docs/rules/no-floating-decimal.md @@ -0,0 +1,43 @@ +# Disallow Floating Decimals (no-floating-decimal) + +Float values in JavaScript contain a decimal point, and there is no requirement that the decimal point be preceded or followed by a number. For example, the following are all valid JavaScript numbers: + +```js +var num = .5; +var num = 2.; +var num = -.7; +``` + +Although not a syntax error, this format for numbers can make it difficult to distinguish between true decimal numbers and the dot operator. For this reason, some recommend that you should always include a number before and after a decimal point to make it clear the intent is to create a decimal number. + +## Rule Details + +This rule is aimed at eliminating floating decimal points and will warn whenever a numeric value has a decimal point but is missing a number either before or after it. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-floating-decimal: "error"*/ + +var num = .5; +var num = 2.; +var num = -.7; +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-floating-decimal: "error"*/ + +var num = 0.5; +var num = 2.0; +var num = -0.7; +``` + +## When Not To Use It + +If you aren't concerned about misinterpreting floating decimal point values, then you can safely turn this rule off. + +## Compatibility + +* **JSHint**: W008, W047 diff --git a/eslint/docs/rules/no-func-assign.md b/eslint/docs/rules/no-func-assign.md new file mode 100644 index 0000000..b4c582e --- /dev/null +++ b/eslint/docs/rules/no-func-assign.md @@ -0,0 +1,51 @@ +# disallow reassigning `function` declarations (no-func-assign) + +JavaScript functions can be written as a FunctionDeclaration `function foo() { ... }` or as a FunctionExpression `var foo = function() { ... };`. While a JavaScript interpreter might tolerate it, overwriting/reassigning a function written as a FunctionDeclaration is often indicative of a mistake or issue. + +```js +function foo() {} +foo = bar; +``` + +## Rule Details + +This rule disallows reassigning `function` declarations. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-func-assign: "error"*/ + +function foo() {} +foo = bar; + +function foo() { + foo = bar; +} +``` + +Examples of **incorrect** code for this rule, unlike the corresponding rule in JSHint: + +```js +/*eslint no-func-assign: "error"*/ + +foo = bar; +function foo() {} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-func-assign: "error"*/ + +var foo = function () {} +foo = bar; + +function foo(foo) { // `foo` is shadowed. + foo = bar; +} + +function foo() { + var foo = bar; // `foo` is shadowed. +} +``` diff --git a/eslint/docs/rules/no-global-assign.md b/eslint/docs/rules/no-global-assign.md new file mode 100644 index 0000000..b3718d1 --- /dev/null +++ b/eslint/docs/rules/no-global-assign.md @@ -0,0 +1,89 @@ +# Disallow assignment to native objects or read-only global variables (no-global-assign) + +JavaScript environments contain a number of built-in global variables, such as `window` in browsers and `process` in Node.js. In almost all cases, you don't want to assign a value to these global variables as doing so could result in losing access to important functionality. For example, you probably don't want to do this in browser code: + +```js +window = {}; +``` + +While examples such as `window` are obvious, there are often hundreds of built-in global objects provided by JavaScript environments. It can be hard to know if you're assigning to a global variable or not. + +## Rule Details + +This rule disallows modifications to read-only global variables. + +ESLint has the capability to configure global variables as read-only. + +* [Specifying Environments](../user-guide/configuring#specifying-environments) +* [Specifying Globals](../user-guide/configuring#specifying-globals) + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-global-assign: "error"*/ + +Object = null +undefined = 1 +``` + +```js +/*eslint no-global-assign: "error"*/ +/*eslint-env browser*/ + +window = {} +length = 1 +top = 1 +``` + +```js +/*eslint no-global-assign: "error"*/ +/*global a:readonly*/ + +a = 1 +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-global-assign: "error"*/ + +a = 1 +var b = 1 +b = 2 +``` + +```js +/*eslint no-global-assign: "error"*/ +/*eslint-env browser*/ + +onload = function() {} +``` + +```js +/*eslint no-global-assign: "error"*/ +/*global a:writable*/ + +a = 1 +``` + +## Options + +This rule accepts an `exceptions` option, which can be used to specify a list of builtins for which reassignments will be allowed: + +```json +{ + "rules": { + "no-global-assign": ["error", {"exceptions": ["Object"]}] + } +} +``` + +## When Not To Use It + +If you are trying to override one of the native objects. + +## Related Rules + +* [no-extend-native](no-extend-native.md) +* [no-redeclare](no-redeclare.md) +* [no-shadow](no-shadow.md) diff --git a/eslint/docs/rules/no-implicit-coercion.md b/eslint/docs/rules/no-implicit-coercion.md new file mode 100644 index 0000000..f78e40c --- /dev/null +++ b/eslint/docs/rules/no-implicit-coercion.md @@ -0,0 +1,124 @@ +# Disallow the type conversion with shorter notations. (no-implicit-coercion) + +In JavaScript, there are a lot of different ways to convert value types. +Some of them might be hard to read and understand. + +Such as: + +```js +var b = !!foo; +var b = ~foo.indexOf("."); +var n = +foo; +var n = 1 * foo; +var s = "" + foo; +foo += ``; +``` + +Those can be replaced with the following code: + +```js +var b = Boolean(foo); +var b = foo.indexOf(".") !== -1; +var n = Number(foo); +var n = Number(foo); +var s = String(foo); +foo = String(foo); +``` + +## Rule Details + +This rule is aimed to flag shorter notations for the type conversion, then suggest a more self-explanatory notation. + +## Options + +This rule has three main options and one override option to allow some coercions as required. + +* `"boolean"` (`true` by default) - When this is `true`, this rule warns shorter type conversions for `boolean` type. +* `"number"` (`true` by default) - When this is `true`, this rule warns shorter type conversions for `number` type. +* `"string"` (`true` by default) - When this is `true`, this rule warns shorter type conversions for `string` type. +* `"allow"` (`empty` by default) - Each entry in this array can be one of `~`, `!!`, `+` or `*` that are to be allowed. + +Note that operator `+` in `allow` list would allow `+foo` (number coercion) as well as `"" + foo` (string coercion). + +### boolean + +Examples of **incorrect** code for the default `{ "boolean": true }` option: + +```js +/*eslint no-implicit-coercion: "error"*/ + +var b = !!foo; +var b = ~foo.indexOf("."); +// bitwise not is incorrect only with `indexOf`/`lastIndexOf` method calling. +``` + +Examples of **correct** code for the default `{ "boolean": true }` option: + +```js +/*eslint no-implicit-coercion: "error"*/ + +var b = Boolean(foo); +var b = foo.indexOf(".") !== -1; + +var n = ~foo; // This is a just bitwise not. +``` + +### number + +Examples of **incorrect** code for the default `{ "number": true }` option: + +```js +/*eslint no-implicit-coercion: "error"*/ + +var n = +foo; +var n = 1 * foo; +``` + +Examples of **correct** code for the default `{ "number": true }` option: + +```js +/*eslint no-implicit-coercion: "error"*/ + +var n = Number(foo); +var n = parseFloat(foo); +var n = parseInt(foo, 10); +``` + +### string + +Examples of **incorrect** code for the default `{ "string": true }` option: + +```js +/*eslint no-implicit-coercion: "error"*/ + +var s = "" + foo; +var s = `` + foo; +foo += ""; +foo += ``; +``` + +Examples of **correct** code for the default `{ "string": true }` option: + +```js +/*eslint no-implicit-coercion: "error"*/ + +var s = String(foo); +foo = String(foo); +``` + +### allow + +Using `allow` list, we can override and allow specific operators. + +Examples of **correct** code for the sample `{ "allow": ["!!", "~"] }` option: + +```js +/*eslint no-implicit-coercion: [2, { "allow": ["!!", "~"] } ]*/ + +var b = !!foo; +var b = ~foo.indexOf("."); +``` + +## When Not To Use It + +If you don't want to be notified about shorter notations for the type conversion, you can safely disable this rule. diff --git a/eslint/docs/rules/no-implicit-globals.md b/eslint/docs/rules/no-implicit-globals.md new file mode 100644 index 0000000..04a3cd3 --- /dev/null +++ b/eslint/docs/rules/no-implicit-globals.md @@ -0,0 +1,226 @@ +# Disallow declarations in the global scope (no-implicit-globals) + +It is the best practice to avoid 'polluting' the global scope with variables that are intended to be local to the script. + +Global variables created from a script can produce name collisions with global variables created from another script, which will +usually lead to runtime errors or unexpected behavior. + +This rule disallows the following: + +* Declarations that create one or more variables in the global scope. +* Global variable leaks. +* Redeclarations of read-only global variables and assignments to read-only global variables. + +There is an explicit way to create a global variable when needed, by assigning to a property of the global object. + +This rule is mostly useful for browser scripts. Top-level declarations in ES modules and CommonJS modules create module-scoped +variables. ES modules also have implicit `strict` mode, which prevents global variable leaks. + +By default, this rule does not check `const`, `let` and `class` declarations. + +This rule has an object option with one option: + +* Set `"lexicalBindings"` to `true` if you want this rule to check `const`, `let` and `class` declarations as well. + +## Rule Details + +### `var` and `function` declarations + +When working with browser scripts, developers often forget that variable and function declarations at the top-level scope become global variables on the `window` object. As opposed to modules which have their own scope. Globals should be explicitly assigned to `window` or `self` if that is the intent. Otherwise variables intended to be local to the script should be wrapped in an IIFE. + +This rule disallows `var` and `function` declarations at the top-level script scope. This does not apply to ES and CommonJS modules since they have a module scope. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-implicit-globals: "error"*/ + +var foo = 1; + +function bar() {} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-implicit-globals: "error"*/ + +// explicitly set on window +window.foo = 1; +window.bar = function() {}; + +// intended to be scope to this file +(function() { + var foo = 1; + + function bar() {} +})(); +``` + +Examples of **correct** code for this rule with `"parserOptions": { "sourceType": "module" }` in the ESLint configuration: + +```js +/*eslint no-implicit-globals: "error"*/ + +// foo and bar are local to module +var foo = 1; +function bar() {} +``` + +### Global variable leaks + +When the code is not in `strict` mode, an assignment to an undeclared variable creates +a new global variable. This will happen even is the code is in a function. + +This does not apply to ES modules since the module code is implicitly in `strict` mode. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-implicit-globals: "error"*/ + +foo = 1; + +Bar.prototype.baz = function () { + a = 1; // Intended to be this.a = 1; +}; +``` + +### Read-only global variables + +This rule also disallows redeclarations of read-only global variables and assignments to read-only global variables. + +A read-only global variable can be a built-in ES global (e.g. `Array`), an environment specific global +(e.g. `window` in the browser environment), or a global variable defined as `readonly` in the configuration file +or in a `/*global */` comment. + +* [Specifying Environments](../user-guide/configuring#specifying-environments) +* [Specifying Globals](../user-guide/configuring#specifying-globals) + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-implicit-globals: "error"*/ + +/*global foo:readonly*/ + +foo = 1; + +Array = []; +var Object; +``` + +### `const`, `let` and `class` declarations + +Lexical declarations `const` and `let`, as well as `class` declarations, create variables that are block-scoped. + +However, when declared in the top-level of a browser script these variables are not 'script-scoped'. +They are actually created in the global scope and could produce name collisions with +`var`, `const` and `let` variables and `function` and `class` declarations from other scripts. +This does not apply to ES and CommonJS modules. + +If the variable is intended to be local to the script, wrap the code with a block or with an immediately-invoked function expression (IIFE). + +Examples of **correct** code for this rule with `"lexicalBindings"` option set to `false` (default): + +```js +/*eslint no-implicit-globals: ["error", {"lexicalBindings": false}]*/ + +const foo = 1; + +let baz; + +class Bar {} +``` + +Examples of **incorrect** code for this rule with `"lexicalBindings"` option set to `true`: + +```js +/*eslint no-implicit-globals: ["error", {"lexicalBindings": true}]*/ + +const foo = 1; + +let baz; + +class Bar {} +``` + +Examples of **correct** code for this rule with `"lexicalBindings"` option set to `true`: + +```js +/*eslint no-implicit-globals: ["error", {"lexicalBindings": true}]*/ + +{ + const foo = 1; + let baz; + class Bar {} +} + +(function() { + const foo = 1; + let baz; + class Bar {} +}()); +``` + +If you intend to create a global `const` or `let` variable or a global `class` declaration, to be used from other scripts, +be aware that there are certain differences when compared to the traditional methods, which are `var` declarations and assigning to a property of the global `window` object: + +* Lexically declared variables cannot be conditionally created. A script cannot check for the existence of +a variable and then create a new one. `var` variables are also always created, but redeclarations do not +cause runtime exceptions. +* Lexically declared variables do not create properties on the global object, which is what a consuming script might expect. +* Lexically declared variables are shadowing properties of the global object, which might produce errors if a +consuming script is using both the variable and the property. +* Lexically declared variables can produce a permanent Temporal Dead Zone (TDZ) if the initialization throws an exception. +Even the `typeof` check is not safe from TDZ reference exceptions. + +Examples of **incorrect** code for this rule with `"lexicalBindings"` option set to `true`: + +```js +/*eslint no-implicit-globals: ["error", {"lexicalBindings": true}]*/ + +const MyGlobalFunction = (function() { + const a = 1; + let b = 2; + return function() { + return a + b; + } +}()); +``` + +Examples of **correct** code for this rule with `"lexicalBindings"` option set to `true`: + +```js +/*eslint no-implicit-globals: ["error", {"lexicalBindings": true}]*/ + +window.MyGlobalFunction = (function() { + const a = 1; + let b = 2; + return function() { + return a + b; + } +}()); +``` + +## When Not To Use It + +In the case of a browser script, if you want to be able to explicitly declare variables and functions in the global scope, +and your code is in strict mode or you don't want this rule to warn you about undeclared variables, +and you also don't want this rule to warn you about read-only globals, you can disable this rule. + +In the case of a CommonJS module, if your code is in strict mode or you don't want this rule to warn you about undeclared variables, +and you also don't want this rule to warn you about the read-only globals, you can disable this rule. + +In the case of an ES module, if you don't want this rule to warn you about the read-only globals you can disable this rule. + +## Further Reading + +* [Immediately-Invoked Function Expression (IIFE)](http://benalman.com/news/2010/11/immediately-invoked-function-expression/) +* [ReferenceError: assignment to undeclared variable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Undeclared_var) +* [Temporal Dead Zone](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let#Temporal_dead_zone) + +## Related Rules + +* [no-undef](no-undef.md) +* [no-global-assign](no-global-assign.md) diff --git a/eslint/docs/rules/no-implied-eval.md b/eslint/docs/rules/no-implied-eval.md new file mode 100644 index 0000000..d300ff6 --- /dev/null +++ b/eslint/docs/rules/no-implied-eval.md @@ -0,0 +1,63 @@ +# Disallow Implied eval() (no-implied-eval) + +It's considered a good practice to avoid using `eval()` in JavaScript. There are security and performance implications involved with doing so, which is why many linters (including ESLint) recommend disallowing `eval()`. However, there are some other ways to pass a string and have it interpreted as JavaScript code that have similar concerns. + +The first is using `setTimeout()`, `setInterval()` or `execScript()` (Internet Explorer only), all of which can accept a string of JavaScript code as their first argument. For example: + +```js +setTimeout("alert('Hi!');", 100); +``` + +This is considered an implied `eval()` because a string of JavaScript code is + passed in to be interpreted. The same can be done with `setInterval()` and `execScript()`. Both interpret the JavaScript code in the global scope. For both `setTimeout()` and `setInterval()`, the first argument can also be a function, and that is considered safer and is more performant: + +```js +setTimeout(function() { + alert("Hi!"); +}, 100); +``` + +The best practice is to always use a function for the first argument of `setTimeout()` and `setInterval()` (and avoid `execScript()`). + + +## Rule Details + +This rule aims to eliminate implied `eval()` through the use of `setTimeout()`, `setInterval()` or `execScript()`. As such, it will warn when either function is used with a string as the first argument. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-implied-eval: "error"*/ + +setTimeout("alert('Hi!');", 100); + +setInterval("alert('Hi!');", 100); + +execScript("alert('Hi!')"); + +window.setTimeout("count = 5", 10); + +window.setInterval("foo = bar", 10); +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-implied-eval: "error"*/ + +setTimeout(function() { + alert("Hi!"); +}, 100); + +setInterval(function() { + alert("Hi!"); +}, 100); +``` + +## When Not To Use It + +If you want to allow `setTimeout()` and `setInterval()` with string arguments, then you can safely disable this rule. + +## Related Rules + +* [no-eval](no-eval.md) diff --git a/eslint/docs/rules/no-import-assign.md b/eslint/docs/rules/no-import-assign.md new file mode 100644 index 0000000..78b228d --- /dev/null +++ b/eslint/docs/rules/no-import-assign.md @@ -0,0 +1,44 @@ +# disallow assigning to imported bindings (no-import-assign) + +The updates of imported bindings by ES Modules cause runtime errors. + +## Rule Details + +This rule warns the assignments, increments, and decrements of imported bindings. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-import-assign: "error"*/ + +import mod, { named } from "./mod.mjs" +import * as mod_ns from "./mod.mjs" + +mod = 1 // ERROR: 'mod' is readonly. +named = 2 // ERROR: 'named' is readonly. +mod_ns.named = 3 // ERROR: the members of 'mod_ns' is readonly. +mod_ns = {} // ERROR: 'mod_ns' is readonly. +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-import-assign: "error"*/ + +import mod, { named } from "./mod.mjs" +import * as mod_ns from "./mod.mjs" + +mod.prop = 1 +named.prop = 2 +mod_ns.named.prop = 3 + +// Known Limitation +function test(obj) { + obj.named = 4 // Not errored because 'obj' is not namespace objects. +} +test(mod_ns) // Not errored because it doesn't know that 'test' updates the member of the argument. +``` + +## When Not To Use It + +If you don't want to be notified about modifying imported bindings, you can disable this rule. diff --git a/eslint/docs/rules/no-inline-comments.md b/eslint/docs/rules/no-inline-comments.md new file mode 100644 index 0000000..cde77e9 --- /dev/null +++ b/eslint/docs/rules/no-inline-comments.md @@ -0,0 +1,89 @@ +# disallow inline comments after code (no-inline-comments) + +Some style guides disallow comments on the same line as code. Code can become difficult to read if comments immediately follow the code on the same line. +On the other hand, it is sometimes faster and more obvious to put comments immediately following code. + +## Rule Details + +This rule disallows comments on the same line as code. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-inline-comments: "error"*/ + +var a = 1; // declaring a to 1 + +function getRandomNumber(){ + return 4; // chosen by fair dice roll. + // guaranteed to be random. +} + +/* A block comment before code */ var b = 2; + +var c = 3; /* A block comment after code */ +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-inline-comments: "error"*/ + +// This is a comment above a line of code +var foo = 5; + +var bar = 5; +//This is a comment below a line of code +``` + +### JSX exception + +Comments inside the curly braces in JSX are allowed to be on the same line as the braces, but only if they are not on the same line with other code, and the braces do not enclose an actual expression. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-inline-comments: "error"*/ + +var foo =
{ /* On the same line with other code */ }

Some heading

; + +var bar = ( +
+ { // These braces are not just for the comment, so it can't be on the same line + baz + } +
+); +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-inline-comments: "error"*/ + +var foo = ( +
+ {/* These braces are just for this comment and there is nothing else on this line */} +

Some heading

+
+) + +var bar = ( +
+ { + // There is nothing else on this line + baz + } +
+); + +var quux = ( +
+ {/* + Multiline + comment + */} +

Some heading

+
+) +``` diff --git a/eslint/docs/rules/no-inner-declarations.md b/eslint/docs/rules/no-inner-declarations.md new file mode 100644 index 0000000..23fd925 --- /dev/null +++ b/eslint/docs/rules/no-inner-declarations.md @@ -0,0 +1,144 @@ +# disallow variable or `function` declarations in nested blocks (no-inner-declarations) + +In JavaScript, prior to ES6, a function declaration is only allowed in the first level of a program or the body of another function, though parsers sometimes [erroneously accept them elsewhere](https://code.google.com/p/esprima/issues/detail?id=422). This only applies to function declarations; named or anonymous function expressions can occur anywhere an expression is permitted. + +```js +// Good +function doSomething() { } + +// Bad +if (test) { + function doSomethingElse () { } +} + +function anotherThing() { + var fn; + + if (test) { + + // Good + fn = function expression() { }; + + // Bad + function declaration() { } + } +} +``` + +A variable declaration is permitted anywhere a statement can go, even nested deeply inside other blocks. This is often undesirable due to variable hoisting, and moving declarations to the root of the program or function body can increase clarity. Note that [block bindings](https://leanpub.com/understandinges6/read#leanpub-auto-block-bindings) (`let`, `const`) are not hoisted and therefore they are not affected by this rule. + +```js +/*eslint-env es6*/ + +// Good +var foo = 42; + +// Good +if (foo) { + let bar1; +} + +// Bad +while (test) { + var bar2; +} + +function doSomething() { + // Good + var baz = true; + + // Bad + if (baz) { + var quux; + } +} +``` + +## Rule Details + +This rule requires that function declarations and, optionally, variable declarations be in the root of a program or the body of a function. + +## Options + +This rule has a string option: + +* `"functions"` (default) disallows `function` declarations in nested blocks +* `"both"` disallows `function` and `var` declarations in nested blocks + +### functions + +Examples of **incorrect** code for this rule with the default `"functions"` option: + +```js +/*eslint no-inner-declarations: "error"*/ + +if (test) { + function doSomething() { } +} + +function doSomethingElse() { + if (test) { + function doAnotherThing() { } + } +} +``` + +Examples of **correct** code for this rule with the default `"functions"` option: + +```js +/*eslint no-inner-declarations: "error"*/ + +function doSomething() { } + +function doSomethingElse() { + function doAnotherThing() { } +} + +if (test) { + asyncCall(id, function (err, data) { }); +} + +var fn; +if (test) { + fn = function fnExpression() { }; +} +``` + +### both + +Examples of **incorrect** code for this rule with the `"both"` option: + +```js +/*eslint no-inner-declarations: ["error", "both"]*/ + +if (test) { + var foo = 42; +} + +function doAnotherThing() { + if (test) { + var bar = 81; + } +} +``` + +Examples of **correct** code for this rule with the `"both"` option: + +```js +/*eslint no-inner-declarations: "error"*/ +/*eslint-env es6*/ + +var bar = 42; + +if (test) { + let baz = 43; +} + +function doAnotherThing() { + var baz = 81; +} +``` + +## When Not To Use It + +The function declaration portion rule will be rendered obsolete when [block-scoped functions](https://bugzilla.mozilla.org/show_bug.cgi?id=585536) land in ES6, but until then, it should be left on to enforce valid constructions. Disable checking variable declarations when using [block-scoped-var](block-scoped-var.md) or if declaring variables in nested blocks is acceptable despite hoisting. diff --git a/eslint/docs/rules/no-invalid-regexp.md b/eslint/docs/rules/no-invalid-regexp.md new file mode 100644 index 0000000..d656da0 --- /dev/null +++ b/eslint/docs/rules/no-invalid-regexp.md @@ -0,0 +1,64 @@ +# disallow invalid regular expression strings in `RegExp` constructors (no-invalid-regexp) + +An invalid pattern in a regular expression literal is a `SyntaxError` when the code is parsed, but an invalid string in `RegExp` constructors throws a `SyntaxError` only when the code is executed. + +## Rule Details + +This rule disallows invalid regular expression strings in `RegExp` constructors. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-invalid-regexp: "error"*/ + +RegExp('[') + +RegExp('.', 'z') + +new RegExp('\\') +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-invalid-regexp: "error"*/ + +RegExp('.') + +new RegExp + +this.RegExp('[') +``` + +## Environments + +ECMAScript 6 adds the following flag arguments to the `RegExp` constructor: + +* `"u"` ([unicode](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-get-regexp.prototype.unicode)) +* `"y"` ([sticky](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-get-regexp.prototype.sticky)) + +You can enable these to be recognized as valid by setting the ECMAScript version to 6 in your [ESLint configuration](../user-guide/configuring). + +If you want to allow additional constructor flags for any reason, you can specify them using an `allowConstructorFlags` option in `.eslintrc`. These flags will then be ignored by the rule regardless of the `ecmaVersion` setting. + +## Options + +This rule has an object option for exceptions: + +* `"allowConstructorFlags"` is an array of flags + +### allowConstructorFlags + +Examples of **correct** code for this rule with the `{ "allowConstructorFlags": ["u", "y"] }` option: + +```js +/*eslint no-invalid-regexp: ["error", { "allowConstructorFlags": ["u", "y"] }]*/ + +new RegExp('.', 'y') + +new RegExp('.', 'yu') +``` + +## Further Reading + +* [Annotated ES5 §7.8.5 - Regular Expression Literals](https://es5.github.io/#x7.8.5) diff --git a/eslint/docs/rules/no-invalid-this.md b/eslint/docs/rules/no-invalid-this.md new file mode 100644 index 0000000..7614b59 --- /dev/null +++ b/eslint/docs/rules/no-invalid-this.md @@ -0,0 +1,251 @@ +# Disallow `this` keywords outside of classes or class-like objects. (no-invalid-this) + +Under the strict mode, `this` keywords outside of classes or class-like objects might be `undefined` and raise a `TypeError`. + +## Rule Details + +This rule aims to flag usage of `this` keywords outside of classes or class-like objects. + +Basically, this rule checks whether or not a function containing `this` keyword is a constructor or a method. + +This rule judges from following conditions whether or not the function is a constructor: + +* The name of the function starts with uppercase. +* The function is assigned to a variable which starts with an uppercase letter. +* The function is a constructor of ES2015 Classes. + +This rule judges from following conditions whether or not the function is a method: + +* The function is on an object literal. +* The function is assigned to a property. +* The function is a method/getter/setter of ES2015 Classes. (excepts static methods) + +And this rule allows `this` keywords in functions below: + +* The `call/apply/bind` method of the function is called directly. +* The function is a callback of array methods (such as `.forEach()`) if `thisArg` is given. +* The function has `@this` tag in its JSDoc comment. + +Otherwise are considered problems. + +This rule applies **only** in strict mode. +With `"parserOptions": { "sourceType": "module" }` in the ESLint configuration, your code is in strict mode even without a `"use strict"` directive. + +Examples of **incorrect** code for this rule in strict mode: + +```js +/*eslint no-invalid-this: "error"*/ +/*eslint-env es6*/ + +"use strict"; + +this.a = 0; +baz(() => this); + +(function() { + this.a = 0; + baz(() => this); +})(); + +function foo() { + this.a = 0; + baz(() => this); +} + +var foo = function() { + this.a = 0; + baz(() => this); +}; + +foo(function() { + this.a = 0; + baz(() => this); +}); + +obj.foo = () => { + // `this` of arrow functions is the outer scope's. + this.a = 0; +}; + +var obj = { + aaa: function() { + return function foo() { + // There is in a method `aaa`, but `foo` is not a method. + this.a = 0; + baz(() => this); + }; + } +}; + +foo.forEach(function() { + this.a = 0; + baz(() => this); +}); +``` + +Examples of **correct** code for this rule in strict mode: + +```js +/*eslint no-invalid-this: "error"*/ +/*eslint-env es6*/ + +"use strict"; + +function Foo() { + // OK, this is in a legacy style constructor. + this.a = 0; + baz(() => this); +} + +class Foo { + constructor() { + // OK, this is in a constructor. + this.a = 0; + baz(() => this); + } +} + +var obj = { + foo: function foo() { + // OK, this is in a method (this function is on object literal). + this.a = 0; + } +}; + +var obj = { + foo() { + // OK, this is in a method (this function is on object literal). + this.a = 0; + } +}; + +var obj = { + get foo() { + // OK, this is in a method (this function is on object literal). + return this.a; + } +}; + +var obj = Object.create(null, { + foo: {value: function foo() { + // OK, this is in a method (this function is on object literal). + this.a = 0; + }} +}); + +Object.defineProperty(obj, "foo", { + value: function foo() { + // OK, this is in a method (this function is on object literal). + this.a = 0; + } +}); + +Object.defineProperties(obj, { + foo: {value: function foo() { + // OK, this is in a method (this function is on object literal). + this.a = 0; + }} +}); + +function Foo() { + this.foo = function foo() { + // OK, this is in a method (this function assigns to a property). + this.a = 0; + baz(() => this); + }; +} + +obj.foo = function foo() { + // OK, this is in a method (this function assigns to a property). + this.a = 0; +}; + +Foo.prototype.foo = function foo() { + // OK, this is in a method (this function assigns to a property). + this.a = 0; +}; + +class Foo { + foo() { + // OK, this is in a method. + this.a = 0; + baz(() => this); + } + + static foo() { + // OK, this is in a method (static methods also have valid this). + this.a = 0; + baz(() => this); + } +} + +var foo = (function foo() { + // OK, the `bind` method of this function is called directly. + this.a = 0; +}).bind(obj); + +foo.forEach(function() { + // OK, `thisArg` of `.forEach()` is given. + this.a = 0; + baz(() => this); +}, thisArg); + +/** @this Foo */ +function foo() { + // OK, this function has a `@this` tag in its JSDoc comment. + this.a = 0; +} +``` + +## Options + +This rule has an object option, with one option: + +* `"capIsConstructor": false` (default `true`) disables the assumption that a function which name starts with an uppercase is a constructor. + +### capIsConstructor + +By default, this rule always allows the use of `this` in functions which name starts with an uppercase and anonymous functions that are assigned to a variable which name starts with an uppercase, assuming that those functions are used as constructor functions. + +Set `"capIsConstructor"` to `false` if you want those functions to be treated as 'regular' functions. + +Examples of **incorrect** code for this rule with `"capIsConstructor"` option set to `false`: + +```js +/*eslint no-invalid-this: ["error", { "capIsConstructor": false }]*/ + +"use strict"; + +function Foo() { + this.a = 0; +} + +var bar = function Foo() { + this.a = 0; +} + +var Bar = function() { + this.a = 0; +}; + +Baz = function() { + this.a = 0; +}; +``` + +Examples of **correct** code for this rule with `"capIsConstructor"` option set to `false`: + +```js +/*eslint no-invalid-this: ["error", { "capIsConstructor": false }]*/ + +"use strict"; + +obj.Foo = function Foo() { + // OK, this is in a method. + this.a = 0; +}; +``` + +## When Not To Use It + +If you don't want to be notified about usage of `this` keyword outside of classes or class-like objects, you can safely disable this rule. diff --git a/eslint/docs/rules/no-irregular-whitespace.md b/eslint/docs/rules/no-irregular-whitespace.md new file mode 100644 index 0000000..a24de70 --- /dev/null +++ b/eslint/docs/rules/no-irregular-whitespace.md @@ -0,0 +1,172 @@ +# disallow irregular whitespace (no-irregular-whitespace) + +Invalid or irregular whitespace causes issues with ECMAScript 5 parsers and also makes code harder to debug in a similar nature to mixed tabs and spaces. + +Various whitespace characters can be inputted by programmers by mistake for example from copying or keyboard shortcuts. Pressing Alt + Space on macOS adds in a non breaking space character for example. + +A simple fix for this problem could be to rewrite the offending line from scratch. This might also be a problem introduced by the text editor: if rewriting the line does not fix it, try using a different editor. + +Known issues these spaces cause: + +* Zero Width Space + * Is NOT considered a separator for tokens and is often parsed as an `Unexpected token ILLEGAL` + * Is NOT shown in modern browsers making code repository software expected to resolve the visualization +* Line Separator + * Is NOT a valid character within JSON which would cause parse errors + +## Rule Details + +This rule is aimed at catching invalid whitespace that is not a normal tab and space. Some of these characters may cause issues in modern browsers and others will be a debugging issue to spot. + +This rule disallows the following characters except where the options allow: + + \u000B - Line Tabulation (\v) - + \u000C - Form Feed (\f) - + \u00A0 - No-Break Space - + \u0085 - Next Line + \u1680 - Ogham Space Mark + \u180E - Mongolian Vowel Separator - + \ufeff - Zero Width No-Break Space - + \u2000 - En Quad + \u2001 - Em Quad + \u2002 - En Space - + \u2003 - Em Space - + \u2004 - Tree-Per-Em + \u2005 - Four-Per-Em + \u2006 - Six-Per-Em + \u2007 - Figure Space + \u2008 - Punctuation Space - + \u2009 - Thin Space + \u200A - Hair Space + \u200B - Zero Width Space - + \u2028 - Line Separator + \u2029 - Paragraph Separator + \u202F - Narrow No-Break Space + \u205f - Medium Mathematical Space + \u3000 - Ideographic Space + +## Options + +This rule has an object option for exceptions: + +* `"skipStrings": true` (default) allows any whitespace characters in string literals +* `"skipComments": true` allows any whitespace characters in comments +* `"skipRegExps": true` allows any whitespace characters in regular expression literals +* `"skipTemplates": true` allows any whitespace characters in template literals + +### skipStrings + +Examples of **incorrect** code for this rule with the default `{ "skipStrings": true }` option: + +```js +/*eslint no-irregular-whitespace: "error"*/ + +function thing() /**/{ + return 'test'; +} + +function thing( /**/){ + return 'test'; +} + +function thing /**/(){ + return 'test'; +} + +function thing᠎/**/(){ + return 'test'; +} + +function thing() { + return 'test'; /**/ +} + +function thing() { + return 'test'; /**/ +} + +function thing() { + // Description : some descriptive text +} + +/* +Description : some descriptive text +*/ + +function thing() { + return / regexp/; +} + +/*eslint-env es6*/ +function thing() { + return `template string`; +} +``` + +Examples of **correct** code for this rule with the default `{ "skipStrings": true }` option: + +```js +/*eslint no-irregular-whitespace: "error"*/ + +function thing() { + return ' thing'; +} + +function thing() { + return '​thing'; +} + +function thing() { + return 'th ing'; +} +``` + +### skipComments + +Examples of additional **correct** code for this rule with the `{ "skipComments": true }` option: + +```js +/*eslint no-irregular-whitespace: ["error", { "skipComments": true }]*/ + +function thing() { + // Description : some descriptive text +} + +/* +Description : some descriptive text +*/ +``` + +### skipRegExps + +Examples of additional **correct** code for this rule with the `{ "skipRegExps": true }` option: + +```js +/*eslint no-irregular-whitespace: ["error", { "skipRegExps": true }]*/ + +function thing() { + return / regexp/; +} +``` + +### skipTemplates + +Examples of additional **correct** code for this rule with the `{ "skipTemplates": true }` option: + +```js +/*eslint no-irregular-whitespace: ["error", { "skipTemplates": true }]*/ +/*eslint-env es6*/ + +function thing() { + return `template string`; +} +``` + +## When Not To Use It + +If you decide that you wish to use whitespace other than tabs and spaces outside of strings in your application. + +## Further Reading + +* [ECMA whitespace](https://es5.github.io/#x7.2) +* [JSON whitespace issues](http://timelessrepo.com/json-isnt-a-javascript-subset) diff --git a/eslint/docs/rules/no-iterator.md b/eslint/docs/rules/no-iterator.md new file mode 100644 index 0000000..95b42a9 --- /dev/null +++ b/eslint/docs/rules/no-iterator.md @@ -0,0 +1,44 @@ +# Disallow Iterator (no-iterator) + +The `__iterator__` property was a SpiderMonkey extension to JavaScript that could be used to create custom iterators that are compatible with JavaScript's `for in` and `for each` constructs. However, this property is now obsolete, so it should not be used. Here's an example of how this used to work: + +```js +Foo.prototype.__iterator__ = function() { + return new FooIterator(this); +} +``` + +You should use ECMAScript 6 iterators and generators instead. + +## Rule Details + +This rule is aimed at preventing errors that may arise from using the `__iterator__` property, which is not implemented in several browsers. As such, it will warn whenever it encounters the `__iterator__` property. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-iterator: "error"*/ + +Foo.prototype.__iterator__ = function() { + return new FooIterator(this); +}; + +foo.__iterator__ = function () {}; + +foo["__iterator__"] = function () {}; + +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-iterator: "error"*/ + +var __iterator__ = foo; // Not using the `__iterator__` property. +``` + +## Further Reading + +* [MDN - Iterators and Generators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators) +* [ECMAScript 6 compatibility table - Iterators](https://kangax.github.io/es5-compat-table/es6/#Iterators) +* [Deprecated and Obsolete Features](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Deprecated_and_obsolete_features#Object_methods) diff --git a/eslint/docs/rules/no-label-var.md b/eslint/docs/rules/no-label-var.md new file mode 100644 index 0000000..9541dea --- /dev/null +++ b/eslint/docs/rules/no-label-var.md @@ -0,0 +1,48 @@ +# Disallow Labels That Are Variables Names (no-label-var) + +## Rule Details + +This rule aims to create clearer code by disallowing the bad practice of creating a label that shares a name with a variable that is in scope. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-label-var: "error"*/ + +var x = foo; +function bar() { +x: + for (;;) { + break x; + } +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-label-var: "error"*/ + +// The variable that has the same name as the label is not in scope. + +function foo() { + var q = t; +} + +function bar() { +q: + for(;;) { + break q; + } +} +``` + +## When Not To Use It + +If you don't want to be notified about usage of labels, then it's safe to disable this rule. + +## Related Rules + +* [no-extra-label](./no-extra-label.md) +* [no-labels](./no-labels.md) +* [no-unused-labels](./no-unused-labels.md) diff --git a/eslint/docs/rules/no-labels.md b/eslint/docs/rules/no-labels.md new file mode 100644 index 0000000..ea46ef0 --- /dev/null +++ b/eslint/docs/rules/no-labels.md @@ -0,0 +1,123 @@ +# Disallow Labeled Statements (no-labels) + +Labeled statements in JavaScript are used in conjunction with `break` and `continue` to control flow around multiple loops. For example: + +```js +outer: + while (true) { + + while (true) { + break outer; + } + } +``` + +The `break outer` statement ensures that this code will not result in an infinite loop because control is returned to the next statement after the `outer` label was applied. If this statement was changed to be just `break`, control would flow back to the outer `while` statement and an infinite loop would result. + +While convenient in some cases, labels tend to be used only rarely and are frowned upon by some as a remedial form of flow control that is more error prone and harder to understand. + +## Rule Details + +This rule aims to eliminate the use of labeled statements in JavaScript. It will warn whenever a labeled statement is encountered and whenever `break` or `continue` are used with a label. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-labels: "error"*/ + +label: + while(true) { + // ... + } + +label: + while(true) { + break label; + } + +label: + while(true) { + continue label; + } + +label: + switch (a) { + case 0: + break label; + } + +label: + { + break label; + } + +label: + if (a) { + break label; + } +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-labels: "error"*/ + +var f = { + label: "foo" +}; + +while (true) { + break; +} + +while (true) { + continue; +} +``` + +## Options + +The options allow labels with loop or switch statements: + +* `"allowLoop"` (`boolean`, default is `false`) - If this option was set `true`, this rule ignores labels which are sticking to loop statements. +* `"allowSwitch"` (`boolean`, default is `false`) - If this option was set `true`, this rule ignores labels which are sticking to switch statements. + +Actually labeled statements in JavaScript can be used with other than loop and switch statements. +However, this way is ultra rare, not well-known, so this would be confusing developers. + +### allowLoop + +Examples of **correct** code for the `{ "allowLoop": true }` option: + +```js +/*eslint no-labels: ["error", { "allowLoop": true }]*/ + +label: + while (true) { + break label; + } +``` + +### allowSwitch + +Examples of **correct** code for the `{ "allowSwitch": true }` option: + +```js +/*eslint no-labels: ["error", { "allowSwitch": true }]*/ + +label: + switch (a) { + case 0: + break label; + } +``` + +## When Not To Use It + +If you need to use labeled statements everywhere, then you can safely disable this rule. + +## Related Rules + +* [no-extra-label](./no-extra-label.md) +* [no-label-var](./no-label-var.md) +* [no-unused-labels](./no-unused-labels.md) diff --git a/eslint/docs/rules/no-lone-blocks.md b/eslint/docs/rules/no-lone-blocks.md new file mode 100644 index 0000000..8cb45d6 --- /dev/null +++ b/eslint/docs/rules/no-lone-blocks.md @@ -0,0 +1,94 @@ +# Disallow Unnecessary Nested Blocks (no-lone-blocks) + +In JavaScript, prior to ES6, standalone code blocks delimited by curly braces do not create a new scope and have no use. For example, these curly braces do nothing to `foo`: + +```js +{ + var foo = bar(); +} +``` + +In ES6, code blocks may create a new scope if a block-level binding (`let` and `const`), a class declaration or a function declaration (in strict mode) are present. A block is not considered redundant in these cases. + +## Rule Details + +This rule aims to eliminate unnecessary and potentially confusing blocks at the top level of a script or within other blocks. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-lone-blocks: "error"*/ + +{} + +if (foo) { + bar(); + { + baz(); + } +} + +function bar() { + { + baz(); + } +} + +{ + function foo() {} +} + +{ + aLabel: { + } +} +``` + +Examples of **correct** code for this rule with ES6 environment: + +```js +/*eslint no-lone-blocks: "error"*/ +/*eslint-env es6*/ + +while (foo) { + bar(); +} + +if (foo) { + if (bar) { + baz(); + } +} + +function bar() { + baz(); +} + +{ + let x = 1; +} + +{ + const y = 1; +} + +{ + class Foo {} +} + +aLabel: { +} +``` + +Examples of **correct** code for this rule with ES6 environment and strict mode via `"parserOptions": { "sourceType": "module" }` in the ESLint configuration or `"use strict"` directive in the code: + +```js +/*eslint no-lone-blocks: "error"*/ +/*eslint-env es6*/ + +"use strict"; + +{ + function foo() {} +} +``` diff --git a/eslint/docs/rules/no-lonely-if.md b/eslint/docs/rules/no-lonely-if.md new file mode 100644 index 0000000..4dab7ef --- /dev/null +++ b/eslint/docs/rules/no-lonely-if.md @@ -0,0 +1,84 @@ +# disallow `if` statements as the only statement in `else` blocks (no-lonely-if) + +If an `if` statement is the only statement in the `else` block, it is often clearer to use an `else if` form. + +```js +if (foo) { + // ... +} else { + if (bar) { + // ... + } +} +``` + +should be rewritten as + +```js +if (foo) { + // ... +} else if (bar) { + // ... +} +``` + +## Rule Details + +This rule disallows `if` statements as the only statement in `else` blocks. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-lonely-if: "error"*/ + +if (condition) { + // ... +} else { + if (anotherCondition) { + // ... + } +} + +if (condition) { + // ... +} else { + if (anotherCondition) { + // ... + } else { + // ... + } +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-lonely-if: "error"*/ + +if (condition) { + // ... +} else if (anotherCondition) { + // ... +} + +if (condition) { + // ... +} else if (anotherCondition) { + // ... +} else { + // ... +} + +if (condition) { + // ... +} else { + if (anotherCondition) { + // ... + } + doSomething(); +} +``` + +## When Not To Use It + +Disable this rule if the code is clearer without requiring the `else if` form. diff --git a/eslint/docs/rules/no-loop-func.md b/eslint/docs/rules/no-loop-func.md new file mode 100644 index 0000000..65c7532 --- /dev/null +++ b/eslint/docs/rules/no-loop-func.md @@ -0,0 +1,98 @@ +# Disallow Functions in Loops (no-loop-func) + +Writing functions within loops tends to result in errors due to the way the function creates a closure around the loop. For example: + +```js +for (var i = 0; i < 10; i++) { + funcs[i] = function() { + return i; + }; +} +``` + +In this case, you would expect each function created within the loop to return a different number. In reality, each function returns 10, because that was the last value of `i` in the scope. + +`let` or `const` mitigate this problem. + +```js +/*eslint-env es6*/ + +for (let i = 0; i < 10; i++) { + funcs[i] = function() { + return i; + }; +} +``` + +In this case, each function created within the loop returns a different number as expected. + + +## Rule Details + +This error is raised to highlight a piece of code that may not work as you expect it to and could also indicate a misunderstanding of how the language works. Your code may run without any problems if you do not fix this error, but in some situations it could behave unexpectedly. + +This rule disallows any function within a loop that contains unsafe references (e.g. to modified variables from the outer scope). + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-loop-func: "error"*/ +/*eslint-env es6*/ + +for (var i=10; i; i--) { + (function() { return i; })(); +} + +while(i) { + var a = function() { return i; }; + a(); +} + +do { + function a() { return i; }; + a(); +} while (i); + +let foo = 0; +for (let i = 0; i < 10; ++i) { + //Bad, `foo` is not in the loop-block's scope and `foo` is modified in/after the loop + setTimeout(() => console.log(foo)); + foo += 1; +} + +for (let i = 0; i < 10; ++i) { + //Bad, `foo` is not in the loop-block's scope and `foo` is modified in/after the loop + setTimeout(() => console.log(foo)); +} +foo = 100; +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-loop-func: "error"*/ +/*eslint-env es6*/ + +var a = function() {}; + +for (var i=10; i; i--) { + a(); +} + +for (var i=10; i; i--) { + var a = function() {}; // OK, no references to variables in the outer scopes. + a(); +} + +for (let i=10; i; i--) { + var a = function() { return i; }; // OK, all references are referring to block scoped variables in the loop. + a(); +} + +var foo = 100; +for (let i=10; i; i--) { + var a = function() { return foo; }; // OK, all references are referring to never modified variables. + a(); +} +//... no modifications of foo after this loop ... +``` diff --git a/eslint/docs/rules/no-magic-numbers.md b/eslint/docs/rules/no-magic-numbers.md new file mode 100644 index 0000000..ee6d3de --- /dev/null +++ b/eslint/docs/rules/no-magic-numbers.md @@ -0,0 +1,176 @@ +# Disallow Magic Numbers (no-magic-numbers) + +'Magic numbers' are numbers that occur multiple times in code without an explicit meaning. +They should preferably be replaced by named constants. + +```js +var now = Date.now(), + inOneHour = now + (60 * 60 * 1000); +``` + +## Rule Details + +The `no-magic-numbers` rule aims to make code more readable and refactoring easier by ensuring that special numbers +are declared as constants to make their meaning explicit. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-magic-numbers: "error"*/ + +var dutyFreePrice = 100, + finalPrice = dutyFreePrice + (dutyFreePrice * 0.25); +``` + +```js +/*eslint no-magic-numbers: "error"*/ + +var data = ['foo', 'bar', 'baz']; + +var dataLast = data[2]; +``` + +```js +/*eslint no-magic-numbers: "error"*/ + +var SECONDS; + +SECONDS = 60; +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-magic-numbers: "error"*/ + +var TAX = 0.25; + +var dutyFreePrice = 100, + finalPrice = dutyFreePrice + (dutyFreePrice * TAX); +``` + +## Options + +### ignore + +An array of numbers to ignore. It's set to `[]` by default. +If provided, it must be an `Array`. + +The array can contain values of `number` and `string` types. +If it's a string, the text must be parsed as `bigint` literal (e.g., `"100n"`). + +Examples of **correct** code for the sample `{ "ignore": [1] }` option: + +```js +/*eslint no-magic-numbers: ["error", { "ignore": [1] }]*/ + +var data = ['foo', 'bar', 'baz']; +var dataLast = data.length && data[data.length - 1]; +``` + +Examples of **correct** code for the sample `{ "ignore": ["1n"] }` option: + +```js +/*eslint no-magic-numbers: ["error", { "ignore": ["1n"] }]*/ + +foo(1n); +``` + +### ignoreArrayIndexes + +A boolean to specify if numbers used in the context of array indexes (e.g., `data[2]`) are considered okay. `false` by default. + +This option allows only valid array indexes: numbers that will be coerced to one of `"0"`, `"1"`, `"2"` ... `"4294967294"`. + +Arrays are objects, so they can have property names such as `"-1"` or `"2.5"`. However, those are just "normal" object properties that don't represent array elements. They don't influence the array's `length`, and they are ignored by array methods like `.map` or `.forEach`. + +Additionally, since the maximum [array length](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/length) is 232 - 1, all values above 232 - 2 also represent just normal property names and are thus not considered to be array indexes. + +Examples of **correct** code for the `{ "ignoreArrayIndexes": true }` option: + +```js +/*eslint no-magic-numbers: ["error", { "ignoreArrayIndexes": true }]*/ + +var item = data[2]; + +data[100] = a; + +f(data[0]); + +a = data[-0]; // same as data[0], -0 will be coerced to "0" + +a = data[0xAB]; + +a = data[5.6e1]; + +a = data[10n]; // same as data[10], 10n will be coerced to "10" + +a = data[4294967294]; // max array index +``` + +Examples of **incorrect** code for the `{ "ignoreArrayIndexes": true }` option: + +```js +/*eslint no-magic-numbers: ["error", { "ignoreArrayIndexes": true }]*/ + +f(2); // not used as array index + +a = data[-1]; + +a = data[2.5]; + +a = data[5.67e1]; + +a = data[-10n]; + +a = data[4294967295]; // above the max array index + +a = data[1e500]; // same as data["Infinity"] +``` + +### enforceConst + +A boolean to specify if we should check for the const keyword in variable declaration of numbers. `false` by default. + +Examples of **incorrect** code for the `{ "enforceConst": true }` option: + +```js +/*eslint no-magic-numbers: ["error", { "enforceConst": true }]*/ + +var TAX = 0.25; + +var dutyFreePrice = 100, + finalPrice = dutyFreePrice + (dutyFreePrice * TAX); +``` + +### detectObjects + +A boolean to specify if we should detect numbers when setting object properties for example. `false` by default. + +Examples of **incorrect** code for the `{ "detectObjects": true }` option: + +```js +/*eslint no-magic-numbers: ["error", { "detectObjects": true }]*/ + +var magic = { + tax: 0.25 +}; + +var dutyFreePrice = 100, + finalPrice = dutyFreePrice + (dutyFreePrice * magic.tax); +``` + +Examples of **correct** code for the `{ "detectObjects": true }` option: + +```js +/*eslint no-magic-numbers: ["error", { "detectObjects": true }]*/ + +var TAX = 0.25; + +var magic = { + tax: TAX +}; + +var dutyFreePrice = 100, + finalPrice = dutyFreePrice + (dutyFreePrice * magic.tax); +``` diff --git a/eslint/docs/rules/no-misleading-character-class.md b/eslint/docs/rules/no-misleading-character-class.md new file mode 100644 index 0000000..f6ef2fb --- /dev/null +++ b/eslint/docs/rules/no-misleading-character-class.md @@ -0,0 +1,73 @@ +# Disallow characters which are made with multiple code points in character class syntax (no-misleading-character-class) + +Unicode includes the characters which are made with multiple code points. +RegExp character class syntax (`/[abc]/`) cannot handle characters which are made by multiple code points as a character; those characters will be dissolved to each code point. For example, `❇️` is made by `❇` (`U+2747`) and VARIATION SELECTOR-16 (`U+FE0F`). If this character is in RegExp character class, it will match to either `❇` (`U+2747`) or VARIATION SELECTOR-16 (`U+FE0F`) rather than `❇️`. + +This rule reports the regular expressions which include multiple code point characters in character class syntax. This rule considers the following characters as multiple code point characters. + +**A character with combining characters:** + +The combining characters are characters which belong to one of `Mc`, `Me`, and `Mn` [Unicode general categories](http://www.unicode.org/L2/L1999/UnicodeData.html#General%20Category). + +```js +/^[Á]$/u.test("Á") //→ false +/^[❇️]$/u.test("❇️") //→ false +``` + +**A character with Emoji modifiers:** + +```js +/^[👶🏻]$/u.test("👶🏻") //→ false +/^[👶🏽]$/u.test("👶🏽") //→ false +``` + +**A pair of regional indicator symbols:** + +```js +/^[🇯🇵]$/u.test("🇯🇵") //→ false +``` + +**Characters that ZWJ joins:** + +```js +/^[👨‍👩‍👦]$/u.test("👨‍👩‍👦") //→ false +``` + +**A surrogate pair without Unicode flag:** + +```js +/^[👍]$/.test("👍") //→ false + +// Surrogate pair is OK if with u flag. +/^[👍]$/u.test("👍") //→ true +``` + +## Rule Details + +This rule reports the regular expressions which include multiple code point characters in character class syntax. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-misleading-character-class: error */ + +/^[Á]$/u +/^[❇️]$/u +/^[👶🏻]$/u +/^[🇯🇵]$/u +/^[👨‍👩‍👦]$/u +/^[👍]$/ +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-misleading-character-class: error */ + +/^[abc]$/ +/^[👍]$/u +``` + +## When Not To Use It + +You can turn this rule off if you don't want to check RegExp character class syntax for multiple code point characters. diff --git a/eslint/docs/rules/no-mixed-operators.md b/eslint/docs/rules/no-mixed-operators.md new file mode 100644 index 0000000..3206d18 --- /dev/null +++ b/eslint/docs/rules/no-mixed-operators.md @@ -0,0 +1,184 @@ +# Disallow mixes of different operators (no-mixed-operators) + +Enclosing complex expressions by parentheses clarifies the developer's intention, which makes the code more readable. +This rule warns when different operators are used consecutively without parentheses in an expression. + +```js +var foo = a && b || c || d; /*BAD: Unexpected mix of '&&' and '||'.*/ +var foo = a && b ? c : d; /*BAD: Unexpected mix of '&&' and '?:'.*/ +var foo = (a && b) ? c : d; /*GOOD*/ +var foo = (a && b) || c || d; /*GOOD*/ +var foo = a && (b || c || d); /*GOOD*/ +``` + +**Note:** +It is expected for this rule to emit one error for each mixed operator in a pair. As a result, for each two consecutive mixed operators used, a distinct error will be displayed, pointing to where the specific operator that breaks the rule is used: + +```js +var foo = a && b || c || d; +``` + +will generate + +```sh +1:13 Unexpected mix of '&&' and '||'. (no-mixed-operators) +1:18 Unexpected mix of '&&' and '||'. (no-mixed-operators) +``` + +```js +var foo = a && b ? c : d; +``` + +will generate + +```sh +1:13 Unexpected mix of '&&' and '?:'. (no-mixed-operators) +1:18 Unexpected mix of '&&' and '?:'. (no-mixed-operators) +``` + + +## Rule Details + +This rule checks `BinaryExpression`, `LogicalExpression` and `ConditionalExpression`. + +This rule may conflict with [no-extra-parens](no-extra-parens.md) rule. +If you use both this and [no-extra-parens](no-extra-parens.md) rule together, you need to use the `nestedBinaryExpressions` option of [no-extra-parens](no-extra-parens.md) rule. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-mixed-operators: "error"*/ + +var foo = a && b < 0 || c > 0 || d + 1 === 0; +var foo = a + b * c; +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-mixed-operators: "error"*/ + +var foo = a || b || c; +var foo = a && b && c; +var foo = (a && b < 0) || c > 0 || d + 1 === 0; +var foo = a && (b < 0 || c > 0 || d + 1 === 0); +var foo = a + (b * c); +var foo = (a + b) * c; +``` + +## Options + +```json +{ + "no-mixed-operators": [ + "error", + { + "groups": [ + ["+", "-", "*", "/", "%", "**"], + ["&", "|", "^", "~", "<<", ">>", ">>>"], + ["==", "!=", "===", "!==", ">", ">=", "<", "<="], + ["&&", "||"], + ["in", "instanceof"] + ], + "allowSamePrecedence": true + } + ] +} +``` + +This rule has 2 options. + +* `groups` (`string[][]`) - specifies operator groups to be checked. The `groups` option is a list of groups, and a group is a list of binary operators. Default operator groups are defined as arithmetic, bitwise, comparison, logical, and relational operators. Note: Ternary operator(?:) can be part of any group and by default is allowed to be mixed with other operators. + +* `allowSamePrecedence` (`boolean`) - specifies whether to allow mixed operators if they are of equal precedence. Default is `true`. + +### groups + +The following operators can be used in `groups` option: + +* Arithmetic Operators: `"+"`, `"-"`, `"*"`, `"/"`, `"%"`, `"**"` +* Bitwise Operators: `"&"`, `"|"`, `"^"`, `"~"`, `"<<"`, `">>"`, `">>>"` +* Comparison Operators: `"=="`, `"!="`, `"==="`, `"!=="`, `">"`, `">="`, `"<"`, `"<="` +* Logical Operators: `"&&"`, `"||"` +* Relational Operators: `"in"`, `"instanceof"` +* Ternary Operator: `?:` + +Now, consider the following group configuration: `{"groups": [["&", "|", "^", "~", "<<", ">>", ">>>"], ["&&", "||"]]}`. +There are 2 groups specified in this configuration: bitwise operators and logical operators. +This rule checks if the operators belong to the same group only. +In this case, this rule checks if bitwise operators and logical operators are mixed, but ignores all other operators. + +Examples of **incorrect** code for this rule with `{"groups": [["&", "|", "^", "~", "<<", ">>", ">>>"], ["&&", "||"]]}` option: + +```js +/*eslint no-mixed-operators: ["error", {"groups": [["&", "|", "^", "~", "<<", ">>", ">>>"], ["&&", "||"]]}]*/ + +var foo = a && b < 0 || c > 0 || d + 1 === 0; +var foo = a & b | c; +``` + +```js +/*eslint no-mixed-operators: ["error", {"groups": [["&&", "||", "?:"]]}]*/ + +var foo = a || b ? c : d; +``` + +Examples of **correct** code for this rule with `{"groups": [["&", "|", "^", "~", "<<", ">>", ">>>"], ["&&", "||"]]}` option: + +```js +/*eslint no-mixed-operators: ["error", {"groups": [["&", "|", "^", "~", "<<", ">>", ">>>"], ["&&", "||"]]}]*/ + +var foo = a || b > 0 || c + 1 === 0; +var foo = a && b > 0 && c + 1 === 0; +var foo = (a && b < 0) || c > 0 || d + 1 === 0; +var foo = a && (b < 0 || c > 0 || d + 1 === 0); +var foo = (a & b) | c; +var foo = a & (b | c); +var foo = a + b * c; +var foo = a + (b * c); +var foo = (a + b) * c; +``` + +```js +/*eslint no-mixed-operators: ["error", {"groups": [["&&", "||", "?:"]]}]*/ + +var foo = (a || b) ? c : d; +var foo = a || (b ? c : d); +``` + +### allowSamePrecedence + +Examples of **correct** code for this rule with `{"allowSamePrecedence": true}` option: + +```js +/*eslint no-mixed-operators: ["error", {"allowSamePrecedence": true}]*/ + +// + and - have the same precedence. +var foo = a + b - c; +``` + +Examples of **incorrect** code for this rule with `{"allowSamePrecedence": false}` option: + +```js +/*eslint no-mixed-operators: ["error", {"allowSamePrecedence": false}]*/ + +// + and - have the same precedence. +var foo = a + b - c; +``` + +Examples of **correct** code for this rule with `{"allowSamePrecedence": false}` option: + +```js +/*eslint no-mixed-operators: ["error", {"allowSamePrecedence": false}]*/ + +// + and - have the same precedence. +var foo = (a + b) - c; +``` + +## When Not To Use It + +If you don't want to be notified about mixed operators, then it's safe to disable this rule. + +## Related Rules + +* [no-extra-parens](no-extra-parens.md) diff --git a/eslint/docs/rules/no-mixed-requires.md b/eslint/docs/rules/no-mixed-requires.md new file mode 100644 index 0000000..ee63c99 --- /dev/null +++ b/eslint/docs/rules/no-mixed-requires.md @@ -0,0 +1,124 @@ +# disallow `require` calls to be mixed with regular variable declarations (no-mixed-requires) + +In the Node.js community it is often customary to separate initializations with calls to `require` modules from other variable declarations, sometimes also grouping them by the type of module. This rule helps you enforce this convention. + +## Rule Details + +When this rule is enabled, each `var` statement must satisfy the following conditions: + +* either none or all variable declarations must be require declarations (default) +* all require declarations must be of the same type (grouping) + +This rule distinguishes between six kinds of variable declaration types: + +* `core`: declaration of a required [core module][1] +* `file`: declaration of a required [file module][2] +* `module`: declaration of a required module from the [node_modules folder][3] +* `computed`: declaration of a required module whose type could not be determined (either because it is computed or because require was called without an argument) +* `uninitialized`: a declaration that is not initialized +* `other`: any other kind of declaration + +In this document, the first four types are summed up under the term *require declaration*. + +```js +var fs = require('fs'), // "core" \ + async = require('async'), // "module" |- these are "require declaration"s + foo = require('./foo'), // "file" | + bar = require(getName()), // "computed" / + baz = 42, // "other" + bam; // "uninitialized" +``` + +## Options + +This rule can have an object literal option whose two properties have `false` values by default. + +Configuring this rule with one boolean option `true` is deprecated. + +Examples of **incorrect** code for this rule with the default `{ "grouping": false, "allowCall": false }` options: + +```js +/*eslint no-mixed-requires: "error"*/ + +var fs = require('fs'), + i = 0; + +var async = require('async'), + debug = require('diagnostics').someFunction('my-module'), + eslint = require('eslint'); +``` + +Examples of **correct** code for this rule with the default `{ "grouping": false, "allowCall": false }` options: + +```js +/*eslint no-mixed-requires: "error"*/ + +// only require declarations (grouping off) +var eventEmitter = require('events').EventEmitter, + myUtils = require('./utils'), + util = require('util'), + bar = require(getBarModuleName()); + +// only non-require declarations +var foo = 42, + bar = 'baz'; + +// always valid regardless of grouping because all declarations are of the same type +var foo = require('foo' + VERSION), + bar = require(getBarModuleName()), + baz = require(); +``` + +### grouping + +Examples of **incorrect** code for this rule with the `{ "grouping": true }` option: + +```js +/*eslint no-mixed-requires: ["error", { "grouping": true }]*/ + +// invalid because of mixed types "core" and "module" +var fs = require('fs'), + async = require('async'); + +// invalid because of mixed types "file" and "unknown" +var foo = require('foo'), + bar = require(getBarModuleName()); +``` + +### allowCall + +Examples of **incorrect** code for this rule with the `{ "allowCall": true }` option: + +```js +/*eslint no-mixed-requires: ["error", { "allowCall": true }]*/ + +var async = require('async'), + debug = require('diagnostics').someFunction('my-module'), /* allowCall doesn't allow calling any function */ + eslint = require('eslint'); +``` + +Examples of **correct** code for this rule with the `{ "allowCall": true }` option: + +```js +/*eslint no-mixed-requires: ["error", { "allowCall": true }]*/ + +var async = require('async'), + debug = require('diagnostics')('my-module'), + eslint = require('eslint'); +``` + +## Known Limitations + +* The implementation is not aware of any local functions with the name `require` that may shadow Node.js' global `require`. + +* Internally, the list of core modules is retrieved via `require("repl")._builtinLibs`. If you use different versions of Node.js for ESLint and your application, the list of core modules for each version may be different. + The above mentioned `_builtinLibs` property became available in 0.8, for earlier versions a hardcoded list of module names is used as a fallback. If your version of Node.js is older than 0.6 that list may be inaccurate. + +## When Not To Use It + +If you use a pattern such as [UMD][4] where the `require`d modules are not loaded in variable declarations, this rule will obviously do nothing for you. + +[1]: https://nodejs.org/api/modules.html#modules_core_modules +[2]: https://nodejs.org/api/modules.html#modules_file_modules +[3]: https://nodejs.org/api/modules.html#modules_loading_from_node_modules_folders +[4]: https://github.com/umdjs/umd diff --git a/eslint/docs/rules/no-mixed-spaces-and-tabs.md b/eslint/docs/rules/no-mixed-spaces-and-tabs.md new file mode 100644 index 0000000..ba34dbe --- /dev/null +++ b/eslint/docs/rules/no-mixed-spaces-and-tabs.md @@ -0,0 +1,65 @@ +# disallow mixed spaces and tabs for indentation (no-mixed-spaces-and-tabs) + +Most code conventions require either tabs or spaces be used for indentation. As such, it's usually an error if a single line of code is indented with both tabs and spaces. + +## Rule Details + +This rule disallows mixed spaces and tabs for indentation. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-mixed-spaces-and-tabs: "error"*/ + +function add(x, y) { +// --->..return x + y; + + return x + y; +} + +function main() { +// --->var x = 5, +// --->....y = 7; + + var x = 5, + y = 7; +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-mixed-spaces-and-tabs: "error"*/ + +function add(x, y) { +// --->return x + y; + return x + y; +} +``` + +## Options + +This rule has a string option. + +* `"smart-tabs"` allows mixed tabs and spaces when the spaces are used for alignment. + +### smart-tabs + +Examples of **correct** code for this rule with the `"smart-tabs"` option: + +```js +/*eslint no-mixed-spaces-and-tabs: ["error", "smart-tabs"]*/ + +function main() { +// --->var x = 5, +// --->....y = 7; + + var x = 5, + y = 7; +} +``` + + +## Further Reading + +* [Smart Tabs](https://www.emacswiki.org/emacs/SmartTabs) diff --git a/eslint/docs/rules/no-multi-assign.md b/eslint/docs/rules/no-multi-assign.md new file mode 100644 index 0000000..41bba9a --- /dev/null +++ b/eslint/docs/rules/no-multi-assign.md @@ -0,0 +1,48 @@ +# Disallow Use of Chained Assignment Expressions (no-multi-assign) + +Chaining the assignment of variables can lead to unexpected results and be difficult to read. + +```js +(function() { + const foo = bar = 0; // Did you mean `foo = bar == 0`? + bar = 1; // This will not fail since `bar` is not constant. +})(); +console.log(bar); // This will output 1 since `bar` is not scoped. +``` + +## Rule Details + +This rule disallows using multiple assignments within a single statement. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-multi-assign: "error"*/ + +var a = b = c = 5; + +const foo = bar = "baz"; + +let a = + b = + c; +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-multi-assign: "error"*/ +var a = 5; +var b = 5; +var c = 5; + +const foo = "baz"; +const bar = "baz"; + +let a = c; +let b = c; +``` + +## Related Rules + +* [max-statements-per-line](max-statements-per-line.md) diff --git a/eslint/docs/rules/no-multi-spaces.md b/eslint/docs/rules/no-multi-spaces.md new file mode 100644 index 0000000..c4b12e9 --- /dev/null +++ b/eslint/docs/rules/no-multi-spaces.md @@ -0,0 +1,171 @@ +# Disallow multiple spaces (no-multi-spaces) + +Multiple spaces in a row that are not used for indentation are typically mistakes. For example: + +```js + +if(foo === "bar") {} + +``` + +It's hard to tell, but there are two spaces between `foo` and `===`. Multiple spaces such as this are generally frowned upon in favor of single spaces: + +```js + +if(foo === "bar") {} + +``` + +## Rule Details + +This rule aims to disallow multiple whitespace around logical expressions, conditional expressions, declarations, array elements, object properties, sequences and function parameters. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-multi-spaces: "error"*/ + +var a = 1; + +if(foo === "bar") {} + +a << b + +var arr = [1, 2]; + +a ? b: c +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-multi-spaces: "error"*/ + +var a = 1; + +if(foo === "bar") {} + +a << b + +var arr = [1, 2]; + +a ? b: c +``` + +## Options + +This rule's configuration consists of an object with the following properties: + +* `"ignoreEOLComments": true` (defaults to `false`) ignores multiple spaces before comments that occur at the end of lines +* `"exceptions": { "Property": true }` (`"Property"` is the only node specified by default) specifies nodes to ignore + +### ignoreEOLComments + +Examples of **incorrect** code for this rule with the `{ "ignoreEOLComments": false }` (default) option: + +```js +/*eslint no-multi-spaces: ["error", { ignoreEOLComments: false }]*/ + +var x = 5; // comment +var x = 5; /* multiline + * comment + */ +``` + +Examples of **correct** code for this rule with the `{ "ignoreEOLComments": false }` (default) option: + +```js +/*eslint no-multi-spaces: ["error", { ignoreEOLComments: false }]*/ + +var x = 5; // comment +var x = 5; /* multiline + * comment + */ +``` + +Examples of **correct** code for this rule with the `{ "ignoreEOLComments": true }` option: + +```js +/*eslint no-multi-spaces: ["error", { ignoreEOLComments: true }]*/ + +var x = 5; // comment +var x = 5; // comment +var x = 5; /* multiline + * comment + */ +var x = 5; /* multiline + * comment + */ +``` + +### exceptions + +To avoid contradictions with other rules that require multiple spaces, this rule has an `exceptions` option to ignore certain nodes. + +This option is an object that expects property names to be AST node types as defined by [ESTree](https://github.com/estree/estree). The easiest way to determine the node types for `exceptions` is to use [AST Explorer](https://astexplorer.net/) with the espree parser. + +Only the `Property` node type is ignored by default, because for the [key-spacing](key-spacing.md) rule some alignment options require multiple spaces in properties of object literals. + +Examples of **correct** code for the default `"exceptions": { "Property": true }` option: + +```js +/*eslint no-multi-spaces: "error"*/ +/*eslint key-spacing: ["error", { align: "value" }]*/ + +var obj = { + first: "first", + second: "second" +}; +``` + +Examples of **incorrect** code for the `"exceptions": { "Property": false }` option: + +```js +/*eslint no-multi-spaces: ["error", { exceptions: { "Property": false } }]*/ +/*eslint key-spacing: ["error", { align: "value" }]*/ + +var obj = { + first: "first", + second: "second" +}; +``` + +Examples of **correct** code for the `"exceptions": { "BinaryExpression": true }` option: + +```js +/*eslint no-multi-spaces: ["error", { exceptions: { "BinaryExpression": true } }]*/ + +var a = 1 * 2; +``` + +Examples of **correct** code for the `"exceptions": { "VariableDeclarator": true }` option: + +```js +/*eslint no-multi-spaces: ["error", { exceptions: { "VariableDeclarator": true } }]*/ + +var someVar = 'foo'; +var someOtherVar = 'barBaz'; +``` + +Examples of **correct** code for the `"exceptions": { "ImportDeclaration": true }` option: + +```js +/*eslint no-multi-spaces: ["error", { exceptions: { "ImportDeclaration": true } }]*/ + +import mod from 'mod'; +import someOtherMod from 'some-other-mod'; +``` + +## When Not To Use It + +If you don't want to check and disallow multiple spaces, then you should turn this rule off. + +## Related Rules + +* [key-spacing](key-spacing.md) +* [space-infix-ops](space-infix-ops.md) +* [space-in-brackets](space-in-brackets.md) (deprecated) +* [space-in-parens](space-in-parens.md) +* [space-after-keywords](space-after-keywords.md) +* [space-unary-ops](space-unary-ops.md) +* [space-return-throw-case](space-return-throw-case.md) diff --git a/eslint/docs/rules/no-multi-str.md b/eslint/docs/rules/no-multi-str.md new file mode 100644 index 0000000..81fc0af --- /dev/null +++ b/eslint/docs/rules/no-multi-str.md @@ -0,0 +1,31 @@ +# Disallow Multiline Strings (no-multi-str) + +It's possible to create multiline strings in JavaScript by using a slash before a newline, such as: + +```js +var x = "Line 1 \ + Line 2"; +``` + +Some consider this to be a bad practice as it was an undocumented feature of JavaScript that was only formalized later. + +## Rule Details + +This rule is aimed at preventing the use of multiline strings. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-multi-str: "error"*/ +var x = "Line 1 \ + Line 2"; +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-multi-str: "error"*/ + +var x = "Line 1\n" + + "Line 2"; +``` diff --git a/eslint/docs/rules/no-multiple-empty-lines.md b/eslint/docs/rules/no-multiple-empty-lines.md new file mode 100644 index 0000000..87b2425 --- /dev/null +++ b/eslint/docs/rules/no-multiple-empty-lines.md @@ -0,0 +1,96 @@ +# disallow multiple empty lines (no-multiple-empty-lines) + +Some developers prefer to have multiple blank lines removed, while others feel that it helps improve readability. Whitespace is useful for separating logical sections of code, but excess whitespace takes up more of the screen. + +## Rule Details + +This rule aims to reduce the scrolling required when reading through your code. It will warn when the maximum amount of empty lines has been exceeded. + +## Options + +This rule has an object option: + +* `"max"` (default: `2`) enforces a maximum number of consecutive empty lines. +* `"maxEOF"` enforces a maximum number of consecutive empty lines at the end of files. +* `"maxBOF"` enforces a maximum number of consecutive empty lines at the beginning of files. + +### max + +Examples of **incorrect** code for this rule with the default `{ "max": 2 }` option: + +```js +/*eslint no-multiple-empty-lines: "error"*/ + +var foo = 5; + + + +var bar = 3; +``` + +Examples of **correct** code for this rule with the default `{ "max": 2 }` option: + +```js +/*eslint no-multiple-empty-lines: "error"*/ + +var foo = 5; + + +var bar = 3; +``` + +### maxEOF + +Examples of **incorrect** code for this rule with the `{ max: 2, maxEOF: 1 }` options: + +```js +/*eslint no-multiple-empty-lines: ["error", { "max": 2, "maxEOF": 1 }]*/ + +var foo = 5; + + +var bar = 3; + + +``` + +Examples of **correct** code for this rule with the `{ max: 2, maxEOF: 1 }` options: + +```js +/*eslint no-multiple-empty-lines: ["error", { "max": 2, "maxEOF": 1 }]*/ + +var foo = 5; + + +var bar = 3; + +``` + +### maxBOF + +Examples of **incorrect** code for this rule with the `{ max: 2, maxBOF: 1 }` options: + +```js +/*eslint no-multiple-empty-lines: ["error", { "max": 2, "maxBOF": 1 }]*/ + + +var foo = 5; + + +var bar = 3; +``` + +Examples of **correct** code for this rule with the `{ max: 2, maxBOF: 1 }` options: + +```js +/*eslint no-multiple-empty-lines: ["error", { "max": 2, "maxBOF": 1}]*/ + +var foo = 5; + + +var bar = 3; +``` + +## When Not To Use It + +If you do not care about extra blank lines, turn this off. diff --git a/eslint/docs/rules/no-native-reassign.md b/eslint/docs/rules/no-native-reassign.md new file mode 100644 index 0000000..bfb511d --- /dev/null +++ b/eslint/docs/rules/no-native-reassign.md @@ -0,0 +1,91 @@ +# Disallow Reassignment of Native Objects (no-native-reassign) + +This rule was **deprecated** in ESLint v3.3.0 and replaced by the [no-global-assign](no-global-assign.md) rule. + +JavaScript environments contain a number of built-in global variables, such as `window` in browsers and `process` in Node.js. In almost all cases, you don't want to assign a value to these global variables as doing so could result in losing access to important functionality. For example, you probably don't want to do this in browser code: + +```js +window = {}; +``` + +While examples such as `window` are obvious, there are often hundreds of built-in global objects provided by JavaScript environments. It can be hard to know if you're assigning to a global variable or not. + +## Rule Details + +This rule disallows modifications to read-only global variables. + +ESLint has the capability to configure global variables as read-only. + +* [Specifying Environments](../user-guide/configuring#specifying-environments) +* [Specifying Globals](../user-guide/configuring#specifying-globals) + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-native-reassign: "error"*/ + +Object = null +undefined = 1 +``` + +```js +/*eslint no-native-reassign: "error"*/ +/*eslint-env browser*/ + +window = {} +length = 1 +top = 1 +``` + +```js +/*eslint no-native-reassign: "error"*/ +/*global a:readonly*/ + +a = 1 +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-native-reassign: "error"*/ + +a = 1 +var b = 1 +b = 2 +``` + +```js +/*eslint no-native-reassign: "error"*/ +/*eslint-env browser*/ + +onload = function() {} +``` + +```js +/*eslint no-native-reassign: "error"*/ +/*global a:writable*/ + +a = 1 +``` + +## Options + +This rule accepts an `exceptions` option, which can be used to specify a list of builtins for which reassignments will be allowed: + +```json +{ + "rules": { + "no-native-reassign": ["error", {"exceptions": ["Object"]}] + } +} +``` + +## When Not To Use It + +If you are trying to override one of the native objects. + +## Related Rules + +* [no-extend-native](no-extend-native.md) +* [no-redeclare](no-redeclare.md) +* [no-shadow](no-shadow.md) diff --git a/eslint/docs/rules/no-negated-condition.md b/eslint/docs/rules/no-negated-condition.md new file mode 100644 index 0000000..58addae --- /dev/null +++ b/eslint/docs/rules/no-negated-condition.md @@ -0,0 +1,58 @@ +# disallow negated conditions (no-negated-condition) + +Negated conditions are more difficult to understand. Code can be made more readable by inverting the condition instead. + +## Rule Details + +This rule disallows negated conditions in either of the following: + +* `if` statements which have an `else` branch +* ternary expressions + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-negated-condition: "error"*/ + +if (!a) { + doSomething(); +} else { + doSomethingElse(); +} + +if (a != b) { + doSomething(); +} else { + doSomethingElse(); +} + +if (a !== b) { + doSomething(); +} else { + doSomethingElse(); +} + +!a ? c : b +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-negated-condition: "error"*/ + +if (!a) { + doSomething(); +} + +if (!a) { + doSomething(); +} else if (b) { + doSomething(); +} + +if (a != b) { + doSomething(); +} + +a ? b : c +``` diff --git a/eslint/docs/rules/no-negated-in-lhs.md b/eslint/docs/rules/no-negated-in-lhs.md new file mode 100644 index 0000000..dd3d422 --- /dev/null +++ b/eslint/docs/rules/no-negated-in-lhs.md @@ -0,0 +1,41 @@ +# disallow negating the left operand in `in` expressions (no-negated-in-lhs) + +This rule was **deprecated** in ESLint v3.3.0 and replaced by the [no-unsafe-negation](no-unsafe-negation.md) rule. + +## Rule Details + +Just as developers might type `-a + b` when they mean `-(a + b)` for the negative of a sum, they might type `!key in object` by mistake when they almost certainly mean `!(key in object)` to test that a key is not in an object. + +## Rule Details + +This rule disallows negating the left operand in `in` expressions. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-negated-in-lhs: "error"*/ + +if(!key in object) { + // operator precedence makes it equivalent to (!key) in object + // and type conversion makes it equivalent to (key ? "false" : "true") in object +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-negated-in-lhs: "error"*/ + +if(!(key in object)) { + // key is not in object +} + +if(('' + !key) in object) { + // make operator precedence and type conversion explicit + // in a rare situation when that is the intended meaning +} +``` + +## When Not To Use It + +Never. diff --git a/eslint/docs/rules/no-nested-ternary.md b/eslint/docs/rules/no-nested-ternary.md new file mode 100644 index 0000000..8c9cb0d --- /dev/null +++ b/eslint/docs/rules/no-nested-ternary.md @@ -0,0 +1,44 @@ +# disallow nested ternary expressions (no-nested-ternary) + +Nesting ternary expressions can make code more difficult to understand. + +```js +var foo = bar ? baz : qux === quxx ? bing : bam; +``` + +## Rule Details + +The `no-nested-ternary` rule disallows nested ternary expressions. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-nested-ternary: "error"*/ + +var thing = foo ? bar : baz === qux ? quxx : foobar; + +foo ? baz === qux ? quxx() : foobar() : bar(); +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-nested-ternary: "error"*/ + +var thing = foo ? bar : foobar; + +var thing; + +if (foo) { + thing = bar; +} else if (baz === qux) { + thing = quxx; +} else { + thing = foobar; +} +``` + +## Related Rules + +* [no-ternary](no-ternary.md) +* [no-unneeded-ternary](no-unneeded-ternary.md) diff --git a/eslint/docs/rules/no-new-func.md b/eslint/docs/rules/no-new-func.md new file mode 100644 index 0000000..16ac00c --- /dev/null +++ b/eslint/docs/rules/no-new-func.md @@ -0,0 +1,36 @@ +# Disallow Function Constructor (no-new-func) + +It's possible to create functions in JavaScript using the `Function` constructor, such as: + +```js +var x = new Function("a", "b", "return a + b"); +``` + +This is considered by many to be a bad practice due to the difficulty in debugging and reading these types of functions. + +## Rule Details + +This error is raised to highlight the use of a bad practice. By passing a string to the Function constructor, you are requiring the engine to parse that string much in the way it has to when you call the `eval` function. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-new-func: "error"*/ + +var x = new Function("a", "b", "return a + b"); +var x = Function("a", "b", "return a + b"); +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-new-func: "error"*/ + +var x = function (a, b) { + return a + b; +}; +``` + +## When Not To Use It + +In more advanced cases where you really need to use the `Function` constructor. diff --git a/eslint/docs/rules/no-new-object.md b/eslint/docs/rules/no-new-object.md new file mode 100644 index 0000000..5186682 --- /dev/null +++ b/eslint/docs/rules/no-new-object.md @@ -0,0 +1,50 @@ +# disallow `Object` constructors (no-new-object) + +The `Object` constructor is used to create new generic objects in JavaScript, such as: + +```js +var myObject = new Object(); +``` + +However, this is no different from using the more concise object literal syntax: + +```js +var myObject = {}; +``` + +For this reason, many prefer to always use the object literal syntax and never use the `Object` constructor. + +While there are no performance differences between the two approaches, the byte savings and conciseness of the object literal form is what has made it the de facto way of creating new objects. + +## Rule Details + +This rule disallows `Object` constructors. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-new-object: "error"*/ + +var myObject = new Object(); + +var myObject = new Object; +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-new-object: "error"*/ + +var myObject = new CustomObject(); + +var myObject = {}; +``` + +## When Not To Use It + +If you wish to allow the use of the `Object` constructor, you can safely turn this rule off. + +## Related Rules + +* [no-array-constructor](no-array-constructor.md) +* [no-new-wrappers](no-new-wrappers.md) diff --git a/eslint/docs/rules/no-new-require.md b/eslint/docs/rules/no-new-require.md new file mode 100644 index 0000000..5ffaf2a --- /dev/null +++ b/eslint/docs/rules/no-new-require.md @@ -0,0 +1,46 @@ +# Disallow new require (no-new-require) + +The `require` function is used to include modules that exist in separate files, such as: + +```js +var appHeader = require('app-header'); +``` + +Some modules return a constructor which can potentially lead to code such as: + +```js +var appHeader = new require('app-header'); +``` + +Unfortunately, this introduces a high potential for confusion since the code author likely meant to write: + +```js +var appHeader = new (require('app-header')); +``` + +For this reason, it is usually best to disallow this particular expression. + +## Rule Details + +This rule aims to eliminate use of the `new require` expression. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-new-require: "error"*/ + +var appHeader = new require('app-header'); +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-new-require: "error"*/ + +var AppHeader = require('app-header'); +var appHeader = new AppHeader(); +``` + +## When Not To Use It + +If you are using a custom implementation of `require` and your code will never be used in projects where a standard `require` (CommonJS, Node.js, AMD) is expected, you can safely turn this rule off. diff --git a/eslint/docs/rules/no-new-symbol.md b/eslint/docs/rules/no-new-symbol.md new file mode 100644 index 0000000..66167cf --- /dev/null +++ b/eslint/docs/rules/no-new-symbol.md @@ -0,0 +1,48 @@ +# Disallow Symbol Constructor (no-new-symbol) + +`Symbol` is not intended to be used with the `new` operator, but to be called as a function. + +```js +var foo = new Symbol("foo"); +``` + +This throws a `TypeError` exception. + +## Rule Details + +This rule is aimed at preventing the accidental calling of `Symbol` with the `new` operator. + +## Examples + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-new-symbol: "error"*/ +/*eslint-env es6*/ + +var foo = new Symbol('foo'); +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-new-symbol: "error"*/ +/*eslint-env es6*/ + +var foo = Symbol('foo'); + + +// Ignores shadowed Symbol. +function bar(Symbol) { + const baz = new Symbol("baz"); +} + +``` + +## When Not To Use It + +This rule should not be used in ES3/5 environments. + +## Further Reading + +* [Symbol Objects specification](https://www.ecma-international.org/ecma-262/6.0/#sec-symbol-objects) diff --git a/eslint/docs/rules/no-new-wrappers.md b/eslint/docs/rules/no-new-wrappers.md new file mode 100644 index 0000000..45247e5 --- /dev/null +++ b/eslint/docs/rules/no-new-wrappers.md @@ -0,0 +1,78 @@ +# Disallow Primitive Wrapper Instances (no-new-wrappers) + +There are three primitive types in JavaScript that have wrapper objects: string, number, and boolean. These are represented by the constructors `String`, `Number`, and `Boolean`, respectively. The primitive wrapper types are used whenever one of these primitive values is read, providing them with object-like capabilities such as methods. Behind the scenes, an object of the associated wrapper type is created and then destroyed, which is why you can call methods on primitive values, such as: + +```js +var text = "Hello world".substring(2); +``` + +Behind the scenes in this example, a `String` object is constructed. The `substring()` method exists on `String.prototype` and so is accessible to the string instance. + +It's also possible to manually create a new wrapper instance: + +```js +var stringObject = new String("Hello world"); +var numberObject = new Number(33); +var booleanObject = new Boolean(false); +``` + +Although possible, there aren't any good reasons to use these primitive wrappers as constructors. They tend to confuse other developers more than anything else because they seem like they should act as primitives, but they do not. For example: + +```js +var stringObject = new String("Hello world"); +console.log(typeof stringObject); // "object" + +var text = "Hello world"; +console.log(typeof text); // "string" + +var booleanObject = new Boolean(false); +if (booleanObject) { // all objects are truthy! + console.log("This executes"); +} +``` + +The first problem is that primitive wrapper objects are, in fact, objects. That means `typeof` will return `"object"` instead of `"string"`, `"number"`, or `"boolean"`. The second problem comes with boolean objects. Every object is truthy, that means an instance of `Boolean` always resolves to `true` even when its actual value is `false`. + +For these reasons, it's considered a best practice to avoid using primitive wrapper types with `new`. + +## Rule Details + +This rule aims to eliminate the use of `String`, `Number`, and `Boolean` with the `new` operator. As such, it warns whenever it sees `new String`, `new Number`, or `new Boolean`. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-new-wrappers: "error"*/ + +var stringObject = new String("Hello world"); +var numberObject = new Number(33); +var booleanObject = new Boolean(false); + +var stringObject = new String; +var numberObject = new Number; +var booleanObject = new Boolean; +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-new-wrappers: "error"*/ + +var text = String(someValue); +var num = Number(someValue); + +var object = new MyString(); +``` + +## When Not To Use It + +If you want to allow the use of primitive wrapper objects, then you can safely disable this rule. + +## Further Reading + +* [Wrapper objects](https://www.inkling.com/read/javascript-definitive-guide-david-flanagan-6th/chapter-3/wrapper-objects) + +## Related Rules + +* [no-array-constructor](no-array-constructor.md) +* [no-new-object](no-new-object.md) diff --git a/eslint/docs/rules/no-new.md b/eslint/docs/rules/no-new.md new file mode 100644 index 0000000..19ae097 --- /dev/null +++ b/eslint/docs/rules/no-new.md @@ -0,0 +1,37 @@ +# Disallow new For Side Effects (no-new) + +The goal of using `new` with a constructor is typically to create an object of a particular type and store that object in a variable, such as: + +```js +var person = new Person(); +``` + +It's less common to use `new` and not store the result, such as: + +```js +new Person(); +``` + +In this case, the created object is thrown away because its reference isn't stored anywhere, and in many cases, this means that the constructor should be replaced with a function that doesn't require `new` to be used. + +## Rule Details + +This rule is aimed at maintaining consistency and convention by disallowing constructor calls using the `new` keyword that do not assign the resulting object to a variable. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-new: "error"*/ + +new Thing(); +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-new: "error"*/ + +var thing = new Thing(); + +Thing(); +``` diff --git a/eslint/docs/rules/no-obj-calls.md b/eslint/docs/rules/no-obj-calls.md new file mode 100644 index 0000000..7c8f715 --- /dev/null +++ b/eslint/docs/rules/no-obj-calls.md @@ -0,0 +1,65 @@ +# disallow calling global object properties as functions (no-obj-calls) + +ECMAScript provides several global objects that are intended to be used as-is. Some of these objects look as if they could be constructors due their capitalization (such as `Math` and `JSON`) but will throw an error if you try to execute them as functions. + +The [ECMAScript 5 specification](https://es5.github.io/#x15.8) makes it clear that both `Math` and `JSON` cannot be invoked: + +> The Math object does not have a `[[Call]]` internal property; it is not possible to invoke the Math object as a function. + +The [ECMAScript 2015 specification](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-reflect-object) makes it clear that `Reflect` cannot be invoked: + +> The Reflect object also does not have a `[[Call]]` internal method; it is not possible to invoke the Reflect object as a function. + +And the [ECMAScript 2017 specification](https://www.ecma-international.org/ecma-262/8.0/index.html#sec-atomics-object) makes it clear that `Atomics` cannot be invoked: + +> The Atomics object does not have a `[[Call]]` internal method; it is not possible to invoke the Atomics object as a function. + +## Rule Details + +This rule disallows calling the `Math`, `JSON`, `Reflect` and `Atomics` objects as functions. + +This rule also disallows using these objects as constructors with the `new` operator. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-obj-calls: "error"*/ +/*eslint-env es2017*/ + +var math = Math(); + +var newMath = new Math(); + +var json = JSON(); + +var newJSON = new JSON(); + +var reflect = Reflect(); + +var newReflect = new Reflect(); + +var atomics = Atomics(); + +var newAtomics = new Atomics(); +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-obj-calls: "error"*/ +/*eslint-env es2017*/ + +function area(r) { + return Math.PI * r * r; +} + +var object = JSON.parse("{}"); + +var value = Reflect.get({ x: 1, y: 2 }, "x"); + +var first = Atomics.load(foo, 0); +``` + +## Further Reading + +* [The Math Object](https://es5.github.io/#x15.8) diff --git a/eslint/docs/rules/no-octal-escape.md b/eslint/docs/rules/no-octal-escape.md new file mode 100644 index 0000000..f595217 --- /dev/null +++ b/eslint/docs/rules/no-octal-escape.md @@ -0,0 +1,31 @@ +# disallow octal escape sequences in string literals (no-octal-escape) + +As of the ECMAScript 5 specification, octal escape sequences in string literals are deprecated and should not be used. Unicode escape sequences should be used instead. + +```js +var foo = "Copyright \251"; +``` + +## Rule Details + +This rule disallows octal escape sequences in string literals. + +If ESLint parses code in strict mode, the parser (instead of this rule) reports the error. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-octal-escape: "error"*/ + +var foo = "Copyright \251"; +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-octal-escape: "error"*/ + +var foo = "Copyright \u00A9"; // unicode + +var foo = "Copyright \xA9"; // hexadecimal +``` diff --git a/eslint/docs/rules/no-octal.md b/eslint/docs/rules/no-octal.md new file mode 100644 index 0000000..a84fcce --- /dev/null +++ b/eslint/docs/rules/no-octal.md @@ -0,0 +1,36 @@ +# disallow octal literals (no-octal) + +Octal literals are numerals that begin with a leading zero, such as: + +```js +var num = 071; // 57 +``` + +Because the leading zero which identifies an octal literal has been a source of confusion and error in JavaScript code, ECMAScript 5 deprecates the use of octal numeric literals. + +## Rule Details + +The rule disallows octal literals. + +If ESLint parses code in strict mode, the parser (instead of this rule) reports the error. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-octal: "error"*/ + +var num = 071; +var result = 5 + 07; +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-octal: "error"*/ + +var num = "071"; +``` + +## Compatibility + +* **JSHint**: W115 diff --git a/eslint/docs/rules/no-param-reassign.md b/eslint/docs/rules/no-param-reassign.md new file mode 100644 index 0000000..47a7af3 --- /dev/null +++ b/eslint/docs/rules/no-param-reassign.md @@ -0,0 +1,160 @@ +# Disallow Reassignment of Function Parameters (no-param-reassign) + +Assignment to variables declared as function parameters can be misleading and lead to confusing behavior, as modifying function parameters will also mutate the `arguments` object. Often, assignment to function parameters is unintended and indicative of a mistake or programmer error. + +This rule can be also configured to fail when function parameters are modified. Side effects on parameters can cause counter-intuitive execution flow and make errors difficult to track down. + +## Rule Details + +This rule aims to prevent unintended behavior caused by modification or reassignment of function parameters. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-param-reassign: "error"*/ + +function foo(bar) { + bar = 13; +} + +function foo(bar) { + bar++; +} + +function foo(bar) { + for (bar in baz) {} +} + +function foo(bar) { + for (bar of baz) {} +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-param-reassign: "error"*/ + +function foo(bar) { + var baz = bar; +} +``` + +## Options + +This rule takes one option, an object, with a boolean property `"props"`, and arrays `"ignorePropertyModificationsFor"` and `"ignorePropertyModificationsForRegex"`. `"props"` is `false` by default. If `"props"` is set to `true`, this rule warns against the modification of parameter properties unless they're included in `"ignorePropertyModificationsFor"` or `"ignorePropertyModificationsForRegex"`, which is an empty array by default. + +### props + +Examples of **correct** code for the default `{ "props": false }` option: + +```js +/*eslint no-param-reassign: ["error", { "props": false }]*/ + +function foo(bar) { + bar.prop = "value"; +} + +function foo(bar) { + delete bar.aaa; +} + +function foo(bar) { + bar.aaa++; +} + +function foo(bar) { + for (bar.aaa in baz) {} +} + +function foo(bar) { + for (bar.aaa of baz) {} +} +``` + +Examples of **incorrect** code for the `{ "props": true }` option: + +```js +/*eslint no-param-reassign: ["error", { "props": true }]*/ + +function foo(bar) { + bar.prop = "value"; +} + +function foo(bar) { + delete bar.aaa; +} + +function foo(bar) { + bar.aaa++; +} + +function foo(bar) { + for (bar.aaa in baz) {} +} + +function foo(bar) { + for (bar.aaa of baz) {} +} +``` + +Examples of **correct** code for the `{ "props": true }` option with `"ignorePropertyModificationsFor"` set: + +```js +/*eslint no-param-reassign: ["error", { "props": true, "ignorePropertyModificationsFor": ["bar"] }]*/ + +function foo(bar) { + bar.prop = "value"; +} + +function foo(bar) { + delete bar.aaa; +} + +function foo(bar) { + bar.aaa++; +} + +function foo(bar) { + for (bar.aaa in baz) {} +} + +function foo(bar) { + for (bar.aaa of baz) {} +} +``` + +Examples of **correct** code for the `{ "props": true }` option with `"ignorePropertyModificationsForRegex"` set: + +```js +/*eslint no-param-reassign: ["error", { "props": true, "ignorePropertyModificationsForRegex": ["^bar"] }]*/ + +function foo(barVar) { + barVar.prop = "value"; +} + +function foo(barrito) { + delete barrito.aaa; +} + +function foo(bar_) { + bar_.aaa++; +} + +function foo(barBaz) { + for (barBaz.aaa in baz) {} +} + +function foo(barBaz) { + for (barBaz.aaa of baz) {} +} +``` + + +## When Not To Use It + +If you want to allow assignment to function parameters, then you can safely disable this rule. + +## Further Reading + +* [JavaScript: Don’t Reassign Your Function Arguments](https://spin.atomicobject.com/2011/04/10/javascript-don-t-reassign-your-function-arguments/) diff --git a/eslint/docs/rules/no-path-concat.md b/eslint/docs/rules/no-path-concat.md new file mode 100644 index 0000000..998ec10 --- /dev/null +++ b/eslint/docs/rules/no-path-concat.md @@ -0,0 +1,50 @@ +# Disallow string concatenation when using `__dirname` and `__filename` (no-path-concat) + +In Node.js, the `__dirname` and `__filename` global variables contain the directory path and the file path of the currently executing script file, respectively. Sometimes, developers try to use these variables to create paths to other files, such as: + +```js +var fullPath = __dirname + "/foo.js"; +``` + +However, there are a few problems with this. First, you can't be sure what type of system the script is running on. Node.js can be run on any computer, including Windows, which uses a different path separator. It's very easy, therefore, to create an invalid path using string concatenation and assuming Unix-style separators. There's also the possibility of having double separators, or otherwise ending up with an invalid path. + +In order to avoid any confusion as to how to create the correct path, Node.js provides the `path` module. This module uses system-specific information to always return the correct value. So you can rewrite the previous example as: + +```js +var fullPath = path.join(__dirname, "foo.js"); +``` + +This example doesn't need to include separators as `path.join()` will do it in the most appropriate manner. Alternately, you can use `path.resolve()` to retrieve the fully-qualified path: + +```js +var fullPath = path.resolve(__dirname, "foo.js"); +``` + +Both `path.join()` and `path.resolve()` are suitable replacements for string concatenation wherever file or directory paths are being created. + +## Rule Details + +This rule aims to prevent string concatenation of directory paths in Node.js + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-path-concat: "error"*/ + +var fullPath = __dirname + "/foo.js"; + +var fullPath = __filename + "/foo.js"; + +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-path-concat: "error"*/ + +var fullPath = dirname + "/foo.js"; +``` + +## When Not To Use It + +If you want to allow string concatenation of path names. diff --git a/eslint/docs/rules/no-plusplus.md b/eslint/docs/rules/no-plusplus.md new file mode 100644 index 0000000..e1b1ca3 --- /dev/null +++ b/eslint/docs/rules/no-plusplus.md @@ -0,0 +1,100 @@ +# disallow the unary operators `++` and `--` (no-plusplus) + +Because the unary `++` and `--` operators are subject to automatic semicolon insertion, differences in whitespace can change semantics of source code. + +```js +var i = 10; +var j = 20; + +i ++ +j +// i = 11, j = 20 +``` + +```js +var i = 10; +var j = 20; + +i +++ +j +// i = 10, j = 21 +``` + +## Rule Details + +This rule disallows the unary operators `++` and `--`. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-plusplus: "error"*/ + +var foo = 0; +foo++; + +var bar = 42; +bar--; + +for (i = 0; i < l; i++) { + return; +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-plusplus: "error"*/ + +var foo = 0; +foo += 1; + +var bar = 42; +bar -= 1; + +for (i = 0; i < l; i += 1) { + return; +} +``` + +## Options + +This rule has an object option. + +* `"allowForLoopAfterthoughts": true` allows unary operators `++` and `--` in the afterthought (final expression) of a `for` loop. + +### allowForLoopAfterthoughts + +Examples of **correct** code for this rule with the `{ "allowForLoopAfterthoughts": true }` option: + +```js +/*eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }]*/ + +for (i = 0; i < l; i++) { + doSomething(i); +} + +for (i = l; i >= 0; i--) { + doSomething(i); +} + +for (i = 0, j = l; i < l; i++, j--) { + doSomething(i, j); +} +``` + +Examples of **incorrect** code for this rule with the `{ "allowForLoopAfterthoughts": true }` option: + +```js +/*eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }]*/ + +for (i = 0; i < l; j = i++) { + doSomething(i, j); +} + +for (i = l; i--;) { + doSomething(i); +} + +for (i = 0; i < l;) i++; +``` diff --git a/eslint/docs/rules/no-process-env.md b/eslint/docs/rules/no-process-env.md new file mode 100644 index 0000000..9db800f --- /dev/null +++ b/eslint/docs/rules/no-process-env.md @@ -0,0 +1,39 @@ +# Disallow process.env (no-process-env) + +The `process.env` object in Node.js is used to store deployment/configuration parameters. Littering it through out a project could lead to maintenance issues as it's another kind of global dependency. As such, it could lead to merge conflicts in a multi-user setup and deployment issues in a multi-server setup. Instead, one of the best practices is to define all those parameters in a single configuration/settings file which could be accessed throughout the project. + + +## Rule Details + +This rule is aimed at discouraging use of `process.env` to avoid global dependencies. As such, it will warn whenever `process.env` is used. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-process-env: "error"*/ + +if(process.env.NODE_ENV === "development") { + //... +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-process-env: "error"*/ + +var config = require("./config"); + +if(config.env === "development") { + //... +} +``` + +## When Not To Use It + +If you prefer to use `process.env` throughout your project to retrieve values from environment variables, then you can safely disable this rule. + +## Further Reading + +* [How to store Node.js deployment settings/configuration files? - Stack Overflow](https://stackoverflow.com/questions/5869216/how-to-store-node-js-deployment-settings-configuration-files) +* [Storing Node.js application config data - Ben Hall's blog](https://blog.benhall.me.uk/2012/02/storing-application-config-data-in/) diff --git a/eslint/docs/rules/no-process-exit.md b/eslint/docs/rules/no-process-exit.md new file mode 100644 index 0000000..0070a09 --- /dev/null +++ b/eslint/docs/rules/no-process-exit.md @@ -0,0 +1,48 @@ +# Disallow process.exit() (no-process-exit) + +The `process.exit()` method in Node.js is used to immediately stop the Node.js process and exit. This is a dangerous operation because it can occur in any method at any point in time, potentially stopping a Node.js application completely when an error occurs. For example: + +```js +if (somethingBadHappened) { + console.error("Something bad happened!"); + process.exit(1); +} +``` + +This code could appear in any module and will stop the entire application when `somethingBadHappened` is truthy. This doesn't give the application any chance to respond to the error. It's usually better to throw an error and allow the application to handle it appropriately: + +```js +if (somethingBadHappened) { + throw new Error("Something bad happened!"); +} +``` + +By throwing an error in this way, other parts of the application have an opportunity to handle the error rather than stopping the application altogether. If the error bubbles all the way up to the process without being handled, then the process will exit and a non-zero exit code will returned, so the end result is the same. + +If you are using `process.exit()` only for specifying the exit code, you can set [`process.exitCode`](https://nodejs.org/api/process.html#process_process_exitcode) (introduced in Node.js 0.11.8) instead. + +## Rule Details + +This rule aims to prevent the use of `process.exit()` in Node.js JavaScript. As such, it warns whenever `process.exit()` is found in code. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-process-exit: "error"*/ + +process.exit(1); +process.exit(0); +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-process-exit: "error"*/ + +Process.exit(); +var exit = process.exit; +``` + +## When Not To Use It + +There may be a part of a Node.js application that is responsible for determining the correct exit code to return upon exiting. In that case, you should turn this rule off to allow proper handling of the exit code. diff --git a/eslint/docs/rules/no-proto.md b/eslint/docs/rules/no-proto.md new file mode 100644 index 0000000..9543b7e --- /dev/null +++ b/eslint/docs/rules/no-proto.md @@ -0,0 +1,42 @@ +# Disallow Use of `__proto__` (no-proto) + +`__proto__` property has been deprecated as of ECMAScript 3.1 and shouldn't be used in the code. Use `Object.getPrototypeOf` and `Object.setPrototypeOf` instead. + +## Rule Details + +When an object is created with the `new` operator, `__proto__` is set to the original "prototype" property of the object's constructor function. `Object.getPrototypeOf` is the preferred method of getting the object's prototype. To change an object's prototype, use `Object.setPrototypeOf`. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-proto: "error"*/ + +var a = obj.__proto__; + +var a = obj["__proto__"]; + +obj.__proto__ = b; + +obj["__proto__"] = b; +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-proto: "error"*/ + +var a = Object.getPrototypeOf(obj); + +Object.setPrototypeOf(obj, b); + +var c = { __proto__: a }; +``` + +## When Not To Use It + +You might want to turn this rule off if you need to support legacy browsers which implement the +`__proto__` property but not `Object.getPrototypeOf` or `Object.setPrototypeOf`. + +## Further Reading + +* [Object.getPrototypeOf](http://ejohn.org/blog/objectgetprototypeof/) diff --git a/eslint/docs/rules/no-prototype-builtins.md b/eslint/docs/rules/no-prototype-builtins.md new file mode 100644 index 0000000..7132bcb --- /dev/null +++ b/eslint/docs/rules/no-prototype-builtins.md @@ -0,0 +1,39 @@ +# Disallow use of Object.prototypes builtins directly (no-prototype-builtins) + +In ECMAScript 5.1, `Object.create` was added, which enables the creation of objects with a specified `[[Prototype]]`. `Object.create(null)` is a common pattern used to create objects that will be used as a Map. This can lead to errors when it is assumed that objects will have properties from `Object.prototype`. This rule prevents calling some `Object.prototype` methods directly from an object. + +Additionally, objects can have properties that shadow the builtins on `Object.prototype`, potentially causing unintended behavior or denial-of-service security vulnerabilities. For example, it would be unsafe for a webserver to parse JSON input from a client and call `hasOwnProperty` directly on the resulting object, because a malicious client could send a JSON value like `{"hasOwnProperty": 1}` and cause the server to crash. + +To avoid subtle bugs like this, it's better to always call these methods from `Object.prototype`. For example, `foo.hasOwnProperty("bar")` should be replaced with `Object.prototype.hasOwnProperty.call(foo, "bar")`. + +## Rule Details + +This rule disallows calling some `Object.prototype` methods directly on object instances. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-prototype-builtins: "error"*/ + +var hasBarProperty = foo.hasOwnProperty("bar"); + +var isPrototypeOfBar = foo.isPrototypeOf(bar); + +var barIsEnumerable = foo.propertyIsEnumerable("bar"); +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-prototype-builtins: "error"*/ + +var hasBarProperty = Object.prototype.hasOwnProperty.call(foo, "bar"); + +var isPrototypeOfBar = Object.prototype.isPrototypeOf.call(foo, bar); + +var barIsEnumerable = {}.propertyIsEnumerable.call(foo, "bar"); +``` + +## When Not To Use It + +You may want to turn this rule off if your code only touches objects with hardcoded keys, and you will never use an object that shadows an `Object.prototype` method or which does not inherit from `Object.prototype`. diff --git a/eslint/docs/rules/no-redeclare.md b/eslint/docs/rules/no-redeclare.md new file mode 100644 index 0000000..c3b8422 --- /dev/null +++ b/eslint/docs/rules/no-redeclare.md @@ -0,0 +1,60 @@ +# disallow variable redeclaration (no-redeclare) + +In JavaScript, it's possible to redeclare the same variable name using `var`. This can lead to confusion as to where the variable is actually declared and initialized. + +## Rule Details + +This rule is aimed at eliminating variables that have multiple declarations in the same scope. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-redeclare: "error"*/ + +var a = 3; +var a = 10; +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-redeclare: "error"*/ + +var a = 3; +// ... +a = 10; +``` + +## Options + +This rule takes one optional argument, an object with a boolean property `"builtinGlobals"`. It defaults to `true`. +If set to `true`, this rule also checks redeclaration of built-in globals, such as `Object`, `Array`, `Number`... + +### builtinGlobals + +The `"builtinGlobals"` option will check for redeclaration of built-in globals in global scope. + +Examples of **incorrect** code for the `{ "builtinGlobals": true }` option: + +```js +/*eslint no-redeclare: ["error", { "builtinGlobals": true }]*/ + +var Object = 0; +``` + +Examples of **incorrect** code for the `{ "builtinGlobals": true }` option and the `browser` environment: + +```js +/*eslint no-redeclare: ["error", { "builtinGlobals": true }]*/ +/*eslint-env browser*/ + +var top = 0; +``` + +The `browser` environment has many built-in global variables (for example, `top`). Some of built-in global variables cannot be redeclared. + +Note that when using the `node` or `commonjs` environments (or `ecmaFeatures.globalReturn`, if using the default parser), the top scope of a program is not actually the global scope, but rather a "module" scope. When this is the case, declaring a variable named after a builtin global is not a redeclaration, but rather a shadowing of the global variable. In that case, the [`no-shadow`](no-shadow.md) rule with the `"builtinGlobals"` option should be used. + +## Related Rules + +* [no-shadow](no-shadow.md) diff --git a/eslint/docs/rules/no-regex-spaces.md b/eslint/docs/rules/no-regex-spaces.md new file mode 100644 index 0000000..63a5174 --- /dev/null +++ b/eslint/docs/rules/no-regex-spaces.md @@ -0,0 +1,46 @@ +# disallow multiple spaces in regular expression literals (no-regex-spaces) + +Regular expressions can be very complex and difficult to understand, which is why it's important to keep them as simple as possible in order to avoid mistakes. One of the more error-prone things you can do with a regular expression is to use more than one space, such as: + +```js +var re = /foo bar/; +``` + +In this regular expression, it's very hard to tell how many spaces are intended to be matched. It's better to use only one space and then specify how many spaces are expected, such as: + +```js +var re = /foo {3}bar/; +``` + +Now it is very clear that three spaces are expected to be matched. + +## Rule Details + +This rule disallows multiple spaces in regular expression literals. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-regex-spaces: "error"*/ + +var re = /foo bar/; +var re = new RegExp("foo bar"); +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-regex-spaces: "error"*/ + +var re = /foo {3}bar/; +var re = new RegExp("foo {3}bar"); +``` + +## When Not To Use It + +If you want to allow multiple spaces in a regular expression, then you can safely turn this rule off. + +## Related Rules + +* [no-div-regex](no-div-regex.md) +* [no-control-regex](no-control-regex.md) diff --git a/eslint/docs/rules/no-reserved-keys.md b/eslint/docs/rules/no-reserved-keys.md new file mode 100644 index 0000000..f1f8e41 --- /dev/null +++ b/eslint/docs/rules/no-reserved-keys.md @@ -0,0 +1,53 @@ +# no-reserved-keys: disallow unquoted reserved words as property names in object literals + +(removed) This rule was **removed** in ESLint v1.0 and **replaced** by the [quote-props](quote-props.md) rule. + +ECMAScript 3 described as series of keywords and reserved words, such as `if` and `public`, that are used or intended to be used for a core language feature. The specification also indicated that these keywords and reserved words could not be used as object property names without being enclosed in strings. An error occurs in an ECMAScript 3 environment when you use a keyword or reserved word in an object literal. For example: + +```js +var values = { + enum: ["red", "blue", "green"] // throws an error in ECMAScript 3 +} +``` + +In this code, `enum` is used as an object key and will throw an error in an ECMAScript 3 environment (such as Internet Explorer 8). + +ECMAScript 5 loosened the restriction such that keywords and reserved words can be used as object keys without causing an error. However, any code that needs to run in ECMAScript 3 still needs to avoid using keywords and reserved words as keys. + +## Rule Details + +This rule is aimed at eliminating the use of ECMAScript 3 keywords and reserved words as object literal keys. As such, it warns whenever an object key would throw an error in an ECMAScript 3 environment. + +Examples of **incorrect** code for this rule: + +```js +var superman = { + class: "Superhero", + private: "Clark Kent" +}; + +var values = { + enum: ["red", "blue", "green"] +}; +``` + +Examples of **correct** code for this rule: + +```js +var superman = { + "class": "Superhero", + "private": "Clark Kent" +}; + +var values = { + "enum": ["red", "blue", "green"] +}; +``` + +## When Not To Use It + +If your code is only going to be executed in an ECMAScript 5 or higher environment, then you can safely leave this rule off. + +## Further Reading + +* [Reserved words as property names](https://kangax.github.io/compat-table/es5/#Reserved_words_as_property_names) diff --git a/eslint/docs/rules/no-restricted-exports.md b/eslint/docs/rules/no-restricted-exports.md new file mode 100644 index 0000000..2993857 --- /dev/null +++ b/eslint/docs/rules/no-restricted-exports.md @@ -0,0 +1,106 @@ +# Disallow specified names in exports (no-restricted-exports) + +In a project, certain names may be disallowed from being used as exported names for various reasons. + +## Rule Details + +This rule disallows specified names from being used as exported names. + +## Options + +By default, this rule doesn't disallow any names. Only the names you specify in the configuration will be disallowed. + +This rule has an object option: + +* `"restrictedNamedExports"` is an array of strings, where each string is a name to be restricted. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-restricted-exports: ["error", { + "restrictedNamedExports": ["foo", "bar", "Baz", "a", "b", "c", "d"] +}]*/ + +export const foo = 1; + +export function bar() {} + +export class Baz {} + +const a = {}; +export { a }; + +function someFunction() {} +export { someFunction as b }; + +export { c } from 'some_module'; + +export { something as d } from 'some_module'; +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-restricted-exports: ["error", { + "restrictedNamedExports": ["foo", "bar", "Baz", "a", "b", "c", "d"] +}]*/ + +export const quux = 1; + +export function myFunction() {} + +export class MyClass {} + +const a = {}; +export { a as myObject }; + +function someFunction() {} +export { someFunction }; + +export { c as someName } from 'some_module'; + +export { something } from 'some_module'; +``` + +### Default exports + +By design, this rule doesn't disallow `export default` declarations. If you configure `"default"` as a restricted name, that restriction will apply only to named export declarations. + +Examples of additional **incorrect** code for this rule: + +```js +/*eslint no-restricted-exports: ["error", { "restrictedNamedExports": ["default"] }]*/ + +function foo() {} + +export { foo as default }; +``` + +```js +/*eslint no-restricted-exports: ["error", { "restrictedNamedExports": ["default"] }]*/ + +export { default } from 'some_module'; +``` + +Examples of additional **correct** code for this rule: + +```js +/*eslint no-restricted-exports: ["error", { "restrictedNamedExports": ["default", "foo"] }]*/ + +export default function foo() {} +``` + +## Known Limitations + +This rule doesn't inspect the content of source modules in re-export declarations. In particular, if you are re-exporting everything from another module's export, that export may include a restricted name. This rule cannot detect such cases. + +```js + +//----- some_module.js ----- +export function foo() {} + +//----- my_module.js ----- +/*eslint no-restricted-exports: ["error", { "restrictedNamedExports": ["foo"] }]*/ + +export * from 'some_module'; // allowed, although this declaration exports "foo" from my_module +``` diff --git a/eslint/docs/rules/no-restricted-globals.md b/eslint/docs/rules/no-restricted-globals.md new file mode 100644 index 0000000..dd3e15b --- /dev/null +++ b/eslint/docs/rules/no-restricted-globals.md @@ -0,0 +1,90 @@ +# Disallow specific global variables (no-restricted-globals) + +Disallowing usage of specific global variables can be useful if you want to allow a set of global +variables by enabling an environment, but still want to disallow some of those. + +For instance, early Internet Explorer versions exposed the current DOM event as a global variable +`event`, but using this variable has been considered as a bad practice for a long time. Restricting +this will make sure this variable isn't used in browser code. + +## Rule Details + +This rule allows you to specify global variable names that you don't want to use in your application. + +## Options + +This rule takes a list of strings, where each string is a global to be restricted: + +```json +{ + "rules": { + "no-restricted-globals": ["error", "event", "fdescribe"] + } +} +``` + +Alternatively, the rule also accepts objects, where the global name and an optional custom message are specified: + +```json +{ + "rules": { + "no-restricted-globals": [ + "error", + { + "name": "event", + "message": "Use local parameter instead." + }, + { + "name": "fdescribe", + "message": "Do not commit fdescribe. Use describe instead." + } + ] + } +} +``` + +Examples of **incorrect** code for sample `"event", "fdescribe"` global variable names: + +```js +/*global event, fdescribe*/ +/*eslint no-restricted-globals: ["error", "event", "fdescribe"]*/ + +function onClick() { + console.log(event); +} + +fdescribe("foo", function() { +}); +``` + +Examples of **correct** code for a sample `"event"` global variable name: + +```js +/*global event*/ +/*eslint no-restricted-globals: ["error", "event"]*/ + +import event from "event-module"; +``` + +```js +/*global event*/ +/*eslint no-restricted-globals: ["error", "event"]*/ + +var event = 1; +``` + +Examples of **incorrect** code for a sample `"event"` global variable name, along with a custom error message: + +```js +/*global event*/ +/* eslint no-restricted-globals: ["error", { name: "event", message: "Use local parameter instead." }] */ + +function onClick() { + console.log(event); // Unexpected global variable 'event'. Use local parameter instead. +} +``` + +## Related Rules + +* [no-restricted-properties](no-restricted-properties.md) +* [no-restricted-syntax](no-restricted-syntax.md) diff --git a/eslint/docs/rules/no-restricted-imports.md b/eslint/docs/rules/no-restricted-imports.md new file mode 100644 index 0000000..e86544d --- /dev/null +++ b/eslint/docs/rules/no-restricted-imports.md @@ -0,0 +1,185 @@ +# Disallow specific imports (no-restricted-imports) + +Imports are an ES6/ES2015 standard for making the functionality of other modules available in your current module. In CommonJS this is implemented through the `require()` call which makes this ESLint rule roughly equivalent to its CommonJS counterpart `no-restricted-modules`. + +Why would you want to restrict imports? + +* Some imports might not make sense in a particular environment. For example, Node.js' `fs` module would not make sense in an environment that didn't have a file system. + +* Some modules provide similar or identical functionality, think `lodash` and `underscore`. Your project may have standardized on a module. You want to make sure that the other alternatives are not being used as this would unnecessarily bloat the project and provide a higher maintenance cost of two dependencies when one would suffice. + +## Rule Details + +This rule allows you to specify imports that you don't want to use in your application. + +## Options + +The syntax to specify restricted imports looks like this: + +```json +"no-restricted-imports": ["error", "import1", "import2"] +``` + +or like this: + +```json +"no-restricted-imports": ["error", { "paths": ["import1", "import2"] }] +``` + +When using the object form, you can also specify an array of gitignore-style patterns: + +```json +"no-restricted-imports": ["error", { + "paths": ["import1", "import2"], + "patterns": ["import1/private/*", "import2/*", "!import2/good"] +}] +``` + +You may also specify a custom message for any paths you want to restrict as follows: + +```json +"no-restricted-imports": ["error", { + "name": "import-foo", + "message": "Please use import-bar instead." +}, { + "name": "import-baz", + "message": "Please use import-quux instead." +}] +``` + +or like this: + +```json +"no-restricted-imports": ["error", { + "paths": [{ + "name": "import-foo", + "message": "Please use import-bar instead." + }, { + "name": "import-baz", + "message": "Please use import-quux instead." + }] +}] +``` + +or like this if you need to restrict only certain imports from a module: + +```json +"no-restricted-imports": ["error", { + "paths": [{ + "name": "import-foo", + "importNames": ["Bar"], + "message": "Please use Bar from /import-bar/baz/ instead." + }] +}] +``` + +The custom message will be appended to the default error message. Please note that you may not specify custom error messages for restricted patterns as a particular import may match more than one pattern. + +To restrict the use of all Node.js core imports (via https://github.com/nodejs/node/tree/master/lib): + +```json + "no-restricted-imports": ["error", + "assert","buffer","child_process","cluster","crypto","dgram","dns","domain","events","freelist","fs","http","https","module","net","os","path","punycode","querystring","readline","repl","smalloc","stream","string_decoder","sys","timers","tls","tracing","tty","url","util","vm","zlib" + ], +``` + +## Examples + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-restricted-imports: ["error", "fs"]*/ + +import fs from 'fs'; +``` + +```js +/*eslint no-restricted-imports: ["error", "fs"]*/ + +export { fs } from 'fs'; +``` + +```js +/*eslint no-restricted-imports: ["error", "fs"]*/ + +export * from 'fs'; +``` + +```js +/*eslint no-restricted-imports: ["error", { "paths": ["cluster"] }]*/ + +import cluster from 'cluster'; +``` + +```js +/*eslint no-restricted-imports: ["error", { "patterns": ["lodash/*"] }]*/ + +import pick from 'lodash/pick'; +``` + +```js +/*eslint no-restricted-imports: ["error", { paths: [{ + name: "foo", + importNames: ["default"], + message: "Please use the default import from '/bar/baz/' instead." +}]}]*/ + +import DisallowedObject from "foo"; +``` + +```js +/*eslint no-restricted-imports: ["error", { paths: [{ + name: "foo", + importNames: ["DisallowedObject"], + message: "Please import 'DisallowedObject' from '/bar/baz/' instead." +}]}]*/ + +import { DisallowedObject as AllowedObject } from "foo"; +``` + +```js +/*eslint no-restricted-imports: ["error", { paths: [{ + name: "foo", + importNames: ["DisallowedObject"], + message: "Please import 'DisallowedObject' from '/bar/baz/' instead." +}]}]*/ + +import * as Foo from "foo"; +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-restricted-imports: ["error", "fs"]*/ + +import crypto from 'crypto'; +export { foo } from "bar"; +``` + +```js +/*eslint no-restricted-imports: ["error", { "paths": ["fs"], "patterns": ["eslint/*"] }]*/ + +import crypto from 'crypto'; +import eslint from 'eslint'; +export * from "path"; +``` + +```js +/*eslint no-restricted-imports: ["error", { paths: [{ name: "foo", importNames: ["DisallowedObject"] }] }]*/ + +import DisallowedObject from "foo" +``` + +```js +/*eslint no-restricted-imports: ["error", { paths: [{ + name: "foo", + importNames: ["DisallowedObject"], + message: "Please import 'DisallowedObject' from '/bar/baz/' instead." +}]}]*/ + +import { AllowedObject as DisallowedObject } from "foo"; +``` + +## When Not To Use It + +Don't use this rule or don't include a module in the list for this rule if you want to be able to import a module in your project without an ESLint error or warning. diff --git a/eslint/docs/rules/no-restricted-modules.md b/eslint/docs/rules/no-restricted-modules.md new file mode 100644 index 0000000..7f9c926 --- /dev/null +++ b/eslint/docs/rules/no-restricted-modules.md @@ -0,0 +1,108 @@ +# Disallow Node.js modules (no-restricted-modules) + +A module in Node.js is a simple or complex functionality organized in a JavaScript file which can be reused throughout the Node.js +application. The keyword `require` is used in Node.js/CommonJS to import modules into an application. This way you can have dynamic loading where the loaded module name isn't predefined /static, or where you conditionally load a module only if it's "truly required". + +Why would you want to restrict a module? + +Disallowing usage of specific Node.js modules can be useful if you want to limit the available methods a developer can use. For example, you can block usage of the `fs` module if you want to disallow file system access. + +## Rule Details + +This rule allows you to specify modules that you don’t want to use in your application. + +## Options + +The rule takes one or more strings as options: the names of restricted modules. + +```json +"no-restricted-modules": ["error", "foo-module", "bar-module"] +``` + +It can also take an object with lists of `paths` and gitignore-style `patterns` strings. + +```json +"no-restricted-modules": ["error", { "paths": ["foo-module", "bar-module"] }] +``` + +```json +"no-restricted-modules": ["error", { + "paths": ["foo-module", "bar-module"], + "patterns": ["foo-module/private/*", "bar-module/*","!baz-module/good"] +}] +``` + +You may also specify a custom message for any paths you want to restrict as follows: + +```json +"no-restricted-modules": ["error", { + "name": "foo-module", + "message": "Please use bar-module instead." + } +] +``` + +or like this: + +```json +"no-restricted-modules": ["error",{ +"paths":[{ + "name": "foo-module", + "message": "Please use bar-module instead." + }] +}] +``` + +The custom message will be appended to the default error message. Please note that you may not specify custom error messages for restricted patterns as a particular module may match more than one pattern. + + +To restrict the use of all Node.js core modules (via https://github.com/nodejs/node/tree/master/lib): + +```json +{ + "no-restricted-modules": ["error", + "assert","buffer","child_process","cluster","crypto","dgram","dns","domain","events","freelist","fs","http","https","module","net","os","path","punycode","querystring","readline","repl","smalloc","stream","string_decoder","sys","timers","tls","tracing","tty","url","util","vm","zlib" + ] +} +``` + +## Examples + +Examples of **incorrect** code for this rule with sample `"fs", "cluster", "lodash"` restricted modules: + +```js +/*eslint no-restricted-modules: ["error", "fs", "cluster"]*/ + +var fs = require('fs'); +var cluster = require('cluster'); +``` + +```js +/*eslint no-restricted-modules: ["error", {"paths": ["cluster"] }]*/ + +var cluster = require('cluster'); +``` + +```js +/*eslint no-restricted-modules: ["error", { "patterns": ["lodash/*"] }]*/ + +var pick = require('lodash/pick'); +``` + +Examples of **correct** code for this rule with sample `"fs", "cluster", "lodash"` restricted modules: + +```js +/*eslint no-restricted-modules: ["error", "fs", "cluster"]*/ + +var crypto = require('crypto'); +``` + +```js +/*eslint no-restricted-modules: ["error", { + "paths": ["fs", "cluster"], + "patterns": ["lodash/*", "!lodash/pick"] +}]*/ + +var crypto = require('crypto'); +var pick = require('lodash/pick'); +``` diff --git a/eslint/docs/rules/no-restricted-properties.md b/eslint/docs/rules/no-restricted-properties.md new file mode 100644 index 0000000..e1824d5 --- /dev/null +++ b/eslint/docs/rules/no-restricted-properties.md @@ -0,0 +1,125 @@ +# disallow certain object properties (no-restricted-properties) + +Certain properties on objects may be disallowed in a codebase. This is useful for deprecating an API or restricting usage of a module's methods. For example, you may want to disallow using `describe.only` when using Mocha or telling people to use `Object.assign` instead of `_.extend`. + + +## Rule Details + +This rule looks for accessing a given property key on a given object name, either when reading the property's value or invoking it as a function. You may specify an optional message to indicate an alternative API or a reason for the restriction. + +### Options + +This rule takes a list of objects, where the object name and property names are specified: + +```json +{ + "rules": { + "no-restricted-properties": [2, { + "object": "disallowedObjectName", + "property": "disallowedPropertyName" + }] + } +} +``` + +Multiple object/property values can be disallowed, and you can specify an optional message: + +```json +{ + "rules": { + "no-restricted-properties": [2, { + "object": "disallowedObjectName", + "property": "disallowedPropertyName" + }, { + "object": "disallowedObjectName", + "property": "anotherDisallowedPropertyName", + "message": "Please use allowedObjectName.allowedPropertyName." + }] + } +} +``` + +If the object name is omitted, the property is disallowed for all objects: + +```json +{ + "rules": { + "no-restricted-properties": [2, { + "property": "__defineGetter__", + "message": "Please use Object.defineProperty instead." + }] + } +} +``` + +If the property name is omitted, accessing any property of the given object is disallowed: + +```json +{ + "rules": { + "no-restricted-properties": [2, { + "object": "require", + "message": "Please call require() directly." + }] + } +} +``` + +Examples of **incorrect** code for this rule: + +```js +/* eslint no-restricted-properties: [2, { + "object": "disallowedObjectName", + "property": "disallowedPropertyName" +}] */ + +var example = disallowedObjectName.disallowedPropertyName; /*error Disallowed object property: disallowedObjectName.disallowedPropertyName.*/ + +disallowedObjectName.disallowedPropertyName(); /*error Disallowed object property: disallowedObjectName.disallowedPropertyName.*/ +``` + +```js +/* eslint no-restricted-properties: [2, { + "property": "__defineGetter__" +}] */ + +foo.__defineGetter__(bar, baz); +``` + +```js +/* eslint no-restricted-properties: [2, { + "object": "require" +}] */ + +require.resolve('foo'); +``` + +Examples of **correct** code for this rule: + +```js +/* eslint no-restricted-properties: [2, { + "object": "disallowedObjectName", + "property": "disallowedPropertyName" +}] */ + +var example = disallowedObjectName.somePropertyName; + +allowedObjectName.disallowedPropertyName(); +``` + +```js +/* eslint no-restricted-properties: [2, { + "object": "require" +}] */ + +require('foo'); +``` + +## When Not To Use It + +If you don't have any object/property combinations to restrict, you should not use this rule. + +## Related Rules + +* [no-restricted-globals](no-restricted-globals.md) +* [no-restricted-syntax](no-restricted-syntax.md) diff --git a/eslint/docs/rules/no-restricted-syntax.md b/eslint/docs/rules/no-restricted-syntax.md new file mode 100644 index 0000000..0665167 --- /dev/null +++ b/eslint/docs/rules/no-restricted-syntax.md @@ -0,0 +1,84 @@ +# disallow specified syntax (no-restricted-syntax) + +JavaScript has a lot of language features, and not everyone likes all of them. As a result, some projects choose to disallow the use of certain language features altogether. For instance, you might decide to disallow the use of `try-catch` or `class`, or you might decide to disallow the use of the `in` operator. + +Rather than creating separate rules for every language feature you want to turn off, this rule allows you to configure the syntax elements you want to restrict use of. These elements are represented by their [ESTree](https://github.com/estree/estree) node types. For example, a function declaration is represented by `FunctionDeclaration` and the `with` statement is represented by `WithStatement`. You may find the full list of AST node names you can use [on GitHub](https://github.com/eslint/espree/blob/master/lib/ast-node-types.js) and use [AST Explorer](https://astexplorer.net/) with the espree parser to see what type of nodes your code consists of. + +You can also specify [AST selectors](../developer-guide/selectors) to restrict, allowing much more precise control over syntax patterns. + +## Rule Details + +This rule disallows specified (that is, user-defined) syntax. + +## Options + +This rule takes a list of strings, where each string is an AST selector: + +```json +{ + "rules": { + "no-restricted-syntax": ["error", "FunctionExpression", "WithStatement", "BinaryExpression[operator='in']"] + } +} +``` + +Alternatively, the rule also accepts objects, where the selector and an optional custom message are specified: + +```json +{ + "rules": { + "no-restricted-syntax": [ + "error", + { + "selector": "FunctionExpression", + "message": "Function expressions are not allowed." + }, + { + "selector": "CallExpression[callee.name='setTimeout'][arguments.length!=2]", + "message": "setTimeout must always be invoked with two arguments." + } + ] + } +} +``` + +If a custom message is specified with the `message` property, ESLint will use that message when reporting occurrences of the syntax specified in the `selector` property. + +The string and object formats can be freely mixed in the configuration as needed. + +Examples of **incorrect** code for this rule with the `"FunctionExpression", "WithStatement", BinaryExpression[operator='in']` options: + +```js +/* eslint no-restricted-syntax: ["error", "FunctionExpression", "WithStatement", "BinaryExpression[operator='in']"] */ + +with (me) { + dontMess(); +} + +var doSomething = function () {}; + +foo in bar; +``` + +Examples of **correct** code for this rule with the `"FunctionExpression", "WithStatement", BinaryExpression[operator='in']` options: + +```js +/* eslint no-restricted-syntax: ["error", "FunctionExpression", "WithStatement", "BinaryExpression[operator='in']"] */ + +me.dontMess(); + +function doSomething() {}; + +foo instanceof bar; +``` + +## When Not To Use It + +If you don't want to restrict your code from using any JavaScript features or syntax, you should not use this rule. + +## Related Rules + +* [no-alert](no-alert.md) +* [no-console](no-console.md) +* [no-debugger](no-debugger.md) +* [no-restricted-properties](no-restricted-properties.md) diff --git a/eslint/docs/rules/no-return-assign.md b/eslint/docs/rules/no-return-assign.md new file mode 100644 index 0000000..781d597 --- /dev/null +++ b/eslint/docs/rules/no-return-assign.md @@ -0,0 +1,102 @@ +# Disallow Assignment in return Statement (no-return-assign) + +One of the interesting, and sometimes confusing, aspects of JavaScript is that assignment can happen at almost any point. Because of this, an errant equals sign can end up causing assignment when the true intent was to do a comparison. This is especially true when using a `return` statement. For example: + +```js +function doSomething() { + return foo = bar + 2; +} +``` + +It is difficult to tell the intent of the `return` statement here. It's possible that the function is meant to return the result of `bar + 2`, but then why is it assigning to `foo`? It's also possible that the intent was to use a comparison operator such as `==` and that this code is an error. + +Because of this ambiguity, it's considered a best practice to not use assignment in `return` statements. + +## Rule Details + +This rule aims to eliminate assignments from `return` statements. As such, it will warn whenever an assignment is found as part of `return`. + +## Options + +The rule takes one option, a string, which must contain one of the following values: + +* `except-parens` (default): Disallow assignments unless they are enclosed in parentheses. +* `always`: Disallow all assignments. + +### except-parens + +This is the default option. +It disallows assignments unless they are enclosed in parentheses. + +Examples of **incorrect** code for the default `"except-parens"` option: + +```js +/*eslint no-return-assign: "error"*/ + +function doSomething() { + return foo = bar + 2; +} + +function doSomething() { + return foo += 2; +} +``` + +Examples of **correct** code for the default `"except-parens"` option: + +```js +/*eslint no-return-assign: "error"*/ + +function doSomething() { + return foo == bar + 2; +} + +function doSomething() { + return foo === bar + 2; +} + +function doSomething() { + return (foo = bar + 2); +} +``` + +### always + +This option disallows all assignments in `return` statements. +All assignments are treated as problems. + +Examples of **incorrect** code for the `"always"` option: + +```js +/*eslint no-return-assign: ["error", "always"]*/ + +function doSomething() { + return foo = bar + 2; +} + +function doSomething() { + return foo += 2; +} + +function doSomething() { + return (foo = bar + 2); +} +``` + +Examples of **correct** code for the `"always"` option: + +```js +/*eslint no-return-assign: ["error", "always"]*/ + +function doSomething() { + return foo == bar + 2; +} + +function doSomething() { + return foo === bar + 2; +} +``` + +## When Not To Use It + +If you want to allow the use of assignment operators in a `return` statement, then you can safely disable this rule. diff --git a/eslint/docs/rules/no-return-await.md b/eslint/docs/rules/no-return-await.md new file mode 100644 index 0000000..a79a46d --- /dev/null +++ b/eslint/docs/rules/no-return-await.md @@ -0,0 +1,51 @@ +# Disallows unnecessary `return await` (no-return-await) + +Inside an `async function`, `return await` is seldom useful. Since the return value of an `async function` is always wrapped in `Promise.resolve`, `return await` doesn’t actually do anything except add extra time before the overarching Promise resolves or rejects. The only valid exception is if `return await` is used in a try/catch statement to catch errors from another Promise-based function. + +## Rule Details + +This rule aims to prevent a likely common performance hazard due to a lack of understanding of the semantics of `async function`. + +Examples of **incorrect** code for this rule: + +```js +async function foo() { + return await bar(); +} +``` + +Examples of **correct** code for this rule: + +```js +async function foo() { + return bar(); +} + +async function foo() { + await bar(); + return; +} + +async function foo() { + const x = await bar(); + return x; +} + +async function foo() { + try { + return await bar(); + } catch (error) {} +} +``` + +In the last example the `await` is necessary to be able to catch errors thrown from `bar()`. + +## When Not To Use It + +If you want to use `await` to denote a value that is a thenable, even when it is not necessary; or if you do not want the performance benefit of avoiding `return await`, you can turn off this rule. + +## Further Reading + +[`async function` on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) + +[`await vs return vs return await` by Jake Archibald](https://jakearchibald.com/2017/await-vs-return-vs-return-await/) diff --git a/eslint/docs/rules/no-script-url.md b/eslint/docs/rules/no-script-url.md new file mode 100644 index 0000000..475959d --- /dev/null +++ b/eslint/docs/rules/no-script-url.md @@ -0,0 +1,21 @@ +# Disallow Script URLs (no-script-url) + +Using `javascript:` URLs is considered by some as a form of `eval`. Code passed in `javascript:` URLs has to be parsed and evaluated by the browser in the same way that `eval` is processed. + +## Rule Details + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-script-url: "error"*/ + +location.href = "javascript:void(0)"; +``` + +## Compatibility + +* **JSHint**: This rule corresponds to `scripturl` rule of JSHint. + +## Further Reading + +* [What is the matter with script-targeted URLs?](https://stackoverflow.com/questions/13497971/what-is-the-matter-with-script-targeted-urls) diff --git a/eslint/docs/rules/no-self-assign.md b/eslint/docs/rules/no-self-assign.md new file mode 100644 index 0000000..2991504 --- /dev/null +++ b/eslint/docs/rules/no-self-assign.md @@ -0,0 +1,86 @@ +# Disallow Self Assignment (no-self-assign) + +Self assignments have no effect, so probably those are an error due to incomplete refactoring. +Those indicate that what you should do is still remaining. + +```js +foo = foo; +[bar, baz] = [bar, qiz]; +``` + +## Rule Details + +This rule is aimed at eliminating self assignments. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-self-assign: "error"*/ + +foo = foo; + +[a, b] = [a, b]; + +[a, ...b] = [x, ...b]; + +({a, b} = {a, x}); +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-self-assign: "error"*/ + +foo = bar; +[a, b] = [b, a]; + +// This pattern is warned by the `no-use-before-define` rule. +let foo = foo; + +// The default values have an effect. +[foo = 1] = [foo]; + +// non-self-assignments with properties. +obj.a = obj.b; +obj.a.b = obj.c.b; +obj.a.b = obj.a.c; +obj[a] = obj["a"]; + +// This ignores if there is a function call. +obj.a().b = obj.a().b; +a().b = a().b; + +// Known limitation: this does not support computed properties except single literal or single identifier. +obj[a + b] = obj[a + b]; +obj["a" + "b"] = obj["a" + "b"]; +``` + +## Options + +This rule has the option to check properties as well. + +```json +{ + "no-self-assign": ["error", {"props": true}] +} +``` + +- `props` - if this is `true`, `no-self-assign` rule warns self-assignments of properties. Default is `true`. + +### props + +Examples of **correct** code with the `{ "props": false }` option: + +```js +/*eslint no-self-assign: ["error", {"props": false}]*/ + +// self-assignments with properties. +obj.a = obj.a; +obj.a.b = obj.a.b; +obj["a"] = obj["a"]; +obj[a] = obj[a]; +``` + +## When Not To Use It + +If you don't want to notify about self assignments, then it's safe to disable this rule. diff --git a/eslint/docs/rules/no-self-compare.md b/eslint/docs/rules/no-self-compare.md new file mode 100644 index 0000000..a745de4 --- /dev/null +++ b/eslint/docs/rules/no-self-compare.md @@ -0,0 +1,20 @@ +# Disallow Self Compare (no-self-compare) + +Comparing a variable against itself is usually an error, either a typo or refactoring error. It is confusing to the reader and may potentially introduce a runtime error. + +The only time you would compare a variable against itself is when you are testing for `NaN`. However, it is far more appropriate to use `typeof x === 'number' && isNaN(x)` or the [Number.isNaN ES2015 function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN) for that use case rather than leaving the reader of the code to determine the intent of self comparison. + +## Rule Details + +This error is raised to highlight a potentially confusing and potentially pointless piece of code. There are almost no situations in which you would need to compare something to itself. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-self-compare: "error"*/ + +var x = 10; +if (x === x) { + x = 20; +} +``` diff --git a/eslint/docs/rules/no-sequences.md b/eslint/docs/rules/no-sequences.md new file mode 100644 index 0000000..602a412 --- /dev/null +++ b/eslint/docs/rules/no-sequences.md @@ -0,0 +1,77 @@ +# Disallow Use of the Comma Operator (no-sequences) + +The comma operator includes multiple expressions where only one is expected. It evaluates each operand from left to right and returns the value of the last operand. However, this frequently obscures side effects, and its use is often an accident. Here are some examples of sequences: + +```js +var a = (3, 5); // a = 5 + +a = b += 5, a + b; + +while (a = next(), a && a.length); + +(0, eval)("doSomething();"); +``` + +## Rule Details + +This rule forbids the use of the comma operator, with the following exceptions: + +* In the initialization or update portions of a `for` statement. +* If the expression sequence is explicitly wrapped in parentheses. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-sequences: "error"*/ + +foo = doSomething(), val; + +0, eval("doSomething();"); + +do {} while (doSomething(), !!test); + +for (; doSomething(), !!test; ); + +if (doSomething(), !!test); + +switch (val = foo(), val) {} + +while (val = foo(), val < 42); + +with (doSomething(), val) {} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-sequences: "error"*/ + +foo = (doSomething(), val); + +(0, eval)("doSomething();"); + +do {} while ((doSomething(), !!test)); + +for (i = 0, j = 10; i < j; i++, j--); + +if ((doSomething(), !!test)); + +switch ((val = foo(), val)) {} + +while ((val = foo(), val < 42)); + +with ((doSomething(), val)) {} +``` + +## When Not To Use It + +Disable this rule if sequence expressions with the comma operator are acceptable. +Another case is where you might want to report all usages of the comma operator, even if they are wrapped in parentheses or in a for loop. You can achieve this using rule `no-restricted-syntax`: + +```js +{ + "rules": { + "no-restricted-syntax": ["error", "SequenceExpression"] + } +} +``` diff --git a/eslint/docs/rules/no-setter-return.md b/eslint/docs/rules/no-setter-return.md new file mode 100644 index 0000000..5308974 --- /dev/null +++ b/eslint/docs/rules/no-setter-return.md @@ -0,0 +1,101 @@ +# Disallow returning values from setters (no-setter-return) + +Setters cannot return values. + +While returning a value from a setter does not produce an error, the returned value is being ignored. Therefore, returning a value from a setter is either unnecessary or a possible error, since the returned value cannot be used. + +## Rule Details + +This rule disallows returning values from setters and reports `return` statements in setter functions. + +Only `return` without a value is allowed, as it's a control flow statement. + +This rule checks setters in: + +* Object literals. +* Class declarations and class expressions. +* Property descriptors in `Object.create`, `Object.defineProperty`, `Object.defineProperties`, and `Reflect.defineProperty` methods of the global objects. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-setter-return: "error"*/ + +var foo = { + set a(value) { + this.val = value; + return value; + } +}; + +class Foo { + set a(value) { + this.val = value * 2; + return this.val; + } +} + +const Bar = class { + static set a(value) { + if (value < 0) { + this.val = 0; + return 0; + } + this.val = value; + } +}; + +Object.defineProperty(foo, "bar", { + set(value) { + if (value < 0) { + return false; + } + this.val = value; + } +}); +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-setter-return: "error"*/ + +var foo = { + set a(value) { + this.val = value; + } +}; + +class Foo { + set a(value) { + this.val = value * 2; + } +} + +const Bar = class { + static set a(value) { + if (value < 0) { + this.val = 0; + return; + } + this.val = value; + } +}; + +Object.defineProperty(foo, "bar", { + set(value) { + if (value < 0) { + throw new Error("Negative value."); + } + this.val = value; + } +}); +``` + +## Further Reading + +* [MDN setter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set) + +## Related Rules + +* [getter-return](getter-return.md) diff --git a/eslint/docs/rules/no-shadow-restricted-names.md b/eslint/docs/rules/no-shadow-restricted-names.md new file mode 100644 index 0000000..890353d --- /dev/null +++ b/eslint/docs/rules/no-shadow-restricted-names.md @@ -0,0 +1,47 @@ +# Disallow Shadowing of Restricted Names (no-shadow-restricted-names) + +ES5 §15.1.1 Value Properties of the Global Object (`NaN`, `Infinity`, `undefined`) as well as strict mode restricted identifiers `eval` and `arguments` are considered to be restricted names in JavaScript. Defining them to mean something else can have unintended consequences and confuse others reading the code. For example, there's nothing preventing you from writing: + +```js +var undefined = "foo"; +``` + +Then any code used within the same scope would not get the global `undefined`, but rather the local version with a very different meaning. + +## Rule Details + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-shadow-restricted-names: "error"*/ + +function NaN(){} + +!function(Infinity){}; + +var undefined = 5; + +try {} catch(eval){} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-shadow-restricted-names: "error"*/ + +var Object; + +function f(a, b){} + +// Exception: `undefined` may be shadowed if the variable is never assigned a value. +var undefined; +``` + +## Further Reading + +* [Annotated ES5 - §15.1.1](https://es5.github.io/#x15.1.1) +* [Annotated ES5 - Annex C](https://es5.github.io/#C) + +## Related Rules + +* [no-shadow](no-shadow.md) diff --git a/eslint/docs/rules/no-shadow.md b/eslint/docs/rules/no-shadow.md new file mode 100644 index 0000000..3bf9783 --- /dev/null +++ b/eslint/docs/rules/no-shadow.md @@ -0,0 +1,173 @@ +# disallow variable declarations from shadowing variables declared in the outer scope (no-shadow) + +Shadowing is the process by which a local variable shares the same name as a variable in its containing scope. For example: + +```js +var a = 3; +function b() { + var a = 10; +} +``` + +In this case, the variable `a` inside of `b()` is shadowing the variable `a` in the global scope. This can cause confusion while reading the code and it's impossible to access the global variable. + +## Rule Details + +This rule aims to eliminate shadowed variable declarations. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-shadow: "error"*/ +/*eslint-env es6*/ + +var a = 3; +function b() { + var a = 10; +} + +var b = function () { + var a = 10; +} + +function b(a) { + a = 10; +} +b(a); + +if (true) { + let a = 5; +} +``` + +## Options + +This rule takes one option, an object, with properties `"builtinGlobals"`, `"hoist"` and `"allow"`. + +```json +{ + "no-shadow": ["error", { "builtinGlobals": false, "hoist": "functions", "allow": [] }] +} +``` + +### builtinGlobals + +The `builtinGlobals` option is `false` by default. +If it is `true`, the rule prevents shadowing of built-in global variables: `Object`, `Array`, `Number`, and so on. + +Examples of **incorrect** code for the `{ "builtinGlobals": true }` option: + +```js +/*eslint no-shadow: ["error", { "builtinGlobals": true }]*/ + +function foo() { + var Object = 0; +} +``` + +### hoist + +The `hoist` option has three settings: + +* `functions` (by default) - reports shadowing before the outer functions are defined. +* `all` - reports all shadowing before the outer variables/functions are defined. +* `never` - never report shadowing before the outer variables/functions are defined. + +#### hoist: functions + +Examples of **incorrect** code for the default `{ "hoist": "functions" }` option: + +```js +/*eslint no-shadow: ["error", { "hoist": "functions" }]*/ +/*eslint-env es6*/ + +if (true) { + let b = 6; +} + +function b() {} +``` + +Although `let b` in the `if` statement is before the *function* declaration in the outer scope, it is incorrect. + +Examples of **correct** code for the default `{ "hoist": "functions" }` option: + +```js +/*eslint no-shadow: ["error", { "hoist": "functions" }]*/ +/*eslint-env es6*/ + +if (true) { + let a = 3; +} + +let a = 5; +``` + +Because `let a` in the `if` statement is before the *variable* declaration in the outer scope, it is correct. + +#### hoist: all + +Examples of **incorrect** code for the `{ "hoist": "all" }` option: + +```js +/*eslint no-shadow: ["error", { "hoist": "all" }]*/ +/*eslint-env es6*/ + +if (true) { + let a = 3; + let b = 6; +} + +let a = 5; +function b() {} +``` + +#### hoist: never + +Examples of **correct** code for the `{ "hoist": "never" }` option: + +```js +/*eslint no-shadow: ["error", { "hoist": "never" }]*/ +/*eslint-env es6*/ + +if (true) { + let a = 3; + let b = 6; +} + +let a = 5; +function b() {} +``` + +Because `let a` and `let b` in the `if` statement are before the declarations in the outer scope, they are correct. + +### allow + +The `allow` option is an array of identifier names for which shadowing is allowed. For example, `"resolve"`, `"reject"`, `"done"`, `"cb"`. + +Examples of **correct** code for the `{ "allow": ["done"] }` option: + +```js +/*eslint no-shadow: ["error", { "allow": ["done"] }]*/ +/*eslint-env es6*/ + +import async from 'async'; + +function foo(done) { + async.map([1, 2], function (e, done) { + done(null, e * 2) + }, done); +} + +foo(function (err, result) { + console.log({ err, result }); +}); +``` + +## Further Reading + +* [Variable Shadowing](https://en.wikipedia.org/wiki/Variable_shadowing) + +## Related Rules + +* [no-shadow-restricted-names](no-shadow-restricted-names.md) diff --git a/eslint/docs/rules/no-space-before-semi.md b/eslint/docs/rules/no-space-before-semi.md new file mode 100644 index 0000000..2e142dd --- /dev/null +++ b/eslint/docs/rules/no-space-before-semi.md @@ -0,0 +1,43 @@ +# no-space-before-semi: disallow spaces before semicolons + +(removed) This rule was **removed** in ESLint v1.0 and **replaced** by the [semi-spacing](semi-spacing.md) rule. + +JavaScript allows for placing unnecessary spaces between an expression and the closing semicolon. + +Space issues can also cause code to look inconsistent and harder to read. + +```js +var thing = function () { + var test = 12 ; +} ; +``` + +## Rule Details + +This rule prevents the use of spaces before a semicolon in expressions. + +Examples of **incorrect** code for this rule: + +```js +var foo = "bar" ; + +var foo = function() {} ; + +var foo = function() { +} ; + +var foo = 1 + 2 ; +``` + +Examples of **correct** code for this rule: + +```js +;(function(){}()); + +var foo = "bar"; +``` + +## Related Rules + +* [semi](semi.md) +* [no-extra-semi](no-extra-semi.md) diff --git a/eslint/docs/rules/no-spaced-func.md b/eslint/docs/rules/no-spaced-func.md new file mode 100644 index 0000000..d424049 --- /dev/null +++ b/eslint/docs/rules/no-spaced-func.md @@ -0,0 +1,28 @@ +# disallow spacing between function identifiers and their applications (no-spaced-func) + +This rule was **deprecated** in ESLint v3.3.0 and replaced by the [func-call-spacing](func-call-spacing.md) rule. + +While it's possible to have whitespace between the name of a function and the parentheses that execute it, such patterns tend to look more like errors. + +## Rule Details + +This rule disallows spacing between function identifiers and their applications. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-spaced-func: "error"*/ + +fn () + +fn +() +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-spaced-func: "error"*/ + +fn() +``` diff --git a/eslint/docs/rules/no-sparse-arrays.md b/eslint/docs/rules/no-sparse-arrays.md new file mode 100644 index 0000000..826cfd8 --- /dev/null +++ b/eslint/docs/rules/no-sparse-arrays.md @@ -0,0 +1,50 @@ +# disallow sparse arrays (no-sparse-arrays) + +Sparse arrays contain empty slots, most frequently due to multiple commas being used in an array literal, such as: + +```js +var items = [,,]; +``` + +While the `items` array in this example has a `length` of 2, there are actually no values in `items[0]` or `items[1]`. The fact that the array literal is valid with only commas inside, coupled with the `length` being set and actual item values not being set, make sparse arrays confusing for many developers. Consider the following: + +```js +var colors = [ "red",, "blue" ]; +``` + +In this example, the `colors` array has a `length` of 3. But did the developer intend for there to be an empty spot in the middle of the array? Or is it a typo? + +The confusion around sparse arrays defined in this manner is enough that it's recommended to avoid using them unless you are certain that they are useful in your code. + +## Rule Details + +This rule disallows sparse array literals which have "holes" where commas are not preceded by elements. It does not apply to a trailing comma following the last element. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-sparse-arrays: "error"*/ + +var items = [,]; +var colors = [ "red",, "blue" ]; +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-sparse-arrays: "error"*/ + +var items = []; +var items = new Array(23); + +// trailing comma (after the last element) is not a problem +var colors = [ "red", "blue", ]; +``` + +## When Not To Use It + +If you want to use sparse arrays, then it is safe to disable this rule. + +## Further Reading + +* [Inconsistent array literals](https://www.nczonline.net/blog/2007/09/09/inconsistent-array-literals/) diff --git a/eslint/docs/rules/no-sync.md b/eslint/docs/rules/no-sync.md new file mode 100644 index 0000000..50ab515 --- /dev/null +++ b/eslint/docs/rules/no-sync.md @@ -0,0 +1,59 @@ +# Disallow Synchronous Methods (no-sync) + +In Node.js, most I/O is done through asynchronous methods. However, there are often synchronous versions of the asynchronous methods. For example, `fs.exists()` and `fs.existsSync()`. In some contexts, using synchronous operations is okay (if, as with ESLint, you are writing a command line utility). However, in other contexts the use of synchronous operations is considered a bad practice that should be avoided. For example, if you are running a high-travel web server on Node.js, you should consider carefully if you want to allow any synchronous operations that could lock up the server. + +## Rule Details + +This rule is aimed at preventing synchronous methods from being called in Node.js. It looks specifically for the method suffix "`Sync`" (as is the convention with Node.js operations). + +## Options + +This rule has an optional object option `{ allowAtRootLevel: }`, which determines whether synchronous methods should be allowed at the top level of a file, outside of any functions. This option defaults to `false`. + +Examples of **incorrect** code for this rule with the default `{ allowAtRootLevel: false }` option: + +```js +/*eslint no-sync: "error"*/ + +fs.existsSync(somePath); + +function foo() { + var contents = fs.readFileSync(somePath).toString(); +} +``` + +Examples of **correct** code for this rule with the default `{ allowAtRootLevel: false }` option: + +```js +/*eslint no-sync: "error"*/ + +obj.sync(); + +async(function() { + // ... +}); +``` + +Examples of **incorrect** code for this rule with the `{ allowAtRootLevel: true }` option + +```js +/*eslint no-sync: ["error", { allowAtRootLevel: true }]*/ + +function foo() { + var contents = fs.readFileSync(somePath).toString(); +} + +var bar = baz => fs.readFileSync(qux); +``` + +Examples of **correct** code for this rule with the `{ allowAtRootLevel: true }` option + +```js +/*eslint no-sync: ["error", { allowAtRootLevel: true }]*/ + +fs.readFileSync(somePath).toString(); +``` + +## When Not To Use It + +If you want to allow synchronous operations in your script, do not enable this rule. diff --git a/eslint/docs/rules/no-tabs.md b/eslint/docs/rules/no-tabs.md new file mode 100644 index 0000000..4e02d9a --- /dev/null +++ b/eslint/docs/rules/no-tabs.md @@ -0,0 +1,61 @@ +# disallow all tabs (no-tabs) + +Some style guides don't allow the use of tab characters at all, including within comments. + +## Rule Details + +This rule looks for tabs anywhere inside a file: code, comments or anything else. + +Examples of **incorrect** code for this rule: + +```js +var a \t= 2; + +/** +* \t\t it's a test function +*/ +function test(){} + +var x = 1; // \t test +``` + +Examples of **correct** code for this rule: + +```js +var a = 2; + +/** +* it's a test function +*/ +function test(){} + +var x = 1; // test +``` + +### Options + +This rule has an optional object option with the following properties: + +* `allowIndentationTabs` (default: false): If this is set to true, then the rule will not report tabs used for indentation. + +#### allowIndentationTabs + +Examples of **correct** code for this rule with the `allowIndentationTabs: true` option: + +```js +/* eslint no-tabs: ["error", { allowIndentationTabs: true }] */ + +function test() { +\tdoSomething(); +} + +\t// comment with leading indentation tab +``` + +## When Not To Use It + +If you have established a standard where having tabs is fine, then you can disable this rule. + +## Compatibility + +* **JSCS**: [disallowTabs](https://jscs-dev.github.io/rule/disallowTabs) diff --git a/eslint/docs/rules/no-template-curly-in-string.md b/eslint/docs/rules/no-template-curly-in-string.md new file mode 100644 index 0000000..9c03b67 --- /dev/null +++ b/eslint/docs/rules/no-template-curly-in-string.md @@ -0,0 +1,33 @@ +# Disallow template literal placeholder syntax in regular strings (no-template-curly-in-string) + +ECMAScript 6 allows programmers to create strings containing variable or expressions using template literals, instead of string concatenation, by writing expressions like `${variable}` between two backtick quotes (\`). It can be easy to use the wrong quotes when wanting to use template literals, by writing `"${variable}"`, and end up with the literal value `"${variable}"` instead of a string containing the value of the injected expressions. + + +## Rule Details + +This rule aims to warn when a regular string contains what looks like a template literal placeholder. It will warn when it finds a string containing the template literal placeholder (`${something}`) that uses either `"` or `'` for the quotes. + +## Examples + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-template-curly-in-string: "error"*/ +"Hello ${name}!"; +'Hello ${name}!'; +"Time: ${12 * 60 * 60 * 1000}"; +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-template-curly-in-string: "error"*/ +`Hello ${name}!`; +`Time: ${12 * 60 * 60 * 1000}`; + +templateFunction`Hello ${name}`; +``` + +## When Not To Use It + +This rule should not be used in ES3/5 environments. diff --git a/eslint/docs/rules/no-ternary.md b/eslint/docs/rules/no-ternary.md new file mode 100644 index 0000000..3c95f82 --- /dev/null +++ b/eslint/docs/rules/no-ternary.md @@ -0,0 +1,50 @@ +# disallow ternary operators (no-ternary) + +The ternary operator is used to conditionally assign a value to a variable. Some believe that the use of ternary operators leads to unclear code. + +```js +var foo = isBar ? baz : qux; +``` + +## Rule Details + +This rule disallows ternary operators. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-ternary: "error"*/ + +var foo = isBar ? baz : qux; + +function quux() { + return foo ? bar() : baz(); +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-ternary: "error"*/ + +var foo; + +if (isBar) { + foo = baz; +} else { + foo = qux; +} + +function quux() { + if (foo) { + return bar(); + } else { + return baz(); + } +} +``` + +## Related Rules + +* [no-nested-ternary](no-nested-ternary.md) +* [no-unneeded-ternary](no-unneeded-ternary.md) diff --git a/eslint/docs/rules/no-this-before-super.md b/eslint/docs/rules/no-this-before-super.md new file mode 100644 index 0000000..53223b7 --- /dev/null +++ b/eslint/docs/rules/no-this-before-super.md @@ -0,0 +1,75 @@ +# Disallow use of `this`/`super` before calling `super()` in constructors. (no-this-before-super) + +In the constructor of derived classes, if `this`/`super` are used before `super()` calls, it raises a reference error. + +This rule checks `this`/`super` keywords in constructors, then reports those that are before `super()`. + +## Rule Details + +This rule is aimed to flag `this`/`super` keywords before `super()` callings. + +## Examples + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-this-before-super: "error"*/ +/*eslint-env es6*/ + +class A extends B { + constructor() { + this.a = 0; + super(); + } +} + +class A extends B { + constructor() { + this.foo(); + super(); + } +} + +class A extends B { + constructor() { + super.foo(); + super(); + } +} + +class A extends B { + constructor() { + super(this.foo()); + } +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-this-before-super: "error"*/ +/*eslint-env es6*/ + +class A { + constructor() { + this.a = 0; // OK, this class doesn't have an `extends` clause. + } +} + +class A extends B { + constructor() { + super(); + this.a = 0; // OK, this is after `super()`. + } +} + +class A extends B { + foo() { + this.a = 0; // OK. this is not in a constructor. + } +} +``` + +## When Not To Use It + +If you don't want to be notified about using `this`/`super` before `super()` in constructors, you can safely disable this rule. diff --git a/eslint/docs/rules/no-throw-literal.md b/eslint/docs/rules/no-throw-literal.md new file mode 100644 index 0000000..ecb19ed --- /dev/null +++ b/eslint/docs/rules/no-throw-literal.md @@ -0,0 +1,77 @@ +# Restrict what can be thrown as an exception (no-throw-literal) + +It is considered good practice to only `throw` the `Error` object itself or an object using the `Error` object as base objects for user-defined exceptions. +The fundamental benefit of `Error` objects is that they automatically keep track of where they were built and originated. + +This rule restricts what can be thrown as an exception. When it was first created, it only prevented literals from being thrown (hence the name), but it has now been expanded to only allow expressions which have a possibility of being an `Error` object. + +## Rule Details + +This rule is aimed at maintaining consistency when throwing exception by disallowing to throw literals and other expressions which cannot possibly be an `Error` object. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-throw-literal: "error"*/ +/*eslint-env es6*/ + +throw "error"; + +throw 0; + +throw undefined; + +throw null; + +var err = new Error(); +throw "an " + err; +// err is recast to a string literal + +var err = new Error(); +throw `${err}` + +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-throw-literal: "error"*/ + +throw new Error(); + +throw new Error("error"); + +var e = new Error("error"); +throw e; + +try { + throw new Error("error"); +} catch (e) { + throw e; +} +``` + +## Known Limitations + +Due to the limits of static analysis, this rule cannot guarantee that you will only throw `Error` objects. + +Examples of **correct** code for this rule, but which do not throw an `Error` object: + +```js +/*eslint no-throw-literal: "error"*/ + +var err = "error"; +throw err; + +function foo(bar) { + console.log(bar); +} +throw foo("error"); + +throw new String("error"); + +var foo = { + bar: "error" +}; +throw foo.bar; +``` diff --git a/eslint/docs/rules/no-trailing-spaces.md b/eslint/docs/rules/no-trailing-spaces.md new file mode 100644 index 0000000..e0724b7 --- /dev/null +++ b/eslint/docs/rules/no-trailing-spaces.md @@ -0,0 +1,63 @@ +# disallow trailing whitespace at the end of lines (no-trailing-spaces) + +Sometimes in the course of editing files, you can end up with extra whitespace at the end of lines. These whitespace differences can be picked up by source control systems and flagged as diffs, causing frustration for developers. While this extra whitespace causes no functional issues, many code conventions require that trailing spaces be removed before check-in. + +## Rule Details + +This rule disallows trailing whitespace (spaces, tabs, and other Unicode whitespace characters) at the end of lines. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-trailing-spaces: "error"*/ + +var foo = 0;//••••• +var baz = 5;//•• +//••••• +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-trailing-spaces: "error"*/ + +var foo = 0; +var baz = 5; +``` + +## Options + +This rule has an object option: + +* `"skipBlankLines": false` (default) disallows trailing whitespace on empty lines +* `"skipBlankLines": true` allows trailing whitespace on empty lines +* `"ignoreComments": false` (default) disallows trailing whitespace in comment blocks +* `"ignoreComments": true` allows trailing whitespace in comment blocks + +### skipBlankLines + +Examples of **correct** code for this rule with the `{ "skipBlankLines": true }` option: + +```js +/*eslint no-trailing-spaces: ["error", { "skipBlankLines": true }]*/ + +var foo = 0; +var baz = 5; +//••••• +``` + +### ignoreComments + +Examples of **correct** code for this rule with the `{ "ignoreComments": true }` option: + +```js +/*eslint no-trailing-spaces: ["error", { "ignoreComments": true }]*/ + +//foo• +//••••• +/** + *•baz + *•• + *•bar + */ +``` diff --git a/eslint/docs/rules/no-undef-init.md b/eslint/docs/rules/no-undef-init.md new file mode 100644 index 0000000..10db58c --- /dev/null +++ b/eslint/docs/rules/no-undef-init.md @@ -0,0 +1,111 @@ +# Disallow Initializing to undefined (no-undef-init) + +In JavaScript, a variable that is declared and not initialized to any value automatically gets the value of `undefined`. For example: + +```js +var foo; + +console.log(foo === undefined); // true +``` + +It's therefore unnecessary to initialize a variable to `undefined`, such as: + +```js +var foo = undefined; +``` + +It's considered a best practice to avoid initializing variables to `undefined`. + + +## Rule Details + +This rule aims to eliminate variable declarations that initialize to `undefined`. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-undef-init: "error"*/ +/*eslint-env es6*/ + +var foo = undefined; +let bar = undefined; +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-undef-init: "error"*/ +/*eslint-env es6*/ + +var foo; +let bar; +const baz = undefined; +``` + +## When Not To Use It + +There is one situation where initializing to `undefined` behaves differently than omitting the initialization, and that's when a `var` declaration occurs inside of a loop. For example: + +Example of **incorrect** code for this rule: + +```js +for (i = 0; i < 10; i++) { + var x = undefined; + console.log(x); + x = i; +} +``` + +In this case, the `var x` is hoisted out of the loop, effectively creating: + +```js +var x; + +for (i = 0; i < 10; i++) { + x = undefined; + console.log(x); + x = i; +} +``` + +If you were to remove the initialization, then the behavior of the loop changes: + +```js +for (i = 0; i < 10; i++) { + var x; + console.log(x); + x = i; +} +``` + +This code is equivalent to: + +```js +var x; + +for (i = 0; i < 10; i++) { + console.log(x); + x = i; +} +``` + +This produces a different outcome than defining `var x = undefined` in the loop, as `x` is no longer reset to `undefined` each time through the loop. + +If you're using such an initialization inside of a loop, then you should disable this rule. + +Example of **correct** code for this rule, because it is disabled on a specific line: + +```js +/*eslint no-undef-init: "error"*/ + +for (i = 0; i < 10; i++) { + var x = undefined; // eslint-disable-line no-undef-init + console.log(x); + x = i; +} +``` + +## Related Rules + +* [no-undefined](no-undefined.md) +* [no-void](no-void.md) diff --git a/eslint/docs/rules/no-undef.md b/eslint/docs/rules/no-undef.md new file mode 100644 index 0000000..faaf978 --- /dev/null +++ b/eslint/docs/rules/no-undef.md @@ -0,0 +1,111 @@ +# Disallow Undeclared Variables (no-undef) + +This rule can help you locate potential ReferenceErrors resulting from misspellings of variable and parameter names, or accidental implicit globals (for example, from forgetting the `var` keyword in a `for` loop initializer). + +## Rule Details + +Any reference to an undeclared variable causes a warning, unless the variable is explicitly mentioned in a `/*global ...*/` comment, or specified in the [`globals` key in the configuration file](https://eslint.org/docs/user-guide/configuring#specifying-globals). A common use case for these is if you intentionally use globals that are defined elsewhere (e.g. in a script sourced from HTML). + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-undef: "error"*/ + +var foo = someFunction(); +var bar = a + 1; +``` + +Examples of **correct** code for this rule with `global` declaration: + +```js +/*global someFunction, a*/ +/*eslint no-undef: "error"*/ + +var foo = someFunction(); +var bar = a + 1; +``` + +Note that this rule does not disallow assignments to read-only global variables. +See [no-global-assign](no-global-assign.md) if you also want to disallow those assignments. + +This rule also does not disallow redeclarations of global variables. +See [no-redeclare](no-redeclare.md) if you also want to disallow those redeclarations. + +## Options + +* `typeof` set to true will warn for variables used inside typeof check (Default false). + +### typeof + +Examples of **correct** code for the default `{ "typeof": false }` option: + +```js +/*eslint no-undef: "error"*/ + +if (typeof UndefinedIdentifier === "undefined") { + // do something ... +} +``` + +You can use this option if you want to prevent `typeof` check on a variable which has not been declared. + +Examples of **incorrect** code for the `{ "typeof": true }` option: + +```js +/*eslint no-undef: ["error", { "typeof": true }] */ + +if(typeof a === "string"){} +``` + +Examples of **correct** code for the `{ "typeof": true }` option with `global` declaration: + +```js +/*global a*/ +/*eslint no-undef: ["error", { "typeof": true }] */ + +if(typeof a === "string"){} +``` + +## Environments + +For convenience, ESLint provides shortcuts that pre-define global variables exposed by popular libraries and runtime environments. This rule supports these environments, as listed in [Specifying Environments](../user-guide/configuring.md#specifying-environments). A few examples are given below. + +### browser + +Examples of **correct** code for this rule with `browser` environment: + +```js +/*eslint no-undef: "error"*/ +/*eslint-env browser*/ + +setTimeout(function() { + alert("Hello"); +}); +``` + +### Node.js + +Examples of **correct** code for this rule with `node` environment: + +```js +/*eslint no-undef: "error"*/ +/*eslint-env node*/ + +var fs = require("fs"); +module.exports = function() { + console.log(fs); +}; +``` + +## When Not To Use It + +If explicit declaration of global variables is not to your taste. + +## Compatibility + +This rule provides compatibility with treatment of global variables in [JSHint](http://jshint.com/) and [JSLint](http://www.jslint.com). + +## Related Rules + +* [no-global-assign](no-global-assign.md) +* [no-redeclare](no-redeclare.md) diff --git a/eslint/docs/rules/no-undefined.md b/eslint/docs/rules/no-undefined.md new file mode 100644 index 0000000..0e183f2 --- /dev/null +++ b/eslint/docs/rules/no-undefined.md @@ -0,0 +1,79 @@ +# Disallow Use of `undefined` Variable (no-undefined) + +The `undefined` variable in JavaScript is actually a property of the global object. As such, in ECMAScript 3 it was possible to overwrite the value of `undefined`. While ECMAScript 5 disallows overwriting `undefined`, it's still possible to shadow `undefined`, such as: + +```js +function doSomething(data) { + var undefined = "hi"; + + // doesn't do what you think it does + if (data === undefined) { + // ... + } + +} +``` + +Because `undefined` can be overwritten or shadowed, reading `undefined` can give an unexpected value. (This is not the case for `null`, which is a keyword that always produces the same value.) To guard against this, you can avoid all uses of `undefined`, which is what some style guides recommend and what this rule enforces. Those style guides then also recommend: + +* Variables that should be `undefined` are simply left uninitialized. (All uninitialized variables automatically get the value of `undefined` in JavaScript.) +* Checking if a value is `undefined` should be done with `typeof`. +* Using the `void` operator to generate the value of `undefined` if necessary. + +As an alternative, you can use the [no-global-assign](no-global-assign.md) and [no-shadow-restricted-names](no-shadow-restricted-names.md) rules to prevent `undefined` from being shadowed or assigned a different value. This ensures that `undefined` will always hold its original, expected value. + + +## Rule Details + +This rule aims to eliminate the use of `undefined`, and as such, generates a warning whenever it is used. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-undefined: "error"*/ + +var foo = undefined; + +var undefined = "foo"; + +if (foo === undefined) { + // ... +} + +function foo(undefined) { + // ... +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-undefined: "error"*/ + +var foo = void 0; + +var Undefined = "foo"; + +if (typeof foo === "undefined") { + // ... +} + +global.undefined = "foo"; +``` + +## When Not To Use It + +If you want to allow the use of `undefined` in your code, then you can safely turn this rule off. + +## Further Reading + +* [undefined - JavaScript \| MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/undefined) +* [Understanding JavaScript’s ‘undefined’ \| JavaScript, JavaScript...](https://javascriptweblog.wordpress.com/2010/08/16/understanding-undefined-and-preventing-referenceerrors/) +* [ECMA262 edition 5.1 §15.1.1.3: undefined](https://es5.github.io/#x15.1.1.3) + +## Related Rules + +* [no-undef-init](no-undef-init.md) +* [no-void](no-void.md) +* [no-shadow-restricted-names](no-shadow-restricted-names.md) +* [no-global-assign](no-global-assign.md) diff --git a/eslint/docs/rules/no-underscore-dangle.md b/eslint/docs/rules/no-underscore-dangle.md new file mode 100644 index 0000000..ebe7fd7 --- /dev/null +++ b/eslint/docs/rules/no-underscore-dangle.md @@ -0,0 +1,118 @@ +# disallow dangling underscores in identifiers (no-underscore-dangle) + +As far as naming conventions for identifiers go, dangling underscores may be the most polarizing in JavaScript. Dangling underscores are underscores at either the beginning or end of an identifier, such as: + +```js +var _foo; +``` + +There is actually a long history of using dangling underscores to indicate "private" members of objects in JavaScript (though JavaScript doesn't have truly private members, this convention served as a warning). This began with SpiderMonkey adding nonstandard methods such as `__defineGetter__()`. The intent with the underscores was to make it obvious that this method was special in some way. Since that time, using a single underscore prefix has become popular as a way to indicate "private" members of objects. + +Whether or not you choose to allow dangling underscores in identifiers is purely a convention and has no effect on performance, readability, or complexity. It's purely a preference. + +## Rule Details + +This rule disallows dangling underscores in identifiers. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-underscore-dangle: "error"*/ + +var foo_; +var __proto__ = {}; +foo._bar(); +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-underscore-dangle: "error"*/ + +var _ = require('underscore'); +var obj = _.contains(items, item); +obj.__proto__ = {}; +var file = __filename; +``` + +## Options + +This rule has an object option: + +* `"allow"` allows specified identifiers to have dangling underscores +* `"allowAfterThis": false` (default) disallows dangling underscores in members of the `this` object +* `"allowAfterSuper": false` (default) disallows dangling underscores in members of the `super` object +* `"allowAfterThisConstructor": false` (default) disallows dangling underscores in members of the `this.constructor` object +* `"enforceInMethodNames": false` (default) allows dangling underscores in method names + +### allow + +Examples of additional **correct** code for this rule with the `{ "allow": ["foo_", "_bar"] }` option: + +```js +/*eslint no-underscore-dangle: ["error", { "allow": ["foo_", "_bar"] }]*/ + +var foo_; +foo._bar(); +``` + +### allowAfterThis + +Examples of **correct** code for this rule with the `{ "allowAfterThis": true }` option: + +```js +/*eslint no-underscore-dangle: ["error", { "allowAfterThis": true }]*/ + +var a = this.foo_; +this._bar(); +``` + +### allowAfterSuper + +Examples of **correct** code for this rule with the `{ "allowAfterSuper": true }` option: + +```js +/*eslint no-underscore-dangle: ["error", { "allowAfterSuper": true }]*/ + +var a = super.foo_; +super._bar(); +``` + +### allowAfterThisConstructor + +Examples of **correct** code for this rule with the `{ "allowAfterThisConstructor": true }` option: + +```js +/*eslint no-underscore-dangle: ["error", { "allowAfterThisConstructor": true }]*/ + +var a = this.constructor.foo_; +this.constructor._bar(); +``` + +### enforceInMethodNames + +Examples of **incorrect** code for this rule with the `{ "enforceInMethodNames": true }` option: + +```js +/*eslint no-underscore-dangle: ["error", { "enforceInMethodNames": true }]*/ + +class Foo { + _bar() {} +} + +class Foo { + bar_() {} +} + +const o = { + _bar() {} +}; + +const o = { + bar_() = {} +}; +``` + +## When Not To Use It + +If you want to allow dangling underscores in identifiers, then you can safely turn this rule off. diff --git a/eslint/docs/rules/no-unexpected-multiline.md b/eslint/docs/rules/no-unexpected-multiline.md new file mode 100644 index 0000000..a328f83 --- /dev/null +++ b/eslint/docs/rules/no-unexpected-multiline.md @@ -0,0 +1,74 @@ +# disallow confusing multiline expressions (no-unexpected-multiline) + +Semicolons are usually optional in JavaScript, because of automatic semicolon insertion (ASI). You can require or disallow semicolons with the [semi](./semi.md) rule. + +The rules for ASI are relatively straightforward: As once described by Isaac Schlueter, a newline character always ends a statement, just like a semicolon, **except** where one of the following is true: + +* The statement has an unclosed paren, array literal, or object literal or ends in some other way that is not a valid way to end a statement. (For instance, ending with `.` or `,`.) +* The line is `--` or `++` (in which case it will decrement/increment the next token.) +* It is a `for()`, `while()`, `do`, `if()`, or `else`, and there is no `{` +* The next line starts with `[`, `(`, `+`, `*`, `/`, `-`, `,`, `.`, or some other binary operator that can only be found between two tokens in a single expression. + +In the exceptions where a newline does **not** end a statement, a typing mistake to omit a semicolon causes two unrelated consecutive lines to be interpreted as one expression. Especially for a coding style without semicolons, readers might overlook the mistake. Although syntactically correct, the code might throw exceptions when it is executed. + +## Rule Details + +This rule disallows confusing multiline expressions where a newline looks like it is ending a statement, but is not. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-unexpected-multiline: "error"*/ + +var foo = bar +(1 || 2).baz(); + +var hello = 'world' +[1, 2, 3].forEach(addNumber); + +let x = function() {} +`hello` + +let x = function() {} +x +`hello` + +let x = foo +/regex/g.test(bar) +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-unexpected-multiline: "error"*/ + +var foo = bar; +(1 || 2).baz(); + +var foo = bar +;(1 || 2).baz() + +var hello = 'world'; +[1, 2, 3].forEach(addNumber); + +var hello = 'world' +void [1, 2, 3].forEach(addNumber); + +let x = function() {}; +`hello` + +let tag = function() {} +tag `hello` +``` + +## When Not To Use It + +You can turn this rule off if you are confident that you will not accidentally introduce code like this. + +Note that the patterns considered problems are **not** flagged by the [semi](semi.md) rule. + +## Related Rules + +* [func-call-spacing](func-call-spacing.md) +* [semi](semi.md) +* [space-unary-ops](space-unary-ops.md) diff --git a/eslint/docs/rules/no-unmodified-loop-condition.md b/eslint/docs/rules/no-unmodified-loop-condition.md new file mode 100644 index 0000000..688c350 --- /dev/null +++ b/eslint/docs/rules/no-unmodified-loop-condition.md @@ -0,0 +1,84 @@ +# Disallow unmodified conditions of loops (no-unmodified-loop-condition) + +Variables in a loop condition often are modified in the loop. +If not, it's possibly a mistake. + +```js +while (node) { + doSomething(node); +} +``` + +```js +while (node) { + doSomething(node); + node = node.parent; +} +``` + +## Rule Details + +This rule finds references which are inside of loop conditions, then checks the +variables of those references are modified in the loop. + +If a reference is inside of a binary expression or a ternary expression, this rule checks the result of +the expression instead. +If a reference is inside of a dynamic expression (e.g. `CallExpression`, +`YieldExpression`, ...), this rule ignores it. + +Examples of **incorrect** code for this rule: + +```js +while (node) { + doSomething(node); +} +node = other; + +for (var j = 0; j < items.length; ++i) { + doSomething(items[j]); +} + +while (node !== root) { + doSomething(node); +} +``` + +Examples of **correct** code for this rule: + +```js +while (node) { + doSomething(node); + node = node.parent; +} + +for (var j = 0; j < items.length; ++j) { + doSomething(items[j]); +} + +// OK, the result of this binary expression is changed in this loop. +while (node !== root) { + doSomething(node); + node = node.parent; +} + +// OK, the result of this ternary expression is changed in this loop. +while (node ? A : B) { + doSomething(node); + node = node.parent; +} + +// A property might be a getter which has side effect... +// Or "doSomething" can modify "obj.foo". +while (obj.foo) { + doSomething(obj); +} + +// A function call can return various values. +while (check(obj)) { + doSomething(obj); +} +``` + +## When Not To Use It + +If you don't want to notified about references inside of loop conditions, then it's safe to disable this rule. diff --git a/eslint/docs/rules/no-unneeded-ternary.md b/eslint/docs/rules/no-unneeded-ternary.md new file mode 100644 index 0000000..e5d4e80 --- /dev/null +++ b/eslint/docs/rules/no-unneeded-ternary.md @@ -0,0 +1,92 @@ +# disallow ternary operators when simpler alternatives exist (no-unneeded-ternary) + +It's a common mistake in JavaScript to use a conditional expression to select between two Boolean values instead of using ! to convert the test to a Boolean. +Here are some examples: + +```js +// Bad +var isYes = answer === 1 ? true : false; + +// Good +var isYes = answer === 1; + + +// Bad +var isNo = answer === 1 ? false : true; + +// Good +var isNo = answer !== 1; +``` + +Another common mistake is using a single variable as both the conditional test and the consequent. In such cases, the logical `OR` can be used to provide the same functionality. +Here is an example: + +```js +// Bad +foo(bar ? bar : 1); + +// Good +foo(bar || 1); +``` + +## Rule Details + +This rule disallow ternary operators when simpler alternatives exist. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-unneeded-ternary: "error"*/ + +var a = x === 2 ? true : false; + +var a = x ? true : false; +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-unneeded-ternary: "error"*/ + +var a = x === 2 ? "Yes" : "No"; + +var a = x !== false; + +var a = x ? "Yes" : "No"; + +var a = x ? y : x; + +f(x ? x : 1); // default assignment - would be disallowed if defaultAssignment option set to false. See option details below. +``` + +## Options + +This rule has an object option: + +* `"defaultAssignment": true` (default) allows the conditional expression as a default assignment pattern +* `"defaultAssignment": false` disallows the conditional expression as a default assignment pattern + +### defaultAssignment + +When set to `true`, which it is by default, The defaultAssignment option allows expressions of the form `x ? x : expr` (where `x` is any identifier and `expr` is any expression). + +Examples of additional **incorrect** code for this rule with the `{ "defaultAssignment": false }` option: + +```js +/*eslint no-unneeded-ternary: ["error", { "defaultAssignment": false }]*/ + +var a = x ? x : 1; + +f(x ? x : 1); +``` + +Note that `defaultAssignment: false` still allows expressions of the form `x ? expr : x` (where the identifier is on the right hand side of the ternary). + +## When Not To Use It + +You can turn this rule off if you are not concerned with unnecessary complexity in conditional expressions. + +## Related Rules + +* [no-ternary](no-ternary.md) +* [no-nested-ternary](no-nested-ternary.md) diff --git a/eslint/docs/rules/no-unreachable.md b/eslint/docs/rules/no-unreachable.md new file mode 100644 index 0000000..7bd16d3 --- /dev/null +++ b/eslint/docs/rules/no-unreachable.md @@ -0,0 +1,75 @@ +# disallow unreachable code after `return`, `throw`, `continue`, and `break` statements (no-unreachable) + +Because the `return`, `throw`, `break`, and `continue` statements unconditionally exit a block of code, any statements after them cannot be executed. Unreachable statements are usually a mistake. + +```js +function fn() { + x = 1; + return x; + x = 3; // this will never execute +} +``` + +## Rule Details + +This rule disallows unreachable code after `return`, `throw`, `continue`, and `break` statements. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-unreachable: "error"*/ + +function foo() { + return true; + console.log("done"); +} + +function bar() { + throw new Error("Oops!"); + console.log("done"); +} + +while(value) { + break; + console.log("done"); +} + +throw new Error("Oops!"); +console.log("done"); + +function baz() { + if (Math.random() < 0.5) { + return; + } else { + throw new Error(); + } + console.log("done"); +} + +for (;;) {} +console.log("done"); +``` + +Examples of **correct** code for this rule, because of JavaScript function and variable hoisting: + +```js +/*eslint no-unreachable: "error"*/ + +function foo() { + return bar(); + function bar() { + return 1; + } +} + +function bar() { + return x; + var x; +} + +switch (foo) { + case 1: + break; + var x; +} +``` diff --git a/eslint/docs/rules/no-unsafe-finally.md b/eslint/docs/rules/no-unsafe-finally.md new file mode 100644 index 0000000..fb3bb1e --- /dev/null +++ b/eslint/docs/rules/no-unsafe-finally.md @@ -0,0 +1,144 @@ +# disallow control flow statements in `finally` blocks (no-unsafe-finally) + +JavaScript suspends the control flow statements of `try` and `catch` blocks until the execution of `finally` block finishes. So, when `return`, `throw`, `break`, or `continue` is used in `finally`, control flow statements inside `try` and `catch` are overwritten, which is considered as unexpected behavior. Such as: + +```js +// We expect this function to return 1; +(() => { + try { + return 1; // 1 is returned but suspended until finally block ends + } catch(err) { + return 2; + } finally { + return 3; // 3 is returned before 1, which we did not expect + } +})(); + +// > 3 +``` + +```js +// We expect this function to throw an error, then return +(() => { + try { + throw new Error("Try"); // error is thrown but suspended until finally block ends + } finally { + return 3; // 3 is returned before the error is thrown, which we did not expect + } +})(); + +// > 3 +``` + +```js +// We expect this function to throw Try(...) error from the catch block +(() => { + try { + throw new Error("Try") + } catch(err) { + throw err; // The error thrown from try block is caught and rethrown + } finally { + throw new Error("Finally"); // Finally(...) is thrown, which we did not expect + } +})(); + +// > Uncaught Error: Finally(...) +``` + +```js +// We expect this function to return 0 from try block. +(() => { + label: try { + return 0; // 0 is returned but suspended until finally block ends + } finally { + break label; // It breaks out the try-finally block, before 0 is returned. + } + return 1; +})(); + +// > 1 +``` + +## Rule Details + +This rule disallows `return`, `throw`, `break`, and `continue` statements inside `finally` blocks. It allows indirect usages, such as in `function` or `class` definitions. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-unsafe-finally: "error"*/ +let foo = function() { + try { + return 1; + } catch(err) { + return 2; + } finally { + return 3; + } +}; +``` + +```js +/*eslint no-unsafe-finally: "error"*/ +let foo = function() { + try { + return 1; + } catch(err) { + return 2; + } finally { + throw new Error; + } +}; +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-unsafe-finally: "error"*/ +let foo = function() { + try { + return 1; + } catch(err) { + return 2; + } finally { + console.log("hola!"); + } +}; +``` + +```js +/*eslint no-unsafe-finally: "error"*/ +let foo = function() { + try { + return 1; + } catch(err) { + return 2; + } finally { + let a = function() { + return "hola!"; + } + } +}; +``` + +```js +/*eslint no-unsafe-finally: "error"*/ +let foo = function(a) { + try { + return 1; + } catch(err) { + return 2; + } finally { + switch(a) { + case 1: { + console.log("hola!") + break; + } + } + } +}; +``` + +## When Not To Use It + +If you want to allow control flow operations in `finally` blocks, you can turn this rule off. diff --git a/eslint/docs/rules/no-unsafe-negation.md b/eslint/docs/rules/no-unsafe-negation.md new file mode 100644 index 0000000..38bb9b0 --- /dev/null +++ b/eslint/docs/rules/no-unsafe-negation.md @@ -0,0 +1,107 @@ +# disallow negating the left operand of relational operators (no-unsafe-negation) + +Just as developers might type `-a + b` when they mean `-(a + b)` for the negative of a sum, they might type `!key in object` by mistake when they almost certainly mean `!(key in object)` to test that a key is not in an object. `!obj instanceof Ctor` is similar. + +## Rule Details + +This rule disallows negating the left operand of the following relational operators: + +- [`in` operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/in). +- [`instanceof` operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof). + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-unsafe-negation: "error"*/ + +if (!key in object) { + // operator precedence makes it equivalent to (!key) in object + // and type conversion makes it equivalent to (key ? "false" : "true") in object +} + +if (!obj instanceof Ctor) { + // operator precedence makes it equivalent to (!obj) instanceof Ctor + // and it equivalent to always false since boolean values are not objects. +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-unsafe-negation: "error"*/ + +if (!(key in object)) { + // key is not in object +} + +if (!(obj instanceof Ctor)) { + // obj is not an instance of Ctor +} +``` + +### Exception + +For rare situations when negating the left operand is intended, this rule allows an exception. +If the whole negation is explicitly wrapped in parentheses, the rule will not report a problem. + +Examples of **correct** code for this rule: + +```js +/*eslint no-unsafe-negation: "error"*/ + +if ((!foo) in object) { + // allowed, because the negation is explicitly wrapped in parentheses + // it is equivalent to (foo ? "false" : "true") in object + // this is allowed as an exception for rare situations when that is the intended meaning +} + +if(("" + !foo) in object) { + // you can also make the intention more explicit, with type conversion +} +``` + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-unsafe-negation: "error"*/ + +if (!(foo) in object) { + // this is not an allowed exception +} +``` + +## Options + +This rule has an object option: + +- `"enforceForOrderingRelations": false` (default) allows negation of the left-hand side of ordering relational operators (`<`, `>`, `<=`, `>=`) +- `"enforceForOrderingRelations": true` disallows negation of the left-hand side of ordering relational operators + +### enforceForOrderingRelations + +With this option set to `true` the rule is additionally enforced for: + +- `<` operator. +- `>` operator. +- `<=` operator. +- `>=` operator. + +The purpose is to avoid expressions such as `! a < b` (which is equivalent to `(a ? 0 : 1) < b`) when what is really intended is `!(a < b)`. + +Examples of additional **incorrect** code for this rule with the `{ "enforceForOrderingRelations": true }` option: + +```js +/*eslint no-unsafe-negation: ["error", { "enforceForOrderingRelations": true }]*/ + +if (! a < b) {} + +while (! a > b) {} + +foo = ! a <= b; + +foo = ! a >= b; +``` + +## When Not To Use It + +If you don't want to notify unsafe logical negations, then it's safe to disable this rule. diff --git a/eslint/docs/rules/no-unused-expressions.md b/eslint/docs/rules/no-unused-expressions.md new file mode 100644 index 0000000..8a9748d --- /dev/null +++ b/eslint/docs/rules/no-unused-expressions.md @@ -0,0 +1,163 @@ +# Disallow Unused Expressions (no-unused-expressions) + +An unused expression which has no effect on the state of the program indicates a logic error. + +For example, `n + 1;` is not a syntax error, but it might be a typing mistake where a programmer meant an assignment statement `n += 1;` instead. Sometimes, such unused expressions may be eliminated by some build tools in production environment, which possibly breaks application logic. + +## Rule Details + +This rule aims to eliminate unused expressions which have no effect on the state of the program. + +This rule does not apply to function calls or constructor calls with the `new` operator, because they could have *side effects* on the state of the program. + +```js +var i = 0; +function increment() { i += 1; } +increment(); // return value is unused, but i changed as a side effect + +var nThings = 0; +function Thing() { nThings += 1; } +new Thing(); // constructed object is unused, but nThings changed as a side effect +``` + +This rule does not apply to directives (which are in the form of literal string expressions such as `"use strict";` at the beginning of a script, module, or function). + +Sequence expressions (those using a comma, such as `a = 1, b = 2`) are always considered unused unless their return value is assigned or used in a condition evaluation, or a function call is made with the sequence expression value. + +## Options + +This rule, in its default state, does not require any arguments. If you would like to enable one or more of the following you may pass an object with the options set as follows: + +* `allowShortCircuit` set to `true` will allow you to use short circuit evaluations in your expressions (Default: `false`). +* `allowTernary` set to `true` will enable you to use ternary operators in your expressions similarly to short circuit evaluations (Default: `false`). +* `allowTaggedTemplates` set to `true` will enable you to use tagged template literals in your expressions (Default: `false`). + +These options allow unused expressions *only if all* of the code paths either directly change the state (for example, assignment statement) or could have *side effects* (for example, function call). + +Examples of **incorrect** code for the default `{ "allowShortCircuit": false, "allowTernary": false }` options: + +```js +/*eslint no-unused-expressions: "error"*/ + +0 + +if(0) 0 + +{0} + +f(0), {} + +a && b() + +a, b() + +c = a, b; + +a() && function namedFunctionInExpressionContext () {f();} + +(function anIncompleteIIFE () {}); + +injectGlobal`body{ color: red; }` + +``` + +Note that one or more string expression statements (with or without semi-colons) will only be considered as unused if they are not in the beginning of a script, module, or function (alone and uninterrupted by other statements). Otherwise, they will be treated as part of a "directive prologue", a section potentially usable by JavaScript engines. This includes "strict mode" directives. + +```js +"use strict"; +"use asm" +"use stricter"; +"use babel" +"any other strings like this in the prologue"; +``` + +Examples of **correct** code for the default `{ "allowShortCircuit": false, "allowTernary": false }` options: + +```js +/*eslint no-unused-expressions: "error"*/ + +{} // In this context, this is a block statement, not an object literal + +{myLabel: someVar} // In this context, this is a block statement with a label and expression, not an object literal + +function namedFunctionDeclaration () {} + +(function aGenuineIIFE () {}()); + +f() + +a = 0 + +new C + +delete a.b + +void a +``` + +### allowShortCircuit + +Examples of **incorrect** code for the `{ "allowShortCircuit": true }` option: + +```js +/*eslint no-unused-expressions: ["error", { "allowShortCircuit": true }]*/ + +a || b +``` + +Examples of **correct** code for the `{ "allowShortCircuit": true }` option: + +```js +/*eslint no-unused-expressions: ["error", { "allowShortCircuit": true }]*/ + +a && b() +a() || (b = c) +``` + +### allowTernary + +Examples of **incorrect** code for the `{ "allowTernary": true }` option: + +```js +/*eslint no-unused-expressions: ["error", { "allowTernary": true }]*/ + +a ? b : 0 +a ? b : c() +``` + +Examples of **correct** code for the `{ "allowTernary": true }` option: + +```js +/*eslint no-unused-expressions: ["error", { "allowTernary": true }]*/ + +a ? b() : c() +a ? (b = c) : d() +``` + +### allowShortCircuit and allowTernary + +Examples of **correct** code for the `{ "allowShortCircuit": true, "allowTernary": true }` options: + +```js +/*eslint no-unused-expressions: ["error", { "allowShortCircuit": true, "allowTernary": true }]*/ + +a ? b() || (c = d) : e() +``` + +### allowTaggedTemplates + +Examples of **incorrect** code for the `{ "allowTaggedTemplates": true }` option: + +```js +/*eslint no-unused-expressions: ["error", { "allowTaggedTemplates": true }]*/ + +`some untagged template string`; +``` + +Examples of **correct** code for the `{ "allowTaggedTemplates": true }` option: + +```js +/*eslint no-unused-expressions: ["error", { "allowTaggedTemplates": true }]*/ + +tag`some tagged template string`; +``` diff --git a/eslint/docs/rules/no-unused-labels.md b/eslint/docs/rules/no-unused-labels.md new file mode 100644 index 0000000..97b21ba --- /dev/null +++ b/eslint/docs/rules/no-unused-labels.md @@ -0,0 +1,68 @@ +# Disallow Unused Labels (no-unused-labels) + +Labels that are declared and not used anywhere in the code are most likely an error due to incomplete refactoring. + +```js +OUTER_LOOP: +for (const student of students) { + if (checkScores(student.scores)) { + continue; + } + doSomething(student); +} +``` + +In this case, probably removing `OUTER_LOOP:` had been forgotten. +Such labels take up space in the code and can lead to confusion by readers. + +## Rule Details + +This rule is aimed at eliminating unused labels. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-unused-labels: "error"*/ + +A: var foo = 0; + +B: { + foo(); +} + +C: +for (let i = 0; i < 10; ++i) { + foo(); +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-unused-labels: "error"*/ + +A: { + if (foo()) { + break A; + } + bar(); +} + +B: +for (let i = 0; i < 10; ++i) { + if (foo()) { + break B; + } + bar(); +} +``` + +## When Not To Use It + +If you don't want to be notified about unused labels, then it's safe to disable this rule. + +## Related Rules + +* [no-extra-label](./no-extra-label.md) +* [no-labels](./no-labels.md) +* [no-label-var](./no-label-var.md) diff --git a/eslint/docs/rules/no-unused-vars.md b/eslint/docs/rules/no-unused-vars.md new file mode 100644 index 0000000..2b3f093 --- /dev/null +++ b/eslint/docs/rules/no-unused-vars.md @@ -0,0 +1,296 @@ +# Disallow Unused Variables (no-unused-vars) + +Variables that are declared and not used anywhere in the code are most likely an error due to incomplete refactoring. Such variables take up space in the code and can lead to confusion by readers. + +## Rule Details + +This rule is aimed at eliminating unused variables, functions, and function parameters. + +A variable `foo` is considered to be used if any of the following are true: + +* It is called (`foo()`) or constructed (`new foo()`) +* It is read (`var bar = foo`) +* It is passed into a function as an argument (`doSomething(foo)`) +* It is read inside of a function that is passed to another function (`doSomething(function() { foo(); })`) + +A variable is *not* considered to be used if it is only ever declared (`var foo = 5`) or assigned to (`foo = 7`). + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-unused-vars: "error"*/ +/*global some_unused_var*/ + +// It checks variables you have defined as global +some_unused_var = 42; + +var x; + +// Write-only variables are not considered as used. +var y = 10; +y = 5; + +// A read for a modification of itself is not considered as used. +var z = 0; +z = z + 1; + +// By default, unused arguments cause warnings. +(function(foo) { + return 5; +})(); + +// Unused recursive functions also cause warnings. +function fact(n) { + if (n < 2) return 1; + return n * fact(n - 1); +} + +// When a function definition destructures an array, unused entries from the array also cause warnings. +function getY([x, y]) { + return y; +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-unused-vars: "error"*/ + +var x = 10; +alert(x); + +// foo is considered used here +myFunc(function foo() { + // ... +}.bind(this)); + +(function(foo) { + return foo; +})(); + +var myFunc; +myFunc = setTimeout(function() { + // myFunc is considered used + myFunc(); +}, 50); + +// Only the second argument from the descructured array is used. +function getY([, y]) { + return y; +} +``` + +### exported + +In environments outside of CommonJS or ECMAScript modules, you may use `var` to create a global variable that may be used by other scripts. You can use the `/* exported variableName */` comment block to indicate that this variable is being exported and therefore should not be considered unused. + +Note that `/* exported */` has no effect for any of the following: + +* when the environment is `node` or `commonjs` +* when `parserOptions.sourceType` is `module` +* when `ecmaFeatures.globalReturn` is `true` + +The line comment `// exported variableName` will not work as `exported` is not line-specific. + +Examples of **correct** code for `/* exported variableName */` operation: + +```js +/* exported global_var */ + +var global_var = 42; +``` + +## Options + +This rule takes one argument which can be a string or an object. The string settings are the same as those of the `vars` property (explained below). + +By default this rule is enabled with `all` option for variables and `after-used` for arguments. + +```json +{ + "rules": { + "no-unused-vars": ["error", { "vars": "all", "args": "after-used", "ignoreRestSiblings": false }] + } +} +``` + +### vars + +The `vars` option has two settings: + +* `all` checks all variables for usage, including those in the global scope. This is the default setting. +* `local` checks only that locally-declared variables are used but will allow global variables to be unused. + +#### vars: local + +Examples of **correct** code for the `{ "vars": "local" }` option: + +```js +/*eslint no-unused-vars: ["error", { "vars": "local" }]*/ +/*global some_unused_var */ + +some_unused_var = 42; +``` + +### varsIgnorePattern + +The `varsIgnorePattern` option specifies exceptions not to check for usage: variables whose names match a regexp pattern. For example, variables whose names contain `ignored` or `Ignored`. + +Examples of **correct** code for the `{ "varsIgnorePattern": "[iI]gnored" }` option: + +```js +/*eslint no-unused-vars: ["error", { "varsIgnorePattern": "[iI]gnored" }]*/ + +var firstVarIgnored = 1; +var secondVar = 2; +console.log(secondVar); +``` + +### args + +The `args` option has three settings: + +* `after-used` - unused positional arguments that occur before the last used argument will not be checked, but all named arguments and all positional arguments after the last used argument will be checked. +* `all` - all named arguments must be used. +* `none` - do not check arguments. + +#### args: after-used + +Examples of **incorrect** code for the default `{ "args": "after-used" }` option: + +```js +/*eslint no-unused-vars: ["error", { "args": "after-used" }]*/ + +// 2 errors, for the parameters after the last used parameter (bar) +// "baz" is defined but never used +// "qux" is defined but never used +(function(foo, bar, baz, qux) { + return bar; +})(); +``` + +Examples of **correct** code for the default `{ "args": "after-used" }` option: + +```js +/*eslint no-unused-vars: ["error", {"args": "after-used"}]*/ + +(function(foo, bar, baz, qux) { + return qux; +})(); +``` + +#### args: all + +Examples of **incorrect** code for the `{ "args": "all" }` option: + +```js +/*eslint no-unused-vars: ["error", { "args": "all" }]*/ + +// 2 errors +// "foo" is defined but never used +// "baz" is defined but never used +(function(foo, bar, baz) { + return bar; +})(); +``` + +#### args: none + +Examples of **correct** code for the `{ "args": "none" }` option: + +```js +/*eslint no-unused-vars: ["error", { "args": "none" }]*/ + +(function(foo, bar, baz) { + return bar; +})(); +``` + +### ignoreRestSiblings + +The `ignoreRestSiblings` option is a boolean (default: `false`). Using a [Rest Property](https://github.com/tc39/proposal-object-rest-spread) it is possible to "omit" properties from an object, but by default the sibling properties are marked as "unused". With this option enabled the rest property's siblings are ignored. + +Examples of **correct** code for the `{ "ignoreRestSiblings": true }` option: + +```js +/*eslint no-unused-vars: ["error", { "ignoreRestSiblings": true }]*/ +// 'type' is ignored because it has a rest property sibling. +var { type, ...coords } = data; +``` + +### argsIgnorePattern + +The `argsIgnorePattern` option specifies exceptions not to check for usage: arguments whose names match a regexp pattern. For example, variables whose names begin with an underscore. + +Examples of **correct** code for the `{ "argsIgnorePattern": "^_" }` option: + +```js +/*eslint no-unused-vars: ["error", { "argsIgnorePattern": "^_" }]*/ + +function foo(x, _y) { + return x + 1; +} +foo(); +``` + +### caughtErrors + +The `caughtErrors` option is used for `catch` block arguments validation. + +It has two settings: + +* `none` - do not check error objects. This is the default setting. +* `all` - all named arguments must be used. + +#### caughtErrors: none + +Not specifying this rule is equivalent of assigning it to `none`. + +Examples of **correct** code for the `{ "caughtErrors": "none" }` option: + +```js +/*eslint no-unused-vars: ["error", { "caughtErrors": "none" }]*/ + +try { + //... +} catch (err) { + console.error("errors"); +} +``` + +#### caughtErrors: all + +Examples of **incorrect** code for the `{ "caughtErrors": "all" }` option: + +```js +/*eslint no-unused-vars: ["error", { "caughtErrors": "all" }]*/ + +// 1 error +// "err" is defined but never used +try { + //... +} catch (err) { + console.error("errors"); +} +``` + +### caughtErrorsIgnorePattern + +The `caughtErrorsIgnorePattern` option specifies exceptions not to check for usage: catch arguments whose names match a regexp pattern. For example, variables whose names begin with a string 'ignore'. + +Examples of **correct** code for the `{ "caughtErrorsIgnorePattern": "^ignore" }` option: + +```js +/*eslint no-unused-vars: ["error", { "caughtErrorsIgnorePattern": "^ignore" }]*/ + +try { + //... +} catch (ignoreErr) { + console.error("errors"); +} +``` + + +## When Not To Use It + +If you don't want to be notified about unused variables or function arguments, you can safely turn this rule off. diff --git a/eslint/docs/rules/no-use-before-define.md b/eslint/docs/rules/no-use-before-define.md new file mode 100644 index 0000000..391c323 --- /dev/null +++ b/eslint/docs/rules/no-use-before-define.md @@ -0,0 +1,161 @@ +# Disallow Early Use (no-use-before-define) + +In JavaScript, prior to ES6, variable and function declarations are hoisted to the top of a scope, so it's possible to use identifiers before their formal declarations in code. This can be confusing and some believe it is best to always declare variables and functions before using them. + +In ES6, block-level bindings (`let` and `const`) introduce a "temporal dead zone" where a `ReferenceError` will be thrown with any attempt to access the variable before its declaration. + +## Rule Details + +This rule will warn when it encounters a reference to an identifier that has not yet been declared. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-use-before-define: "error"*/ +/*eslint-env es6*/ + +alert(a); +var a = 10; + +f(); +function f() {} + +function g() { + return b; +} +var b = 1; + +{ + alert(c); + let c = 1; +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-use-before-define: "error"*/ +/*eslint-env es6*/ + +var a; +a = 10; +alert(a); + +function f() {} +f(1); + +var b = 1; +function g() { + return b; +} + +{ + let c; + c++; +} +``` + +## Options + +```json +{ + "no-use-before-define": ["error", { "functions": true, "classes": true }] +} +``` + +* `functions` (`boolean`) - + The flag which shows whether or not this rule checks function declarations. + If this is `true`, this rule warns every reference to a function before the function declaration. + Otherwise, ignores those references. + Function declarations are hoisted, so it's safe. + Default is `true`. +* `classes` (`boolean`) - + The flag which shows whether or not this rule checks class declarations of upper scopes. + If this is `true`, this rule warns every reference to a class before the class declaration. + Otherwise, ignores those references if the declaration is in upper function scopes. + Class declarations are not hoisted, so it might be danger. + Default is `true`. +* `variables` (`boolean`) - + This flag determines whether or not the rule checks variable declarations in upper scopes. + If this is `true`, the rule warns every reference to a variable before the variable declaration. + Otherwise, the rule ignores a reference if the declaration is in an upper scope, while still reporting the reference if it's in the same scope as the declaration. + Default is `true`. + +This rule accepts `"nofunc"` string as an option. +`"nofunc"` is the same as `{ "functions": false, "classes": true, "variables": true }`. + +### functions + +Examples of **correct** code for the `{ "functions": false }` option: + +```js +/*eslint no-use-before-define: ["error", { "functions": false }]*/ + +f(); +function f() {} +``` + +This option allows references to function declarations. For function expressions and arrow functions, please see the [`variables`](#variables) option. + +### classes + +Examples of **incorrect** code for the `{ "classes": false }` option: + +```js +/*eslint no-use-before-define: ["error", { "classes": false }]*/ +/*eslint-env es6*/ + +new A(); +class A { +} +``` + +Examples of **correct** code for the `{ "classes": false }` option: + +```js +/*eslint no-use-before-define: ["error", { "classes": false }]*/ +/*eslint-env es6*/ + +function foo() { + return new A(); +} + +class A { +} +``` + +### variables + +Examples of **incorrect** code for the `{ "variables": false }` option: + +```js +/*eslint no-use-before-define: ["error", { "variables": false }]*/ + +console.log(foo); +var foo = 1; + +f(); +const f = () => {}; + +g(); +const g = function() {}; +``` + +Examples of **correct** code for the `{ "variables": false }` option: + +```js +/*eslint no-use-before-define: ["error", { "variables": false }]*/ + +function baz() { + console.log(foo); +} +var foo = 1; + +const a = () => f(); +function b() { return f(); } +const c = function() { return f(); } +const f = () => {}; + +const e = function() { return g(); } +const g = function() {} +``` diff --git a/eslint/docs/rules/no-useless-backreference.md b/eslint/docs/rules/no-useless-backreference.md new file mode 100644 index 0000000..d2360b3 --- /dev/null +++ b/eslint/docs/rules/no-useless-backreference.md @@ -0,0 +1,130 @@ +# Disallow useless backreferences in regular expressions (no-useless-backreference) + +In JavaScript regular expressions, it's syntactically valid to define a backreference to a group that belongs to another alternative part of the pattern, a backreference to a group that appears after the backreference, a backreference to a group that contains that backreference, or a backreference to a group that is inside a negative lookaround. However, by the specification, in any of these cases the backreference always ends up matching only zero-length (the empty string), regardless of the context in which the backreference and the group appear. + +Backreferences that always successfully match zero-length and cannot match anything else are useless. They are basically ignored and can be removed without changing the behavior of the regular expression. + +```js +var regex = /^(?:(a)|\1b)$/; + +regex.test("a"); // true +regex.test("b"); // true! +regex.test("ab"); // false + +var equivalentRegex = /^(?:(a)|b)$/; + +equivalentRegex.test("a"); // true +equivalentRegex.test("b"); // true +equivalentRegex.test("ab"); // false +``` + +Useless backreference is a possible error in the code. It usually indicates that the regular expression does not work as intended. + +## Rule Details + +This rule aims to detect and disallow the following backreferences in regular expression: + +* Backreference to a group that is in another alternative, e.g., `/(a)|\1b/`. In such constructed regular expression, the backreference is expected to match what's been captured in, at that point, a non-participating group. +* Backreference to a group that appears later in the pattern, e.g., `/\1(a)/`. The group hasn't captured anything yet, and ECMAScript doesn't support forward references. Inside lookbehinds, which match backward, the opposite applies and this rule disallows backreference to a group that appears before in the same lookbehind, e.g., `/(?<=(a)\1)b/`. +* Backreference to a group from within the same group, e.g., `/(\1)/`. Similar to the previous, the group hasn't captured anything yet, and ECMAScript doesn't support nested references. +* Backreference to a group that is in a negative lookaround, if the backreference isn't in the same negative lookaround, e.g., `/a(?!(b)).\1/`. A negative lookaround (lookahead or lookbehind) succeeds only if its pattern cannot match, meaning that the group has failed. + +By the ECMAScript specification, all backreferences listed above are valid, always succeed to match zero-length, and cannot match anything else. Consequently, they don't produce parsing or runtime errors, but also don't affect the behavior of their regular expressions. They are syntactically valid but useless. + +This might be surprising to developers coming from other languages where some of these backreferences can be used in a meaningful way. + +```js +// in some other languages, this pattern would successfully match "aab" + +/^(?:(a)(?=a)|\1b)+$/.test("aab"); // false +``` + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-useless-backreference: "error"*/ + +/^(?:(a)|\1b)$/; // reference to (a) into another alternative + +/^(?:(a)|b(?:c|\1))$/; // reference to (a) into another alternative + +/^(?:a|b(?:(c)|\1))$/; // reference to (c) into another alternative + +/\1(a)/; // forward reference to (a) + +RegExp('(a)\\2(b)'); // forward reference to (b) + +/(?:a)(b)\2(c)/; // forward reference to (c) + +/\k(?a)/; // forward reference to (?a) + +/(?<=(a)\1)b/; // backward reference to (a) from within the same lookbehind + +/(?(.)b\1)/; // nested reference to (?(.)b\1) + +/a(?!(b)).\1/; // reference to (b) into a negative lookahead + +/(?a)\k/; // reference to (?a) + +/(?<=\1(a))b/; // reference to (a), correctly before the group as they're in the same lookbehind + +/(?<=(a))b\1/; // reference to (a), correctly after the group as the backreference isn't in the lookbehind + +new RegExp('(.)\\1'); // reference to (.) + +/^(?:(a)\1)$/; // reference to (a) + +/^((a)\2)$/; // reference to (a) + +/a(?(.)b\2)/; // reference to (.) + +/a(?!(b|c)\1)./; // reference to (b|c), correct as it's from within the same negative lookahead + +/(? foo = "hola" +let bar = `${foo}\!`; // > bar = "hola!" +let baz = /\:/ // same functionality with /:/ +``` + +## Rule Details + +This rule flags escapes that can be safely removed without changing behavior. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-useless-escape: "error"*/ + +"\'"; +'\"'; +"\#"; +"\e"; +`\"`; +`\"${foo}\"`; +`\#{foo}`; +/\!/; +/\@/; + +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-useless-escape: "error"*/ + +"\""; +'\''; +"\x12"; +"\u00a9"; +"\371"; +"xs\u2111"; +`\``; +`\${${foo}}`; +`$\{${foo}}`; +/\\/g; +/\t/g; +/\w\$\*\^\./; + +``` + +## When Not To Use It + +If you don't want to be notified about unnecessary escapes, you can safely disable this rule. diff --git a/eslint/docs/rules/no-useless-rename.md b/eslint/docs/rules/no-useless-rename.md new file mode 100644 index 0000000..ea38e84 --- /dev/null +++ b/eslint/docs/rules/no-useless-rename.md @@ -0,0 +1,124 @@ +# Disallow renaming import, export, and destructured assignments to the same name (no-useless-rename) + +ES2015 allows for the renaming of references in import and export statements as well as destructuring assignments. This gives programmers a concise syntax for performing these operations while renaming these references: + +```js +import { foo as bar } from "baz"; +export { foo as bar }; +let { foo: bar } = baz; +``` + +With this syntax, it is possible to rename a reference to the same name. This is a completely redundant operation, as this is the same as not renaming at all. For example, this: + +```js +import { foo as foo } from "bar"; +export { foo as foo }; +let { foo: foo } = bar; +``` + +is the same as: + +```js +import { foo } from "bar"; +export { foo }; +let { foo } = bar; +``` + +## Rule Details + +This rule disallows the renaming of import, export, and destructured assignments to the same name. + +See Also: + +- [`object-shorthand`](https://eslint.org/docs/rules/object-shorthand) which can enforce this behavior for properties in object literals. + +## Options + +This rule allows for more fine-grained control with the following options: + +- `ignoreImport`: When set to `true`, this rule does not check imports +- `ignoreExport`: When set to `true`, this rule does not check exports +- `ignoreDestructuring`: When set to `true`, this rule does not check destructuring assignments + +By default, all options are set to `false`: + +```json +"no-useless-rename": ["error", { + "ignoreDestructuring": false, + "ignoreImport": false, + "ignoreExport": false +}] +``` + +Examples of **incorrect** code for this rule by default: + +```js +/*eslint no-useless-rename: "error"*/ + +import { foo as foo } from "bar"; +export { foo as foo }; +export { foo as foo } from "bar"; +let { foo: foo } = bar; +let { 'foo': foo } = bar; +function foo({ bar: bar }) {} +({ foo: foo }) => {} +``` + +Examples of **correct** code for this rule by default: + +```js +/*eslint no-useless-rename: "error"*/ + +import * as foo from "foo"; +import { foo } from "bar"; +import { foo as bar } from "baz"; + +export { foo }; +export { foo as bar }; +export { foo as bar } from "foo"; + +let { foo } = bar; +let { foo: bar } = baz; +let { [foo]: foo } = bar; + +function foo({ bar }) {} +function foo({ bar: baz }) {} + +({ foo }) => {} +({ foo: bar }) => {} +``` + +Examples of **correct** code for this rule with `{ ignoreImport: true }`: + +```js +/*eslint no-useless-rename: ["error", { ignoreImport: true }]*/ + +import { foo as foo } from "bar"; +``` + +Examples of **correct** code for this rule with `{ ignoreExport: true }`: + +```js +/*eslint no-useless-rename: ["error", { ignoreExport: true }]*/ + +export { foo as foo }; +export { foo as foo } from "bar"; +``` + +Examples of **correct** code for this rule with `{ ignoreDestructuring: true }`: + +```js +/*eslint no-useless-rename: ["error", { ignoreDestructuring: true }]*/ + +let { foo: foo } = bar; +function foo({ bar: bar }) {} +({ foo: foo }) => {} +``` + +## When Not To Use It + +You can safely disable this rule if you do not care about redundantly renaming import, export, and destructuring assignments. + +## Compatibility + +- **JSCS**: [disallowIdenticalDestructuringNames](https://jscs-dev.github.io/rule/disallowIdenticalDestructuringNames) diff --git a/eslint/docs/rules/no-useless-return.md b/eslint/docs/rules/no-useless-return.md new file mode 100644 index 0000000..4e8de68 --- /dev/null +++ b/eslint/docs/rules/no-useless-return.md @@ -0,0 +1,83 @@ +# Disallow redundant return statements (no-useless-return) + +A `return;` statement with nothing after it is redundant, and has no effect on the runtime behavior of a function. This can be confusing, so it's better to disallow these redundant statements. + +## Rule Details + +This rule aims to report redundant `return` statements. + +Examples of **incorrect** code for this rule: + +```js +/* eslint no-useless-return: "error" */ + +function foo() { return; } + +function foo() { + doSomething(); + return; +} + +function foo() { + if (condition) { + bar(); + return; + } else { + baz(); + } +} + +function foo() { + switch (bar) { + case 1: + doSomething(); + default: + doSomethingElse(); + return; + } +} + +``` + +Examples of **correct** code for this rule: + +```js +/* eslint no-useless-return: "error" */ + +function foo() { return 5; } + +function foo() { + return doSomething(); +} + +function foo() { + if (condition) { + bar(); + return; + } else { + baz(); + } + qux(); +} + +function foo() { + switch (bar) { + case 1: + doSomething(); + return; + default: + doSomethingElse(); + } +} + +function foo() { + for (const foo of bar) { + return; + } +} + +``` + +## When Not To Use It + +If you don't care about disallowing redundant return statements, you can turn off this rule. diff --git a/eslint/docs/rules/no-var.md b/eslint/docs/rules/no-var.md new file mode 100644 index 0000000..83950c3 --- /dev/null +++ b/eslint/docs/rules/no-var.md @@ -0,0 +1,48 @@ +# require `let` or `const` instead of `var` (no-var) + +ECMAScript 6 allows programmers to create variables with block scope instead of function scope using the `let` +and `const` keywords. Block scope is common in many other programming languages and helps programmers avoid mistakes +such as: + +```js +var count = people.length; +var enoughFood = count > sandwiches.length; + +if (enoughFood) { + var count = sandwiches.length; // accidentally overriding the count variable + console.log("We have " + count + " sandwiches for everyone. Plenty for all!"); +} + +// our count variable is no longer accurate +console.log("We have " + count + " people and " + sandwiches.length + " sandwiches!"); +``` + +## Rule Details + +This rule is aimed at discouraging the use of `var` and encouraging the use of `const` or `let` instead. + +## Examples + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-var: "error"*/ + +var x = "y"; +var CONFIG = {}; +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-var: "error"*/ +/*eslint-env es6*/ + +let x = "y"; +const CONFIG = {}; +``` + +## When Not To Use It + +In addition to non-ES6 environments, existing JavaScript projects that are beginning to introduce ES6 into their +codebase may not want to apply this rule if the cost of migrating from `var` to `let` is too costly. diff --git a/eslint/docs/rules/no-void.md b/eslint/docs/rules/no-void.md new file mode 100644 index 0000000..6b77748 --- /dev/null +++ b/eslint/docs/rules/no-void.md @@ -0,0 +1,107 @@ +# Disallow use of the void operator. (no-void) + +The `void` operator takes an operand and returns `undefined`: `void expression` will evaluate `expression` and return `undefined`. It can be used to ignore any side effects `expression` may produce: + +The common case of using `void` operator is to get a "pure" `undefined` value as prior to ES5 the `undefined` variable was mutable: + +```js +// will always return undefined +(function(){ + return void 0; +})(); + +// will return 1 in ES3 and undefined in ES5+ +(function(){ + undefined = 1; + return undefined; +})(); + +// will throw TypeError in ES5+ +(function(){ + 'use strict'; + undefined = 1; +})(); +``` + +Another common case is to minify code as `void 0` is shorter than `undefined`: + +```js +foo = void 0; +foo = undefined; +``` + +When used with IIFE (immediately-invoked function expression), `void` can be used to force the function keyword to be treated as an expression instead of a declaration: + +```js +var foo = 1; +void function(){ foo = 1; }() // will assign foo a value of 1 ++function(){ foo = 1; }() // same as above +``` + +``` +function(){ foo = 1; }() // will throw SyntaxError +``` + +Some code styles prohibit `void` operator, marking it as non-obvious and hard to read. + +## Rule Details + +This rule aims to eliminate use of void operator. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-void: "error"*/ + +void foo +void someFunction(); + +var foo = void bar(); +function baz() { + return void 0; +} +``` + +## Options + +This rule has an object option: + +* `allowAsStatement` set to `true` allows the void operator to be used as a statement (Default `false`). + +### allowAsStatement + +When `allowAsStatement` is set to true, the rule will not error on cases that the void operator is used as a statement, i.e. when it's not used in an expression position, like in a variable assignment or a function return. + +Examples of **incorrect** code for `{ "allowAsStatement": true }`: + +```js +/*eslint no-void: ["error", { "allowAsStatement": true }]*/ + +var foo = void bar(); +function baz() { + return void 0; +} +``` + +Examples of **correct** code for `{ "allowAsStatement": true }`: + +```js +/*eslint no-void: ["error", { "allowAsStatement": true }]*/ + +void foo; +void someFunction(); +``` + +## When Not To Use It + +If you intentionally use the `void` operator then you can disable this rule. + +## Further Reading + +* [Mozilla Developer Network](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/void) +* [Bad Parts: Appendix B - JavaScript: The Good Parts by Douglas Crockford](https://oreilly.com/javascript/excerpts/javascript-good-parts/bad-parts.html) + +## Related Rules + +* [no-undef-init](no-undef-init.md) +* [no-undefined](no-undefined.md) diff --git a/eslint/docs/rules/no-warning-comments.md b/eslint/docs/rules/no-warning-comments.md new file mode 100644 index 0000000..4836560 --- /dev/null +++ b/eslint/docs/rules/no-warning-comments.md @@ -0,0 +1,85 @@ +# Disallow Warning Comments (no-warning-comments) + +Developers often add comments to code which is not complete or needs review. Most likely you want to fix or review the code, and then remove the comment, before you consider the code to be production ready. + +```js +// TODO: do something +// FIXME: this is not a good idea +``` + +## Rule Details + +This rule reports comments that include any of the predefined terms specified in its configuration. + +## Options + +This rule has an options object literal: + +* `"terms"`: optional array of terms to match. Defaults to `["todo", "fixme", "xxx"]`. Terms are matched case-insensitive and as whole words: `fix` would match `FIX` but not `fixing`. Terms can consist of multiple words: `really bad idea`. +* `"location"`: optional string that configures where in your comments to check for matches. Defaults to `"start"`. The other value is match `anywhere` in comments. + +Example of **incorrect** code for the default `{ "terms": ["todo", "fixme", "xxx"], "location": "start" }` options: + +```js +/*eslint no-warning-comments: "error"*/ + +function callback(err, results) { + if (err) { + console.error(err); + return; + } + // TODO +} +``` + +Example of **correct** code for the default `{ "terms": ["todo", "fixme", "xxx"], "location": "start" }` options: + +```js +/*eslint no-warning-comments: "error"*/ + +function callback(err, results) { + if (err) { + console.error(err); + return; + } + // NOT READY FOR PRIME TIME + // but too bad, it is not a predefined warning term +} +``` + +### terms and location + +Examples of **incorrect** code for the `{ "terms": ["todo", "fixme", "any other term"], "location": "anywhere" }` options: + +```js +/*eslint no-warning-comments: ["error", { "terms": ["todo", "fixme", "any other term"], "location": "anywhere" }]*/ + +// TODO: this +// todo: this too +// Even this: TODO +/* /* + * The same goes for this TODO comment + * Or a fixme + * as well as any other term + */ +``` + +Examples of **correct** code for the `{ "terms": ["todo", "fixme", "any other term"], "location": "anywhere" }` options: + +```js +/*eslint no-warning-comments: ["error", { "terms": ["todo", "fixme", "any other term"], "location": "anywhere" }]*/ + +// This is to do +// even not any other term +// any other terminal +/* + * The same goes for block comments + * with any other interesting term + * or fix me this + */ +``` + +## When Not To Use It + +* If you have a large code base that was not developed with a policy to not use such warning terms, you might get hundreds of warnings / errors which might be counter-productive if you can't fix all of them (e.g. if you don't get the time to do it) as you might overlook other warnings / errors or get used to many of them and don't pay attention on it anymore. +* Same reason as the point above: You shouldn't configure terms that are used very often (e.g. central parts of the native language used in your comments). diff --git a/eslint/docs/rules/no-whitespace-before-property.md b/eslint/docs/rules/no-whitespace-before-property.md new file mode 100644 index 0000000..70ecc14 --- /dev/null +++ b/eslint/docs/rules/no-whitespace-before-property.md @@ -0,0 +1,67 @@ +# disallow whitespace before properties (no-whitespace-before-property) + +JavaScript allows whitespace between objects and their properties. However, inconsistent spacing can make code harder to read and can lead to errors. + +```js +foo. bar .baz . quz +``` + +## Rule Details + +This rule disallows whitespace around the dot or before the opening bracket before properties of objects if they are on the same line. This rule allows whitespace when the object and property are on separate lines, as it is common to add newlines to longer chains of properties: + +```js +foo + .bar() + .baz() + .qux() +``` + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-whitespace-before-property: "error"*/ + +foo [bar] + +foo. bar + +foo .bar + +foo. bar. baz + +foo. bar() + .baz() + +foo + .bar(). baz() +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-whitespace-before-property: "error"*/ + +foo.bar + +foo[bar] + +foo[ bar ] + +foo.bar.baz + +foo + .bar().baz() + +foo + .bar() + .baz() + +foo. + bar(). + baz() +``` + +## When Not To Use It + +Turn this rule off if you do not care about allowing whitespace around the dot or before the opening bracket before properties of objects if they are on the same line. diff --git a/eslint/docs/rules/no-with.md b/eslint/docs/rules/no-with.md new file mode 100644 index 0000000..979c58b --- /dev/null +++ b/eslint/docs/rules/no-with.md @@ -0,0 +1,36 @@ +# disallow `with` statements (no-with) + +The `with` statement is potentially problematic because it adds members of an object to the current scope, making it impossible to tell what a variable inside the block actually refers to. + +## Rule Details + +This rule disallows `with` statements. + +If ESLint parses code in strict mode, the parser (instead of this rule) reports the error. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-with: "error"*/ + +with (point) { + r = Math.sqrt(x * x + y * y); // is r a member of point? +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-with: "error"*/ +/*eslint-env es6*/ + +const r = ({x, y}) => Math.sqrt(x * x + y * y); +``` + +## When Not To Use It + +If you intentionally use `with` statements then you can disable this rule. + +## Further Reading + +* [with Statement Considered Harmful](https://yuiblog.com/blog/2006/04/11/with-statement-considered-harmful/) diff --git a/eslint/docs/rules/no-wrap-func.md b/eslint/docs/rules/no-wrap-func.md new file mode 100644 index 0000000..6732c92 --- /dev/null +++ b/eslint/docs/rules/no-wrap-func.md @@ -0,0 +1,34 @@ +# no-wrap-func: disallow unnecessary parentheses around function expressions + +(removed) This rule was **removed** in ESLint v1.0 and **replaced** by the [no-extra-parens](no-extra-parens.md) rule. The `"functions"` option in the new rule is equivalent to the removed rule. + + +Although it's possible to wrap functions in parentheses, this can be confusing when the code also contains immediately-invoked function expressions (IIFEs) since parentheses are often used to make this distinction. For example: + +```js +var foo = (function() { + // IIFE +}()); + +var bar = (function() { + // not an IIFE +}); +``` + +## Rule Details + +This rule will raise a warning when it encounters a function expression wrapped in parentheses with no following invoking parentheses. + +Example of **incorrect** code for this rule: + +```js +var a = (function() {/*...*/}); +``` + +Examples of **correct** code for this rule: + +```js +var a = function() {/*...*/}; + +(function() {/*...*/})(); +``` diff --git a/eslint/docs/rules/nonblock-statement-body-position.md b/eslint/docs/rules/nonblock-statement-body-position.md new file mode 100644 index 0000000..c2de17d --- /dev/null +++ b/eslint/docs/rules/nonblock-statement-body-position.md @@ -0,0 +1,158 @@ +# enforce the location of single-line statements (nonblock-statement-body-position) + +When writing `if`, `else`, `while`, `do-while`, and `for` statements, the body can be a single statement instead of a block. It can be useful to enforce a consistent location for these single statements. + +For example, some developers avoid writing code like this: + +```js +if (foo) + bar(); +``` + +If another developer attempts to add `baz();` to the `if` statement, they might mistakenly change the code to + +```js +if (foo) + bar(); + baz(); // this line is not in the `if` statement! +``` + +To avoid this issue, one might require all single-line `if` statements to appear directly after the conditional, without a linebreak: + +```js +if (foo) bar(); +``` + +## Rule Details + +This rule aims to enforce a consistent location for single-line statements. + +Note that this rule does not enforce the usage of single-line statements in general. If you would like to disallow single-line statements, use the [`curly`](/docs/rules/curly.md) rule instead. + +### Options + +This rule accepts a string option: + +* `"beside"` (default) disallows a newline before a single-line statement. +* `"below"` requires a newline before a single-line statement. +* `"any"` does not enforce the position of a single-line statement. + +Additionally, the rule accepts an optional object option with an `"overrides"` key. This can be used to specify a location for particular statements that override the default. For example: + +* `"beside", { "overrides": { "while": "below" } }` requires all single-line statements to appear on the same line as their parent, unless the parent is a `while` statement, in which case the single-line statement must not be on the same line. +* `"below", { "overrides": { "do": "any" } }` disallows all single-line statements from appearing on the same line as their parent, unless the parent is a `do-while` statement, in which case the position of the single-line statement is not enforced. + +Examples of **incorrect** code for this rule with the default `"beside"` option: + +```js +/* eslint nonblock-statement-body-position: ["error", "beside"] */ + +if (foo) + bar(); +else + baz(); + +while (foo) + bar(); + +for (let i = 1; i < foo; i++) + bar(); + +do + bar(); +while (foo) + +``` + +Examples of **correct** code for this rule with the default `"beside"` option: + +```js +/* eslint nonblock-statement-body-position: ["error", "beside"] */ + +if (foo) bar(); +else baz(); + +while (foo) bar(); + +for (let i = 1; i < foo; i++) bar(); + +do bar(); while (foo) + +if (foo) { // block statements are always allowed with this rule + bar(); +} else { + baz(); +} +``` + +Examples of **incorrect** code for this rule with the `"below"` option: + +```js +/* eslint nonblock-statement-body-position: ["error", "below"] */ + +if (foo) bar(); +else baz(); + +while (foo) bar(); + +for (let i = 1; i < foo; i++) bar(); + +do bar(); while (foo) +``` + +Examples of **correct** code for this rule with the `"below"` option: + +```js +/* eslint nonblock-statement-body-position: ["error", "below"] */ + +if (foo) + bar(); +else + baz(); + +while (foo) + bar(); + +for (let i = 1; i < foo; i++) + bar(); + +do + bar(); +while (foo) + +if (foo) { + // Although the second `if` statement is on the same line as the `else`, this is a very common + // pattern, so it's not checked by this rule. +} else if (bar) { +} +``` + +Examples of **incorrect** code for this rule with the `"beside", { "overrides": { "while": "below" } }` rule: + +```js +/* eslint nonblock-statement-body-position: ["error", "beside", { "overrides": { "while": "below" } }] */ + +if (foo) + bar(); + +while (foo) bar(); +``` + +Examples of **correct** code for this rule with the `"beside", { "overrides": { "while": "below" } }` rule: + +```js +/* eslint nonblock-statement-body-position: ["error", "beside", { "overrides": { "while": "below" } }] */ + +if (foo) bar(); + +while (foo) + bar(); +``` + +## When Not To Use It + +If you're not concerned about consistent locations of single-line statements, you should not turn on this rule. You can also disable this rule if you're using the `"all"` option for the [`curly`](/docs/rules/curly.md) rule, because this will disallow single-line statements entirely. + +## Further Reading + +* JSCS: [requireNewlineBeforeSingleStatementsInIf](https://jscs-dev.github.io/rule/requireNewlineBeforeSingleStatementsInIf) diff --git a/eslint/docs/rules/object-curly-newline.md b/eslint/docs/rules/object-curly-newline.md new file mode 100644 index 0000000..4e1cce7 --- /dev/null +++ b/eslint/docs/rules/object-curly-newline.md @@ -0,0 +1,524 @@ +# enforce consistent line breaks inside braces (object-curly-newline) + +A number of style guides require or disallow line breaks inside of object braces and other tokens. + +## Rule Details + +This rule enforces consistent line breaks inside braces of object literals or destructuring assignments. + +## Options + +This rule has either a string option: + +* `"always"` requires line breaks inside braces +* `"never"` disallows line breaks inside braces + +Or an object option: + +* `"multiline": true` requires line breaks if there are line breaks inside properties or between properties. Otherwise, it disallows line breaks. +* `"minProperties"` requires line breaks if the number of properties is at least the given integer. By default, an error will also be reported if an object contains linebreaks and has fewer properties than the given integer. However, the second behavior is disabled if the `consistent` option is set to `true` +* `"consistent": true` (default) requires that either both curly braces, or neither, directly enclose newlines. Note that enabling this option will also change the behavior of the `minProperties` option. (See `minProperties` above for more information) + +You can specify different options for object literals, destructuring assignments, and named imports and exports: + +```json +{ + "object-curly-newline": ["error", { + "ObjectExpression": "always", + "ObjectPattern": { "multiline": true }, + "ImportDeclaration": "never", + "ExportDeclaration": { "multiline": true, "minProperties": 3 } + }] +} +``` + +* `"ObjectExpression"` configuration for object literals +* `"ObjectPattern"` configuration for object patterns of destructuring assignments +* `"ImportDeclaration"` configuration for named imports +* `"ExportDeclaration"` configuration for named exports + +### always + +Examples of **incorrect** code for this rule with the `"always"` option: + +```js +/*eslint object-curly-newline: ["error", "always"]*/ +/*eslint-env es6*/ + +let a = {}; +let b = {foo: 1}; +let c = {foo: 1, bar: 2}; +let d = {foo: 1, + bar: 2}; +let e = {foo() { + dosomething(); +}}; + +let {} = obj; +let {f} = obj; +let {g, h} = obj; +let {i, + j} = obj; +let {k = function() { + dosomething(); +}} = obj; +``` + +Examples of **correct** code for this rule with the `"always"` option: + +```js +/*eslint object-curly-newline: ["error", "always"]*/ +/*eslint-env es6*/ + +let a = { +}; +let b = { + foo: 1 +}; +let c = { + foo: 1, bar: 2 +}; +let d = { + foo: 1, + bar: 2 +}; +let e = { + foo: function() { + dosomething(); + } +}; + +let { +} = obj; +let { + f +} = obj; +let { + g, h +} = obj; +let { + i, + j +} = obj; +let { + k = function() { + dosomething(); + } +} = obj; +``` + +### never + +Examples of **incorrect** code for this rule with the `"never"` option: + +```js +/*eslint object-curly-newline: ["error", "never"]*/ +/*eslint-env es6*/ + +let a = { +}; +let b = { + foo: 1 +}; +let c = { + foo: 1, bar: 2 +}; +let d = { + foo: 1, + bar: 2 +}; +let e = { + foo: function() { + dosomething(); + } +}; + +let { +} = obj; +let { + f +} = obj; +let { + g, h +} = obj; +let { + i, + j +} = obj; +let { + k = function() { + dosomething(); + } +} = obj; +``` + +Examples of **correct** code for this rule with the `"never"` option: + +```js +/*eslint object-curly-newline: ["error", "never"]*/ +/*eslint-env es6*/ + +let a = {}; +let b = {foo: 1}; +let c = {foo: 1, bar: 2}; +let d = {foo: 1, + bar: 2}; +let e = {foo: function() { + dosomething(); +}}; + +let {} = obj; +let {f} = obj; +let {g, h} = obj; +let {i, + j} = obj; +let {k = function() { + dosomething(); +}} = obj; +``` + +### multiline + +Examples of **incorrect** code for this rule with the `{ "multiline": true }` option: + +```js +/*eslint object-curly-newline: ["error", { "multiline": true }]*/ +/*eslint-env es6*/ + +let a = { +}; +let b = { + foo: 1 +}; +let c = { + foo: 1, bar: 2 +}; +let d = {foo: 1, + bar: 2}; +let e = {foo: function() { + dosomething(); +}}; + +let { +} = obj; +let { + f +} = obj; +let { + g, h +} = obj; +let {i, + j} = obj; +let {k = function() { + dosomething(); +}} = obj; +``` + +Examples of **correct** code for this rule with the `{ "multiline": true }` option: + +```js +/*eslint object-curly-newline: ["error", { "multiline": true }]*/ +/*eslint-env es6*/ + +let a = {}; +let b = {foo: 1}; +let c = {foo: 1, bar: 2}; +let d = { + foo: 1, + bar: 2 +}; +let e = { + foo: function() { + dosomething(); + } +}; + +let {} = obj; +let {f} = obj; +let {g, h} = obj; +let { + i, + j +} = obj; +let { + k = function() { + dosomething(); + } +} = obj; +``` + +### minProperties + +Examples of **incorrect** code for this rule with the `{ "minProperties": 2 }` option: + +```js +/*eslint object-curly-newline: ["error", { "minProperties": 2 }]*/ +/*eslint-env es6*/ + +let a = { +}; +let b = { + foo: 1 +}; +let c = {foo: 1, bar: 2}; +let d = {foo: 1, + bar: 2}; +let e = { + foo: function() { + dosomething(); + } +}; + +let { +} = obj; +let { + f +} = obj; +let {g, h} = obj; +let {i, + j} = obj; +let { + k = function() { + dosomething(); + } +} = obj; +``` + +Examples of **correct** code for this rule with the `{ "minProperties": 2 }` option: + +```js +/*eslint object-curly-newline: ["error", { "minProperties": 2 }]*/ +/*eslint-env es6*/ + +let a = {}; +let b = {foo: 1}; +let c = { + foo: 1, bar: 2 +}; +let d = { + foo: 1, + bar: 2 +}; +let e = {foo: function() { + dosomething(); +}}; + +let {} = obj; +let {f} = obj; +let { + g, h +} = obj; +let { + i, + j +} = obj; +let {k = function() { + dosomething(); +}} = obj; +``` + +### consistent + +Examples of **incorrect** code for this rule with the default `{ "consistent": true }` option: + +```js +/*eslint object-curly-newline: ["error", { "consistent": true }]*/ +/*eslint-env es6*/ + +let a = {foo: 1 +}; +let b = { + foo: 1}; +let c = {foo: 1, bar: 2 +}; +let d = { + foo: 1, bar: 2}; +let e = {foo: function() { + dosomething(); +}}; + +let {f +} = obj; +let { + g} = obj; +let {h, i +} = obj; +let { + j, k} = obj; +let {l = function() { + dosomething(); +}} = obj; +``` + +Examples of **correct** code for this rule with the default `{ "consistent": true }` option: + +```js +/*eslint object-curly-newline: ["error", { "consistent": true }]*/ +/*eslint-env es6*/ + +let a = {}; +let b = {foo: 1}; +let c = { + foo: 1 +}; +let d = { + foo: 1, bar: 2 +}; +let e = { + foo: 1, + bar: 2 +}; +let f = {foo: function() {dosomething();}}; +let g = { + foo: function() { + dosomething(); + } +}; + +let {} = obj; +let {h} = obj; +let {i, j} = obj; +let { + k, l +} = obj; +let {m, + n} = obj; +let { + o, + p +} = obj; +let {q = function() {dosomething();}} = obj; +let { + r = function() { + dosomething(); + } +} = obj; +``` + +### ObjectExpression and ObjectPattern + +Examples of **incorrect** code for this rule with the `{ "ObjectExpression": "always", "ObjectPattern": "never" }` options: + +```js +/*eslint object-curly-newline: ["error", { "ObjectExpression": "always", "ObjectPattern": "never" }]*/ +/*eslint-env es6*/ + +let a = {}; +let b = {foo: 1}; +let c = {foo: 1, bar: 2}; +let d = {foo: 1, + bar: 2}; +let e = {foo: function() { + dosomething(); +}}; + +let { +} = obj; +let { + f +} = obj; +let { + g, h +} = obj; +let { + i, + j +} = obj; +let { + k = function() { + dosomething(); + } +} = obj; +``` + +Examples of **correct** code for this rule with the `{ "ObjectExpression": "always", "ObjectPattern": "never" }` options: + +```js +/*eslint object-curly-newline: ["error", { "ObjectExpression": "always", "ObjectPattern": "never" }]*/ +/*eslint-env es6*/ + +let a = { +}; +let b = { + foo: 1 +}; +let c = { + foo: 1, bar: 2 +}; +let d = { + foo: 1, + bar: 2 +}; +let e = { + foo: function() { + dosomething(); + } +}; + +let {} = obj; +let {f} = obj; +let {g, h} = obj; +let {i, + j} = obj; +let {k = function() { + dosomething(); +}} = obj; +``` + +### ImportDeclaration and ExportDeclaration + +Examples of **incorrect** code for this rule with the `{ "ImportDeclaration": "always", "ExportDeclaration": "never" }` options: + +```js +/*eslint object-curly-newline: ["error", { "ImportDeclaration": "always", "ExportDeclaration": "never" }]*/ +/*eslint-env es6*/ + +import {foo, bar} from 'foo-bar'; +import {foo as f, bar} from 'foo-bar'; +import {foo, + bar} from 'foo-bar'; + +export { + foo, + bar +}; +export { + foo as f, + bar +} from 'foo-bar'; +``` + +Examples of **correct** code for this rule with the `{ "ImportDeclaration": "always", "ExportDeclaration": "never" }` options: + +```js +/*eslint object-curly-newline: ["error", { "ImportDeclaration": "always", "ExportDeclaration": "never" }]*/ +/*eslint-env es6*/ + +import { + foo, + bar +} from 'foo-bar'; +import { + foo as f, + bar +} from 'foo-bar'; + +export { foo, bar } from 'foo-bar'; +export { foo as f, bar } from 'foo-bar'; +``` + +## Compatibility + +* **JSCS**: [requirePaddingNewLinesInObjects](https://jscs-dev.github.io/rule/requirePaddingNewLinesInObjects) +* **JSCS**: [disallowPaddingNewLinesInObjects](https://jscs-dev.github.io/rule/disallowPaddingNewLinesInObjects) + +## When Not To Use It + +If you don't want to enforce consistent line breaks inside braces, then it's safe to disable this rule. + +## Related Rules + +* [comma-spacing](comma-spacing.md) +* [key-spacing](key-spacing.md) +* [object-curly-spacing](object-curly-spacing.md) +* [object-property-newline](object-property-newline.md) diff --git a/eslint/docs/rules/object-curly-spacing.md b/eslint/docs/rules/object-curly-spacing.md new file mode 100644 index 0000000..e7b054c --- /dev/null +++ b/eslint/docs/rules/object-curly-spacing.md @@ -0,0 +1,156 @@ +# enforce consistent spacing inside braces (object-curly-spacing) + +While formatting preferences are very personal, a number of style guides require +or disallow spaces between curly braces in the following situations: + +```js +// simple object literals +var obj = { foo: "bar" }; + +// nested object literals +var obj = { foo: { zoo: "bar" } }; + +// destructuring assignment (EcmaScript 6) +var { x, y } = y; + +// import/export declarations (EcmaScript 6) +import { foo } from "bar"; +export { foo }; +``` + +## Rule Details + +This rule enforces consistent spacing inside braces of object literals, destructuring assignments, and import/export specifiers. + +## Options + +This rule has two options, a string option and an object option. + +String option: + +* `"never"` (default) disallows spacing inside of braces +* `"always"` requires spacing inside of braces (except `{}`) + +Object option: + +* `"arraysInObjects": true` requires spacing inside of braces of objects beginning and/or ending with an array element (applies when the first option is set to `never`) +* `"arraysInObjects": false` disallows spacing inside of braces of objects beginning and/or ending with an array element (applies when the first option is set to `always`) +* `"objectsInObjects": true` requires spacing inside of braces of objects beginning and/or ending with an object element (applies when the first option is set to `never`) +* `"objectsInObjects": false` disallows spacing inside of braces of objects beginning and/or ending with an object element (applies when the first option is set to `always`) + +### never + +Examples of **incorrect** code for this rule with the default `"never"` option: + +```js +/*eslint object-curly-spacing: ["error", "never"]*/ + +var obj = { 'foo': 'bar' }; +var obj = {'foo': 'bar' }; +var obj = { baz: {'foo': 'qux'}, bar}; +var obj = {baz: { 'foo': 'qux'}, bar}; +var {x } = y; +import { foo } from 'bar'; +``` + +Examples of **correct** code for this rule with the default `"never"` option: + +```js +/*eslint object-curly-spacing: ["error", "never"]*/ + +var obj = {'foo': 'bar'}; +var obj = {'foo': {'bar': 'baz'}, 'qux': 'quxx'}; +var obj = { + 'foo': 'bar' +}; +var obj = {'foo': 'bar' +}; +var obj = { + 'foo':'bar'}; +var obj = {}; +var {x} = y; +import {foo} from 'bar'; +``` + +### always + +Examples of **incorrect** code for this rule with the `"always"` option: + +```js +/*eslint object-curly-spacing: ["error", "always"]*/ + +var obj = {'foo': 'bar'}; +var obj = {'foo': 'bar' }; +var obj = { baz: {'foo': 'qux'}, bar}; +var obj = {baz: { 'foo': 'qux' }, bar}; +var obj = {'foo': 'bar' +}; +var obj = { + 'foo':'bar'}; +var {x} = y; +import {foo } from 'bar'; +``` + +Examples of **correct** code for this rule with the `"always"` option: + +```js +/*eslint object-curly-spacing: ["error", "always"]*/ + +var obj = {}; +var obj = { 'foo': 'bar' }; +var obj = { 'foo': { 'bar': 'baz' }, 'qux': 'quxx' }; +var obj = { + 'foo': 'bar' +}; +var { x } = y; +import { foo } from 'bar'; +``` + +#### arraysInObjects + +Examples of additional **correct** code for this rule with the `"never", { "arraysInObjects": true }` options: + +```js +/*eslint object-curly-spacing: ["error", "never", { "arraysInObjects": true }]*/ + +var obj = {"foo": [ 1, 2 ] }; +var obj = {"foo": [ "baz", "bar" ] }; +``` + +Examples of additional **correct** code for this rule with the `"always", { "arraysInObjects": false }` options: + +```js +/*eslint object-curly-spacing: ["error", "always", { "arraysInObjects": false }]*/ + +var obj = { "foo": [ 1, 2 ]}; +var obj = { "foo": [ "baz", "bar" ]}; +``` + +#### objectsInObjects + +Examples of additional **correct** code for this rule with the `"never", { "objectsInObjects": true }` options: + +```js +/*eslint object-curly-spacing: ["error", "never", { "objectsInObjects": true }]*/ + +var obj = {"foo": {"baz": 1, "bar": 2} }; +``` + +Examples of additional **correct** code for this rule with the `"always", { "objectsInObjects": false }` options: + +```js +/*eslint object-curly-spacing: ["error", "always", { "objectsInObjects": false }]*/ + +var obj = { "foo": { "baz": 1, "bar": 2 }}; +``` + +## When Not To Use It + +You can turn this rule off if you are not concerned with the consistency of spacing between curly braces. + +## Related Rules + +* [array-bracket-spacing](array-bracket-spacing.md) +* [comma-spacing](comma-spacing.md) +* [computed-property-spacing](computed-property-spacing.md) +* [space-in-parens](space-in-parens.md) diff --git a/eslint/docs/rules/object-property-newline.md b/eslint/docs/rules/object-property-newline.md new file mode 100644 index 0000000..893e54f --- /dev/null +++ b/eslint/docs/rules/object-property-newline.md @@ -0,0 +1,270 @@ +# enforce placing object properties on separate lines (object-property-newline) + +This rule permits you to restrict the locations of property specifications in object literals. You may prohibit any part of any property specification from appearing on the same line as any part of any other property specification. You may make this prohibition absolute, or, by invoking an object option, you may allow an exception, permitting an object literal to have all parts of all of its property specifications on a single line. + +## Rule Details + +### Motivations + +This rule makes it possible to ensure, as some style guides require, that property specifications appear on separate lines for better readability. For example, you can prohibit all of these: + +```js +const newObject = {a: 1, b: [2, {a: 3, b: 4}]}; +const newObject = { + a: 1, b: [2, {a: 3, b: 4}] +}; +const newObject = { + a: 1, + b: [2, {a: 3, b: 4}] +}; +const newObject = { + a: 1, + b: [ + 2, + {a: 3, b: 4} + ] +}; + +``` + +Instead of those, you can comply with the rule by writing + +```js +const newObject = { + a: 1, + b: [2, { + a: 3, + b: 4 + }] +}; +``` + +or + +```js +const newObject = { + a: 1, + b: [ + 2, + { + a: 3, + b: 4 + } + ] +}; +``` + +Another benefit of this rule is specificity of diffs when a property is changed: + +```diff +// More specific + var obj = { + foo: "foo", +- bar: "bar", ++ bar: "bazz", + baz: "baz" + }; +``` + +```diff +// Less specific +-var obj = { foo: "foo", bar: "bar", baz: "baz" }; ++var obj = { foo: "foo", bar: "bazz", baz: "baz" }; +``` + +### Optional Exception + +The rule offers one object option, `allowAllPropertiesOnSameLine` (a deprecated synonym is `allowMultiplePropertiesPerLine`). If you set it to `true`, object literals such as the first two above, with all property specifications on the same line, will be permitted, but one like + +```js +const newObject = { + a: 'a.m.', b: 'p.m.', + c: 'daylight saving time' +}; + +``` + +will be prohibited, because two properties, but not all properties, appear on the same line. + +### Notations + +This rule applies equally to all property specifications, regardless of notation, including: + +- `a: 1` (ES5) +- `a` (ES2015 shorthand property) +- ``[`prop${a}`]`` (ES2015 computed property name) + +Thus, the rule (without the object option) prohibits both of these: + +```js +const newObject = { + a: 1, [ + process.argv[4] + ]: '01' +}; +const newObject = { + a: 1, [process.argv[4]]: '01' +}; +``` + +(This behavior differs from that of the JSCS rule cited below, which does not treat the leading `[` of a computed property name as part of that property specification. The JSCS rule prohibits the second of these formats but permits the first.) + +### Multiline Properties + +The rule prohibits the colocation on any line of at least 1 character of one property specification with at least 1 character of any other property specification. For example, the rule prohibits + +```js +const newObject = {a: [ + 'Officiële website van de Europese Unie', + 'Официален уебсайт на Европейския съюз' +], b: 2}; +``` + +because 1 character of the specification of `a` (i.e. the trailing `]` of its value) is on the same line as the specification of `b`. + +The optional exception does not excuse this case, because the entire collection of property specifications spans 4 lines, not 1. + +### Inter-property Delimiters + +The comma and any whitespace that delimit property specifications are not considered parts of them. Therefore, the rule permits both of these formats: + +```js +const newFunction = multiplier => ({ + a: 2 * multiplier, + b: 4 * multiplier, + c: 8 * multiplier +}); +const newFunction = multiplier => ({ + a: 2 * multiplier + , b: 4 * multiplier + , c: 8 * multiplier +}); +``` + +(This behavior differs from that of the JSCS rule cited below, which permits the first but prohibits the second format.) + +### --fix + +If this rule is invoked with the command-line `--fix` option, object literals that violate the rule are generally modified to comply with it. The modification in each case is to move a property specification to the next line whenever there is part or all of a previous property specification on the same line. For example, + +```js +const newObject = { + a: 'a.m.', b: 'p.m.', + c: 'daylight saving time' +}; +``` + +is converted to + +```js +const newObject = { + a: 'a.m.', +b: 'p.m.', + c: 'daylight saving time' +}; +``` + +The modification does not depend on whether the object option is set to `true`. In other words, ESLint never collects all the property specifications onto a single line, even when the object option would permit that. + +ESLint does not correct a violation of this rule if a comment immediately precedes the second or subsequent property specification on a line, since ESLint cannot determine which line to put the comment onto. + +As illustrated above, the `--fix` option, applied to this rule, does not comply with other rules, such as `indent`, but, if those other rules are also in effect, the option applies them, too. + +## Examples + +Examples of **incorrect** code for this rule, with no object option or with `allowAllPropertiesOnSameLine` set to `false`: + +```js +/*eslint object-property-newline: "error"*/ + +const obj0 = { foo: "foo", bar: "bar", baz: "baz" }; + +const obj1 = { + foo: "foo", bar: "bar", baz: "baz" +}; + +const obj2 = { + foo: "foo", bar: "bar", + baz: "baz" +}; + +const obj3 = { + [process.argv[3] ? "foo" : "bar"]: 0, baz: [ + 1, + 2, + 4, + 8 + ] +}; + +const a = "antidisestablishmentarianistically"; +const b = "yugoslavyalılaştırabildiklerimizdenmişsiniz"; +const obj4 = {a, b}; + +const domain = process.argv[4]; +const obj5 = { + foo: "foo", [ + domain.includes(":") ? "complexdomain" : "simpledomain" +]: true}; +``` + +Examples of **correct** code for this rule, with no object option or with `allowAllPropertiesOnSameLine` set to `false`: + +```js +/*eslint object-property-newline: "error"*/ + +const obj1 = { + foo: "foo", + bar: "bar", + baz: "baz" +}; + +const obj2 = { + foo: "foo" + , bar: "bar" + , baz: "baz" +}; + +const user = process.argv[2]; +const obj3 = { + user, + [process.argv[3] ? "foo" : "bar"]: 0, + baz: [ + 1, + 2, + 4, + 8 + ] +}; +``` + +Examples of additional **correct** code for this rule with the `{ "allowAllPropertiesOnSameLine": true }` option: + +```js +/*eslint object-property-newline: ["error", { "allowAllPropertiesOnSameLine": true }]*/ + +const obj = { foo: "foo", bar: "bar", baz: "baz" }; + +const obj2 = { + foo: "foo", bar: "bar", baz: "baz" +}; +const user = process.argv[2]; +const obj3 = { + user, [process.argv[3] ? "foo" : "bar"]: 0, baz: [1, 2, 4, 8] +}; +``` + +## When Not To Use It + +You can turn this rule off if you want to decide, case-by-case, whether to place property specifications on separate lines. + +## Compatibility + +- **JSCS**: This rule provides partial compatibility with [requireObjectKeysOnNewLine](https://jscs-dev.github.io/rule/requireObjectKeysOnNewLine). + +## Related Rules + +- [brace-style](brace-style.md) +- [comma-dangle](comma-dangle.md) +- [key-spacing](key-spacing.md) +- [object-curly-spacing](object-curly-spacing.md) diff --git a/eslint/docs/rules/object-shorthand.md b/eslint/docs/rules/object-shorthand.md new file mode 100644 index 0000000..79e7665 --- /dev/null +++ b/eslint/docs/rules/object-shorthand.md @@ -0,0 +1,252 @@ +# Require Object Literal Shorthand Syntax (object-shorthand) + +ECMAScript 6 provides a concise form for defining object literal methods and properties. This +syntax can make defining complex object literals much cleaner. + +Here are a few common examples using the ES5 syntax: + +```js +// properties +var foo = { + x: x, + y: y, + z: z, +}; + +// methods +var foo = { + a: function() {}, + b: function() {} +}; +``` + +Now here are ES6 equivalents: + +```js +/*eslint-env es6*/ + +// properties +var foo = {x, y, z}; + +// methods +var foo = { + a() {}, + b() {} +}; +``` + +## Rule Details + +This rule enforces the use of the shorthand syntax. This applies +to all methods (including generators) defined in object literals and any +properties defined where the key name matches name of the assigned variable. + +Each of the following properties would warn: + + +```js +/*eslint object-shorthand: "error"*/ +/*eslint-env es6*/ + +var foo = { + w: function() {}, + x: function *() {}, + [y]: function() {}, + z: z +}; +``` + +In that case the expected syntax would have been: + +```js +/*eslint object-shorthand: "error"*/ +/*eslint-env es6*/ + +var foo = { + w() {}, + *x() {}, + [y]() {}, + z +}; +``` + +This rule does not flag arrow functions inside of object literals. +The following will *not* warn: + +```js +/*eslint object-shorthand: "error"*/ +/*eslint-env es6*/ + +var foo = { + x: (y) => y +}; +``` + +See Also: + +- [`no-useless-rename`](https://eslint.org/docs/rules/no-useless-rename) which disallows renaming import, export, and destructured assignments to the same name. + +## Options + +The rule takes an option which specifies when it should be applied. It can be set to one of the following values: + +- `"always"` (default) expects that the shorthand will be used whenever possible. +- `"methods"` ensures the method shorthand is used (also applies to generators). +- `"properties"` ensures the property shorthand is used (where the key and variable name match). +- `"never"` ensures that no property or method shorthand is used in any object literal. +- `"consistent"` ensures that either all shorthand or all long-form will be used in an object literal. +- `"consistent-as-needed"` ensures that either all shorthand or all long-form will be used in an object literal, but ensures all shorthand whenever possible. + +You can set the option in configuration like this: + +```json +{ + "object-shorthand": ["error", "always"] +} +``` + +Additionally, the rule takes an optional object configuration: + +- `"avoidQuotes": true` indicates that long-form syntax is preferred whenever the object key is a string literal (default: `false`). Note that this option can only be enabled when the string option is set to `"always"`, `"methods"`, or `"properties"`. +- `"ignoreConstructors": true` can be used to prevent the rule from reporting errors for constructor functions. (By default, the rule treats constructors the same way as other functions.) Note that this option can only be enabled when the string option is set to `"always"` or `"methods"`. +- `"avoidExplicitReturnArrows": true` indicates that methods are preferred over explicit-return arrow functions for function properties. (By default, the rule allows either of these.) Note that this option can only be enabled when the string option is set to `"always"` or `"methods"`. + +### `avoidQuotes` + +```json +{ + "object-shorthand": ["error", "always", { "avoidQuotes": true }] +} +``` + +Example of **incorrect** code for this rule with the `"always", { "avoidQuotes": true }` option: + +```js +/*eslint object-shorthand: ["error", "always", { "avoidQuotes": true }]*/ +/*eslint-env es6*/ + +var foo = { + "bar-baz"() {} +}; +``` + +Example of **correct** code for this rule with the `"always", { "avoidQuotes": true }` option: + +```js +/*eslint object-shorthand: ["error", "always", { "avoidQuotes": true }]*/ +/*eslint-env es6*/ + +var foo = { + "bar-baz": function() {}, + "qux": qux +}; +``` + +### `ignoreConstructors` + +```json +{ + "object-shorthand": ["error", "always", { "ignoreConstructors": true }] +} +``` + +Example of **correct** code for this rule with the `"always", { "ignoreConstructors": true }` option: + +```js +/*eslint object-shorthand: ["error", "always", { "ignoreConstructors": true }]*/ +/*eslint-env es6*/ + +var foo = { + ConstructorFunction: function() {} +}; +``` + +### `avoidExplicitReturnArrows` + +```json +{ + "object-shorthand": ["error", "always", { "avoidExplicitReturnArrows": true }] +} +``` + +Example of **incorrect** code for this rule with the `"always", { "avoidExplicitReturnArrows": true }` option: + +```js +/*eslint object-shorthand: ["error", "always", { "avoidExplicitReturnArrows": true }]*/ +/*eslint-env es6*/ + +var foo = { + foo: (bar, baz) => { + return bar + baz; + }, + + qux: (foobar) => { + return foobar * 2; + } +}; +``` + +Example of **correct** code for this rule with the `"always", { "avoidExplicitReturnArrows": true }` option: + +```js +/*eslint object-shorthand: ["error", "always", { "avoidExplicitReturnArrows": true }]*/ +/*eslint-env es6*/ + +var foo = { + foo(bar, baz) { + return bar + baz; + }, + + qux: foobar => foobar * 2 +}; +``` + +Example of **incorrect** code for this rule with the `"consistent"` option: + +```js +/*eslint object-shorthand: [2, "consistent"]*/ +/*eslint-env es6*/ + +var foo = { + a, + b: "foo", +}; +``` + +Examples of **correct** code for this rule with the `"consistent"` option: + +```js +/*eslint object-shorthand: [2, "consistent"]*/ +/*eslint-env es6*/ + +var foo = { + a: a, + b: "foo" +}; + +var bar = { + a, + b, +}; +``` + +Example of **incorrect** code with the `"consistent-as-needed"` option, which is very similar to `"consistent"`: + +```js +/*eslint object-shorthand: [2, "consistent-as-needed"]*/ +/*eslint-env es6*/ + +var foo = { + a: a, + b: b, +}; +``` + +## When Not To Use It + +Anyone not yet in an ES6 environment would not want to apply this rule. Others may find the terseness of the shorthand +syntax harder to read and may not want to encourage it with this rule. + +## Further Reading + +[Object initializer - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer) diff --git a/eslint/docs/rules/one-var-declaration-per-line.md b/eslint/docs/rules/one-var-declaration-per-line.md new file mode 100644 index 0000000..90f67a9 --- /dev/null +++ b/eslint/docs/rules/one-var-declaration-per-line.md @@ -0,0 +1,89 @@ +# require or disallow newlines around variable declarations (one-var-declaration-per-line) + +Some developers declare multiple var statements on the same line: + +```js +var foo, bar, baz; +``` + +Others prefer to declare one var per line. + +```js +var foo, + bar, + baz; +``` + +Keeping to one of these styles across a project's codebase can help with maintaining code consistency. + +## Rule Details + +This rule enforces a consistent newlines around variable declarations. This rule ignores variable declarations inside `for` loop conditionals. + +## Options + +This rule has a single string option: + +* `"initializations"` (default) enforces a newline around variable initializations +* `"always"` enforces a newline around variable declarations + +### initializations + +Examples of **incorrect** code for this rule with the default `"initializations"` option: + +```js +/*eslint one-var-declaration-per-line: ["error", "initializations"]*/ +/*eslint-env es6*/ + +var a, b, c = 0; + +let a, + b = 0, c; +``` + +Examples of **correct** code for this rule with the default `"initializations"` option: + +```js +/*eslint one-var-declaration-per-line: ["error", "initializations"]*/ +/*eslint-env es6*/ + +var a, b; + +let a, + b; + +let a, + b = 0; +``` + +### always + +Examples of **incorrect** code for this rule with the `"always"` option: + +```js +/*eslint one-var-declaration-per-line: ["error", "always"]*/ +/*eslint-env es6*/ + +var a, b; + +let a, b = 0; + +const a = 0, b = 0; +``` + +Examples of **correct** code for this rule with the `"always"` option: + +```js +/*eslint one-var-declaration-per-line: ["error", "always"]*/ +/*eslint-env es6*/ + +var a, + b; + +let a, + b = 0; +``` + +## Related Rules + +* [one-var](one-var.md) diff --git a/eslint/docs/rules/one-var.md b/eslint/docs/rules/one-var.md new file mode 100644 index 0000000..3c964c4 --- /dev/null +++ b/eslint/docs/rules/one-var.md @@ -0,0 +1,542 @@ +# enforce variables to be declared either together or separately in functions (one-var) + +Variables can be declared at any point in JavaScript code using `var`, `let`, or `const`. There are many styles and preferences related to the declaration of variables, and one of those is deciding on how many variable declarations should be allowed in a single function. + +There are two schools of thought in this regard: + +1. There should be just one variable declaration for all variables in the function. That declaration typically appears at the top of the function. +1. You should use one variable declaration for each variable you want to define. + +For instance: + +```js +// one variable declaration per function +function foo() { + var bar, baz; +} + +// multiple variable declarations per function +function foo() { + var bar; + var baz; +} +``` + +The single-declaration school of thought is based in pre-ECMAScript 6 behaviors, where there was no such thing as block scope, only function scope. Since all `var` statements are hoisted to the top of the function anyway, some believe that declaring all variables in a single declaration at the top of the function removes confusion around scoping rules. + +## Rule Details + +This rule enforces variables to be declared either together or separately per function ( for `var`) or block (for `let` and `const`) scope. + +## Options + +This rule has one option, which can be a string option or an object option. + +String option: + +* `"always"` (default) requires one variable declaration per scope +* `"never"` requires multiple variable declarations per scope +* `"consecutive"` allows multiple variable declarations per scope but requires consecutive variable declarations to be combined into a single declaration + +Object option: + +* `"var": "always"` requires one `var` declaration per function +* `"var": "never"` requires multiple `var` declarations per function +* `"var": "consecutive"` requires consecutive `var` declarations to be a single declaration +* `"let": "always"` requires one `let` declaration per block +* `"let": "never"` requires multiple `let` declarations per block +* `"let": "consecutive"` requires consecutive `let` declarations to be a single declaration +* `"const": "always"` requires one `const` declaration per block +* `"const": "never"` requires multiple `const` declarations per block +* `"const": "consecutive"` requires consecutive `const` declarations to be a single declaration +* `"separateRequires": true` enforces `requires` to be separate from declarations + +Alternate object option: + +* `"initialized": "always"` requires one variable declaration for initialized variables per scope +* `"initialized": "never"` requires multiple variable declarations for initialized variables per scope +* `"initialized": "consecutive"` requires consecutive variable declarations for initialized variables to be a single declaration +* `"uninitialized": "always"` requires one variable declaration for uninitialized variables per scope +* `"uninitialized": "never"` requires multiple variable declarations for uninitialized variables per scope +* `"uninitialized": "consecutive"` requires consecutive variable declarations for uninitialized variables to be a single declaration + +### always + +Examples of **incorrect** code for this rule with the default `"always"` option: + +```js +/*eslint one-var: ["error", "always"]*/ +/*eslint-env es6*/ + +function foo() { + var bar; + var baz; + let qux; + let norf; +} + +function foo(){ + const bar = false; + const baz = true; + let qux; + let norf; +} + +function foo() { + var bar; + + if (baz) { + var qux = true; + } +} +``` + +Examples of **correct** code for this rule with the default `"always"` option: + +```js +/*eslint one-var: ["error", "always"]*/ +/*eslint-env es6*/ + +function foo() { + var bar, + baz; + let qux, + norf; +} + +function foo(){ + const bar = true, + baz = false; + let qux, + norf; +} + +function foo() { + var bar, + qux; + + if (baz) { + qux = true; + } +} + +function foo(){ + let bar; + + if (baz) { + let qux; + } +} +``` + +### never + +Examples of **incorrect** code for this rule with the `"never"` option: + +```js +/*eslint one-var: ["error", "never"]*/ +/*eslint-env es6*/ + +function foo() { + var bar, + baz; + const bar = true, + baz = false; +} + +function foo() { + var bar, + qux; + + if (baz) { + qux = true; + } +} + +function foo(){ + let bar = true, + baz = false; +} +``` + +Examples of **correct** code for this rule with the `"never"` option: + +```js +/*eslint one-var: ["error", "never"]*/ +/*eslint-env es6*/ + +function foo() { + var bar; + var baz; +} + +function foo() { + var bar; + + if (baz) { + var qux = true; + } +} + +function foo() { + let bar; + + if (baz) { + let qux = true; + } +} +``` + +### consecutive + +Examples of **incorrect** code for this rule with the `"consecutive"` option: + +```js +/*eslint one-var: ["error", "consecutive"]*/ +/*eslint-env es6*/ + +function foo() { + var bar; + var baz; +} + +function foo(){ + var bar = 1; + var baz = 2; + + qux(); + + var qux = 3; + var quux; +} +``` + +Examples of **correct** code for this rule with the `"consecutive"` option: + +```js +/*eslint one-var: ["error", "consecutive"]*/ +/*eslint-env es6*/ + + +function foo() { + var bar, + baz; +} + +function foo(){ + var bar = 1, + baz = 2; + + qux(); + + var qux = 3, + quux; +} +``` + +### var, let, and const + +Examples of **incorrect** code for this rule with the `{ var: "always", let: "never", const: "never" }` option: + +```js +/*eslint one-var: ["error", { var: "always", let: "never", const: "never" }]*/ +/*eslint-env es6*/ + +function foo() { + var bar; + var baz; + let qux, + norf; +} + +function foo() { + const bar = 1, + baz = 2; + let qux, + norf; +} +``` + +Examples of **correct** code for this rule with the `{ var: "always", let: "never", const: "never" }` option: + +```js +/*eslint one-var: ["error", { var: "always", let: "never", const: "never" }]*/ +/*eslint-env es6*/ + +function foo() { + var bar, + baz; + let qux; + let norf; +} + +function foo() { + const bar = 1; + const baz = 2; + let qux; + let norf; +} +``` + +Examples of **incorrect** code for this rule with the `{ var: "never" }` option: + +```js +/*eslint one-var: ["error", { var: "never" }]*/ +/*eslint-env es6*/ + +function foo() { + var bar, + baz; +} +``` + +Examples of **correct** code for this rule with the `{ var: "never" }` option: + +```js +/*eslint one-var: ["error", { var: "never" }]*/ +/*eslint-env es6*/ + +function foo() { + var bar, + baz; + const bar = 1; // `const` and `let` declarations are ignored if they are not specified + const baz = 2; + let qux; + let norf; +} +``` + +Examples of **incorrect** code for this rule with the `{ separateRequires: true }` option: + +```js +/*eslint one-var: ["error", { separateRequires: true, var: "always" }]*/ +/*eslint-env node*/ + +var foo = require("foo"), + bar = "bar"; +``` + +Examples of **correct** code for this rule with the `{ separateRequires: true }` option: + +```js +/*eslint one-var: ["error", { separateRequires: true, var: "always" }]*/ +/*eslint-env node*/ + +var foo = require("foo"); +var bar = "bar"; +``` + +```js +var foo = require("foo"), + bar = require("bar"); +``` + +Examples of **incorrect** code for this rule with the `{ var: "never", let: "consecutive", const: "consecutive" }` option: + +```js +/*eslint one-var: ["error", { var: "never", let: "consecutive", const: "consecutive" }]*/ +/*eslint-env es6*/ + +function foo() { + let a, + b; + let c; + + var d, + e; +} + +function foo() { + const a = 1, + b = 2; + const c = 3; + + var d, + e; +} +``` + +Examples of **correct** code for this rule with the `{ var: "never", let: "consecutive", const: "consecutive" }` option: + +```js +/*eslint one-var: ["error", { var: "never", let: "consecutive", const: "consecutive" }]*/ +/*eslint-env es6*/ + +function foo() { + let a, + b; + + var d; + var e; + + let f; +} + +function foo() { + const a = 1, + b = 2; + + var c; + var d; + + const e = 3; +} +``` + +Examples of **incorrect** code for this rule with the `{ var: "consecutive" }` option: + +```js +/*eslint one-var: ["error", { var: "consecutive" }]*/ +/*eslint-env es6*/ + +function foo() { + var a; + var b; +} +``` + +Examples of **correct** code for this rule with the `{ var: "consecutive" }` option: + +```js +/*eslint one-var: ["error", { var: "consecutive" }]*/ +/*eslint-env es6*/ + +function foo() { + var a, + b; + const c = 1; // `const` and `let` declarations are ignored if they are not specified + const d = 2; + let e; + let f; +} +``` + +### initialized and uninitialized + +Examples of **incorrect** code for this rule with the `{ "initialized": "always", "uninitialized": "never" }` option: + +```js +/*eslint one-var: ["error", { "initialized": "always", "uninitialized": "never" }]*/ +/*eslint-env es6*/ + +function foo() { + var a, b, c; + var foo = true; + var bar = false; +} +``` + +Examples of **correct** code for this rule with the `{ "initialized": "always", "uninitialized": "never" }` option: + +```js +/*eslint one-var: ["error", { "initialized": "always", "uninitialized": "never" }]*/ + +function foo() { + var a; + var b; + var c; + var foo = true, + bar = false; +} + +for (let z of foo) { + doSomething(z); +} + +let z; +for (z of foo) { + doSomething(z); +} +``` + +Examples of **incorrect** code for this rule with the `{ "initialized": "never" }` option: + +```js +/*eslint one-var: ["error", { "initialized": "never" }]*/ +/*eslint-env es6*/ + +function foo() { + var foo = true, + bar = false; +} +``` + +Examples of **correct** code for this rule with the `{ "initialized": "never" }` option: + +```js +/*eslint one-var: ["error", { "initialized": "never" }]*/ + +function foo() { + var foo = true; + var bar = false; + var a, b, c; // Uninitialized variables are ignored +} +``` + +Examples of **incorrect** code for this rule with the `{ "initialized": "consecutive", "uninitialized": "never" }` option: + +```js +/*eslint one-var: ["error", { "initialized": "consecutive", "uninitialized": "never" }]*/ + +function foo() { + var a = 1; + var b = 2; + var c, + d; + var e = 3; + var f = 4; +} +``` + +Examples of **correct** code for this rule with the `{ "initialized": "consecutive", "uninitialized": "never" }` option: + +```js +/*eslint one-var: ["error", { "initialized": "consecutive", "uninitialized": "never" }]*/ + +function foo() { + var a = 1, + b = 2; + var c; + var d; + var e = 3, + f = 4; +} +``` + +Examples of **incorrect** code for this rule with the `{ "initialized": "consecutive" }` option: + +```js +/*eslint one-var: ["error", { "initialized": "consecutive" }]*/ + +function foo() { + var a = 1; + var b = 2; + + foo(); + + var c = 3; + var d = 4; +} +``` + +Examples of **correct** code for this rule with the `{ "initialized": "consecutive" }` option: + +```js +/*eslint one-var: ["error", { "initialized": "consecutive" }]*/ + +function foo() { + var a = 1, + b = 2; + + foo(); + + var c = 3, + d = 4; +} +``` + +## Compatibility + +* **JSHint**: This rule maps to the `onevar` JSHint rule, but allows `let` and `const` to be configured separately. +* **JSCS**: This rule roughly maps to [disallowMultipleVarDecl](https://jscs-dev.github.io/rule/disallowMultipleVarDecl). +* **JSCS**: This rule option `separateRequires` roughly maps to [requireMultipleVarDecl](https://jscs-dev.github.io/rule/requireMultipleVarDecl). diff --git a/eslint/docs/rules/operator-assignment.md b/eslint/docs/rules/operator-assignment.md new file mode 100644 index 0000000..570f57c --- /dev/null +++ b/eslint/docs/rules/operator-assignment.md @@ -0,0 +1,81 @@ +# require or disallow assignment operator shorthand where possible (operator-assignment) + +JavaScript provides shorthand operators that combine variable assignment and some simple mathematical operations. For example, `x = x + 4` can be shortened to `x += 4`. The supported shorthand forms are as follows: + +```text + Shorthand | Separate +-----------|------------ + x += y | x = x + y + x -= y | x = x - y + x *= y | x = x * y + x /= y | x = x / y + x %= y | x = x % y + x <<= y | x = x << y + x >>= y | x = x >> y + x >>>= y | x = x >>> y + x &= y | x = x & y + x ^= y | x = x ^ y + x |= y | x = x | y +``` + +## Rule Details + +This rule requires or disallows assignment operator shorthand where possible. + +## Options + +This rule has a single string option: + +* `"always"` (default) requires assignment operator shorthand where possible +* `"never"` disallows assignment operator shorthand + +### always + +Examples of **incorrect** code for this rule with the default `"always"` option: + +```js +/*eslint operator-assignment: ["error", "always"]*/ + +x = x + y; +x = y * x; +x[0] = x[0] / y; +x.y = x.y << z; +``` + +Examples of **correct** code for this rule with the default `"always"` option: + +```js +/*eslint operator-assignment: ["error", "always"]*/ + +x = y; +x += y; +x = y * z; +x = (x * y) * z; +x[0] /= y; +x[foo()] = x[foo()] % 2; +x = y + x; // `+` is not always commutative (e.g. x = "abc") +``` + +### never + +Examples of **incorrect** code for this rule with the `"never"` option: + +```js +/*eslint operator-assignment: ["error", "never"]*/ + +x *= y; +x ^= (y + z) / foo(); +``` + +Examples of **correct** code for this rule with the `"never"` option: + +```js +/*eslint operator-assignment: ["error", "never"]*/ + +x = x + y; +x.y = x.y / a.b; +``` + +## When Not To Use It + +Use of operator assignment shorthand is a stylistic choice. Leaving this rule turned off would allow developers to choose which style is more readable on a case-by-case basis. diff --git a/eslint/docs/rules/operator-linebreak.md b/eslint/docs/rules/operator-linebreak.md new file mode 100644 index 0000000..3b1986e --- /dev/null +++ b/eslint/docs/rules/operator-linebreak.md @@ -0,0 +1,266 @@ +# enforce consistent linebreak style for operators (operator-linebreak) + +When a statement is too long to fit on a single line, line breaks are generally inserted next to the operators separating expressions. The first style coming to mind would be to place the operator at the end of the line, following the English punctuation rules. + +```js +var fullHeight = borderTop + + innerHeight + + borderBottom; +``` + +Some developers find that placing operators at the beginning of the line makes the code more readable. + +```js +var fullHeight = borderTop + + innerHeight + + borderBottom; +``` + +## Rule Details + +This rule enforces a consistent linebreak style for operators. + +## Options + +This rule has one option, which can be a string option or an object option. + +String option: + +* `"after"` requires linebreaks to be placed after the operator +* `"before"` requires linebreaks to be placed before the operator +* `"none"` disallows linebreaks on either side of the operator + +Object option: + +* `"overrides"` overrides the global setting for specified operators + +The default configuration is `"after", { "overrides": { "?": "before", ":": "before" } }` + +### after + +Examples of **incorrect** code for this rule with the `"after"` option: + +```js +/*eslint operator-linebreak: ["error", "after"]*/ + +foo = 1 ++ +2; + +foo = 1 + + 2; + +foo + = 5; + +if (someCondition + || otherCondition) { +} + +answer = everything + ? 42 + : foo; +``` + +Examples of **correct** code for this rule with the `"after"` option: + +```js +/*eslint operator-linebreak: ["error", "after"]*/ + +foo = 1 + 2; + +foo = 1 + + 2; + +foo = + 5; + +if (someCondition || + otherCondition) { +} + +answer = everything ? + 42 : + foo; +``` + +### before + +Examples of **incorrect** code for this rule with the `"before"` option: + +```js +/*eslint operator-linebreak: ["error", "before"]*/ + +foo = 1 + + 2; + +foo = + 5; + +if (someCondition || + otherCondition) { +} + +answer = everything ? + 42 : + foo; +``` + +Examples of **correct** code for this rule with the `"before"` option: + +```js +/*eslint operator-linebreak: ["error", "before"]*/ + +foo = 1 + 2; + +foo = 1 + + 2; + +foo + = 5; + +if (someCondition + || otherCondition) { +} + +answer = everything + ? 42 + : foo; +``` + +### none + +Examples of **incorrect** code for this rule with the `"none"` option: + +```js +/*eslint operator-linebreak: ["error", "none"]*/ + +foo = 1 + + 2; + +foo = 1 + + 2; + +if (someCondition || + otherCondition) { +} + +if (someCondition + || otherCondition) { +} + +answer = everything + ? 42 + : foo; + +answer = everything ? + 42 : + foo; +``` + +Examples of **correct** code for this rule with the `"none"` option: + +```js +/*eslint operator-linebreak: ["error", "none"]*/ + +foo = 1 + 2; + +foo = 5; + +if (someCondition || otherCondition) { +} + +answer = everything ? 42 : foo; +``` + +### overrides + +Examples of additional **incorrect** code for this rule with the `{ "overrides": { "+=": "before" } }` option: + +```js +/*eslint operator-linebreak: ["error", "after", { "overrides": { "+=": "before" } }]*/ + +var thing = 'thing'; +thing += + 's'; +``` + +Examples of additional **correct** code for this rule with the `{ "overrides": { "+=": "before" } }` option: + +```js +/*eslint operator-linebreak: ["error", "after", { "overrides": { "+=": "before" } }]*/ + +var thing = 'thing'; +thing + += 's'; +``` + +Examples of additional **correct** code for this rule with the `{ "overrides": { "?": "ignore", ":": "ignore" } }` option: + +```js +/*eslint operator-linebreak: ["error", "after", { "overrides": { "?": "ignore", ":": "ignore" } }]*/ + +answer = everything ? + 42 + : foo; + +answer = everything + ? + 42 + : + foo; +``` + +Examples of **incorrect** code for this rule with the default `"after", { "overrides": { "?": "before", ":": "before" } }` option: + +```js +/*eslint operator-linebreak: ["error", "after", { "overrides": { "?": "before", ":": "before" } }]*/ + +foo = 1 ++ +2; + +foo = 1 + + 2; + +foo + = 5; + +if (someCondition + || otherCondition) { +} + +answer = everything ? + 42 : + foo; +``` + +Examples of **correct** code for this rule with the default `"after", { "overrides": { "?": "before", ":": "before" } }` option: + +```js +/*eslint operator-linebreak: ["error", "after", { "overrides": { "?": "before", ":": "before" } }]*/ + +foo = 1 + 2; + +foo = 1 + + 2; + +foo = + 5; + +if (someCondition || + otherCondition) { +} + +answer = everything + ? 42 + : foo; +``` + +## When Not To Use It + +If your project will not be using a common operator line break style, turn this rule off. + +## Related Rules + +* [comma-style](comma-style.md) diff --git a/eslint/docs/rules/padded-blocks.md b/eslint/docs/rules/padded-blocks.md new file mode 100644 index 0000000..d8b855e --- /dev/null +++ b/eslint/docs/rules/padded-blocks.md @@ -0,0 +1,399 @@ +# require or disallow padding within blocks (padded-blocks) + +Some style guides require block statements to start and end with blank lines. The goal is +to improve readability by visually separating the block content and the surrounding code. + +```js +if (a) { + + b(); + +} +``` + +Since it's good to have a consistent code style, you should either always write +padded blocks or never do it. + +## Rule Details + +This rule enforces consistent empty line padding within blocks. + +## Options + +This rule has two options, the first one can be a string option or an object option. +The second one is an object option, it can allow exceptions. + +### First option + +String option: + +* `"always"` (default) requires empty lines at the beginning and ending of block statements and classes +* `"never"` disallows empty lines at the beginning and ending of block statements and classes + +Object option: + +* `"blocks"` require or disallow padding within block statements +* `"classes"` require or disallow padding within classes +* `"switches"` require or disallow padding within `switch` statements + +### Second option + +* `"allowSingleLineBlocks": true` allows single-line blocks + +### always + +Examples of **incorrect** code for this rule with the default `"always"` option: + +```js +/*eslint padded-blocks: ["error", "always"]*/ + +if (a) { + b(); +} + +if (a) { b(); } + +if (a) +{ + b(); +} + +if (a) { + b(); + +} + +if (a) { + // comment + b(); + +} +``` + +Examples of **correct** code for this rule with the default `"always"` option: + +```js +/*eslint padded-blocks: ["error", "always"]*/ + +if (a) { + + b(); + +} + +if (a) +{ + + b(); + +} + +if (a) { + + // comment + b(); + +} +``` + +### never + +Examples of **incorrect** code for this rule with the `"never"` option: + +```js +/*eslint padded-blocks: ["error", "never"]*/ + +if (a) { + + b(); + +} + +if (a) +{ + + b(); + +} + +if (a) { + + b(); +} + +if (a) { + b(); + +} +``` + +Examples of **correct** code for this rule with the `"never"` option: + +```js +/*eslint padded-blocks: ["error", "never"]*/ + +if (a) { + b(); +} + +if (a) +{ + b(); +} +``` + +### blocks + +Examples of **incorrect** code for this rule with the `{ "blocks": "always" }` option: + +```js +/*eslint padded-blocks: ["error", { "blocks": "always" }]*/ + +if (a) { + b(); +} + +if (a) { b(); } + +if (a) +{ + b(); +} + +if (a) { + + b(); +} + +if (a) { + b(); + +} + +if (a) { + // comment + b(); + +} +``` + +Examples of **correct** code for this rule with the `{ "blocks": "always" }` option: + +```js +/*eslint padded-blocks: ["error", { "blocks": "always" }]*/ + +if (a) { + + b(); + +} + +if (a) +{ + + b(); + +} + +if (a) { + + // comment + b(); + +} +``` + +Examples of **incorrect** code for this rule with the `{ "blocks": "never" }` option: + +```js +/*eslint padded-blocks: ["error", { "blocks": "never" }]*/ + +if (a) { + + b(); + +} + +if (a) +{ + + b(); + +} + +if (a) { + + b(); +} + +if (a) { + b(); + +} +``` + +Examples of **correct** code for this rule with the `{ "blocks": "never" }` option: + +```js +/*eslint padded-blocks: ["error", { "blocks": "never" }]*/ + +if (a) { + b(); +} + +if (a) +{ + b(); +} +``` + +### classes + +Examples of **incorrect** code for this rule with the `{ "classes": "always" }` option: + +```js +/*eslint padded-blocks: ["error", { "classes": "always" }]*/ + +class A { + constructor(){ + } +} +``` + +Examples of **correct** code for this rule with the `{ "classes": "always" }` option: + +```js +/*eslint padded-blocks: ["error", { "classes": "always" }]*/ + +class A { + + constructor(){ + } + +} +``` + +Examples of **incorrect** code for this rule with the `{ "classes": "never" }` option: + +```js +/*eslint padded-blocks: ["error", { "classes": "never" }]*/ + +class A { + + constructor(){ + } + +} +``` + +Examples of **correct** code for this rule with the `{ "classes": "never" }` option: + +```js +/*eslint padded-blocks: ["error", { "classes": "never" }]*/ + +class A { + constructor(){ + } +} +``` + +### switches + +Examples of **incorrect** code for this rule with the `{ "switches": "always" }` option: + +```js +/*eslint padded-blocks: ["error", { "switches": "always" }]*/ + +switch (a) { + case 0: foo(); +} +``` + +Examples of **correct** code for this rule with the `{ "switches": "always" }` option: + +```js +/*eslint padded-blocks: ["error", { "switches": "always" }]*/ + +switch (a) { + + case 0: foo(); + +} + +if (a) { + b(); +} +``` + +Examples of **incorrect** code for this rule with the `{ "switches": "never" }` option: + +```js +/*eslint padded-blocks: ["error", { "switches": "never" }]*/ + +switch (a) { + + case 0: foo(); + +} +``` + +Examples of **correct** code for this rule with the `{ "switches": "never" }` option: + +```js +/*eslint padded-blocks: ["error", { "switches": "never" }]*/ + +switch (a) { + case 0: foo(); +} + +if (a) { + + b(); + +} +``` + +### always + allowSingleLineBlocks + +Examples of **incorrect** code for this rule with the `"always", {"allowSingleLineBlocks": true}` options: + +```js +/*eslint padded-blocks: ["error", "always", { allowSingleLineBlocks: true }]*/ + +if (a) { + b(); +} + +if (a) { + + b(); +} + +if (a) { + b(); + +} +``` + +Examples of **correct** code for this rule with the `"always", {"allowSingleLineBlocks": true}` options: + +```js +/*eslint padded-blocks: ["error", "always", { allowSingleLineBlocks: true }]*/ + +if (a) { b(); } + +if (a) { + + b(); + +} +``` + +## When Not To Use It + +You can turn this rule off if you are not concerned with the consistency of padding within blocks. + +## Related Rules + +* [lines-between-class-members](lines-between-class-members.md) +* [padding-line-between-statements](padding-line-between-statements.md) diff --git a/eslint/docs/rules/padding-line-between-statements.md b/eslint/docs/rules/padding-line-between-statements.md new file mode 100644 index 0000000..1f2c4ab --- /dev/null +++ b/eslint/docs/rules/padding-line-between-statements.md @@ -0,0 +1,242 @@ +# Require or disallow padding lines between statements (padding-line-between-statements) + +This rule requires or disallows blank lines between the given 2 kinds of statements. +Properly blank lines help developers to understand the code. + +For example, the following configuration requires a blank line between a variable declaration and a `return` statement. + +```js +/*eslint padding-line-between-statements: [ + "error", + { blankLine: "always", prev: "var", next: "return" } +]*/ + +function foo() { + var a = 1; + + return a; +} +``` + +## Rule Details + +This rule does nothing if no configurations are provided. + +A configuration is an object which has 3 properties; `blankLine`, `prev` and `next`. For example, `{ blankLine: "always", prev: "var", next: "return" }` means "one or more blank lines are required between a variable declaration and a `return` statement." +You can supply any number of configurations. If a statement pair matches multiple configurations, the last matched configuration will be used. + +```json +{ + "padding-line-between-statements": [ + "error", + { "blankLine": LINEBREAK_TYPE, "prev": STATEMENT_TYPE, "next": STATEMENT_TYPE }, + { "blankLine": LINEBREAK_TYPE, "prev": STATEMENT_TYPE, "next": STATEMENT_TYPE }, + { "blankLine": LINEBREAK_TYPE, "prev": STATEMENT_TYPE, "next": STATEMENT_TYPE }, + { "blankLine": LINEBREAK_TYPE, "prev": STATEMENT_TYPE, "next": STATEMENT_TYPE }, + ... + ] +} +``` + +- `LINEBREAK_TYPE` is one of the following. + - `"any"` just ignores the statement pair. + - `"never"` disallows blank lines. + - `"always"` requires one or more blank lines. Note it does not count lines that comments exist as blank lines. + +- `STATEMENT_TYPE` is one of the following, or an array of the following. + - `"*"` is wildcard. This matches any statements. + - `"block"` is lonely blocks. + - `"block-like"` is block like statements. This matches statements that the last token is the closing brace of blocks; e.g. `{ }`, `if (a) { }`, and `while (a) { }`. Also matches immediately invoked function expression statements. + - `"break"` is `break` statements. + - `"case"` is `case` labels. + - `"cjs-export"` is `export` statements of CommonJS; e.g. `module.exports = 0`, `module.exports.foo = 1`, and `exports.foo = 2`. This is a special case of assignment. + - `"cjs-import"` is `import` statements of CommonJS; e.g. `const foo = require("foo")`. This is a special case of variable declarations. + - `"class"` is `class` declarations. + - `"const"` is `const` variable declarations, both single-line and multiline. + - `"continue"` is `continue` statements. + - `"debugger"` is `debugger` statements. + - `"default"` is `default` labels. + - `"directive"` is directive prologues. This matches directives; e.g. `"use strict"`. + - `"do"` is `do-while` statements. This matches all statements that the first token is `do` keyword. + - `"empty"` is empty statements. + - `"export"` is `export` declarations. + - `"expression"` is expression statements. + - `"for"` is `for` loop families. This matches all statements that the first token is `for` keyword. + - `"function"` is function declarations. + - `"if"` is `if` statements. + - `"iife"` is immediately invoked function expression statements. This matches calls on a function expression, optionally prefixed with a unary operator. + - `"import"` is `import` declarations. + - `"let"` is `let` variable declarations, both single-line and multiline. + - `"multiline-block-like"` is block like statements. This is the same as `block-like` type, but only if the block is multiline. + - `"multiline-const"` is multiline `const` variable declarations. + - `"multiline-expression"` is expression statements. This is the same as `expression` type, but only if the statement is multiline. + - `"multiline-let"` is multiline `let` variable declarations. + - `"multiline-var"` is multiline `var` variable declarations. + - `"return"` is `return` statements. + - `"singleline-const"` is single-line `const` variable declarations. + - `"singleline-let"` is single-line `let` variable declarations. + - `"singleline-var"` is single-line `var` variable declarations. + - `"switch"` is `switch` statements. + - `"throw"` is `throw` statements. + - `"try"` is `try` statements. + - `"var"` is `var` variable declarations, both single-line and multiline. + - `"while"` is `while` loop statements. + - `"with"` is `with` statements. + +## Examples + +This configuration would require blank lines before all `return` statements, like the [newline-before-return] rule. + +Examples of **incorrect** code for the `[{ blankLine: "always", prev: "*", next: "return" }]` configuration: + +```js +/*eslint padding-line-between-statements: [ + "error", + { blankLine: "always", prev: "*", next: "return" } +]*/ + +function foo() { + bar(); + return; +} +``` + +Examples of **correct** code for the `[{ blankLine: "always", prev: "*", next: "return" }]` configuration: + +```js +/*eslint padding-line-between-statements: [ + "error", + { blankLine: "always", prev: "*", next: "return" } +]*/ + +function foo() { + bar(); + + return; +} + +function foo() { + return; +} +``` + +---- + +This configuration would require blank lines after every sequence of variable declarations, like the [newline-after-var] rule. + +Examples of **incorrect** code for the `[{ blankLine: "always", prev: ["const", "let", "var"], next: "*"}, { blankLine: "any", prev: ["const", "let", "var"], next: ["const", "let", "var"]}]` configuration: + +```js +/*eslint padding-line-between-statements: [ + "error", + { blankLine: "always", prev: ["const", "let", "var"], next: "*"}, + { blankLine: "any", prev: ["const", "let", "var"], next: ["const", "let", "var"]} +]*/ + +function foo() { + var a = 0; + bar(); +} + +function foo() { + let a = 0; + bar(); +} + +function foo() { + const a = 0; + bar(); +} +``` + +Examples of **correct** code for the `[{ blankLine: "always", prev: ["const", "let", "var"], next: "*"}, { blankLine: "any", prev: ["const", "let", "var"], next: ["const", "let", "var"]}]` configuration: + +```js +/*eslint padding-line-between-statements: [ + "error", + { blankLine: "always", prev: ["const", "let", "var"], next: "*"}, + { blankLine: "any", prev: ["const", "let", "var"], next: ["const", "let", "var"]} +]*/ + +function foo() { + var a = 0; + var b = 0; + + bar(); +} + +function foo() { + let a = 0; + const b = 0; + + bar(); +} + +function foo() { + const a = 0; + const b = 0; + + bar(); +} +``` + +---- + +This configuration would require blank lines after all directive prologues, like the [lines-around-directive] rule. + +Examples of **incorrect** code for the `[{ blankLine: "always", prev: "directive", next: "*" }, { blankLine: "any", prev: "directive", next: "directive" }]` configuration: + +```js +/*eslint padding-line-between-statements: [ + "error", + { blankLine: "always", prev: "directive", next: "*" }, + { blankLine: "any", prev: "directive", next: "directive" } +]*/ + +"use strict"; +foo(); +``` + +Examples of **correct** code for the `[{ blankLine: "always", prev: "directive", next: "*" }, { blankLine: "any", prev: "directive", next: "directive" }]` configuration: + +```js +/*eslint padding-line-between-statements: [ + "error", + { blankLine: "always", prev: "directive", next: "*" }, + { blankLine: "any", prev: "directive", next: "directive" } +]*/ + +"use strict"; +"use asm"; + +foo(); +``` + +## Compatibility + +- **JSCS:** [requirePaddingNewLineAfterVariableDeclaration] +- **JSCS:** [requirePaddingNewLinesAfterBlocks] +- **JSCS:** [disallowPaddingNewLinesAfterBlocks] +- **JSCS:** [requirePaddingNewLinesAfterUseStrict] +- **JSCS:** [disallowPaddingNewLinesAfterUseStrict] +- **JSCS:** [requirePaddingNewLinesBeforeExport] +- **JSCS:** [disallowPaddingNewLinesBeforeExport] +- **JSCS:** [requirePaddingNewlinesBeforeKeywords] +- **JSCS:** [disallowPaddingNewlinesBeforeKeywords] + +## When Not To Use It + +If you don't want to notify warnings about linebreaks, then it's safe to disable this rule. + +[lines-around-directive]: https://eslint.org/docs/rules/lines-around-directive +[newline-after-var]: https://eslint.org/docs/rules/newline-after-var +[newline-before-return]: https://eslint.org/docs/rules/newline-before-return +[requirePaddingNewLineAfterVariableDeclaration]: https://jscs-dev.github.io/rule/requirePaddingNewLineAfterVariableDeclaration +[requirePaddingNewLinesAfterBlocks]: https://jscs-dev.github.io/rule/requirePaddingNewLinesAfterBlocks +[disallowPaddingNewLinesAfterBlocks]: https://jscs-dev.github.io/rule/disallowPaddingNewLinesAfterBlocks +[requirePaddingNewLinesAfterUseStrict]: https://jscs-dev.github.io/rule/requirePaddingNewLinesAfterUseStrict +[disallowPaddingNewLinesAfterUseStrict]: https://jscs-dev.github.io/rule/disallowPaddingNewLinesAfterUseStrict +[requirePaddingNewLinesBeforeExport]: https://jscs-dev.github.io/rule/requirePaddingNewLinesBeforeExport +[disallowPaddingNewLinesBeforeExport]: https://jscs-dev.github.io/rule/disallowPaddingNewLinesBeforeExport +[requirePaddingNewlinesBeforeKeywords]: https://jscs-dev.github.io/rule/requirePaddingNewlinesBeforeKeywords +[disallowPaddingNewlinesBeforeKeywords]: https://jscs-dev.github.io/rule/disallowPaddingNewlinesBeforeKeywords diff --git a/eslint/docs/rules/prefer-arrow-callback.md b/eslint/docs/rules/prefer-arrow-callback.md new file mode 100644 index 0000000..c913212 --- /dev/null +++ b/eslint/docs/rules/prefer-arrow-callback.md @@ -0,0 +1,100 @@ +# Require using arrow functions for callbacks (prefer-arrow-callback) + +Arrow functions can be an attractive alternative to function expressions for callbacks or function arguments. + +For example, arrow functions are automatically bound to their surrounding scope/context. This provides an alternative to the pre-ES6 standard of explicitly binding function expressions to achieve similar behavior. + +Additionally, arrow functions are: + +- less verbose, and easier to reason about. + +- bound lexically regardless of where or when they are invoked. + +## Rule Details + +This rule locates function expressions used as callbacks or function arguments. An error will be produced for any that could be replaced by an arrow function without changing the result. + +The following examples **will** be flagged: + +```js +/* eslint prefer-arrow-callback: "error" */ + +foo(function(a) { return a; }); // ERROR +// prefer: foo(a => a) + +foo(function() { return this.a; }.bind(this)); // ERROR +// prefer: foo(() => this.a) +``` + +Instances where an arrow function would not produce identical results will be ignored. + +The following examples **will not** be flagged: + +```js +/* eslint prefer-arrow-callback: "error" */ +/* eslint-env es6 */ + +// arrow function callback +foo(a => a); // OK + +// generator as callback +foo(function*() { yield; }); // OK + +// function expression not used as callback or function argument +var foo = function foo(a) { return a; }; // OK + +// unbound function expression callback +foo(function() { return this.a; }); // OK + +// recursive named function callback +foo(function bar(n) { return n && n + bar(n - 1); }); // OK +``` + +## Options + +Access further control over this rule's behavior via an options object. + +Default: `{ allowNamedFunctions: false, allowUnboundThis: true }` + +### allowNamedFunctions + +By default `{ "allowNamedFunctions": false }`, this `boolean` option prohibits using named functions as callbacks or function arguments. + +Changing this value to `true` will reverse this option's behavior by allowing use of named functions without restriction. + +`{ "allowNamedFunctions": true }` **will not** flag the following example: + +```js +/* eslint prefer-arrow-callback: [ "error", { "allowNamedFunctions": true } ] */ + +foo(function bar() {}); +``` + +### allowUnboundThis + +By default `{ "allowUnboundThis": true }`, this `boolean` option allows function expressions containing `this` to be used as callbacks, as long as the function in question has not been explicitly bound. + +When set to `false` this option prohibits the use of function expressions as callbacks or function arguments entirely, without exception. + +`{ "allowUnboundThis": false }` **will** flag the following examples: + +```js +/* eslint prefer-arrow-callback: [ "error", { "allowUnboundThis": false } ] */ +/* eslint-env es6 */ + +foo(function() { this.a; }); + +foo(function() { (() => this); }); + +someArray.map(function(itm) { return this.doSomething(itm); }, someObject); +``` + +## When Not To Use It + +- In environments that have not yet adopted ES6 language features (ES3/5). + +- In ES6+ environments that allow the use of function expressions when describing callbacks or function arguments. + +## Further Reading + +- [More on ES6 arrow functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) diff --git a/eslint/docs/rules/prefer-const.md b/eslint/docs/rules/prefer-const.md new file mode 100644 index 0000000..3e5a5d0 --- /dev/null +++ b/eslint/docs/rules/prefer-const.md @@ -0,0 +1,205 @@ +# Suggest using `const` (prefer-const) + +If a variable is never reassigned, using the `const` declaration is better. + +`const` declaration tells readers, "this variable is never reassigned," reducing cognitive load and improving maintainability. + +## Rule Details + +This rule is aimed at flagging variables that are declared using `let` keyword, but never reassigned after the initial assignment. + +Examples of **incorrect** code for this rule: + +```js +/*eslint prefer-const: "error"*/ +/*eslint-env es6*/ + +// it's initialized and never reassigned. +let a = 3; +console.log(a); + +let a; +a = 0; +console.log(a); + +// `i` is redefined (not reassigned) on each loop step. +for (let i in [1, 2, 3]) { + console.log(i); +} + +// `a` is redefined (not reassigned) on each loop step. +for (let a of [1, 2, 3]) { + console.log(a); +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint prefer-const: "error"*/ +/*eslint-env es6*/ + +// using const. +const a = 0; + +// it's never initialized. +let a; +console.log(a); + +// it's reassigned after initialized. +let a; +a = 0; +a = 1; +console.log(a); + +// it's initialized in a different block from the declaration. +let a; +if (true) { + a = 0; +} +console.log(a); + +// it's initialized at a place that we cannot write a variable declaration. +let a; +if (true) a = 0; +console.log(a); + +// `i` gets a new binding each iteration +for (const i in [1, 2, 3]) { + console.log(i); +} + +// `a` gets a new binding each iteration +for (const a of [1, 2, 3]) { + console.log(a); +} + +// `end` is never reassigned, but we cannot separate the declarations without modifying the scope. +for (let i = 0, end = 10; i < end; ++i) { + console.log(a); +} + +// `predicate` is only assigned once but cannot be separately declared as `const` +let predicate; +[object.type, predicate] = foo(); + +// `a` is only assigned once but cannot be separately declared as `const` +let a; +const b = {}; +({ a, c: b.c } = func()); + +// suggest to use `no-var` rule. +var b = 3; +console.log(b); +``` + +## Options + +```json +{ + "prefer-const": ["error", { + "destructuring": "any", + "ignoreReadBeforeAssign": false + }] +} +``` + +### destructuring + +The kind of the way to address variables in destructuring. +There are 2 values: + +* `"any"` (default) - If any variables in destructuring should be `const`, this rule warns for those variables. +* `"all"` - If all variables in destructuring should be `const`, this rule warns the variables. Otherwise, ignores them. + +Examples of **incorrect** code for the default `{"destructuring": "any"}` option: + +```js +/*eslint prefer-const: "error"*/ +/*eslint-env es6*/ + +let {a, b} = obj; /*error 'b' is never reassigned, use 'const' instead.*/ +a = a + 1; +``` + +Examples of **correct** code for the default `{"destructuring": "any"}` option: + +```js +/*eslint prefer-const: "error"*/ +/*eslint-env es6*/ + +// using const. +const {a: a0, b} = obj; +const a = a0 + 1; + +// all variables are reassigned. +let {a, b} = obj; +a = a + 1; +b = b + 1; +``` + +Examples of **incorrect** code for the `{"destructuring": "all"}` option: + +```js +/*eslint prefer-const: ["error", {"destructuring": "all"}]*/ +/*eslint-env es6*/ + +// all of `a` and `b` should be const, so those are warned. +let {a, b} = obj; /*error 'a' is never reassigned, use 'const' instead. + 'b' is never reassigned, use 'const' instead.*/ +``` + +Examples of **correct** code for the `{"destructuring": "all"}` option: + +```js +/*eslint prefer-const: ["error", {"destructuring": "all"}]*/ +/*eslint-env es6*/ + +// 'b' is never reassigned, but all of `a` and `b` should not be const, so those are ignored. +let {a, b} = obj; +a = a + 1; +``` + +### ignoreReadBeforeAssign + +This is an option to avoid conflicting with `no-use-before-define` rule (without `"nofunc"` option). +If `true` is specified, this rule will ignore variables that are read between the declaration and the first assignment. +Default is `false`. + +Examples of **correct** code for the `{"ignoreReadBeforeAssign": true}` option: + +```js +/*eslint prefer-const: ["error", {"ignoreReadBeforeAssign": true}]*/ +/*eslint-env es6*/ + +let timer; +function initialize() { + if (foo()) { + clearInterval(timer); + } +} +timer = setInterval(initialize, 100); +``` + +Examples of **correct** code for the default `{"ignoreReadBeforeAssign": false}` option: + +```js +/*eslint prefer-const: ["error", {"ignoreReadBeforeAssign": false}]*/ +/*eslint-env es6*/ + +const timer = setInterval(initialize, 100); +function initialize() { + if (foo()) { + clearInterval(timer); + } +} +``` + +## When Not To Use It + +If you don't want to be notified about variables that are never reassigned after initial assignment, you can safely disable this rule. + +## Related Rules + +* [no-var](no-var.md) +* [no-use-before-define](no-use-before-define.md) diff --git a/eslint/docs/rules/prefer-destructuring.md b/eslint/docs/rules/prefer-destructuring.md new file mode 100644 index 0000000..00b5577 --- /dev/null +++ b/eslint/docs/rules/prefer-destructuring.md @@ -0,0 +1,175 @@ +# Prefer destructuring from arrays and objects (prefer-destructuring) + +With JavaScript ES6, a new syntax was added for creating variables from an array index or object property, called [destructuring](#further-reading). This rule enforces usage of destructuring instead of accessing a property through a member expression. + +## Rule Details + +### Options + +This rule takes two sets of configuration objects. The first object parameter determines what types of destructuring the rule applies to. + +The two properties, `array` and `object`, can be used to turn on or off the destructuring requirement for each of those types independently. By default, both are true. + +Alternatively, you can use separate configurations for different assignment types. It accepts 2 other keys instead of `array` and `object`. + +One key is `VariableDeclarator` and the other is `AssignmentExpression`, which can be used to control the destructuring requirement for each of those types independently. Each property accepts an object that accepts two properties, `array` and `object`, which can be used to control the destructuring requirement for each of `array` and `object` independently for variable declarations and assignment expressions. By default, `array` and `object` are set to true for both `VariableDeclarator` and `AssignmentExpression`. + +The rule has a second object with a single key, `enforceForRenamedProperties`, which determines whether the `object` destructuring applies to renamed variables. + +**Note**: It is not possible to determine if a variable will be referring to an object or an array at runtime. This rule therefore guesses the assignment type by checking whether the key being accessed is an integer. This can lead to the following possibly confusing situations: + +- Accessing an object property whose key is an integer will fall under the category `array` destructuring. +- Accessing an array element through a computed index will fall under the category `object` destructuring. + +Examples of **incorrect** code for this rule: + +```javascript +// With `array` enabled +var foo = array[0]; + +// With `object` enabled +var foo = object.foo; +var foo = object['foo']; +``` + +Examples of **correct** code for this rule: + +```javascript +// With `array` enabled +var [ foo ] = array; +var foo = array[someIndex]; + +// With `object` enabled +var { foo } = object; + +var foo = object.bar; + +let foo; +({ foo } = object); +``` + +Examples of **incorrect** code when `enforceForRenamedProperties` is enabled: + +```javascript +var foo = object.bar; +``` + +Examples of **correct** code when `enforceForRenamedProperties` is enabled: + +```javascript +var { bar: foo } = object; +``` + +An example configuration, with the defaults `array` and `object` filled in, looks like this: + +```json +{ + "rules": { + "prefer-destructuring": ["error", { + "array": true, + "object": true + }, { + "enforceForRenamedProperties": false + }] + } +} +``` + +The two properties, `array` and `object`, which can be used to turn on or off the destructuring requirement for each of those types independently. By default, both are true. + +For example, the following configuration enforces only object destructuring, but not array destructuring: + +```json +{ + "rules": { + "prefer-destructuring": ["error", {"object": true, "array": false}] + } +} +``` + +An example configuration, with the defaults `VariableDeclarator` and `AssignmentExpression` filled in, looks like this: + +```json +{ + "rules": { + "prefer-destructuring": ["error", { + "VariableDeclarator": { + "array": false, + "object": true + }, + "AssignmentExpression": { + "array": true, + "object": true + } + }, { + "enforceForRenamedProperties": false + }] + } +} +``` + +The two properties, `VariableDeclarator` and `AssignmentExpression`, which can be used to turn on or off the destructuring requirement for `array` and `object`. By default, all values are true. + +For example, the following configuration enforces object destructuring in variable declarations and enforces array destructuring in assignment expressions. + +```json +{ + "rules": { + "prefer-destructuring": ["error", { + "VariableDeclarator": { + "array": false, + "object": true + }, + "AssignmentExpression": { + "array": true, + "object": false + } + }, { + "enforceForRenamedProperties": false + }] + } +} + +``` + +Examples of **correct** code when object destructuring in `VariableDeclarator` is enforced: + +```javascript +/* eslint prefer-destructuring: ["error", {VariableDeclarator: {object: true}}] */ +var {bar: foo} = object; +``` + +Examples of **correct** code when array destructuring in `AssignmentExpression` is enforced: + +```javascript +/* eslint prefer-destructuring: ["error", {AssignmentExpression: {array: true}}] */ +[bar] = array; +``` + +## When Not To Use It + +If you want to be able to access array indices or object properties directly, you can either configure the rule to your tastes or disable the rule entirely. + +Additionally, if you intend to access large array indices directly, like: + +```javascript +var foo = array[100]; +``` + +Then the `array` part of this rule is not recommended, as destructuring does not match this use case very well. + +Or for non-iterable 'array-like' objects: + +```javascript +var $ = require('jquery'); +var foo = $('body')[0]; +var [bar] = $('body'); // fails with a TypeError +``` + + +## Further Reading + +If you want to learn more about destructuring, check out the links below: + +- [Destructuring Assignment (MDN)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) +- [Destructuring and parameter handling in ECMAScript 6 (2ality blog)](http://2ality.com/2015/01/es6-destructuring.html) diff --git a/eslint/docs/rules/prefer-exponentiation-operator.md b/eslint/docs/rules/prefer-exponentiation-operator.md new file mode 100644 index 0000000..f5f9e47 --- /dev/null +++ b/eslint/docs/rules/prefer-exponentiation-operator.md @@ -0,0 +1,46 @@ +# Disallow the use of `Math.pow` in favor of the `**` operator (prefer-exponentiation-operator) + +Introduced in ES2016, the infix exponentiation operator `**` is an alternative for the standard `Math.pow` function. + +Infix notation is considered to be more readable and thus more preferable than the function notation. + +## Rule Details + +This rule disallows calls to `Math.pow` and suggests using the `**` operator instead. + +Examples of **incorrect** code for this rule: + +```js +/*eslint prefer-exponentiation-operator: "error"*/ + +const foo = Math.pow(2, 8); + +const bar = Math.pow(a, b); + +let baz = Math.pow(a + b, c + d); + +let quux = Math.pow(-1, n); +``` + +Examples of **correct** code for this rule: + +```js +/*eslint prefer-exponentiation-operator: "error"*/ + +const foo = 2 ** 8; + +const bar = a ** b; + +let baz = (a + b) ** (c + d); + +let quux = (-1) ** n; +``` + +## When Not To Use It + +This rule should not be used unless ES2016 is supported in your codebase. + +## Further Reading + +* [MDN Arithmetic Operators - Exponentiation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Exponentiation) +* [Issue 5848: Exponentiation operator ** has different results for numbers and variables from 50 upwards](https://bugs.chromium.org/p/v8/issues/detail?id=5848) diff --git a/eslint/docs/rules/prefer-named-capture-group.md b/eslint/docs/rules/prefer-named-capture-group.md new file mode 100644 index 0000000..ff8790c --- /dev/null +++ b/eslint/docs/rules/prefer-named-capture-group.md @@ -0,0 +1,43 @@ +# Suggest using named capture group in regular expression (prefer-named-capture-group) + +With the landing of ECMAScript 2018, named capture groups can be used in regular expressions, which can improve their readability. + +```js +const regex = /(?[0-9]{4})/; +``` + +## Rule Details + +This rule is aimed at using named capture groups instead of numbered capture groups in regular expressions. + +Examples of **incorrect** code for this rule: + +```js +/*eslint prefer-named-capture-group: "error"*/ + +const foo = /(ba[rz])/; +const bar = new RegExp('(ba[rz])'); +const baz = RegExp('(ba[rz])'); + +foo.exec('bar')[1]; // Retrieve the group result. +``` + +Examples of **correct** code for this rule: + +```js +/*eslint prefer-named-capture-group: "error"*/ + +const foo = /(?ba[rz])/; +const bar = new RegExp('(?ba[rz])'); +const baz = RegExp('(?ba[rz])'); + +foo.exec('bar').groups.id; // Retrieve the group result. +``` + +## When Not To Use It + +If you are targeting ECMAScript 2017 and/or older environments, you can disable this rule, because this ECMAScript feature is only supported in ECMAScript 2018 and/or newer environments. + +## Related Rules + +* [no-invalid-regexp](./no-invalid-regexp.md) diff --git a/eslint/docs/rules/prefer-numeric-literals.md b/eslint/docs/rules/prefer-numeric-literals.md new file mode 100644 index 0000000..bf81e5e --- /dev/null +++ b/eslint/docs/rules/prefer-numeric-literals.md @@ -0,0 +1,57 @@ +# disallow `parseInt()` and `Number.parseInt()` in favor of binary, octal, and hexadecimal literals (prefer-numeric-literals) + +The `parseInt()` and `Number.parseInt()` functions can be used to turn binary, octal, and hexadecimal strings into integers. As binary, octal, and hexadecimal literals are supported in ES6, this rule encourages use of those numeric literals instead of `parseInt()` or `Number.parseInt()`. + +```js +0b111110111 === 503; +0o767 === 503; +``` + +## Rule Details + +This rule disallows calls to `parseInt()` or `Number.parseInt()` if called with two arguments: a string; and a radix option of 2 (binary), 8 (octal), or 16 (hexadecimal). + +Examples of **incorrect** code for this rule: + +```js +/*eslint prefer-numeric-literals: "error"*/ + +parseInt("111110111", 2) === 503; +parseInt(`111110111`, 2) === 503; +parseInt("767", 8) === 503; +parseInt("1F7", 16) === 503; +Number.parseInt("111110111", 2) === 503; +Number.parseInt("767", 8) === 503; +Number.parseInt("1F7", 16) === 503; +``` + +Examples of **correct** code for this rule: + +```js +/*eslint prefer-numeric-literals: "error"*/ +/*eslint-env es6*/ + +parseInt(1); +parseInt(1, 3); +Number.parseInt(1); +Number.parseInt(1, 3); + +0b111110111 === 503; +0o767 === 503; +0x1F7 === 503; + +a[parseInt](1,2); + +parseInt(foo); +parseInt(foo, 2); +Number.parseInt(foo); +Number.parseInt(foo, 2); +``` + +## When Not To Use It + +If you want to allow use of `parseInt()` or `Number.parseInt()` for binary, octal, or hexadecimal integers, or if you are not using ES6 (because binary and octal literals are not supported in ES5 and below), you may wish to disable this rule. + +## Compatibility + +* **JSCS**: [requireNumericLiterals](https://jscs-dev.github.io/rule/requireNumericLiterals) diff --git a/eslint/docs/rules/prefer-object-spread.md b/eslint/docs/rules/prefer-object-spread.md new file mode 100644 index 0000000..95a6460 --- /dev/null +++ b/eslint/docs/rules/prefer-object-spread.md @@ -0,0 +1,49 @@ +# Prefer use of an object spread over `Object.assign` (prefer-object-spread) + +When Object.assign is called using an object literal as the first argument, this rule requires using the object spread syntax instead. This rule also warns on cases where an `Object.assign` call is made using a single argument that is an object literal, in this case, the `Object.assign` call is not needed. + +Introduced in ES2018, object spread is a declarative alternative which may perform better than the more dynamic, imperative `Object.assign`. + +## Rule Details + +Examples of **incorrect** code for this rule: + +```js + +Object.assign({}, foo) + +Object.assign({}, {foo: 'bar'}) + +Object.assign({ foo: 'bar'}, baz) + +Object.assign({ foo: 'bar' }, Object.assign({ bar: 'foo' })) + +Object.assign({}, { foo, bar, baz }) + +Object.assign({}, { ...baz }) + +// Object.assign with a single argument that is an object literal +Object.assign({}); + +Object.assign({ foo: bar }); +``` + +Examples of **correct** code for this rule: + +```js + +Object.assign(...foo); + +// Any Object.assign call without an object literal as the first argument +Object.assign(foo, { bar: baz }); + +Object.assign(foo, Object.assign(bar)); + +Object.assign(foo, { bar, baz }) + +Object.assign(foo, { ...baz }); +``` + +## When Not To Use It + +This rule should not be used unless ES2018 is supported in your codebase. diff --git a/eslint/docs/rules/prefer-promise-reject-errors.md b/eslint/docs/rules/prefer-promise-reject-errors.md new file mode 100644 index 0000000..ac0a227 --- /dev/null +++ b/eslint/docs/rules/prefer-promise-reject-errors.md @@ -0,0 +1,79 @@ +# require using Error objects as Promise rejection reasons (prefer-promise-reject-errors) + +It is considered good practice to only pass instances of the built-in `Error` object to the `reject()` function for user-defined errors in Promises. `Error` objects automatically store a stack trace, which can be used to debug an error by determining where it came from. If a Promise is rejected with a non-`Error` value, it can be difficult to determine where the rejection occurred. + + +## Rule Details + +This rule aims to ensure that Promises are only rejected with `Error` objects. + +## Options + +This rule takes one optional object argument: + +* `allowEmptyReject: true` (`false` by default) allows calls to `Promise.reject()` with no arguments. + +Examples of **incorrect** code for this rule: + +```js +/*eslint prefer-promise-reject-errors: "error"*/ + +Promise.reject("something bad happened"); + +Promise.reject(5); + +Promise.reject(); + +new Promise(function(resolve, reject) { + reject("something bad happened"); +}); + +new Promise(function(resolve, reject) { + reject(); +}); + +``` + +Examples of **correct** code for this rule: + +```js +/*eslint prefer-promise-reject-errors: "error"*/ + +Promise.reject(new Error("something bad happened")); + +Promise.reject(new TypeError("something bad happened")); + +new Promise(function(resolve, reject) { + reject(new Error("something bad happened")); +}); + +var foo = getUnknownValue(); +Promise.reject(foo); +``` + +Examples of **correct** code for this rule with the `allowEmptyReject: true` option: + +```js +/*eslint prefer-promise-reject-errors: ["error", {"allowEmptyReject": true}]*/ + +Promise.reject(); + +new Promise(function(resolve, reject) { + reject(); +}); +``` + +## Known Limitations + +Due to the limits of static analysis, this rule cannot guarantee that you will only reject Promises with `Error` objects. While the rule will report cases where it can guarantee that the rejection reason is clearly not an `Error`, it will not report cases where there is uncertainty about whether a given reason is an `Error`. For more information on this caveat, see the [similar limitations](no-throw-literal.md#known-limitations) in the `no-throw-literal` rule. + +To avoid conflicts between rules, this rule does not report non-error values used in `throw` statements in async functions, even though these lead to Promise rejections. To lint for these cases, use the [`no-throw-literal`](https://eslint.org/docs/rules/no-throw-literal) rule. + +## When Not To Use It + +If you're using custom non-error values as Promise rejection reasons, you can turn off this rule. + +## Further Reading + +* [`no-throw-literal`](https://eslint.org/docs/rules/no-throw-literal) +* [Warning: a promise was rejected with a non-error](http://bluebirdjs.com/docs/warning-explanations.html#warning-a-promise-was-rejected-with-a-non-error) diff --git a/eslint/docs/rules/prefer-reflect.md b/eslint/docs/rules/prefer-reflect.md new file mode 100644 index 0000000..55bc1bb --- /dev/null +++ b/eslint/docs/rules/prefer-reflect.md @@ -0,0 +1,339 @@ +# Suggest using Reflect methods where applicable (prefer-reflect) + +This rule was **deprecated** in ESLint v3.9.0 and will not be replaced. The original intent of this rule now seems misguided as we have come to understand that `Reflect` methods are not actually intended to replace the `Object` counterparts the rule suggests, but rather exist as low-level primitives to be used with proxies in order to replicate the default behavior of various previously existing functionality. + +The ES6 Reflect API comes with a handful of methods which somewhat deprecate methods on old constructors: + +* [`Reflect.apply`](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-reflect.apply) effectively deprecates [`Function.prototype.apply`](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-function.prototype.apply) and [`Function.prototype.call`](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-function.prototype.call) +* [`Reflect.deleteProperty`](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-reflect.deleteproperty) effectively deprecates the [`delete` keyword](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-delete-operator-runtime-semantics-evaluation) +* [`Reflect.getOwnPropertyDescriptor`](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-reflect.getownpropertydescriptor) effectively deprecates [`Object.getOwnPropertyDescriptor`](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-object.getownpropertydescriptor) +* [`Reflect.getPrototypeOf`](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-reflect.getprototypeof) effectively deprecates [`Object.getPrototypeOf`](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-object.getprototypeof) +* [`Reflect.setPrototypeOf`](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-reflect.setprototypeof) effectively deprecates [`Object.setPrototypeOf`](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-object.setprototypeof) +* [`Reflect.preventExtensions`](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-reflect.preventextensions) effectively deprecates [`Object.preventExtensions`](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-object.preventextensions) + +The prefer-reflect rule will flag usage of any older method, suggesting to instead use the newer Reflect version. + +## Rule Details + +## Options + +### Exceptions + +``` +"prefer-reflect": [, { "exceptions": [<...exceptions>] }] +``` + +The `exceptions` option allows you to pass an array of methods names you'd like to continue to use in the old style. + +For example if you wish to use all Reflect methods, except for `Function.prototype.apply` then your config would look like `prefer-reflect: [2, { "exceptions": ["apply"] }]`. + +If you want to use Reflect methods, but keep using the `delete` keyword, then your config would look like `prefer-reflect: [2, { "exceptions": ["delete"] }]`. + +These can be combined as much as you like. To make all methods exceptions (thereby rendering this rule useless), use `prefer-reflect: [2, { "exceptions": ["apply", "call", "defineProperty", "getOwnPropertyDescriptor", "getPrototypeOf", "setPrototypeOf", "isExtensible", "getOwnPropertyNames", "preventExtensions", "delete"] }]` + +### Reflect.apply + +Deprecates `Function.prototype.apply()` and `Function.prototype.call()` + +Examples of **incorrect** code for this rule when used without exceptions: + +```js +/*eslint prefer-reflect: "error"*/ + +myFunction.apply(undefined, args); +myFunction.apply(null, args); +obj.myMethod.apply(obj, args); +obj.myMethod.apply(other, args); + +myFunction.call(undefined, arg); +myFunction.call(null, arg); +obj.myMethod.call(obj, arg); +obj.myMethod.call(other, arg); +``` + +Examples of **correct** code for this rule when used without exceptions: + +```js +/*eslint prefer-reflect: "error"*/ + +Reflect.apply(myFunction, undefined, args); +Reflect.apply(myFunction, null, args); +Reflect.apply(obj.myMethod, obj, args); +Reflect.apply(obj.myMethod, other, args); +Reflect.apply(myFunction, undefined, [arg]); +Reflect.apply(myFunction, null, [arg]); +Reflect.apply(obj.myMethod, obj, [arg]); +Reflect.apply(obj.myMethod, other, [arg]); +``` + +Examples of **correct** code for this rule with the `{ "exceptions": ["apply"] }` option: + +```js +/*eslint prefer-reflect: ["error", { "exceptions": ["apply"] }]*/ + +// in addition to Reflect.apply(...): +myFunction.apply(undefined, args); +myFunction.apply(null, args); +obj.myMethod.apply(obj, args); +obj.myMethod.apply(other, args); +``` + +Examples of **correct** code for this rule with the `{ "exceptions": ["call"] }` option: + +```js +/*eslint prefer-reflect: ["error", { "exceptions": ["call"] }]*/ + +// in addition to Reflect.apply(...): +myFunction.call(undefined, arg); +myFunction.call(null, arg); +obj.myMethod.call(obj, arg); +obj.myMethod.call(other, arg); +``` + +### Reflect.defineProperty + +Deprecates `Object.defineProperty()` + +Examples of **incorrect** code for this rule when used without exceptions: + +```js +/*eslint prefer-reflect: "error"*/ + +Object.defineProperty({}, 'foo', {value: 1}) +``` + +Examples of **correct** code for this rule when used without exceptions: + +```js +/*eslint prefer-reflect: "error"*/ + +Reflect.defineProperty({}, 'foo', {value: 1}) +``` + +Examples of **correct** code for this rule with the `{ "exceptions": ["defineProperty"] }` option: + +```js +/*eslint prefer-reflect: ["error", { "exceptions": ["defineProperty"] }]*/ + +Object.defineProperty({}, 'foo', {value: 1}) +Reflect.defineProperty({}, 'foo', {value: 1}) +``` + +### Reflect.getOwnPropertyDescriptor + +Deprecates `Object.getOwnPropertyDescriptor()` + +Examples of **incorrect** code for this rule when used without exceptions: + +```js +/*eslint prefer-reflect: "error"*/ + +Object.getOwnPropertyDescriptor({}, 'foo') +``` + +Examples of **correct** code for this rule when used without exceptions: + +```js +/*eslint prefer-reflect: "error"*/ + +Reflect.getOwnPropertyDescriptor({}, 'foo') +``` + +Examples of **correct** code for this rule with the `{ "exceptions": ["getOwnPropertyDescriptor"] }` option: + +```js +/*eslint prefer-reflect: ["error", { "exceptions": ["getOwnPropertyDescriptor"] }]*/ + +Object.getOwnPropertyDescriptor({}, 'foo') +Reflect.getOwnPropertyDescriptor({}, 'foo') +``` + +### Reflect.getPrototypeOf + +Deprecates `Object.getPrototypeOf()` + +Examples of **incorrect** code for this rule when used without exceptions: + +```js +/*eslint prefer-reflect: "error"*/ + +Object.getPrototypeOf({}, 'foo') +``` + +Examples of **correct** code for this rule when used without exceptions: + +```js +/*eslint prefer-reflect: "error"*/ + +Reflect.getPrototypeOf({}, 'foo') +``` + +Examples of **correct** code for this rule with the `{ "exceptions": ["getPrototypeOf"] }` option: + +```js +/*eslint prefer-reflect: ["error", { "exceptions": ["getPrototypeOf"] }]*/ + +Object.getPrototypeOf({}, 'foo') +Reflect.getPrototypeOf({}, 'foo') +``` + +### Reflect.setPrototypeOf + +Deprecates `Object.setPrototypeOf()` + +Examples of **incorrect** code for this rule when used without exceptions: + +```js +/*eslint prefer-reflect: "error"*/ + +Object.setPrototypeOf({}, Object.prototype) +``` + +Examples of **correct** code for this rule when used without exceptions: + +```js +/*eslint prefer-reflect: "error"*/ + +Reflect.setPrototypeOf({}, Object.prototype) +``` + +Examples of **correct** code for this rule with the `{ "exceptions": ["setPrototypeOf"] }` option: + +```js +/*eslint prefer-reflect: ["error", { "exceptions": ["setPrototypeOf"] }]*/ + +Object.setPrototypeOf({}, Object.prototype) +Reflect.setPrototypeOf({}, Object.prototype) +``` + +### Reflect.isExtensible + +Deprecates `Object.isExtensible` + +Examples of **incorrect** code for this rule when used without exceptions: + +```js +/*eslint prefer-reflect: "error"*/ + +Object.isExtensible({}) +``` + +Examples of **correct** code for this rule when used without exceptions: + +```js +/*eslint prefer-reflect: "error"*/ + +Reflect.isExtensible({}) +``` + +Examples of **correct** code for this rule with the `{ "exceptions": ["isExtensible"] }` option: + +```js +/*eslint prefer-reflect: ["error", { "exceptions": ["isExtensible"] }]*/ + +Object.isExtensible({}) +Reflect.isExtensible({}) +``` + +### Reflect.getOwnPropertyNames + +Deprecates `Object.getOwnPropertyNames()` + +Examples of **incorrect** code for this rule when used without exceptions: + +```js +/*eslint prefer-reflect: "error"*/ + +Object.getOwnPropertyNames({}) +``` + +Examples of **correct** code for this rule when used without exceptions: + +```js +/*eslint prefer-reflect: "error"*/ + +Reflect.getOwnPropertyNames({}) +``` + +Examples of **correct** code for this rule with the `{ "exceptions": ["getOwnPropertyNames"] }` option: + +```js +/*eslint prefer-reflect: ["error", { "exceptions": ["getOwnPropertyNames"] }]*/ + +Object.getOwnPropertyNames({}) +Reflect.getOwnPropertyNames({}) +``` + +### Reflect.preventExtensions + +Deprecates `Object.preventExtensions()` + +Examples of **incorrect** code for this rule when used without exceptions: + +```js +/*eslint prefer-reflect: "error"*/ + +Object.preventExtensions({}) +``` + +Examples of **correct** code for this rule when used without exceptions: + +```js +/*eslint prefer-reflect: "error"*/ + +Reflect.preventExtensions({}) +``` + +Examples of **correct** code for this rule with the `{ "exceptions": ["preventExtensions"] }` option: + +```js +/*eslint prefer-reflect: ["error", { "exceptions": ["preventExtensions"] }]*/ + +Object.preventExtensions({}) +Reflect.preventExtensions({}) +``` + +### Reflect.deleteProperty + +Deprecates the `delete` keyword + +Examples of **incorrect** code for this rule when used without exceptions: + +```js +/*eslint prefer-reflect: "error"*/ + +delete foo.bar; // deleting object property +``` + +Examples of **correct** code for this rule when used without exceptions: + +```js +/*eslint prefer-reflect: "error"*/ + +delete bar; // deleting variable +Reflect.deleteProperty(foo, 'bar'); +``` + +Note: For a rule preventing deletion of variables, see [no-delete-var instead](no-delete-var.md) + +Examples of **correct** code for this rule with the `{ "exceptions": ["delete"] }` option: + +```js +/*eslint prefer-reflect: ["error", { "exceptions": ["delete"] }]*/ + +delete bar +delete foo.bar +Reflect.deleteProperty(foo, 'bar'); +``` + +## When Not To Use It + +This rule should not be used in ES3/5 environments. + +In ES2015 (ES6) or later, if you don't want to be notified about places where Reflect could be used, you can safely disable this rule. + +## Related Rules + +* [no-useless-call](no-useless-call.md) +* [prefer-spread](prefer-spread.md) +* [no-delete-var](no-delete-var.md) diff --git a/eslint/docs/rules/prefer-regex-literals.md b/eslint/docs/rules/prefer-regex-literals.md new file mode 100644 index 0000000..fea589d --- /dev/null +++ b/eslint/docs/rules/prefer-regex-literals.md @@ -0,0 +1,94 @@ +# Disallow use of the `RegExp` constructor in favor of regular expression literals (prefer-regex-literals) + +There are two ways to create a regular expression: + +* Regular expression literals, e.g., `/abc/u`. +* The `RegExp` constructor function, e.g., `new RegExp("abc", "u")` or `RegExp("abc", "u")`. + +The constructor function is particularly useful when you want to dynamically generate the pattern, +because it takes string arguments. + +When using the constructor function with string literals, don't forget that the string escaping rules still apply. +If you want to put a backslash in the pattern, you need to escape it in the string literal. +Thus, the following are equivalent: + +```js +new RegExp("^\\d\\.$"); + +/^\d\.$/; + +// matches "0.", "1.", "2." ... "9." +``` + +In the above example, the regular expression literal is easier to read and reason about. +Also, it's a common mistake to omit the extra `\` in the string literal, which would produce a completely different regular expression: + +```js +new RegExp("^\d\.$"); + +// equivalent to /^d.$/, matches "d1", "d2", "da", "db" ... +``` + +When a regular expression is known in advance, it is considered a best practice to avoid the string literal notation on top +of the regular expression notation, and use regular expression literals instead of the constructor function. + +## Rule Details + +This rule disallows the use of the `RegExp` constructor function with string literals as its arguments. + +This rule also disallows the use of the `RegExp` constructor function with template literals without expressions +and `String.raw` tagged template literals without expressions. + +The rule does not disallow all use of the `RegExp` constructor. It should be still used for +dynamically generated regular expressions. + +Examples of **incorrect** code for this rule: + +```js +/*eslint prefer-regex-literals: "error"*/ + +new RegExp("abc"); + +new RegExp("abc", "u"); + +RegExp("abc"); + +RegExp("abc", "u"); + +new RegExp("\\d\\d\\.\\d\\d\\.\\d\\d\\d\\d"); + +RegExp(`^\\d\\.$`); + +new RegExp(String.raw`^\d\.$`); +``` + +Examples of **correct** code for this rule: + +```js +/*eslint prefer-regex-literals: "error"*/ + +/abc/; + +/abc/u; + +/\d\d\.\d\d\.\d\d\d\d/; + +/^\d\.$/; + +// RegExp constructor is allowed for dynamically generated regular expressions + +new RegExp(pattern); + +RegExp("abc", flags); + +new RegExp(prefix + "abc"); + +RegExp(`${prefix}abc`); + +new RegExp(String.raw`^\d\. ${suffix}`); +``` + +## Further Reading + +* [MDN: Regular Expressions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions) +* [MDN: RegExp Constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp) diff --git a/eslint/docs/rules/prefer-rest-params.md b/eslint/docs/rules/prefer-rest-params.md new file mode 100644 index 0000000..6930b4f --- /dev/null +++ b/eslint/docs/rules/prefer-rest-params.md @@ -0,0 +1,61 @@ +# Suggest using the rest parameters instead of `arguments` (prefer-rest-params) + +There are rest parameters in ES2015. +We can use that feature for variadic functions instead of the `arguments` variable. + +`arguments` does not have methods of `Array.prototype`, so it's a bit of an inconvenience. + +## Rule Details + +This rule is aimed to flag usage of `arguments` variables. + +## Examples + +Examples of **incorrect** code for this rule: + +```js +function foo() { + console.log(arguments); +} + +function foo(action) { + var args = Array.prototype.slice.call(arguments, 1); + action.apply(null, args); +} + +function foo(action) { + var args = [].slice.call(arguments, 1); + action.apply(null, args); +} +``` + +Examples of **correct** code for this rule: + +```js +function foo(...args) { + console.log(args); +} + +function foo(action, ...args) { + action.apply(null, args); // or `action(...args)`, related to the `prefer-spread` rule. +} + +// Note: the implicit arguments can be overwritten. +function foo(arguments) { + console.log(arguments); // This is the first argument. +} +function foo() { + var arguments = 0; + console.log(arguments); // This is a local variable. +} +``` + +## When Not To Use It + +This rule should not be used in ES3/5 environments. + +In ES2015 (ES6) or later, if you don't want to be notified about `arguments` variables, then it's safe to disable this rule. + +## Related Rules + +* [prefer-spread](prefer-spread.md) diff --git a/eslint/docs/rules/prefer-spread.md b/eslint/docs/rules/prefer-spread.md new file mode 100644 index 0000000..0011560 --- /dev/null +++ b/eslint/docs/rules/prefer-spread.md @@ -0,0 +1,78 @@ +# Suggest using spread syntax instead of `.apply()`. (prefer-spread) + +Before ES2015, one must use `Function.prototype.apply()` to call variadic functions. + +```js +var args = [1, 2, 3, 4]; +Math.max.apply(Math, args); +``` + +In ES2015, one can use spread syntax to call variadic functions. + +```js +/*eslint-env es6*/ + +var args = [1, 2, 3, 4]; +Math.max(...args); +``` + +## Rule Details + +This rule is aimed to flag usage of `Function.prototype.apply()` in situations where spread syntax could be used instead. + +## Examples + +Examples of **incorrect** code for this rule: + +```js +/*eslint prefer-spread: "error"*/ + +foo.apply(undefined, args); +foo.apply(null, args); +obj.foo.apply(obj, args); +``` + +Examples of **correct** code for this rule: + +```js +/*eslint prefer-spread: "error"*/ + +// Using spread syntax +foo(...args); +obj.foo(...args); + +// The `this` binding is different. +foo.apply(obj, args); +obj.foo.apply(null, args); +obj.foo.apply(otherObj, args); + +// The argument list is not variadic. +// Those are warned by the `no-useless-call` rule. +foo.apply(undefined, [1, 2, 3]); +foo.apply(null, [1, 2, 3]); +obj.foo.apply(obj, [1, 2, 3]); +``` + +Known limitations: + +This rule analyzes code statically to check whether or not the `this` argument is changed. So, if the `this` argument is computed in a dynamic expression, this rule cannot detect a violation. + +```js +/*eslint prefer-spread: "error"*/ + +// This warns. +a[i++].foo.apply(a[i++], args); + +// This does not warn. +a[++i].foo.apply(a[i], args); +``` + +## When Not To Use It + +This rule should not be used in ES3/5 environments. + +In ES2015 (ES6) or later, if you don't want to be notified about `Function.prototype.apply()` callings, you can safely disable this rule. + +## Related Rules + +* [no-useless-call](no-useless-call.md) diff --git a/eslint/docs/rules/prefer-template.md b/eslint/docs/rules/prefer-template.md new file mode 100644 index 0000000..234bd02 --- /dev/null +++ b/eslint/docs/rules/prefer-template.md @@ -0,0 +1,53 @@ +# Suggest using template literals instead of string concatenation. (prefer-template) + +In ES2015 (ES6), we can use template literals instead of string concatenation. + +```js +var str = "Hello, " + name + "!"; +``` + +```js +/*eslint-env es6*/ + +var str = `Hello, ${name}!`; +``` + +## Rule Details + +This rule is aimed to flag usage of `+` operators with strings. + +## Examples + +Examples of **incorrect** code for this rule: + +```js +/*eslint prefer-template: "error"*/ + +var str = "Hello, " + name + "!"; +var str = "Time: " + (12 * 60 * 60 * 1000); +``` + +Examples of **correct** code for this rule: + +```js +/*eslint prefer-template: "error"*/ +/*eslint-env es6*/ + +var str = "Hello World!"; +var str = `Hello, ${name}!`; +var str = `Time: ${12 * 60 * 60 * 1000}`; + +// This is reported by `no-useless-concat`. +var str = "Hello, " + "World!"; +``` + +## When Not To Use It + +This rule should not be used in ES3/5 environments. + +In ES2015 (ES6) or later, if you don't want to be notified about string concatenation, you can safely disable this rule. + +## Related Rules + +* [no-useless-concat](no-useless-concat.md) +* [quotes](quotes.md) diff --git a/eslint/docs/rules/quote-props.md b/eslint/docs/rules/quote-props.md new file mode 100644 index 0000000..cccb48d --- /dev/null +++ b/eslint/docs/rules/quote-props.md @@ -0,0 +1,268 @@ +# require quotes around object literal property names (quote-props) + +Object literal property names can be defined in two ways: using literals or using strings. For example, these two objects are equivalent: + +```js +var object1 = { + property: true +}; + +var object2 = { + "property": true +}; +``` + +In many cases, it doesn't matter if you choose to use an identifier instead of a string or vice-versa. Even so, you might decide to enforce a consistent style in your code. + +There are, however, some occasions when you must use quotes: + +1. If you are using an ECMAScript 3 JavaScript engine (such as IE8) and you want to use a keyword (such as `if`) as a property name. This restriction was removed in ECMAScript 5. +2. You want to use a non-identifier character in your property name, such as having a property with a space like `"one two"`. + +Another example where quotes do matter is when using numeric literals as property keys: + +```js +var object = { + 1e2: 1, + 100: 2 +}; +``` + +This may look alright at first sight, but this code in fact throws a syntax error in ECMAScript 5 strict mode. This happens because `1e2` and `100` are coerced into strings before getting used as the property name. Both `String(1e2)` and `String(100)` happen to be equal to `"100"`, which causes the "Duplicate data property in object literal not allowed in strict mode" error. Issues like that can be tricky to debug, so some prefer to require quotes around all property names. + +## Rule Details + +This rule requires quotes around object literal property names. + +## Options + +This rule has two options, a string option and an object option. + +String option: + +* `"always"` (default) requires quotes around all object literal property names +* `"as-needed"` disallows quotes around object literal property names that are not strictly required +* `"consistent"` enforces a consistent quote style; in a given object, either all of the properties should be quoted, or none of the properties should be quoted +* `"consistent-as-needed"` requires quotes around all object literal property names if any name strictly requires quotes, otherwise disallows quotes around object property names + +Object option: + +* `"keywords": true` requires quotes around language keywords used as object property names (only applies when using `as-needed` or `consistent-as-needed`) +* `"unnecessary": true` (default) disallows quotes around object literal property names that are not strictly required (only applies when using `as-needed`) +* `"unnecessary": false` allows quotes around object literal property names that are not strictly required (only applies when using `as-needed`) +* `"numbers": true` requires quotes around numbers used as object property names (only applies when using `as-needed`) + +### always + +Examples of **incorrect** code for this rule with the default `"always"` option: + +```js +/*eslint quote-props: ["error", "always"]*/ + +var object = { + foo: "bar", + baz: 42 +}; +``` + +Examples of **correct** code for this rule with the default `"always"` option: + +```js +/*eslint quote-props: ["error", "always"]*/ +/*eslint-env es6*/ + +var object1 = { + "foo": "bar", + "baz": 42, + "qux-lorem": true +}; + +var object2 = { + 'foo': 'bar', + 'baz': 42, + 'qux-lorem': true +}; + +var object3 = { + foo() { + return; + } +}; +``` + +### as-needed + +Examples of **incorrect** code for this rule with the `"as-needed"` option: + +```js +/*eslint quote-props: ["error", "as-needed"]*/ + +var object = { + "a": 0, + "0": 0, + "true": 0, + "null": 0 +}; +``` + +Examples of **correct** code for this rule with the `"as-needed"` option: + +```js +/*eslint quote-props: ["error", "as-needed"]*/ +/*eslint-env es6*/ + +var object1 = { + "a-b": 0, + "0x0": 0, + "1e2": 0 +}; + +var object2 = { + foo: 'bar', + baz: 42, + true: 0, + 0: 0, + 'qux-lorem': true +}; + +var object3 = { + foo() { + return; + } +}; +``` + +### consistent + +Examples of **incorrect** code for this rule with the `"consistent"` option: + +```js +/*eslint quote-props: ["error", "consistent"]*/ + +var object1 = { + foo: "bar", + "baz": 42, + "qux-lorem": true +}; + +var object2 = { + 'foo': 'bar', + baz: 42 +}; +``` + +Examples of **correct** code for this rule with the `"consistent"` option: + +```js +/*eslint quote-props: ["error", "consistent"]*/ + +var object1 = { + "foo": "bar", + "baz": 42, + "qux-lorem": true +}; + +var object2 = { + 'foo': 'bar', + 'baz': 42 +}; + +var object3 = { + foo: 'bar', + baz: 42 +}; +``` + +### consistent-as-needed + +Examples of **incorrect** code for this rule with the `"consistent-as-needed"` option: + +```js +/*eslint quote-props: ["error", "consistent-as-needed"]*/ + +var object1 = { + foo: "bar", + "baz": 42, + "qux-lorem": true +}; + +var object2 = { + 'foo': 'bar', + 'baz': 42 +}; +``` + +Examples of **correct** code for this rule with the `"consistent-as-needed"` option: + +```js +/*eslint quote-props: ["error", "consistent-as-needed"]*/ + +var object1 = { + "foo": "bar", + "baz": 42, + "qux-lorem": true +}; + +var object2 = { + foo: 'bar', + baz: 42 +}; +``` + +### keywords + +Examples of additional **incorrect** code for this rule with the `"as-needed", { "keywords": true }` options: + +```js +/*eslint quote-props: ["error", "as-needed", { "keywords": true }]*/ + +var x = { + while: 1, + volatile: "foo" +}; +``` + +Examples of additional **incorrect** code for this rule with the `"consistent-as-needed", { "keywords": true }` options: + +```js +/*eslint quote-props: ["error", "consistent-as-needed", { "keywords": true }]*/ + +var x = { + "prop": 1, + "bar": "foo" +}; +``` + +### unnecessary + +Examples of additional **correct** code for this rule with the `"as-needed", { "unnecessary": false }` options: + +```js +/*eslint quote-props: ["error", "as-needed", { "keywords": true, "unnecessary": false }]*/ + +var x = { + "while": 1, + "foo": "bar" // Would normally have caused a warning +}; +``` + +### numbers + +Examples of additional **incorrect** code for this rule with the `"as-needed", { "numbers": true }` options: + +```js +/*eslint quote-props: ["error", "as-needed", { "numbers": true }]*/ + +var x = { + 100: 1 +} +``` + +## When Not To Use It + +If you don't care if property names are consistently wrapped in quotes or not, and you don't target legacy ES3 environments, turn this rule off. + +## Further Reading + +* [Reserved words as property names](https://kangax.github.io/compat-table/es5/#Reserved_words_as_property_names) +* [Unquoted property names / object keys in JavaScript](https://mathiasbynens.be/notes/javascript-properties) diff --git a/eslint/docs/rules/quotes.md b/eslint/docs/rules/quotes.md new file mode 100644 index 0000000..25a09fd --- /dev/null +++ b/eslint/docs/rules/quotes.md @@ -0,0 +1,154 @@ +# enforce the consistent use of either backticks, double, or single quotes (quotes) + +JavaScript allows you to define strings in one of three ways: double quotes, single quotes, and backticks (as of ECMAScript 6). For example: + +```js +/*eslint-env es6*/ + +var double = "double"; +var single = 'single'; +var backtick = `backtick`; // ES6 only +``` + +Each of these lines creates a string and, in some cases, can be used interchangeably. The choice of how to define strings in a codebase is a stylistic one outside of template literals (which allow embedded of expressions to be interpreted). + +Many codebases require strings to be defined in a consistent manner. + +## Rule Details + +This rule enforces the consistent use of either backticks, double, or single quotes. + +## Options + +This rule has two options, a string option and an object option. + +String option: + +* `"double"` (default) requires the use of double quotes wherever possible +* `"single"` requires the use of single quotes wherever possible +* `"backtick"` requires the use of backticks wherever possible + +Object option: + +* `"avoidEscape": true` allows strings to use single-quotes or double-quotes so long as the string contains a quote that would have to be escaped otherwise +* `"allowTemplateLiterals": true` allows strings to use backticks + +**Deprecated**: The object property `avoid-escape` is deprecated; please use the object property `avoidEscape` instead. + +### double + +Examples of **incorrect** code for this rule with the default `"double"` option: + +```js +/*eslint quotes: ["error", "double"]*/ + +var single = 'single'; +var unescaped = 'a string containing "double" quotes'; +var backtick = `back\ntick`; // you can use \n in single or double quoted strings +``` + +Examples of **correct** code for this rule with the default `"double"` option: + +```js +/*eslint quotes: ["error", "double"]*/ +/*eslint-env es6*/ + +var double = "double"; +var backtick = `back +tick`; // backticks are allowed due to newline +var backtick = tag`backtick`; // backticks are allowed due to tag +``` + +### single + +Examples of **incorrect** code for this rule with the `"single"` option: + +```js +/*eslint quotes: ["error", "single"]*/ + +var double = "double"; +var unescaped = "a string containing 'single' quotes"; +``` + +Examples of **correct** code for this rule with the `"single"` option: + +```js +/*eslint quotes: ["error", "single"]*/ +/*eslint-env es6*/ + +var single = 'single'; +var backtick = `back${x}tick`; // backticks are allowed due to substitution +``` + +### backticks + +Examples of **incorrect** code for this rule with the `"backtick"` option: + +```js +/*eslint quotes: ["error", "backtick"]*/ + +var single = 'single'; +var double = "double"; +var unescaped = 'a string containing `backticks`'; +``` + +Examples of **correct** code for this rule with the `"backtick"` option: + +```js +/*eslint quotes: ["error", "backtick"]*/ +/*eslint-env es6*/ + +var backtick = `backtick`; +``` + +### avoidEscape + +Examples of additional **correct** code for this rule with the `"double", { "avoidEscape": true }` options: + +```js +/*eslint quotes: ["error", "double", { "avoidEscape": true }]*/ + +var single = 'a string containing "double" quotes'; +``` + +Examples of additional **correct** code for this rule with the `"single", { "avoidEscape": true }` options: + +```js +/*eslint quotes: ["error", "single", { "avoidEscape": true }]*/ + +var double = "a string containing 'single' quotes"; +``` + +Examples of additional **correct** code for this rule with the `"backtick", { "avoidEscape": true }` options: + +```js +/*eslint quotes: ["error", "backtick", { "avoidEscape": true }]*/ + +var double = "a string containing `backtick` quotes" +``` + +### allowTemplateLiterals + +Examples of additional **correct** code for this rule with the `"double", { "allowTemplateLiterals": true }` options: + +```js +/*eslint quotes: ["error", "double", { "allowTemplateLiterals": true }]*/ + +var double = "double"; +var double = `double`; +``` + +Examples of additional **correct** code for this rule with the `"single", { "allowTemplateLiterals": true }` options: + +```js +/*eslint quotes: ["error", "single", { "allowTemplateLiterals": true }]*/ + +var single = 'single'; +var single = `single`; +``` + +`{ "allowTemplateLiterals": false }` will not disallow the usage of all template literals. If you want to forbid any instance of template literals, use [no-restricted-syntax](https://eslint.org/docs/rules/no-restricted-syntax) and target the `TemplateLiteral` selector. + +## When Not To Use It + +If you do not need consistency in your string styles, you can safely disable this rule. diff --git a/eslint/docs/rules/radix.md b/eslint/docs/rules/radix.md new file mode 100644 index 0000000..da3196c --- /dev/null +++ b/eslint/docs/rules/radix.md @@ -0,0 +1,95 @@ +# Require Radix Parameter (radix) + +When using the `parseInt()` function it is common to omit the second argument, the radix, and let the function try to determine from the first argument what type of number it is. By default, `parseInt()` will autodetect decimal and hexadecimal (via `0x` prefix). Prior to ECMAScript 5, `parseInt()` also autodetected octal literals, which caused problems because many developers assumed a leading `0` would be ignored. + +This confusion led to the suggestion that you always use the radix parameter to `parseInt()` to eliminate unintended consequences. So instead of doing this: + +```js +var num = parseInt("071"); // 57 +``` + +Do this: + +```js +var num = parseInt("071", 10); // 71 +``` + +ECMAScript 5 changed the behavior of `parseInt()` so that it no longer autodetects octal literals and instead treats them as decimal literals. However, the differences between hexadecimal and decimal interpretation of the first parameter causes many developers to continue using the radix parameter to ensure the string is interpreted in the intended way. + +On the other hand, if the code is targeting only ES5-compliant environments passing the radix `10` may be redundant. In such a case you might want to disallow using such a radix. + +## Rule Details + +This rule is aimed at preventing the unintended conversion of a string to a number of a different base than intended or at preventing the redundant `10` radix if targeting modern environments only. + +## Options + +There are two options for this rule: + +* `"always"` enforces providing a radix (default) +* `"as-needed"` disallows providing the `10` radix + + +### always + +Examples of **incorrect** code for the default `"always"` option: + +```js +/*eslint radix: "error"*/ + +var num = parseInt("071"); + +var num = parseInt(someValue); + +var num = parseInt("071", "abc"); + +var num = parseInt("071", 37); + +var num = parseInt(); +``` + +Examples of **correct** code for the default `"always"` option: + +```js +/*eslint radix: "error"*/ + +var num = parseInt("071", 10); + +var num = parseInt("071", 8); + +var num = parseFloat(someValue); +``` + +### as-needed + +Examples of **incorrect** code for the `"as-needed"` option: + +```js +/*eslint radix: ["error", "as-needed"]*/ + +var num = parseInt("071", 10); + +var num = parseInt("071", "abc"); + +var num = parseInt(); +``` + +Examples of **correct** code for the `"as-needed"` option: + +```js +/*eslint radix: ["error", "as-needed"]*/ + +var num = parseInt("071"); + +var num = parseInt("071", 8); + +var num = parseFloat(someValue); +``` + +## When Not To Use It + +If you don't want to enforce either presence or omission of the `10` radix value you can turn this rule off. + +## Further Reading + +* [parseInt and radix](https://davidwalsh.name/parseint-radix) diff --git a/eslint/docs/rules/require-atomic-updates.md b/eslint/docs/rules/require-atomic-updates.md new file mode 100644 index 0000000..593ccdd --- /dev/null +++ b/eslint/docs/rules/require-atomic-updates.md @@ -0,0 +1,97 @@ +# Disallow assignments that can lead to race conditions due to usage of `await` or `yield` (require-atomic-updates) + +When writing asynchronous code, it is possible to create subtle race condition bugs. Consider the following example: + +```js +let totalLength = 0; + +async function addLengthOfSinglePage(pageNum) { + totalLength += await getPageLength(pageNum); +} + +Promise.all([addLengthOfSinglePage(1), addLengthOfSinglePage(2)]).then(() => { + console.log('The combined length of both pages is', totalLength); +}); +``` + +This code looks like it will sum the results of calling `getPageLength(1)` and `getPageLength(2)`, but in reality the final value of `totalLength` will only be the length of one of the two pages. The bug is in the statement `totalLength += await getPageLength(pageNum);`. This statement first reads an initial value of `totalLength`, then calls `getPageLength(pageNum)` and waits for that Promise to fulfill. Finally, it sets the value of `totalLength` to the sum of `await getPageLength(pageNum)` and the *initial* value of `totalLength`. If the `totalLength` variable is updated in a separate function call during the time that the `getPageLength(pageNum)` Promise is pending, that update will be lost because the new value is overwritten without being read. + +One way to fix this issue would be to ensure that `totalLength` is read at the same time as it's updated, like this: + +```js +async function addLengthOfSinglePage(pageNum) { + const lengthOfThisPage = await getPageLength(pageNum); + + totalLength += lengthOfThisPage; +} +``` + +Another solution would be to avoid using a mutable variable reference at all: + +```js +Promise.all([getPageLength(1), getPageLength(2)]).then(pageLengths => { + const totalLength = pageLengths.reduce((accumulator, length) => accumulator + length, 0); + + console.log('The combined length of both pages is', totalLength); +}); +``` + +## Rule Details + +This rule aims to report assignments to variables or properties where all of the following are true: + +* A variable or property is reassigned to a new value which is based on its old value. +* A `yield` or `await` expression interrupts the assignment after the old value is read, and before the new value is set. +* The rule cannot easily verify that the assignment is safe (e.g. if an assigned variable is local and would not be readable from anywhere else while the function is paused). + +Examples of **incorrect** code for this rule: + +```js +/* eslint require-atomic-updates: error */ + +let result; +async function foo() { + result += await somethingElse; + + result = result + await somethingElse; + + result = result + doSomething(await somethingElse); +} + +function* bar() { + result += yield; + + result = result + (yield somethingElse); + + result = result + doSomething(yield somethingElse); +} +``` + +Examples of **correct** code for this rule: + +```js +/* eslint require-atomic-updates: error */ + +let result; +async function foo() { + result = await somethingElse + result; + + let tmp = await somethingElse; + result += tmp; + + let localVariable = 0; + localVariable += await somethingElse; +} + +function* bar() { + result += yield; + + result = (yield somethingElse) + result; + + result = doSomething(yield somethingElse, result); +} +``` + +## When Not To Use It + +If you don't use async or generator functions, you don't need to enable this rule. diff --git a/eslint/docs/rules/require-await.md b/eslint/docs/rules/require-await.md new file mode 100644 index 0000000..595d0ac --- /dev/null +++ b/eslint/docs/rules/require-await.md @@ -0,0 +1,87 @@ +# Disallow async functions which have no `await` expression (require-await) + +Asynchronous functions in JavaScript behave differently than other functions in two important ways: + +1. The return value is always a `Promise`. +2. You can use the `await` operator inside of them. + +The primary reason to use asynchronous functions is typically to use the `await` operator, such as this: + +```js +async function fetchData(processDataItem) { + const response = await fetch(DATA_URL); + const data = await response.json(); + + return data.map(processDataItem); +} +``` + +Asynchronous functions that don't use `await` might not need to be asynchronous functions and could be the unintentional result of refactoring. + +Note: this rule ignores async generator functions. This is because generators yield rather than return a value and async generators might yield all the values of another async generator without ever actually needing to use await. + +## Rule Details + +This rule warns async functions which have no `await` expression. + +Examples of **incorrect** code for this rule: + +```js +/*eslint require-await: "error"*/ + +async function foo() { + doSomething(); +} + +bar(async () => { + doSomething(); +}); +``` + +Examples of **correct** code for this rule: + +```js +/*eslint require-await: "error"*/ + +async function foo() { + await doSomething(); +} + +bar(async () => { + await doSomething(); +}); + +function foo() { + doSomething(); +} + +bar(() => { + doSomething(); +}); + +// Allow empty functions. +async function noop() {} +``` + +## When Not To Use It + +Asynchronous functions are designed to work with promises such that throwing an error will cause a promise's rejection handler (such as `catch()`) to be called. For example: + +```js +async function fail() { + throw new Error("Failure!"); +} + +fail().catch(error => { + console.log(error.message); +}); +``` + +In this case, the `fail()` function throws an error that is intended to be caught by the `catch()` handler assigned later. Converting the `fail()` function into a synchronous function would require the call to `fail()` to be refactored to use a `try-catch` statement instead of a promise. + +If you are throwing an error inside of an asynchronous function for this purpose, then you may want to disable this rule. + + +## Related Rules + +* [require-yield](require-yield.md) diff --git a/eslint/docs/rules/require-jsdoc.md b/eslint/docs/rules/require-jsdoc.md new file mode 100644 index 0000000..1e31aed --- /dev/null +++ b/eslint/docs/rules/require-jsdoc.md @@ -0,0 +1,189 @@ +# require JSDoc comments (require-jsdoc) + +This rule was [**deprecated**](https://eslint.org/blog/2018/11/jsdoc-end-of-life) in ESLint v5.10.0. + +[JSDoc](http://usejsdoc.org) is a JavaScript API documentation generator. It uses specially-formatted comments inside of code to generate API documentation automatically. For example, this is what a JSDoc comment looks like for a function: + +```js +/** + * Adds two numbers together. + * @param {int} num1 The first number. + * @param {int} num2 The second number. + * @returns {int} The sum of the two numbers. + */ +function sum(num1, num2) { + return num1 + num2; +} +``` + +Some style guides require JSDoc comments for all functions as a way of explaining function behavior. + +## Rule Details + +This rule requires JSDoc comments for specified nodes. Supported nodes: + +* `"FunctionDeclaration"` +* `"ClassDeclaration"` +* `"MethodDefinition"` +* `"ArrowFunctionExpression"` +* `"FunctionExpression"` + +## Options + +This rule has a single object option: + +* `"require"` requires JSDoc comments for the specified nodes + +Default option settings are: + +```json +{ + "require-jsdoc": ["error", { + "require": { + "FunctionDeclaration": true, + "MethodDefinition": false, + "ClassDeclaration": false, + "ArrowFunctionExpression": false, + "FunctionExpression": false + } + }] +} +``` + +### require + +Examples of **incorrect** code for this rule with the `{ "require": { "FunctionDeclaration": true, "MethodDefinition": true, "ClassDeclaration": true, "ArrowFunctionExpression": true, "FunctionExpression": true } }` option: + +```js +/*eslint "require-jsdoc": ["error", { + "require": { + "FunctionDeclaration": true, + "MethodDefinition": true, + "ClassDeclaration": true, + "ArrowFunctionExpression": true, + "FunctionExpression": true + } +}]*/ + +function foo() { + return 10; +} + +var foo = () => { + return 10; +}; + +class Foo { + bar() { + return 10; + } +} + +var foo = function() { + return 10; +}; + +var foo = { + bar: function() { + return 10; + }, + + baz() { + return 10; + } +}; +``` + +Examples of **correct** code for this rule with the `{ "require": { "FunctionDeclaration": true, "MethodDefinition": true, "ClassDeclaration": true, "ArrowFunctionExpression": true, "FunctionExpression": true } }` option: + +```js +/*eslint "require-jsdoc": ["error", { + "require": { + "FunctionDeclaration": true, + "MethodDefinition": true, + "ClassDeclaration": true, + "ArrowFunctionExpression": true, + "FunctionExpression": true + } +}]*/ + +/** + * It returns 10 + */ +function foo() { + return 10; +} + +/** + * It returns test + 10 + * @params {int} test - some number + * @returns {int} sum of test and 10 + */ +var foo = (test) => { + return test + 10; +} + +/** + * It returns 10 + */ +var foo = () => { + return 10; +} + +/** + * It returns 10 + */ +var foo = function() { + return 10; +} + +var array = [1,2,3]; +array.filter(function(item) { + return item > 2; +}); + +/** + * A class that can return the number 10 + */ +class Foo { + /** + * It returns 10 + */ + bar() { + return 10; + } +} + +/** + * It returns 10 + */ +var foo = function() { + return 10; +}; + +var foo = { + /** + * It returns 10 + */ + bar: function() { + return 10; + }, + + /** + * It returns 10 + */ + baz() { + return 10; + } +}; + +setTimeout(() => {}, 10); // since it's an anonymous arrow function +``` + +## When Not To Use It + +If you do not require JSDoc for your functions, then you can leave this rule off. + +## Related Rules + +* [valid-jsdoc](valid-jsdoc.md) diff --git a/eslint/docs/rules/require-unicode-regexp.md b/eslint/docs/rules/require-unicode-regexp.md new file mode 100644 index 0000000..3077d4d --- /dev/null +++ b/eslint/docs/rules/require-unicode-regexp.md @@ -0,0 +1,55 @@ +# Enforce the use of `u` flag on RegExp (require-unicode-regexp) + +RegExp `u` flag has two effects: + +1. **Make the regular expression handling UTF-16 surrogate pairs correctly.** + + Especially, character range syntax gets the correct behavior. + + ```js + /^[👍]$/.test("👍") //→ false + /^[👍]$/u.test("👍") //→ true + ``` + +2. **Make the regular expression throwing syntax errors early as disabling [Annex B extensions](https://www.ecma-international.org/ecma-262/6.0/#sec-regular-expressions-patterns).** + + Because of historical reason, JavaScript regular expressions are tolerant of syntax errors. For example, `/\w{1, 2/` is a syntax error, but JavaScript doesn't throw the error. It matches strings such as `"a{1, 2"` instead. Such a recovering logic is defined in Annex B. + + The `u` flag disables the recovering logic Annex B defined. As a result, you can find errors early. This is similar to [the strict mode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode). + +Therefore, the `u` flag lets us work better with regular expressions. + +## Rule Details + +This rule aims to enforce the use of `u` flag on regular expressions. + +Examples of **incorrect** code for this rule: + +```js +/*eslint require-unicode-regexp: error */ + +const a = /aaa/ +const b = /bbb/gi +const c = new RegExp("ccc") +const d = new RegExp("ddd", "gi") +``` + +Examples of **correct** code for this rule: + +```js +/*eslint require-unicode-regexp: error */ + +const a = /aaa/u +const b = /bbb/giu +const c = new RegExp("ccc", "u") +const d = new RegExp("ddd", "giu") + +// This rule ignores RegExp calls if the flags could not be evaluated to a static value. +function f(flags) { + return new RegExp("eee", flags) +} +``` + +## When Not To Use It + +If you don't want to notify regular expressions with no `u` flag, then it's safe to disable this rule. diff --git a/eslint/docs/rules/require-yield.md b/eslint/docs/rules/require-yield.md new file mode 100644 index 0000000..22c262b --- /dev/null +++ b/eslint/docs/rules/require-yield.md @@ -0,0 +1,45 @@ +# Disallow generator functions that do not have `yield` (require-yield) + +## Rule Details + +This rule generates warnings for generator functions that do not have the `yield` keyword. + +## Examples + +Examples of **incorrect** code for this rule: + +```js +/*eslint require-yield: "error"*/ +/*eslint-env es6*/ + +function* foo() { + return 10; +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint require-yield: "error"*/ +/*eslint-env es6*/ + +function* foo() { + yield 5; + return 10; +} + +function foo() { + return 10; +} + +// This rule does not warn on empty generator functions. +function* foo() { } +``` + +## When Not To Use It + +If you don't want to notify generator functions that have no `yield` expression, then it's safe to disable this rule. + +## Related Rules + +* [require-await](require-await.md) diff --git a/eslint/docs/rules/rest-spread-spacing.md b/eslint/docs/rules/rest-spread-spacing.md new file mode 100644 index 0000000..d8d6063 --- /dev/null +++ b/eslint/docs/rules/rest-spread-spacing.md @@ -0,0 +1,140 @@ +# Enforce spacing between rest and spread operators and their expressions (rest-spread-spacing) + +ES2015 introduced the rest and spread operators, which expand an iterable structure into its individual parts. Some examples of their usage are as follows: + +```js +let numArr = [1, 2, 3]; +function add(a, b, c) { + return a + b + c; +} +add(...numArr); // -> 6 + +let arr1 = [1, 2, 3]; +let arr2 = [4, 5, 6]; +arr1.push(...arr2); // -> [1, 2, 3, 4, 5, 6] + +let [a, b, ...arr] = [1, 2, 3, 4, 5]; +a; // -> 1 +b // -> 2 +arr; // -> [3, 4, 5] + +function numArgs(...args) { + return args.length; +} +numArgs(a, b, c); // -> 3 +``` + +In addition to the above, there is currently a proposal to add object rest and spread properties to the spec. They can be used as follows: + +```js + +let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 }; +x; // -> 1 +y; // -> 2 +z; // -> { a: 3, b: 4 } + +let n = { x, y, ...z }; +n; // -> { x: 1, y: 2, a: 3, b: 4 } +``` + +As with other operators, whitespace is allowed between the rest or spread operator and the expression it is operating on, which can lead to inconsistent spacing within a codebase. + +## Rule Details + +This rule aims to enforce consistent spacing between rest and spread operators and their expressions. The rule also supports object rest and spread properties in ES2018: + +```json +{ + "parserOptions": { + "ecmaVersion": 2018 + } +} +``` + +Please read the user guide's section on [configuring parser options](/docs/user-guide/configuring#specifying-parser-options) to learn more. + +## Options + +This rule takes one option: a string with the value of `"never"` or `"always"`. The default value is `"never"`. + +### "never" + +When using the default `"never"` option, whitespace is not allowed between spread operators and their expressions. + +```json +rest-spread-spacing: ["error"] +``` + +or + +```json +rest-spread-spacing: ["error", "never"] +``` + +Examples of **incorrect** code for this rule with `"never"`: + +```js +/*eslint rest-spread-spacing: ["error", "never"]*/ + +fn(... args) +[... arr, 4, 5, 6] +let [a, b, ... arr] = [1, 2, 3, 4, 5]; +function fn(... args) { console.log(args); } +let { x, y, ... z } = { x: 1, y: 2, a: 3, b: 4 }; +let n = { x, y, ... z }; +``` + +Examples of **correct** code for this rule with `"never"`: + +```js +/*eslint rest-spread-spacing: ["error", "never"]*/ + +fn(...args) +[...arr, 4, 5, 6] +let [a, b, ...arr] = [1, 2, 3, 4, 5]; +function fn(...args) { console.log(args); } +let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 }; +let n = { x, y, ...z }; +``` + +### "always" + +When using the `"always"` option, whitespace is required between spread operators and their expressions. + +```json +rest-spread-spacing: ["error", "always"] +``` + +Examples of **incorrect** code for this rule with `"always"`: + +```js +/*eslint rest-spread-spacing:["error", "always"]*/ + +fn(...args) +[...arr, 4, 5, 6] +let [a, b, ...arr] = [1, 2, 3, 4, 5]; +function fn(...args) { console.log(args); } +let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 }; +let n = { x, y, ...z }; +``` + +Examples of **correct** code for this rule with `"always"`: + +```js +/*eslint rest-spread-spacing: ["error", "always"]*/ + +fn(... args) +[... arr, 4, 5, 6] +let [a, b, ... arr] = [1, 2, 3, 4, 5]; +function fn(... args) { console.log(args); } +let { x, y, ... z } = { x: 1, y: 2, a: 3, b: 4 }; +let n = { x, y, ... z }; +``` + +## When Not To Use It + +You can safely disable this rule if you do not care about enforcing consistent spacing between spread operators and their expressions. + +## Further Reading + +* [Object Rest/Spread Properties for ECMAScript](https://github.com/tc39/proposal-object-rest-spread) diff --git a/eslint/docs/rules/semi-spacing.md b/eslint/docs/rules/semi-spacing.md new file mode 100644 index 0000000..4db5e18 --- /dev/null +++ b/eslint/docs/rules/semi-spacing.md @@ -0,0 +1,109 @@ +# Enforce spacing before and after semicolons (semi-spacing) + +JavaScript allows you to place unnecessary spaces before or after a semicolon. + +Disallowing or enforcing space around a semicolon can improve the readability of your program. + +```js +var a = "b" ; + +var c = "d";var e = "f"; +``` + +## Rule Details + +This rule aims to enforce spacing around a semicolon. This rule prevents the use of spaces before a semicolon in expressions. + +This rule doesn't check spacing in the following cases: + +* The spacing after the semicolon if it is the first token in the line. + +* The spacing before the semicolon if it is after an opening parenthesis (`(` or `{`), or the spacing after the semicolon if it is before a closing parenthesis (`)` or `}`). That spacing is checked by `space-in-parens` or `block-spacing`. + +* The spacing around the semicolon in a for loop with an empty condition (`for(;;)`). + +## Options + +The rule takes one option, an object, which has two keys `before` and `after` having boolean values `true` or `false`. +If `before` is `true`, space is enforced before semicolons and if it's `false`, space is disallowed before semicolons. +If `after` is `true`, space is enforced after semicolons and if it's `false`, space is disallowed after semicolons. +The `after` option will be only applied if a semicolon is not at the end of line. + +The default is `{"before": false, "after": true}`. + +```json + "semi-spacing": ["error", {"before": false, "after": true}] +``` + +### `{"before": false, "after": true}` + +This is the default option. It enforces spacing after semicolons and disallows spacing before semicolons. + +Examples of **incorrect** code for this rule: + +```js +/*eslint semi-spacing: "error"*/ + +var foo ; +var foo;var bar; +throw new Error("error") ; +while (a) { break ; } +for (i = 0 ; i < 10 ; i++) {} +for (i = 0;i < 10;i++) {} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint semi-spacing: "error"*/ + +var foo; +var foo; var bar; +throw new Error("error"); +while (a) { break; } +for (i = 0; i < 10; i++) {} +for (;;) {} +if (true) {;} +;foo(); +``` + +### `{"before": true, "after": false}` + +This option enforces spacing before semicolons and disallows spacing after semicolons. + +Examples of **incorrect** code for this rule with the `{"before": true, "after": false}` option: + +```js +/*eslint semi-spacing: ["error", { "before": true, "after": false }]*/ + +var foo; +var foo ; var bar; +throw new Error("error"); +while (a) { break; } +for (i = 0;i < 10;i++) {} +for (i = 0; i < 10; i++) {} +``` + +Examples of **correct** code for this rule with the `{"before": true, "after": false}` option: + +```js +/*eslint semi-spacing: ["error", { "before": true, "after": false }]*/ + +var foo ; +var foo ;var bar ; +throw new Error("error") ; +while (a) {break ;} +for (i = 0 ;i < 10 ;i++) {} +``` + +## When Not To Use It + +You can turn this rule off if you are not concerned with the consistency of spacing before or after semicolons. + +## Related Rules + +* [semi](semi.md) +* [no-extra-semi](no-extra-semi.md) +* [comma-spacing](comma-spacing.md) +* [block-spacing](block-spacing.md) +* [space-in-parens](space-in-parens.md) diff --git a/eslint/docs/rules/semi-style.md b/eslint/docs/rules/semi-style.md new file mode 100644 index 0000000..7bd916f --- /dev/null +++ b/eslint/docs/rules/semi-style.md @@ -0,0 +1,96 @@ +# Enforce location of semicolons (semi-style) + +Generally, semicolons are at the end of lines. However, in semicolon-less style, semicolons are at the beginning of lines. This rule enforces that semicolons are at the configured location. + +## Rule Details + +This rule reports line terminators around semicolons. + +This rule has an option. + +```json +{ + "semi-style": ["error", "last"], +} +``` + +- `"last"` (Default) enforces that semicolons are at the end of statements. +- `"first"` enforces that semicolons are at the beginning of statements. Semicolons of `for` loop heads (`for(a;b;c){}`) should be at the end of lines even if you use this option. + +Examples of **incorrect** code for this rule with `"last"` option: + +```js +/*eslint semi-style: ["error", "last"]*/ + +foo() +;[1, 2, 3].forEach(bar) + +for ( + var i = 0 + ; i < 10 + ; ++i +) { + foo() +} +``` + +Examples of **correct** code for this rule with `"last"` option: + +```js +/*eslint semi-style: ["error", "last"]*/ + +foo(); +[1, 2, 3].forEach(bar) + +for ( + var i = 0; + i < 10; + ++i +) { + foo() +} +``` + +Examples of **incorrect** code for this rule with `"first"` option: + +```js +/*eslint semi-style: ["error", "first"]*/ + +foo(); +[1, 2, 3].forEach(bar) + +for ( + var i = 0 + ; i < 10 + ; ++i +) { + foo() +} +``` + +Examples of **correct** code for this rule with `"first"` option: + +```js +/*eslint semi-style: ["error", "first"]*/ + +foo() +;[1, 2, 3].forEach(bar) + +for ( + var i = 0; + i < 10; + ++i +) { + foo() +} +``` + +## When Not To Use It + +If you don't want to notify the location of semicolons, then it's safe to disable this rule. + +## Related rules + +- [no-extra-semi](./no-extra-semi.md) +- [semi](./semi.md) +- [semi-spacing](./semi-spacing.md) diff --git a/eslint/docs/rules/semi.md b/eslint/docs/rules/semi.md new file mode 100644 index 0000000..049ae41 --- /dev/null +++ b/eslint/docs/rules/semi.md @@ -0,0 +1,196 @@ +# require or disallow semicolons instead of ASI (semi) + +JavaScript doesn't require semicolons at the end of each statement. In many cases, the JavaScript engine can determine that a semicolon should be in a certain spot and will automatically add it. This feature is known as **automatic semicolon insertion (ASI)** and is considered one of the more controversial features of JavaScript. For example, the following lines are both valid: + +```js +var name = "ESLint" +var website = "eslint.org"; +``` + +On the first line, the JavaScript engine will automatically insert a semicolon, so this is not considered a syntax error. The JavaScript engine still knows how to interpret the line and knows that the line end indicates the end of the statement. + +In the debate over ASI, there are generally two schools of thought. The first is that we should treat ASI as if it didn't exist and always include semicolons manually. The rationale is that it's easier to always include semicolons than to try to remember when they are or are not required, and thus decreases the possibility of introducing an error. + +However, the ASI mechanism can sometimes be tricky to people who are using semicolons. For example, consider this code: + +```js +return +{ + name: "ESLint" +}; +``` + +This may look like a `return` statement that returns an object literal, however, the JavaScript engine will interpret this code as: + +```js +return; +{ + name: "ESLint"; +} +``` + +Effectively, a semicolon is inserted after the `return` statement, causing the code below it (a labeled literal inside a block) to be unreachable. This rule and the [no-unreachable](no-unreachable.md) rule will protect your code from such cases. + +On the other side of the argument are those who say that since semicolons are inserted automatically, they are optional and do not need to be inserted manually. However, the ASI mechanism can also be tricky to people who don't use semicolons. For example, consider this code: + +```js +var globalCounter = { } + +(function () { + var n = 0 + globalCounter.increment = function () { + return ++n + } +})() +``` + +In this example, a semicolon will not be inserted after the first line, causing a run-time error (because an empty object is called as if it's a function). The [no-unexpected-multiline](no-unexpected-multiline.md) rule can protect your code from such cases. + +Although ASI allows for more freedom over your coding style, it can also make your code behave in an unexpected way, whether you use semicolons or not. Therefore, it is best to know when ASI takes place and when it does not, and have ESLint protect your code from these potentially unexpected cases. In short, as once described by Isaac Schlueter, a `\n` character always ends a statement (just like a semicolon) unless one of the following is true: + +1. The statement has an unclosed paren, array literal, or object literal or ends in some other way that is not a valid way to end a statement. (For instance, ending with `.` or `,`.) +1. The line is `--` or `++` (in which case it will decrement/increment the next token.) +1. It is a `for()`, `while()`, `do`, `if()`, or `else`, and there is no `{` +1. The next line starts with `[`, `(`, `+`, `*`, `/`, `-`, `,`, `.`, or some other binary operator that can only be found between two tokens in a single expression. + +## Rule Details + +This rule enforces consistent use of semicolons. + +## Options + +This rule has two options, a string option and an object option. + +String option: + +* `"always"` (default) requires semicolons at the end of statements +* `"never"` disallows semicolons as the end of statements (except to disambiguate statements beginning with `[`, `(`, `/`, `+`, or `-`) + +Object option (when `"always"`): + +* `"omitLastInOneLineBlock": true` ignores the last semicolon in a block in which its braces (and therefore the content of the block) are in the same line + +Object option (when `"never"`): + +* `"beforeStatementContinuationChars": "any"` (default) ignores semicolons (or lacking semicolon) at the end of statements if the next line starts with `[`, `(`, `/`, `+`, or `-`. +* `"beforeStatementContinuationChars": "always"` requires semicolons at the end of statements if the next line starts with `[`, `(`, `/`, `+`, or `-`. +* `"beforeStatementContinuationChars": "never"` disallows semicolons as the end of statements if it doesn't make ASI hazard even if the next line starts with `[`, `(`, `/`, `+`, or `-`. + +### always + +Examples of **incorrect** code for this rule with the default `"always"` option: + +```js +/*eslint semi: ["error", "always"]*/ + +var name = "ESLint" + +object.method = function() { + // ... +} +``` + +Examples of **correct** code for this rule with the default `"always"` option: + +```js +/*eslint semi: "error"*/ + +var name = "ESLint"; + +object.method = function() { + // ... +}; +``` + +### never + +Examples of **incorrect** code for this rule with the `"never"` option: + +```js +/*eslint semi: ["error", "never"]*/ + +var name = "ESLint"; + +object.method = function() { + // ... +}; +``` + +Examples of **correct** code for this rule with the `"never"` option: + +```js +/*eslint semi: ["error", "never"]*/ + +var name = "ESLint" + +object.method = function() { + // ... +} + +var name = "ESLint" + +;(function() { + // ... +})() + +import a from "a" +(function() { + // ... +})() + +import b from "b" +;(function() { + // ... +})() +``` + +#### omitLastInOneLineBlock + +Examples of additional **correct** code for this rule with the `"always", { "omitLastInOneLineBlock": true }` options: + +```js +/*eslint semi: ["error", "always", { "omitLastInOneLineBlock": true}] */ + +if (foo) { bar() } + +if (foo) { bar(); baz() } +``` + +#### beforeStatementContinuationChars + +Examples of additional **incorrect** code for this rule with the `"never", { "beforeStatementContinuationChars": "always" }` options: + +```js +/*eslint semi: ["error", "never", { "beforeStatementContinuationChars": "always"}] */ +import a from "a" + +(function() { + // ... +})() +``` + +Examples of additional **incorrect** code for this rule with the `"never", { "beforeStatementContinuationChars": "never" }` options: + +```js +/*eslint semi: ["error", "never", { "beforeStatementContinuationChars": "never"}] */ +import a from "a" + +;(function() { + // ... +})() +``` + +## When Not To Use It + +If you do not want to enforce semicolon usage (or omission) in any particular way, then you can turn this rule off. + +## Further Reading + +* [An Open Letter to JavaScript Leaders Regarding Semicolons](http://blog.izs.me/post/2353458699/an-open-letter-to-javascript-leaders-regarding) +* [JavaScript Semicolon Insertion](http://inimino.org/~inimino/blog/javascript_semicolons) + +## Related Rules + +* [no-extra-semi](no-extra-semi.md) +* [no-unexpected-multiline](no-unexpected-multiline.md) +* [semi-spacing](semi-spacing.md) diff --git a/eslint/docs/rules/sort-imports.md b/eslint/docs/rules/sort-imports.md new file mode 100644 index 0000000..f6fd04f --- /dev/null +++ b/eslint/docs/rules/sort-imports.md @@ -0,0 +1,236 @@ +# Import Sorting (sort-imports) + +The import statement is used to import members (functions, objects or primitives) that have been exported from an external module. Using a specific member syntax: + +```js +// single - Import single member. +import myMember from "my-module.js"; + +// multiple - Import multiple members. +import {foo, bar} from "my-module.js"; + +// all - Import all members, where myModule contains all the exported bindings. +import * as myModule from "my-module.js"; +``` + +The import statement can also import a module without exported bindings. Used when the module does not export anything, but runs it own code or changes the global context object. + +```js +// none - Import module without exported bindings. +import "my-module.js" +``` + +When declaring multiple imports, a sorted list of import declarations make it easier for developers to read the code and find necessary imports later. This rule is purely a matter of style. + + +## Rule Details + +This rule checks all import declarations and verifies that all imports are first sorted by the used member syntax and then alphabetically by the first member or alias name. + +The `--fix` option on the command line automatically fixes some problems reported by this rule: multiple members on a single line are automatically sorted (e.g. `import { b, a } from 'foo.js'` is corrected to `import { a, b } from 'foo.js'`), but multiple lines are not reordered. + +## Options + +This rule accepts an object with its properties as + +* `ignoreCase` (default: `false`) +* `ignoreDeclarationSort` (default: `false`) +* `ignoreMemberSort` (default: `false`) +* `memberSyntaxSortOrder` (default: `["none", "all", "multiple", "single"]`); all 4 items must be present in the array, but you can change the order: + * `none` = import module without exported bindings. + * `all` = import all members provided by exported bindings. + * `multiple` = import multiple members. + * `single` = import single member. + +Default option settings are: + +```json +{ + "sort-imports": ["error", { + "ignoreCase": false, + "ignoreDeclarationSort": false, + "ignoreMemberSort": false, + "memberSyntaxSortOrder": ["none", "all", "multiple", "single"] + }] +} +``` + +## Examples + +### Default settings + +Examples of **correct** code for this rule when using default options: + +```js +/*eslint sort-imports: "error"*/ +import 'module-without-export.js'; +import * as bar from 'bar.js'; +import * as foo from 'foo.js'; +import {alpha, beta} from 'alpha.js'; +import {delta, gamma} from 'delta.js'; +import a from 'baz.js'; +import b from 'qux.js'; + +/*eslint sort-imports: "error"*/ +import a from 'foo.js'; +import b from 'bar.js'; +import c from 'baz.js'; + +/*eslint sort-imports: "error"*/ +import 'foo.js' +import * as bar from 'bar.js'; +import {a, b} from 'baz.js'; +import c from 'qux.js'; + +/*eslint sort-imports: "error"*/ +import {a, b, c} from 'foo.js' +``` + +Examples of **incorrect** code for this rule when using default options: + +```js +/*eslint sort-imports: "error"*/ +import b from 'foo.js'; +import a from 'bar.js'; + +/*eslint sort-imports: "error"*/ +import a from 'foo.js'; +import A from 'bar.js'; + +/*eslint sort-imports: "error"*/ +import {b, c} from 'foo.js'; +import {a, b} from 'bar.js'; + +/*eslint sort-imports: "error"*/ +import a from 'foo.js'; +import {b, c} from 'bar.js'; + +/*eslint sort-imports: "error"*/ +import a from 'foo.js'; +import * as b from 'bar.js'; + +/*eslint sort-imports: "error"*/ +import {b, a, c} from 'foo.js' +``` + +### `ignoreCase` + +When `true` the rule ignores the case-sensitivity of the imports local name. + +Examples of **incorrect** code for this rule with the `{ "ignoreCase": true }` option: + +```js +/*eslint sort-imports: ["error", { "ignoreCase": true }]*/ + +import B from 'foo.js'; +import a from 'bar.js'; +``` + +Examples of **correct** code for this rule with the `{ "ignoreCase": true }` option: + +```js +/*eslint sort-imports: ["error", { "ignoreCase": true }]*/ + +import a from 'foo.js'; +import B from 'bar.js'; +import c from 'baz.js'; +``` + +Default is `false`. + +### `ignoreDeclarationSort` + +Ignores the sorting of import declaration statements. + +Examples of **incorrect** code for this rule with the default `{ "ignoreDeclarationSort": false }` option: + +```js +/*eslint sort-imports: ["error", { "ignoreDeclarationSort": false }]*/ +import b from 'foo.js' +import a from 'bar.js' +``` + +Examples of **correct** code for this rule with the `{ "ignoreDeclarationSort": true }` option: + +```js +/*eslint sort-imports: ["error", { "ignoreDeclarationSort": true }]*/ +import a from 'foo.js' +import b from 'bar.js' +``` + +```js +/*eslint sort-imports: ["error", { "ignoreDeclarationSort": true }]*/ +import b from 'foo.js' +import a from 'bar.js' +``` + +Default is `false`. + +### `ignoreMemberSort` + +Ignores the member sorting within a `multiple` member import declaration. + +Examples of **incorrect** code for this rule with the default `{ "ignoreMemberSort": false }` option: + +```js +/*eslint sort-imports: ["error", { "ignoreMemberSort": false }]*/ +import {b, a, c} from 'foo.js' +``` + +Examples of **correct** code for this rule with the `{ "ignoreMemberSort": true }` option: + +```js +/*eslint sort-imports: ["error", { "ignoreMemberSort": true }]*/ +import {b, a, c} from 'foo.js' +``` + +Default is `false`. + +### `memberSyntaxSortOrder` + +There are four different styles and the default member syntax sort order is: + +* `none` - import module without exported bindings. +* `all` - import all members provided by exported bindings. +* `multiple` - import multiple members. +* `single` - import single member. + +All four options must be specified in the array, but you can customize their order. + +Examples of **incorrect** code for this rule with the default `{ "memberSyntaxSortOrder": ["none", "all", "multiple", "single"] }` option: + +```js +/*eslint sort-imports: "error"*/ +import a from 'foo.js'; +import * as b from 'bar.js'; +``` + +Examples of **correct** code for this rule with the `{ "memberSyntaxSortOrder": ['single', 'all', 'multiple', 'none'] }` option: + +```js +/*eslint sort-imports: ["error", { "memberSyntaxSortOrder": ['single', 'all', 'multiple', 'none'] }]*/ + +import a from 'foo.js'; +import * as b from 'bar.js'; +``` + +Examples of **correct** code for this rule with the `{ "memberSyntaxSortOrder": ['all', 'single', 'multiple', 'none'] }` option: + +```js +/*eslint sort-imports: ["error", { "memberSyntaxSortOrder": ['all', 'single', 'multiple', 'none'] }]*/ + +import * as foo from 'foo.js'; +import z from 'zoo.js'; +import {a, b} from 'foo.js'; +``` + +Default is `["none", "all", "multiple", "single"]`. + +## When Not To Use It + +This rule is a formatting preference and not following it won't negatively affect the quality of your code. If alphabetizing imports isn't a part of your coding standards, then you can leave this rule disabled. + +## Related Rules + +* [sort-keys](sort-keys.md) +* [sort-vars](sort-vars.md) diff --git a/eslint/docs/rules/sort-keys.md b/eslint/docs/rules/sort-keys.md new file mode 100644 index 0000000..937a7da --- /dev/null +++ b/eslint/docs/rules/sort-keys.md @@ -0,0 +1,228 @@ +# require object keys to be sorted (sort-keys) + +When declaring multiple properties, some developers prefer to sort property names alphabetically to more easily find and/or diff necessary properties at a later time. Others feel that it adds complexity and becomes burden to maintain. + +## Rule Details + +This rule checks all property definitions of object expressions and verifies that all variables are sorted alphabetically. + +Examples of **incorrect** code for this rule: + +```js +/*eslint sort-keys: "error"*/ +/*eslint-env es6*/ + +let obj = {a: 1, c: 3, b: 2}; +let obj = {a: 1, "c": 3, b: 2}; + +// Case-sensitive by default. +let obj = {a: 1, b: 2, C: 3}; + +// Non-natural order by default. +let obj = {1: a, 2: c, 10: b}; + +// This rule checks computed properties which have a simple name as well. +// Simple names are names which are expressed by an Identifier node or a Literal node. +const S = Symbol("s") +let obj = {a: 1, ["c"]: 3, b: 2}; +let obj = {a: 1, [S]: 3, b: 2}; +``` + +Examples of **correct** code for this rule: + +```js +/*eslint sort-keys: "error"*/ +/*eslint-env es6*/ + +let obj = {a: 1, b: 2, c: 3}; +let obj = {a: 1, "b": 2, c: 3}; + +// Case-sensitive by default. +let obj = {C: 3, a: 1, b: 2}; + +// Non-natural order by default. +let obj = {1: a, 10: b, 2: c}; + +// This rule checks computed properties which have a simple name as well. +let obj = {a: 1, ["b"]: 2, c: 3}; +let obj = {a: 1, [b]: 2, c: 3}; + +// This rule ignores computed properties which have a non-simple name. +let obj = {a: 1, [c + d]: 3, b: 2}; +let obj = {a: 1, ["c" + "d"]: 3, b: 2}; +let obj = {a: 1, [`${c}`]: 3, b: 2}; +let obj = {a: 1, [tag`c`]: 3, b: 2}; + +// This rule does not report unsorted properties that are separated by a spread property. +let obj = {b: 1, ...c, a: 2}; +``` + +## Options + +```json +{ + "sort-keys": ["error", "asc", {"caseSensitive": true, "natural": false, "minKeys": 2}] +} +``` + +The 1st option is `"asc"` or `"desc"`. + +* `"asc"` (default) - enforce properties to be in ascending order. +* `"desc"` - enforce properties to be in descending order. + +The 2nd option is an object which has 3 properties. + +* `caseSensitive` - if `true`, enforce properties to be in case-sensitive order. Default is `true`. +* `minKeys` - Specifies the minimum number of keys that an object should have in order for the object's unsorted keys to produce an error. Default is `2`, which means by default all objects with unsorted keys will result in lint errors. +* `natural` - if `true`, enforce properties to be in natural order. Default is `false`. Natural Order compares strings containing combination of letters and numbers in the way a human being would sort. It basically sorts numerically, instead of sorting alphabetically. So the number 10 comes after the number 3 in Natural Sorting. + +Example for a list: + +With `natural` as true, the ordering would be +1 +3 +6 +8 +10 + +With `natural` as false, the ordering would be +1 +10 +3 +6 +8 + +### desc + +Examples of **incorrect** code for the `"desc"` option: + +```js +/*eslint sort-keys: ["error", "desc"]*/ +/*eslint-env es6*/ + +let obj = {b: 2, c: 3, a: 1}; +let obj = {"b": 2, c: 3, a: 1}; + +// Case-sensitive by default. +let obj = {C: 1, b: 3, a: 2}; + +// Non-natural order by default. +let obj = {10: b, 2: c, 1: a}; +``` + +Examples of **correct** code for the `"desc"` option: + +```js +/*eslint sort-keys: ["error", "desc"]*/ +/*eslint-env es6*/ + +let obj = {c: 3, b: 2, a: 1}; +let obj = {c: 3, "b": 2, a: 1}; + +// Case-sensitive by default. +let obj = {b: 3, a: 2, C: 1}; + +// Non-natural order by default. +let obj = {2: c, 10: b, 1: a}; +``` + +### insensitive + +Examples of **incorrect** code for the `{caseSensitive: false}` option: + +```js +/*eslint sort-keys: ["error", "asc", {caseSensitive: false}]*/ +/*eslint-env es6*/ + +let obj = {a: 1, c: 3, C: 4, b: 2}; +let obj = {a: 1, C: 3, c: 4, b: 2}; +``` + +Examples of **correct** code for the `{caseSensitive: false}` option: + +```js +/*eslint sort-keys: ["error", "asc", {caseSensitive: false}]*/ +/*eslint-env es6*/ + +let obj = {a: 1, b: 2, c: 3, C: 4}; +let obj = {a: 1, b: 2, C: 3, c: 4}; +``` + +### natural + +Examples of **incorrect** code for the `{natural: true}` option: + +```js +/*eslint sort-keys: ["error", "asc", {natural: true}]*/ +/*eslint-env es6*/ + +let obj = {1: a, 10: c, 2: b}; +``` + +Examples of **correct** code for the `{natural: true}` option: + +```js +/*eslint sort-keys: ["error", "asc", {natural: true}]*/ +/*eslint-env es6*/ + +let obj = {1: a, 2: b, 10: c}; +``` + +### minKeys + +Examples of **incorrect** code for the `{minKeys: 4}` option: + +```js +/*eslint sort-keys: ["error", "asc", {minKeys: 4}]*/ +/*eslint-env es6*/ + +// 4 keys +let obj = { + b: 2, + a: 1, // not sorted correctly (should be 1st key) + c: 3, + d: 4, +}; + +// 5 keys +let obj = { + 2: 'a', + 1: 'b', // not sorted correctly (should be 1st key) + 3: 'c', + 4: 'd', + 5: 'e', +}; +``` + +Examples of **correct** code for the `{minKeys: 4}` option: + +```js +/*eslint sort-keys: ["error", "asc", {minKeys: 4}]*// +/*eslint-env es6*/ + +// 3 keys +let obj = { + b: 2, + a: 1, + c: 3, +}; + +// 2 keys +let obj = { + 2: 'b', + 1: 'a', +}; +``` + +## When Not To Use It + +If you don't want to notify about properties' order, then it's safe to disable this rule. + +## Related Rules + +* [sort-imports](sort-imports.md) +* [sort-vars](sort-vars.md) + +## Compatibility + +* **JSCS:** [validateOrderInObjectKeys](https://jscs-dev.github.io/rule/validateOrderInObjectKeys) diff --git a/eslint/docs/rules/sort-vars.md b/eslint/docs/rules/sort-vars.md new file mode 100644 index 0000000..8a201f3 --- /dev/null +++ b/eslint/docs/rules/sort-vars.md @@ -0,0 +1,78 @@ +# Variable Sorting (sort-vars) + +When declaring multiple variables within the same block, some developers prefer to sort variable names alphabetically to be able to find necessary variable easier at the later time. Others feel that it adds complexity and becomes burden to maintain. + +## Rule Details + +This rule checks all variable declaration blocks and verifies that all variables are sorted alphabetically. +The default configuration of the rule is case-sensitive. + +Examples of **incorrect** code for this rule: + +```js +/*eslint sort-vars: "error"*/ + +var b, a; + +var a, B, c; + +var a, A; +``` + +Examples of **correct** code for this rule: + +```js +/*eslint sort-vars: "error"*/ + +var a, b, c, d; + +var _a = 10; +var _b = 20; + +var A, a; + +var B, a, c; +``` + +Alphabetical list is maintained starting from the first variable and excluding any that are considered problems. So the following code will produce two problems: + +```js +/*eslint sort-vars: "error"*/ + +var c, d, a, b; +``` + +But this one, will only produce one: + +```js +/*eslint sort-vars: "error"*/ + +var c, d, a, e; +``` + +## Options + +This rule has an object option: + +* `"ignoreCase": true` (default `false`) ignores the case-sensitivity of the variables order + +### ignoreCase + +Examples of **correct** code for this rule with the `{ "ignoreCase": true }` option: + +```js +/*eslint sort-vars: ["error", { "ignoreCase": true }]*/ + +var a, A; + +var a, B, c; +``` + +## When Not To Use It + +This rule is a formatting preference and not following it won't negatively affect the quality of your code. If you alphabetizing variables isn't a part of your coding standards, then you can leave this rule off. + +## Related Rules + +* [sort-keys](sort-keys.md) +* [sort-imports](sort-imports.md) diff --git a/eslint/docs/rules/space-after-function-name.md b/eslint/docs/rules/space-after-function-name.md new file mode 100644 index 0000000..4607ff4 --- /dev/null +++ b/eslint/docs/rules/space-after-function-name.md @@ -0,0 +1,52 @@ +# space-after-function-name: enforce consistent spacing after name in function definitions + +(removed) This rule was **removed** in ESLint v1.0 and **replaced** by the [space-before-function-paren](space-before-function-paren.md) rule. + +Whitespace between a function name and its parameter list is optional. + +```js +function withoutSpace(x) { + // ... +} + +function withSpace (x) { + // ... +} +``` + +Some style guides may require a consistent spacing for function names. + +## Rule Details + +This rule aims to enforce a consistent spacing after function names. It takes one argument. If it is `"always"` then all function names must be followed by at least one space. If `"never"` then there should be no spaces between the name and the parameter list. The default is `"never"`. + + +Examples of **incorrect** code for this rule: + +```js +function foo (x) { + // ... +} + +var x = function named (x) {}; + +// When ["error", "always"] +function bar(x) { + // ... +} +``` + +Examples of **correct** code for this rule: + +```js +function foo(x) { + // ... +} + +var x = function named(x) {}; + +// When ["error", "always"] +function bar (x) { + // ... +} +``` diff --git a/eslint/docs/rules/space-after-keywords.md b/eslint/docs/rules/space-after-keywords.md new file mode 100644 index 0000000..af1b739 --- /dev/null +++ b/eslint/docs/rules/space-after-keywords.md @@ -0,0 +1,62 @@ +# space-after-keywords: enforce consistent spacing after keywords + +(removed) This rule was **removed** in ESLint v2.0 and replaced by the [keyword-spacing](keyword-spacing.md) rule. + +(fixable) The `--fix` option on the [command line](../user-guide/command-line-interface#fix) automatically fixed problems reported by this rule. + +Some style guides will require or disallow spaces following the certain keywords. + +```js +if (condition) { + doSomething(); +} else { + doSomethingElse(); +} + +if(condition) { + doSomething(); +}else{ + doSomethingElse(); +} +``` + +## Rule Details + +This rule will enforce consistency of spacing after the keywords `if`, `else`, `for`, `while`, `do`, `switch`, `try`, `catch`, `finally`, and `with`. + +This rule takes one argument. If it is `"always"` then the keywords must be followed by at least one space. If `"never"` +then there should be no spaces following. The default is `"always"`. + +Examples of **incorrect** code for this rule: + +```js +/*eslint space-after-keywords: "error"*/ + +if(a) {} + +if (a) {} else{} + +do{} while (a); +``` + +```js +/*eslint space-after-keywords: ["error", "never"]*/ + +if (a) {} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint space-after-keywords: "error"*/ + +if (a) {} + +if (a) {} else {} +``` + +```js +/*eslint space-after-keywords: ["error", "never"]*/ + +if(a) {} +``` diff --git a/eslint/docs/rules/space-before-blocks.md b/eslint/docs/rules/space-before-blocks.md new file mode 100644 index 0000000..25b81ca --- /dev/null +++ b/eslint/docs/rules/space-before-blocks.md @@ -0,0 +1,213 @@ +# Require Or Disallow Space Before Blocks (space-before-blocks) + +Consistency is an important part of any style guide. +While it is a personal preference where to put the opening brace of blocks, +it should be consistent across a whole project. +Having an inconsistent style distracts the reader from seeing the important parts of the code. + +## Rule Details + +This rule will enforce consistency of spacing before blocks. It is only applied on blocks that don’t begin on a new line. + +* This rule ignores spacing which is between `=>` and a block. The spacing is handled by the `arrow-spacing` rule. +* This rule ignores spacing which is between a keyword and a block. The spacing is handled by the `keyword-spacing` rule. + +## Options + +This rule takes one argument. If it is `"always"` then blocks must always have at least one preceding space. If `"never"` +then all blocks should never have any preceding space. If different spacing is desired for function +blocks, keyword blocks and classes, an optional configuration object can be passed as the rule argument to +configure the cases separately. If any value in the configuration object is `"off"`, then neither style will be enforced for blocks of that kind. + +( e.g. `{ "functions": "never", "keywords": "always", "classes": "always" }` ) + +The default is `"always"`. + +### "always" + +Examples of **incorrect** code for this rule with the "always" option: + +```js +/*eslint space-before-blocks: "error"*/ + +if (a){ + b(); +} + +function a(){} + +for (;;){ + b(); +} + +try {} catch(a){} + +class Foo{ + constructor(){} +} +``` + +Examples of **correct** code for this rule with the `"always"` option: + +```js +/*eslint space-before-blocks: "error"*/ + +if (a) { + b(); +} + +if (a) { + b(); +} else{ /*no error. this is checked by `keyword-spacing` rule.*/ + c(); +} + + +function a() {} + +for (;;) { + b(); +} + +try {} catch(a) {} +``` + +### "never" + +Examples of **incorrect** code for this rule with the `"never"` option: + +```js +/*eslint space-before-blocks: ["error", "never"]*/ + +if (a) { + b(); +} + +function a() {} + +for (;;) { + b(); +} + +try {} catch(a) {} +``` + +Examples of **correct** code for this rule with the `"never"` option: + +```js +/*eslint space-before-blocks: ["error", "never"]*/ + +if (a){ + b(); +} + +function a(){} + +for (;;){ + b(); +} + +try{} catch(a){} + +class Foo{ + constructor(){} +} +``` + +Examples of **incorrect** code for this rule when configured `{ "functions": "never", "keywords": "always", "classes": "never" }`: + +```js +/*eslint space-before-blocks: ["error", { "functions": "never", "keywords": "always", "classes": "never" }]*/ +/*eslint-env es6*/ + +function a() {} + +try {} catch(a){} + +class Foo{ + constructor() {} +} +``` + +Examples of **correct** code for this rule when configured `{ "functions": "never", "keywords": "always", "classes": "never" }`: + +```js +/*eslint space-before-blocks: ["error", { "functions": "never", "keywords": "always", "classes": "never" }]*/ +/*eslint-env es6*/ + +for (;;) { + // ... +} + +describe(function(){ + // ... +}); + +class Foo{ + constructor(){} +} +``` + +Examples of **incorrect** code for this rule when configured `{ "functions": "always", "keywords": "never", "classes": "never" }`: + +```js +/*eslint space-before-blocks: ["error", { "functions": "always", "keywords": "never", "classes": "never" }]*/ +/*eslint-env es6*/ + +function a(){} + +try {} catch(a) {} + +class Foo { + constructor(){} +} +``` + +Examples of **correct** code for this rule when configured `{ "functions": "always", "keywords": "never", "classes": "never" }`: + +```js +/*eslint space-before-blocks: ["error", { "functions": "always", "keywords": "never", "classes": "never" }]*/ +/*eslint-env es6*/ + +if (a){ + b(); +} + +var a = function() {} + +class Foo{ + constructor() {} +} +``` + +Examples of **incorrect** code for this rule when configured `{ "functions": "never", "keywords": "never", "classes": "always" }`: + +```js +/*eslint space-before-blocks: ["error", { "functions": "never", "keywords": "never", "classes": "always" }]*/ +/*eslint-env es6*/ + +class Foo{ + constructor(){} +} +``` + +Examples of **correct** code for this rule when configured `{ "functions": "never", "keywords": "never", "classes": "always" }`: + +```js +/*eslint space-before-blocks: ["error", { "functions": "never", "keywords": "never", "classes": "always" }]*/ +/*eslint-env es6*/ + +class Foo { + constructor(){} +} +``` + +## When Not To Use It + +You can turn this rule off if you are not concerned with the consistency of spacing before blocks. + +## Related Rules + +* [keyword-spacing](keyword-spacing.md) +* [arrow-spacing](arrow-spacing.md) +* [brace-style](brace-style.md) diff --git a/eslint/docs/rules/space-before-function-paren.md b/eslint/docs/rules/space-before-function-paren.md new file mode 100644 index 0000000..c9d0c39 --- /dev/null +++ b/eslint/docs/rules/space-before-function-paren.md @@ -0,0 +1,368 @@ +# Require or disallow a space before function parenthesis (space-before-function-paren) + +When formatting a function, whitespace is allowed between the function name or `function` keyword and the opening paren. Named functions also require a space between the `function` keyword and the function name, but anonymous functions require no whitespace. For example: + +```js +function withoutSpace(x) { + // ... +} + +function withSpace (x) { + // ... +} + +var anonymousWithoutSpace = function() {}; + +var anonymousWithSpace = function () {}; +``` + +Style guides may require a space after the `function` keyword for anonymous functions, while others specify no whitespace. Similarly, the space after a function name may or may not be required. + +## Rule Details + +This rule aims to enforce consistent spacing before function parentheses and as such, will warn whenever whitespace doesn't match the preferences specified. + +## Options + +This rule has a string option or an object option: + +```js +{ + "space-before-function-paren": ["error", "always"], + // or + "space-before-function-paren": ["error", { + "anonymous": "always", + "named": "always", + "asyncArrow": "always" + }], +} +``` + +* `always` (default) requires a space followed by the `(` of arguments. +* `never` disallows any space followed by the `(` of arguments. + +The string option does not check async arrow function expressions for backward compatibility. + +You can also use a separate option for each type of function. +Each of the following options can be set to `"always"`, `"never"`, or `"ignore"`. The default is `"always"`. + +* `anonymous` is for anonymous function expressions (e.g. `function () {}`). +* `named` is for named function expressions (e.g. `function foo () {}`). +* `asyncArrow` is for async arrow function expressions (e.g. `async () => {}`). + +### "always" + +Examples of **incorrect** code for this rule with the default `"always"` option: + +```js +/*eslint space-before-function-paren: "error"*/ +/*eslint-env es6*/ + +function foo() { + // ... +} + +var bar = function() { + // ... +}; + +var bar = function foo() { + // ... +}; + +class Foo { + constructor() { + // ... + } +} + +var foo = { + bar() { + // ... + } +}; + +var foo = async() => 1 +``` + +Examples of **correct** code for this rule with the default `"always"` option: + +```js +/*eslint space-before-function-paren: "error"*/ +/*eslint-env es6*/ + +function foo () { + // ... +} + +var bar = function () { + // ... +}; + +var bar = function foo () { + // ... +}; + +class Foo { + constructor () { + // ... + } +} + +var foo = { + bar () { + // ... + } +}; + +var foo = async () => 1 +``` + +### "never" + +Examples of **incorrect** code for this rule with the `"never"` option: + +```js +/*eslint space-before-function-paren: ["error", "never"]*/ +/*eslint-env es6*/ + +function foo () { + // ... +} + +var bar = function () { + // ... +}; + +var bar = function foo () { + // ... +}; + +class Foo { + constructor () { + // ... + } +} + +var foo = { + bar () { + // ... + } +}; + +var foo = async () => 1 +``` + +Examples of **correct** code for this rule with the `"never"` option: + +```js +/*eslint space-before-function-paren: ["error", "never"]*/ +/*eslint-env es6*/ + +function foo() { + // ... +} + +var bar = function() { + // ... +}; + +var bar = function foo() { + // ... +}; + +class Foo { + constructor() { + // ... + } +} + +var foo = { + bar() { + // ... + } +}; + +var foo = async() => 1 +``` + +### `{"anonymous": "always", "named": "never", "asyncArrow": "always"}` + +Examples of **incorrect** code for this rule with the `{"anonymous": "always", "named": "never", "asyncArrow": "always"}` option: + +```js +/*eslint space-before-function-paren: ["error", {"anonymous": "always", "named": "never", "asyncArrow": "always"}]*/ +/*eslint-env es6*/ + +function foo () { + // ... +} + +var bar = function() { + // ... +}; + +class Foo { + constructor () { + // ... + } +} + +var foo = { + bar () { + // ... + } +}; + +var foo = async(a) => await a +``` + +Examples of **correct** code for this rule with the `{"anonymous": "always", "named": "never", "asyncArrow": "always"}` option: + +```js +/*eslint space-before-function-paren: ["error", {"anonymous": "always", "named": "never", "asyncArrow": "always"}]*/ +/*eslint-env es6*/ + +function foo() { + // ... +} + +var bar = function () { + // ... +}; + +class Foo { + constructor() { + // ... + } +} + +var foo = { + bar() { + // ... + } +}; + +var foo = async (a) => await a +``` + +### `{"anonymous": "never", "named": "always"}` + +Examples of **incorrect** code for this rule with the `{"anonymous": "never", "named": "always"}` option: + +```js +/*eslint space-before-function-paren: ["error", { "anonymous": "never", "named": "always" }]*/ +/*eslint-env es6*/ + +function foo() { + // ... +} + +var bar = function () { + // ... +}; + +class Foo { + constructor() { + // ... + } +} + +var foo = { + bar() { + // ... + } +}; +``` + +Examples of **correct** code for this rule with the `{"anonymous": "never", "named": "always"}` option: + +```js +/*eslint space-before-function-paren: ["error", { "anonymous": "never", "named": "always" }]*/ +/*eslint-env es6*/ + +function foo () { + // ... +} + +var bar = function() { + // ... +}; + +class Foo { + constructor () { + // ... + } +} + +var foo = { + bar () { + // ... + } +}; +``` + +### `{"anonymous": "ignore", "named": "always"}` + +Examples of **incorrect** code for this rule with the `{"anonymous": "ignore", "named": "always"}` option: + +```js +/*eslint space-before-function-paren: ["error", { "anonymous": "ignore", "named": "always" }]*/ +/*eslint-env es6*/ + +function foo() { + // ... +} + +class Foo { + constructor() { + // ... + } +} + +var foo = { + bar() { + // ... + } +}; +``` + +Examples of **correct** code for this rule with the `{"anonymous": "ignore", "named": "always"}` option: + +```js +/*eslint space-before-function-paren: ["error", { "anonymous": "ignore", "named": "always" }]*/ +/*eslint-env es6*/ + +var bar = function() { + // ... +}; + +var bar = function () { + // ... +}; + +function foo () { + // ... +} + +class Foo { + constructor () { + // ... + } +} + +var foo = { + bar () { + // ... + } +}; +``` + +## When Not To Use It + +You can turn this rule off if you are not concerned with the consistency of spacing before function parenthesis. + +## Related Rules + +* [space-after-keywords](space-after-keywords.md) +* [space-return-throw-case](space-return-throw-case.md) diff --git a/eslint/docs/rules/space-before-function-parentheses.md b/eslint/docs/rules/space-before-function-parentheses.md new file mode 100644 index 0000000..3856c22 --- /dev/null +++ b/eslint/docs/rules/space-before-function-parentheses.md @@ -0,0 +1,260 @@ +# space-before-function-parentheses: enforce consistent spacing before opening parenthesis in function definitions + +(removed) This rule was **removed** in ESLint v1.0 and **replaced** by the [space-before-function-paren](space-before-function-paren.md) rule. The name of the rule changed from "parentheses" to "paren" for consistency with the names of other rules. + +When formatting a function, whitespace is allowed between the function name or `function` keyword and the opening paren. Named functions also require a space between the `function` keyword and the function name, but anonymous functions require no whitespace. For example: + +```js +function withoutSpace(x) { + // ... +} + +function withSpace (x) { + // ... +} + +var anonymousWithoutSpace = function() {}; + +var anonymousWithSpace = function () {}; +``` + +Style guides may require a space after the `function` keyword for anonymous functions, while others specify no whitespace. Similarly, the space after a function name may or may not be required. + +## Rule Details + +This rule aims to enforce consistent spacing before function parentheses and as such, will warn whenever whitespace doesn't match the preferences specified. + +This rule takes one argument. If it is `"always"`, which is the default option, all named functions and anonymous functions must have space before function parentheses. If `"never"` then all named functions and anonymous functions must not have space before function parentheses. If you want different spacing for named and anonymous functions you can pass a configuration object as the rule argument to configure those separately (e. g. `{"anonymous": "always", "named": "never"}`). + +Examples of **incorrect** code for this rule with the default `"always"` option: + +```js +/*eslint-env es6*/ + +function foo() { + // ... +} + +var bar = function() { + // ... +}; + +var bar = function foo() { + // ... +}; + +class Foo { + constructor() { + // ... + } +} + +var foo = { + bar() { + // ... + } +}; +``` + +Examples of **correct** code for this rule with the default `"always"` option: + +```js +/*eslint-env es6*/ + +function foo () { + // ... +} + +var bar = function () { + // ... +}; + +var bar = function foo () { + // ... +}; + +class Foo { + constructor () { + // ... + } +} + +var foo = { + bar () { + // ... + } +}; +``` + +Examples of **incorrect** code for this rule with the `"never"` option: + +```js +/*eslint-env es6*/ + +function foo () { + // ... +} + +var bar = function () { + // ... +}; + +var bar = function foo () { + // ... +}; + +class Foo { + constructor () { + // ... + } +} + +var foo = { + bar () { + // ... + } +}; +``` + +Examples of **correct** code for this rule with the `"never"` option: + +```js +/*eslint-env es6*/ + +function foo() { + // ... +} + +var bar = function() { + // ... +}; + +var bar = function foo() { + // ... +}; + +class Foo { + constructor() { + // ... + } +} + +var foo = { + bar() { + // ... + } +}; +``` + +Examples of **incorrect** code for this rule with the `{"anonymous": "always", "named": "never"}` option: + +```js +/*eslint-env es6*/ + +function foo () { + // ... +} + +var bar = function() { + // ... +}; + +class Foo { + constructor () { + // ... + } +} + +var foo = { + bar () { + // ... + } +}; +``` + +Examples of **correct** code for this rule with the `{"anonymous": "always", "named": "never"}` option: + +```js +/*eslint-env es6*/ + +function foo() { + // ... +} + +var bar = function () { + // ... +}; + +class Foo { + constructor() { + // ... + } +} + +var foo = { + bar() { + // ... + } +}; +``` + +Examples of **incorrect** code for this rule with the `{"anonymous": "never", "named": "always"}` option: + +```js +/*eslint-env es6*/ + +function foo() { + // ... +} + +var bar = function () { + // ... +}; + +class Foo { + constructor() { + // ... + } +} + +var foo = { + bar() { + // ... + } +}; +``` + +Examples of **correct** code for this rule with the `{"anonymous": "never", "named": "always"}` option: + +```js +/*eslint-env es6*/ + +function foo () { + // ... +} + +var bar = function() { + // ... +}; + +class Foo { + constructor () { + // ... + } +} + +var foo = { + bar () { + // ... + } +}; +``` + +## When Not To Use It + +You can turn this rule off if you are not concerned with the consistency of spacing before function parenthesis. + +## Related Rules + +* [space-after-keywords](space-after-keywords.md) +* [space-return-throw-case](space-return-throw-case.md) diff --git a/eslint/docs/rules/space-before-keywords.md b/eslint/docs/rules/space-before-keywords.md new file mode 100644 index 0000000..8adf593 --- /dev/null +++ b/eslint/docs/rules/space-before-keywords.md @@ -0,0 +1,114 @@ +# space-before-keywords: enforce consistent spacing before keywords + +(removed) This rule was **removed** in ESLint v2.0 and **replaced** by the [keyword-spacing](keyword-spacing.md) rule. + +(fixable) The `--fix` option on the [command line](../user-guide/command-line-interface#fix) automatically fixed problems reported by this rule. + +Keywords are syntax elements of JavaScript, such as `function` and `if`. These identifiers have special meaning to the language and so often appear in a different color in code editors. As an important part of the language, style guides often refer to the spacing that should be used around keywords. For example, you might have a style guide that says keywords should be always be preceded by spaces, which would mean `if-else` statements must look like this: + +```js +if (foo) { + // ... +} else { + // ... +} +``` + +Of course, you could also have a style guide that disallows spaces before keywords. + +## Rule Details + +This rule will enforce consistency of spacing before the keywords `if`, `else`, `for`, +`while`, `do`, `switch`, `throw`, `try`, `catch`, `finally`, `with`, `break`, `continue`, +`return`, `function`, `yield`, `class` and variable declarations (`let`, `const`, `var`) +and label statements. + +This rule takes one argument: `"always"` or `"never"`. If `"always"` then the keywords +must be preceded by at least one space. If `"never"` then no spaces will be allowed before +the keywords `else`, `while` (do...while), `finally` and `catch`. The default value is `"always"`. + +This rule will allow keywords to be preceded by an opening curly brace (`{`). If you wish to alter +this behavior, consider using the [block-spacing](block-spacing.md) rule. + +Examples of **incorrect** code for this rule with the default `"always"` option: + +```js +/*eslint space-before-keywords: ["error", "always"]*/ +/*eslint-env es6*/ + +if (foo) { + // ... +}else {} + +const foo = 'bar';let baz = 'qux'; + +var foo =function bar () {} + +function bar() { + if (foo) {return; } +} +``` + +Examples of **correct** code for this rule with the default `"always"` option: + +```js +/*eslint space-before-keywords: ["error", "always"]*/ +/*eslint-env es6*/ + +if (foo) { + // ... +} else {} + +(function() {})() + + + +for (let foo of ['bar', 'baz', 'qux']) {} +``` + +Examples of **incorrect** code for this rule with the `"never"` option: + +```js +/*eslint space-before-keywords: ["error", "never"]*/ + +if (foo) { + // ... +} else {} + +do { + +} +while (foo) + +try {} finally {} + +try {} catch(e) {} +``` + +Examples of **correct** code for this rule with the `"never"` option: + +```js +/*eslint space-before-keywords: ["error", "never"]*/ + +if (foo) { + // ... +}else {} + +do {}while (foo) + +try {}finally {} + +try{}catch(e) {} +``` + +## When Not To Use It + +If you do not wish to enforce consistency on keyword spacing. + +## Related Rules + +* [space-after-keywords](space-after-keywords.md) +* [block-spacing](block-spacing.md) +* [space-return-throw-case](space-return-throw-case.md) +* [space-unary-ops](space-unary-ops.md) +* [space-infix-ops](space-infix-ops.md) diff --git a/eslint/docs/rules/space-in-brackets.md b/eslint/docs/rules/space-in-brackets.md new file mode 100644 index 0000000..9bb2477 --- /dev/null +++ b/eslint/docs/rules/space-in-brackets.md @@ -0,0 +1,308 @@ +# space-in-brackets: enforce consistent spacing inside braces of object literals and brackets of array literals + +(removed) This rule was **removed** in ESLint v1.0 and **replaced** by the [object-curly-spacing](object-curly-spacing.md) and [array-bracket-spacing](array-bracket-spacing.md) rules. + +While formatting preferences are very personal, a number of style guides require or disallow spaces between brackets: + +```js +var obj = { foo: 'bar' }; +var arr = [ 'foo', 'bar' ]; +foo[ 'bar' ]; + +var obj = {foo: 'bar'}; +var arr = ['foo', 'bar']; +foo['bar']; +``` + +## Rule Details + +This rule aims to maintain consistency around the spacing inside of square brackets, either by disallowing spaces inside of brackets between the brackets and other tokens or enforcing spaces. Brackets that are separated from the adjacent value by a new line are excepted from this rule, as this is a common pattern. Object literals that are used as the first or last element in an array are also ignored. + +## Options + +There are two options for this rule: + +* `"always"` enforces a space inside of object and array literals +* `"never"` enforces zero spaces inside of object and array literals (default) + +Depending on your coding conventions, you can choose either option by specifying it in your configuration: + +```json +"space-in-brackets": ["error", "always"] +``` + +### "never" + +Examples of **incorrect** code for this rule with the default `"never"` option: + +```js +/*eslint-env es6*/ + +foo[ 'bar' ]; +foo['bar' ]; + +var arr = [ 'foo', 'bar' ]; +var arr = ['foo', 'bar' ]; +var arr = [ ['foo'], 'bar']; +var arr = [[ 'foo' ], 'bar']; +var arr = ['foo', + 'bar' +]; + +var obj = { 'foo': 'bar' }; +var obj = {'foo': 'bar' }; +var obj = { baz: {'foo': 'qux'}, bar}; +var obj = {baz: { 'foo': 'qux' }, bar}; +``` + +Examples of **correct** code for this rule with the default `"never"` option: + +```js +// When options are ["error", "never"] + +foo['bar']; +foo[ + 'bar' +]; +foo[ + 'bar']; + +var arr = []; +var arr = ['foo', 'bar', 'baz']; +var arr = [['foo'], 'bar', 'baz']; +var arr = [ + 'foo', + 'bar', + 'baz' +]; + +var arr = [ + 'foo', + 'bar']; + +var obj = {'foo': 'bar'}; + +var obj = {'foo': {'bar': 'baz'}, 'qux': 'quxx'}; + +var obj = { + 'foo': 'bar' +}; +var obj = {'foo': 'bar' +}; +var obj = { + 'foo':'bar'}; + +var obj = {}; +``` + +### "always" + +Examples of **incorrect** code for this rule with the `"always"` option: + +```js +/*eslint-env es6*/ + +foo['bar']; +foo['bar' ]; +foo[ 'bar']; + +var arr = ['foo', 'bar']; +var arr = ['foo', 'bar' ]; +var arr = [ ['foo'], 'bar' ]; +var arr = ['foo', + 'bar' +]; + +var arr = [ + 'foo', + 'bar']; + +var obj = {'foo': 'bar'}; +var obj = {'foo': 'bar' }; +var obj = { baz: {'foo': 'qux'}, bar}; +var obj = {baz: { 'foo': 'qux' }, bar}; +var obj = {'foo': 'bar' +}; + +var obj = { + 'foo':'bar'}; +``` + +Examples of **correct** code for this rule with the `"always"` option: + +```js +foo[ 'bar' ]; +foo[ + 'bar' +]; + +var arr = []; +var arr = [ 'foo', 'bar', 'baz' ]; +var arr = [ [ 'foo' ], 'bar', 'baz' ]; + +var arr = [ + 'foo', + 'bar', + 'baz' +]; + +var obj = {}; +var obj = { 'foo': 'bar' }; +var obj = { 'foo': { 'bar': 'baz' }, 'qux': 'quxx' }; +var obj = { + 'foo': 'bar' +}; +``` + +Note that `"always"` has a special case where `{}` and `[]` are not considered problems. + +### Exceptions + +An object literal may be used as a third array item to specify spacing exceptions. These exceptions work in the context of the first option. That is, if `"always"` is set to enforce spacing and an exception is set to `false`, it will disallow spacing for cases matching the exception. Likewise, if `"never"` is set to disallow spacing and an exception is set to `true`, it will enforce spacing for cases matching the exception. + +You can add exceptions like so: + +In case of `"always"` option, set an exception to `false` to enable it: + +```json +"space-in-brackets": ["error", "always", { + "singleValue": false, + "objectsInArrays": false, + "arraysInArrays": false, + "arraysInObjects": false, + "objectsInObjects": false, + "propertyName": false +}] +``` + +In case of `"never"` option, set an exception to `true` to enable it: + +```json +"space-in-brackets": ["error", "never", { + "singleValue": true, + "objectsInArrays": true, + "arraysInArrays": true, + "arraysInObjects": true, + "objectsInObjects": true, + "propertyName": true +}] +``` + +The following exceptions are available: + +* `singleValue` sets the spacing of a single value inside of square brackets of an array. +* `objectsInArrays` sets the spacings between the curly braces and square brackets of object literals that are the first or last element in an array. +* `arraysInArrays` sets the spacing between the square brackets of array literals that are the first or last element in an array. +* `arraysInObjects` sets the spacing between the square bracket and the curly brace of an array literal that is the last element in an object. +* `objectsInObjects` sets the spacing between the curly brace of an object literal that is the last element in an object and the curly brace of the containing object. +* `propertyName` sets the spacing in square brackets of computed member expressions. + +In each of the following examples, the `"always"` option is assumed. + +Examples of **incorrect** code for this rule when `"singleValue"` is set to `false`: + +```js +var foo = [ 'foo' ]; +var foo = [ 'foo']; +var foo = ['foo' ]; +var foo = [ 1 ]; +var foo = [ 1]; +var foo = [1 ]; +var foo = [ [ 1, 2 ] ]; +var foo = [ { 'foo': 'bar' } ]; +``` + +Examples of **correct** code for this rule when `"singleValue"` is set to `false`: + +```js +var foo = ['foo']; +var foo = [1]; +var foo = [[ 1, 1 ]]; +var foo = [{ 'foo': 'bar' }]; +``` + +Examples of **incorrect** code when `"objectsInArrays"` is set to `false`: + +```js +var arr = [ { 'foo': 'bar' } ]; +var arr = [ { + 'foo': 'bar' +} ] +``` + +Examples of **correct** code when `"objectsInArrays"` is set to `false`: + + +```js +var arr = [{ 'foo': 'bar' }]; +var arr = [{ + 'foo': 'bar' +}]; +``` + +Examples of **incorrect** code when `"arraysInArrays"` is set to `false`: + +```js +var arr = [ [ 1, 2 ], 2, 3, 4 ]; +var arr = [ [ 1, 2 ], 2, [ 3, 4 ] ]; +``` + +Examples of **correct** code when `"arraysInArrays"` is set to `false`: + +```js +var arr = [[ 1, 2 ], 2, 3, 4 ]; +var arr = [[ 1, 2 ], 2, [ 3, 4 ]]; +``` + +Examples of **incorrect** code when `"arraysInObjects"` is set to `false`: + +```js +var obj = { "foo": [ 1, 2 ] }; +var obj = { "foo": [ "baz", "bar" ] }; +``` + +Examples of **correct** code when `"arraysInObjects"` is set to `false`: + +```js +var obj = { "foo": [ 1, 2 ]}; +var obj = { "foo": [ "baz", "bar" ]}; +``` + +Examples of **incorrect** code when `"objectsInObjects"` is set to `false`: + +```js +var obj = { "foo": { "baz": 1, "bar": 2 } }; +var obj = { "foo": [ "baz", "bar" ], "qux": { "baz": 1, "bar": 2 } }; +``` + +Examples of **correct** code when `"objectsInObjects"` is set to `false`: + +```js +var obj = { "foo": { "baz": 1, "bar": 2 }}; +var obj = { "foo": [ "baz", "bar" ], "qux": { "baz": 1, "bar": 2 }}; +``` + +Examples of **incorrect** code when `"propertyName"` is set to `false`: + +```js +var foo = obj[ 1 ]; +var foo = obj[ bar ]; +``` + +Examples of **correct** code when `"propertyName"` is set to `false`: + +```js +var foo = obj[bar]; +var foo = obj[0, 1]; +``` + +## When Not To Use It + +You can turn this rule off if you are not concerned with the consistency of spacing between brackets. + +## Related Rules + +* [array-bracket-spacing](array-bracket-spacing.md) +* [object-curly-spacing](object-curly-spacing.md) +* [space-in-parens](space-in-parens.md) +* [computed-property-spacing](computed-property-spacing.md) diff --git a/eslint/docs/rules/space-in-parens.md b/eslint/docs/rules/space-in-parens.md new file mode 100644 index 0000000..bfc0b46 --- /dev/null +++ b/eslint/docs/rules/space-in-parens.md @@ -0,0 +1,279 @@ +# Disallow or enforce spaces inside of parentheses (space-in-parens) + +Some style guides require or disallow spaces inside of parentheses: + +```js +foo( 'bar' ); +var x = ( 1 + 2 ) * 3; + +foo('bar'); +var x = (1 + 2) * 3; +``` + +## Rule Details + +This rule will enforce consistent spacing directly inside of parentheses, by disallowing or requiring one or more spaces to the right of `(` and to the left of `)`. + +As long as you do not explicitly disallow empty parentheses using the `"empty"` exception , `()` will be allowed. + +## Options + +There are two options for this rule: + +* `"never"` (default) enforces zero spaces inside of parentheses +* `"always"` enforces a space inside of parentheses + +Depending on your coding conventions, you can choose either option by specifying it in your configuration: + +```json +"space-in-parens": ["error", "always"] +``` + +### "never" + +Examples of **incorrect** code for this rule with the default `"never"` option: + +```js +/*eslint space-in-parens: ["error", "never"]*/ + +foo( 'bar'); +foo('bar' ); +foo( 'bar' ); + +var foo = ( 1 + 2 ) * 3; +( function () { return 'bar'; }() ); +``` + +Examples of **correct** code for this rule with the default `"never"` option: + +```js +/*eslint space-in-parens: ["error", "never"]*/ + +foo(); + +foo('bar'); + +var foo = (1 + 2) * 3; +(function () { return 'bar'; }()); +``` + +### "always" + +Examples of **incorrect** code for this rule with the `"always"` option: + +```js +/*eslint space-in-parens: ["error", "always"]*/ + +foo( 'bar'); +foo('bar' ); +foo('bar'); + +var foo = (1 + 2) * 3; +(function () { return 'bar'; }()); +``` + +Examples of **correct** code for this rule with the `"always"` option: + +```js +/*eslint space-in-parens: ["error", "always"]*/ + +foo(); + +foo( 'bar' ); + +var foo = ( 1 + 2 ) * 3; +( function () { return 'bar'; }() ); +``` + +### Exceptions + +An object literal may be used as a third array item to specify exceptions, with the key `"exceptions"` and an array as the value. These exceptions work in the context of the first option. That is, if `"always"` is set to enforce spacing, then any "exception" will *disallow* spacing. Conversely, if `"never"` is set to disallow spacing, then any "exception" will *enforce* spacing. + +Note that this rule only enforces spacing within parentheses; it does not check spacing within curly or square brackets, but will enforce or disallow spacing of those brackets if and only if they are adjacent to an opening or closing parenthesis. + +The following exceptions are available: `["{}", "[]", "()", "empty"]`. + +### Empty Exception + +Empty parens exception and behavior: + +* `always` allows for both `()` and `( )` +* `never` (default) requires `()` +* `always` excepting `empty` requires `()` +* `never` excepting `empty` requires `( )` (empty parens without a space is here forbidden) + +### Examples + +Examples of **incorrect** code for this rule with the `"never", { "exceptions": ["{}"] }` option: + +```js +/*eslint space-in-parens: ["error", "never", { "exceptions": ["{}"] }]*/ + +foo({bar: 'baz'}); +foo(1, {bar: 'baz'}); +``` + +Examples of **correct** code for this rule with the `"never", { "exceptions": ["{}"] }` option: + +```js +/*eslint space-in-parens: ["error", "never", { "exceptions": ["{}"] }]*/ + +foo( {bar: 'baz'} ); +foo(1, {bar: 'baz'} ); +``` + +Examples of **incorrect** code for this rule with the `"always", { "exceptions": ["{}"] }` option: + +```js +/*eslint space-in-parens: ["error", "always", { "exceptions": ["{}"] }]*/ + +foo( {bar: 'baz'} ); +foo( 1, {bar: 'baz'} ); +``` + +Examples of **correct** code for this rule with the `"always", { "exceptions": ["{}"] }` option: + +```js +/*eslint space-in-parens: ["error", "always", { "exceptions": ["{}"] }]*/ + +foo({bar: 'baz'}); +foo( 1, {bar: 'baz'}); +``` + +Examples of **incorrect** code for this rule with the `"never", { "exceptions": ["[]"] }` option: + +```js +/*eslint space-in-parens: ["error", "never", { "exceptions": ["[]"] }]*/ + +foo([bar, baz]); +foo([bar, baz], 1); +``` + +Examples of **correct** code for this rule with the `"never", { "exceptions": ["[]"] }` option: + +```js +/*eslint space-in-parens: ["error", "never", { "exceptions": ["[]"] }]*/ + +foo( [bar, baz] ); +foo( [bar, baz], 1); +``` + +Examples of **incorrect** code for this rule with the `"always", { "exceptions": ["[]"] }` option: + +```js +/*eslint space-in-parens: ["error", "always", { "exceptions": ["[]"] }]*/ + +foo( [bar, baz] ); +foo( [bar, baz], 1 ); +``` + +Examples of **correct** code for this rule with the `"always", { "exceptions": ["[]"] }` option: + +```js +/*eslint space-in-parens: ["error", "always", { "exceptions": ["[]"] }]*/ + +foo([bar, baz]); +foo([bar, baz], 1 ); +``` + +Examples of **incorrect** code for this rule with the `"never", { "exceptions": ["()"] }]` option: + +```js +/*eslint space-in-parens: ["error", "never", { "exceptions": ["()"] }]*/ + +foo((1 + 2)); +foo((1 + 2), 1); +foo(bar()); +``` + +Examples of **correct** code for this rule with the `"never", { "exceptions": ["()"] }]` option: + +```js +/*eslint space-in-parens: ["error", "never", { "exceptions": ["()"] }]*/ + +foo( (1 + 2) ); +foo( (1 + 2), 1); +foo(bar() ); +``` + +Examples of **incorrect** code for this rule with the `"always", { "exceptions": ["()"] }]` option: + +```js +/*eslint space-in-parens: ["error", "always", { "exceptions": ["()"] }]*/ + +foo( ( 1 + 2 ) ); +foo( ( 1 + 2 ), 1 ); +``` + +Examples of **correct** code for this rule with the `"always", { "exceptions": ["()"] }]` option: + +```js +/*eslint space-in-parens: ["error", "always", { "exceptions": ["()"] }]*/ + +foo(( 1 + 2 )); +foo(( 1 + 2 ), 1 ); +``` + +The `"empty"` exception concerns empty parentheses, and works the same way as the other exceptions, inverting the first option. + +Example of **incorrect** code for this rule with the `"never", { "exceptions": ["empty"] }]` option: + +```js +/*eslint space-in-parens: ["error", "never", { "exceptions": ["empty"] }]*/ + +foo(); +``` + +Example of **correct** code for this rule with the `"never", { "exceptions": ["empty"] }]` option: + +```js +/*eslint space-in-parens: ["error", "never", { "exceptions": ["empty"] }]*/ + +foo( ); +``` + +Example of **incorrect** code for this rule with the `"always", { "exceptions": ["empty"] }]` option: + +```js +/*eslint space-in-parens: ["error", "always", { "exceptions": ["empty"] }]*/ + +foo( ); +``` + +Example of **correct** code for this rule with the `"always", { "exceptions": ["empty"] }]` option: + +```js +/*eslint space-in-parens: ["error", "always", { "exceptions": ["empty"] }]*/ + +foo(); +``` + +You can include multiple entries in the `"exceptions"` array. + +Examples of **incorrect** code for this rule with the `"always", { "exceptions": ["{}", "[]"] }]` option: + +```js +/*eslint space-in-parens: ["error", "always", { "exceptions": ["{}", "[]"] }]*/ + +bar( {bar:'baz'} ); +baz( 1, [1,2] ); +foo( {bar: 'baz'}, [1, 2] ); +``` + +Examples of **correct** code for this rule with the `"always", { "exceptions": ["{}", "[]"] }]` option: + +```js +/*eslint space-in-parens: ["error", "always", { "exceptions": ["{}", "[]"] }]*/ + +bar({bar:'baz'}); +baz( 1, [1,2]); +foo({bar: 'baz'}, [1, 2]); +``` + +## When Not To Use It + +You can turn this rule off if you are not concerned with the consistency of spacing between parentheses. + +## Related Rules + +* [space-in-brackets](space-in-brackets.md) (deprecated) diff --git a/eslint/docs/rules/space-infix-ops.md b/eslint/docs/rules/space-infix-ops.md new file mode 100644 index 0000000..6a00e50 --- /dev/null +++ b/eslint/docs/rules/space-infix-ops.md @@ -0,0 +1,79 @@ +# require spacing around infix operators (space-infix-ops) + +While formatting preferences are very personal, a number of style guides require spaces around operators, such as: + +```js +var sum = 1 + 2; +``` + +The proponents of these extra spaces believe it make the code easier to read and can more easily highlight potential errors, such as: + +```js +var sum = i+++2; +``` + +While this is valid JavaScript syntax, it is hard to determine what the author intended. + +## Rule Details + +This rule is aimed at ensuring there are spaces around infix operators. + +## Options + +This rule accepts a single options argument with the following defaults: + +```json +"space-infix-ops": ["error", { "int32Hint": false }] +``` + +### `int32Hint` + +Set the `int32Hint` option to `true` (default is `false`) to allow write `a|0` without space. + +```js +var foo = bar|0; // `foo` is forced to be signed 32 bit integer +``` + +Examples of **incorrect** code for this rule: + +```js +/*eslint space-infix-ops: "error"*/ +/*eslint-env es6*/ + +a+b + +a+ b + +a +b + +a?b:c + +const a={b:1}; + +var {a=0}=bar; + +function foo(a=0) { } +``` + +Examples of **correct** code for this rule: + +```js +/*eslint space-infix-ops: "error"*/ +/*eslint-env es6*/ + +a + b + +a + b + +a ? b : c + +const a = {b:1}; + +var {a = 0} = bar; + +function foo(a = 0) { } +``` + +## When Not To Use It + +You can turn this rule off if you are not concerned with the consistency of spacing around infix operators. diff --git a/eslint/docs/rules/space-return-throw-case.md b/eslint/docs/rules/space-return-throw-case.md new file mode 100644 index 0000000..024eb3c --- /dev/null +++ b/eslint/docs/rules/space-return-throw-case.md @@ -0,0 +1,33 @@ +# space-return-throw-case: require spaces after `return`, `throw`, and `case` keywords + +(removed) This rule was **removed** in ESLint v2.0 and **replaced** by the [keyword-spacing](keyword-spacing.md) rule. + +(fixable) The `--fix` option on the [command line](../user-guide/command-line-interface#fix) automatically fixed problems reported by this rule. + +Require spaces following `return`, `throw`, and `case`. + +## Rule Details + +Examples of **incorrect** code for this rule: + +```js +/*eslint space-return-throw-case: "error"*/ + +throw{a:0} + +function f(){ return-a; } + +switch(a){ case'a': break; } +``` + +Examples of **correct** code for this rule: + +```js +/*eslint space-return-throw-case: "error"*/ + +throw {a: 0}; + +function f(){ return -a; } + +switch(a){ case 'a': break; } +``` diff --git a/eslint/docs/rules/space-unary-ops.md b/eslint/docs/rules/space-unary-ops.md new file mode 100644 index 0000000..d04bd4a --- /dev/null +++ b/eslint/docs/rules/space-unary-ops.md @@ -0,0 +1,143 @@ +# Require or disallow spaces before/after unary operators (space-unary-ops) + +Some style guides require or disallow spaces before or after unary operators. This is mainly a stylistic issue, however, some JavaScript expressions can be written without spacing which makes it harder to read and maintain. + +## Rule Details + +This rule enforces consistency regarding the spaces after `words` unary operators and after/before `nonwords` unary operators. + +Examples of unary `words` operators: + +```js +// new +var joe = new Person(); + +// delete +var obj = { + foo: 'bar' +}; +delete obj.foo; + +// typeof +typeof {} // object + +// void +void 0 // undefined +``` + +Examples of unary `nonwords` operators: + +```js +if ([1,2,3].indexOf(1) !== -1) {}; +foo = --foo; +bar = bar++; +baz = !foo; +qux = !!baz; +``` + +## Options + +This rule has three options: + +* `words` - applies to unary word operators such as: `new`, `delete`, `typeof`, `void`, `yield` +* `nonwords` - applies to unary operators such as: `-`, `+`, `--`, `++`, `!`, `!!` +* `overrides` - specifies overwriting usage of spacing for each + operator, word or non word. This is empty by default, but can be used + to enforce or disallow spacing around operators. For example: + +```js + "space-unary-ops": [ + 2, { + "words": true, + "nonwords": false, + "overrides": { + "new": false, + "++": true + } + }] +``` + +In this case, spacing will be disallowed after a `new` operator and required before/after a `++` operator. + +Examples of **incorrect** code for this rule with the default `{"words": true, "nonwords": false}` option: + +```js +/*eslint space-unary-ops: "error"*/ + +typeof!foo; + +void{foo:0}; + +new[foo][0]; + +delete(foo.bar); + +++ foo; + +foo --; + +- foo; + ++ "3"; +``` + +```js +/*eslint space-unary-ops: "error"*/ +/*eslint-env es6*/ + +function *foo() { + yield(0) +} +``` + +```js +/*eslint space-unary-ops: "error"*/ + +async function foo() { + await(bar); +} +``` + +Examples of **correct** code for this rule with the `{"words": true, "nonwords": false}` option: + +```js +/*eslint space-unary-ops: "error"*/ + +// Word unary operator "delete" is followed by a whitespace. +delete foo.bar; + +// Word unary operator "new" is followed by a whitespace. +new Foo; + +// Word unary operator "void" is followed by a whitespace. +void 0; + +// Unary operator "++" is not followed by whitespace. +++foo; + +// Unary operator "--" is not preceded by whitespace. +foo--; + +// Unary operator "-" is not followed by whitespace. +-foo; + +// Unary operator "+" is not followed by whitespace. ++"3"; +``` + +```js +/*eslint space-unary-ops: "error"*/ +/*eslint-env es6*/ + +function *foo() { + yield (0) +} +``` + +```js +/*eslint space-unary-ops: "error"*/ + +async function foo() { + await (bar); +} +``` diff --git a/eslint/docs/rules/space-unary-word-ops.md b/eslint/docs/rules/space-unary-word-ops.md new file mode 100644 index 0000000..a155866 --- /dev/null +++ b/eslint/docs/rules/space-unary-word-ops.md @@ -0,0 +1,39 @@ +# space-unary-word-ops: require spaces after unary word operators + +(removed) This rule was **removed** in ESLint v0.10.0 and **replaced** by the [space-unary-ops](space-unary-ops.md) rule. + +Require spaces following unary word operators. + +## Rule Details + +Examples of **incorrect** code for this rule: + +```js +typeof!a +``` + +```js +void{a:0} +``` + +```js +new[a][0] +``` + +```js +delete(a.b) +``` + +Examples of **correct** code for this rule: + +```js +delete a.b +``` + +```js +new C +``` + +```js +void 0 +``` diff --git a/eslint/docs/rules/spaced-comment.md b/eslint/docs/rules/spaced-comment.md new file mode 100644 index 0000000..3d0a5d9 --- /dev/null +++ b/eslint/docs/rules/spaced-comment.md @@ -0,0 +1,271 @@ +# Requires or disallows a whitespace (space or tab) beginning a comment (spaced-comment) + +Some style guides require or disallow a whitespace immediately after the initial `//` or `/*` of a comment. +Whitespace after the `//` or `/*` makes it easier to read text in comments. +On the other hand, commenting out code is easier without having to put a whitespace right after the `//` or `/*`. + +## Rule Details + +This rule will enforce consistency of spacing after the start of a comment `//` or `/*`. It also provides several +exceptions for various documentation styles. + +## Options + +The rule takes two options. + +* The first is a string which be either `"always"` or `"never"`. The default is `"always"`. + + * If `"always"` then the `//` or `/*` must be followed by at least one whitespace. + + * If `"never"` then there should be no whitespace following. + +* This rule can also take a 2nd option, an object with any of the following keys: `"exceptions"` and `"markers"`. + + * The `"exceptions"` value is an array of string patterns which are considered exceptions to the rule. + Please note that exceptions are ignored if the first argument is `"never"`. + + ``` + "spaced-comment": ["error", "always", { "exceptions": ["-", "+"] }] + ``` + + * The `"markers"` value is an array of string patterns which are considered markers for docblock-style comments, + such as an additional `/`, used to denote documentation read by doxygen, vsdoc, etc. which must have additional characters. + The `"markers"` array will apply regardless of the value of the first argument, e.g. `"always"` or `"never"`. + + ``` + "spaced-comment": ["error", "always", { "markers": ["/"] }] + ``` + +The difference between a marker and an exception is that a marker only appears at the beginning of the comment whereas +exceptions can occur anywhere in the comment string. + +You can also define separate exceptions and markers for block and line comments. The `"block"` object can have an additional key `"balanced"`, a boolean that specifies if inline block comments should have balanced spacing. The default value is `false`. + +* If `"balanced": true` and `"always"` then the `/*` must be followed by at least one whitespace, and the `*/` must be preceded by at least one whitespace. + +* If `"balanced": true` and `"never"` then there should be no whitespace following `/*` or preceding `*/`. + +* If `"balanced": false` then balanced whitespace is not enforced. + +```json +"spaced-comment": ["error", "always", { + "line": { + "markers": ["/"], + "exceptions": ["-", "+"] + }, + "block": { + "markers": ["!"], + "exceptions": ["*"], + "balanced": true + } +}] +``` + +### always + +Examples of **incorrect** code for this rule with the `"always"` option: + +```js +/*eslint spaced-comment: ["error", "always"]*/ + +//This is a comment with no whitespace at the beginning + +/*This is a comment with no whitespace at the beginning */ +``` + +```js +/* eslint spaced-comment: ["error", "always", { "block": { "balanced": true } }] */ +/* This is a comment with whitespace at the beginning but not the end*/ +``` + +Examples of **correct** code for this rule with the `"always"` option: + +```js +/* eslint spaced-comment: ["error", "always"] */ + +// This is a comment with a whitespace at the beginning + +/* This is a comment with a whitespace at the beginning */ + +/* + * This is a comment with a whitespace at the beginning + */ + +/* +This comment has a newline +*/ +``` + +```js +/* eslint spaced-comment: ["error", "always"] */ + +/** +* I am jsdoc +*/ +``` + +### never + +Examples of **incorrect** code for this rule with the `"never"` option: + +```js +/*eslint spaced-comment: ["error", "never"]*/ + +// This is a comment with a whitespace at the beginning + +/* This is a comment with a whitespace at the beginning */ + +/* \nThis is a comment with a whitespace at the beginning */ +``` + +```js +/*eslint spaced-comment: ["error", "never", { "block": { "balanced": true } }]*/ +/*This is a comment with whitespace at the end */ +``` + +Examples of **correct** code for this rule with the `"never"` option: + +```js +/*eslint spaced-comment: ["error", "never"]*/ + +/*This is a comment with no whitespace at the beginning */ +``` + +```js +/*eslint spaced-comment: ["error", "never"]*/ + +/** +* I am jsdoc +*/ +``` + +### exceptions + +Examples of **incorrect** code for this rule with the `"always"` option combined with `"exceptions"`: + +```js +/* eslint spaced-comment: ["error", "always", { "block": { "exceptions": ["-"] } }] */ + +//-------------- +// Comment block +//-------------- +``` + +```js +/* eslint spaced-comment: ["error", "always", { "exceptions": ["-", "+"] }] */ + +//------++++++++ +// Comment block +//------++++++++ +``` + +```js +/* eslint spaced-comment: ["error", "always", { "exceptions": ["-", "+"] }] */ + +/*------++++++++*/ +/* Comment block */ +/*------++++++++*/ +``` + +```js +/* eslint spaced-comment: ["error", "always", { "line": { "exceptions": ["-+"] } }] */ + +/*-+-+-+-+-+-+-+*/ +// Comment block +/*-+-+-+-+-+-+-+*/ +``` + +Examples of **correct** code for this rule with the `"always"` option combined with `"exceptions"`: + +```js +/* eslint spaced-comment: ["error", "always", { "exceptions": ["-"] }] */ + +//-------------- +// Comment block +//-------------- +``` + +```js +/* eslint spaced-comment: ["error", "always", { "line": { "exceptions": ["-"] } }] */ + +//-------------- +// Comment block +//-------------- +``` + +```js +/* eslint spaced-comment: ["error", "always", { "exceptions": ["*"] }] */ + +/**************** + * Comment block + ****************/ +``` + +```js +/* eslint spaced-comment: ["error", "always", { "exceptions": ["-+"] }] */ + +//-+-+-+-+-+-+-+ +// Comment block +//-+-+-+-+-+-+-+ + +/*-+-+-+-+-+-+-+*/ +// Comment block +/*-+-+-+-+-+-+-+*/ +``` + +```js +/* eslint spaced-comment: ["error", "always", { "block": { "exceptions": ["-+"] } }] */ + +/*-+-+-+-+-+-+-+*/ +// Comment block +/*-+-+-+-+-+-+-+*/ +``` + +### markers + +Examples of **incorrect** code for this rule with the `"always"` option combined with `"markers"`: + +```js +/* eslint spaced-comment: ["error", "always", { "markers": ["/"] }] */ + +///This is a comment with a marker but without whitespace +``` + +```js +/*eslint spaced-comment: ["error", "always", { "block": { "markers": ["!"], "balanced": true } }]*/ +/*! This is a comment with a marker but without whitespace at the end*/ +``` + +```js +/*eslint spaced-comment: ["error", "never", { "block": { "markers": ["!"], "balanced": true } }]*/ +/*!This is a comment with a marker but with whitespace at the end */ +``` + +Examples of **correct** code for this rule with the `"always"` option combined with `"markers"`: + +```js +/* eslint spaced-comment: ["error", "always", { "markers": ["/"] }] */ + +/// This is a comment with a marker +``` + +```js +/*eslint spaced-comment: ["error", "never", { "markers": ["!<"] }]*/ + +//!
`eslint:recommended` changes + +Two new rules have been added to the [`eslint:recommended`](https://eslint.org/docs/user-guide/configuring#using-eslintrecommended) config: + +* [`no-compare-neg-zero`](/docs/rules/no-compare-neg-zero) disallows comparisons to `-0` +* [`no-useless-escape`](/docs/rules/no-useless-escape) disallows uselessly-escaped characters in strings and regular expressions + +**To address:** To mimic the `eslint:recommended` behavior from 3.x, you can disable these rules in a config file: + +```json +{ + "extends": "eslint:recommended", + + "rules": { + "no-compare-neg-zero": "off", + "no-useless-escape": "off" + } +} +``` + +## The `indent` rule is more strict + +Previously, the [`indent`](/docs/rules/indent) rule was fairly lenient about checking indentation; there were many code patterns where indentation was not validated by the rule. This caused confusion for users, because they were accidentally writing code with incorrect indentation, and they expected ESLint to catch the issues. + +In 4.0.0, the `indent` rule has been rewritten. The new version of the rule will report some indentation errors that the old version of the rule did not catch. Additionally, the indentation of `MemberExpression` nodes, function parameters, and function arguments will now be checked by default (it was previously ignored by default for backwards compatibility). + +To make the upgrade process easier, we've introduced the [`indent-legacy`](/docs/rules/indent-legacy) rule as a snapshot of the `indent` rule from 3.x. If you run into issues from the `indent` rule when you upgrade, you should be able to use the `indent-legacy` rule to replicate the 3.x behavior. However, the `indent-legacy` rule is deprecated and will not receive bugfixes or improvements in the future, so you should eventually switch back to the `indent` rule. + +**To address:** We recommend upgrading without changing your `indent` configuration, and fixing any new indentation errors that appear in your codebase. However, if you want to mimic how the `indent` rule worked in 3.x, you can update your configuration: + +```js +{ + rules: { + indent: "off", + "indent-legacy": "error" // replace this with your previous `indent` configuration + } +} +``` + +## Unrecognized properties in config files now cause a fatal error + +When creating a config, users sometimes make typos or misunderstand how the config is supposed to be structured. Previously, ESLint did not validate the properties of a config file, so a typo in a config could be very tedious to debug. Starting in 4.0.0, ESLint will raise an error if a property in a config file is unrecognized or has the wrong type. + +**To address:** If you see a config validation error after upgrading, verify that your config doesn't contain any typos. If you are using an unrecognized property, you should be able to remove it from your config to restore the previous behavior. + +## .eslintignore patterns are now resolved from the location of the file + +Due to a bug, glob patterns in an `.eslintignore` file were previously resolved from the current working directory of the process, rather than the location of the `.eslintignore` file. Starting in 4.0, patterns in an `.eslintignore` file will be resolved from the `.eslintignore` file's location. + +**To address:** If you use an `.eslintignore` file and you frequently run ESLint from somewhere other than the project root, it's possible that the patterns will be matched differently. You should update the patterns in the `.eslintignore` file to ensure they are relative to the file, not to the working directory. + +## The `padded-blocks` rule is more strict by default + +By default, the [`padded-blocks`](/docs/rules/padded-blocks) rule will now enforce padding in class bodies and switch statements. Previously, the rule would ignore these cases unless the user opted into enforcing them. + +**To address:** If this change results in more linting errors in your codebase, you should fix them or reconfigure the rule. + +## The `space-before-function-paren` rule is more strict by default + +By default, the [`space-before-function-paren`](/docs/rules/space-before-function-paren) rule will now enforce spacing for async arrow functions. Previously, the rule would ignore these cases unless the user opted into enforcing them. + +**To address:** To mimic the default config from 3.x, you can use: + +```json +{ + "rules": { + "space-before-function-paren": ["error", { + "anonymous": "always", + "named": "always", + "asyncArrow": "ignore" + }] + } +} +``` + +## The `no-multi-spaces` rule is more strict by default + +By default, the [`no-multi-spaces`](/docs/rules/no-multi-spaces) rule will now disallow multiple spaces before comments at the end of a line. Previously, the rule did not check this case. + +**To address:** To mimic the default config from 3.x, you can use: + +```json +{ + "rules": { + "no-multi-spaces": ["error", {"ignoreEOLComments": true}] + } +} +``` + +## References to scoped plugins in config files are now required to include the scope + +In 3.x, there was a bug where references to scoped NPM packages as plugins in config files could omit the scope. For example, in 3.x the following config was legal: + +```json +{ + "plugins": [ + "@my-organization/foo" + ], + "rules": { + "foo/some-rule": "error" + } +} +``` + +In other words, it was possible to reference a rule from a scoped plugin (such as `foo/some-rule`) without explicitly stating the `@my-organization` scope. This was a bug because it could lead to ambiguous rule references if there was also an unscoped plugin called `eslint-plugin-foo` loaded at the same time. + +To avoid this ambiguity, in 4.0 references to scoped plugins must include the scope. The config from above should be fixed to: + +```json +{ + "plugins": [ + "@my-organization/foo" + ], + "rules": { + "@my-organization/foo/some-rule": "error" + } +} +``` + +**To address:** If you reference a scoped NPM package as a plugin in a config file, be sure to include the scope wherever you reference it. + +--- + +## `RuleTester` now validates properties of test cases + +Starting in 4.0, the `RuleTester` utility will validate properties of test case objects, and an error will be thrown if an unknown property is encountered. This change was added because we found that it was relatively common for developers to make typos in rule tests, often invalidating the assertions that the test cases were trying to make. + +**To address:** If your tests for custom rules have extra properties, you should remove those properties. + +## AST Nodes no longer have comment properties + +Prior to 4.0, ESLint required parsers to implement comment attachment, a process where AST nodes would gain additional properties corresponding to their leading and trailing comments in the source file. This made it difficult for users to develop custom parsers, because they would have to replicate the confusing comment attachment semantics required by ESLint. + +In 4.0, we have moved away from the concept of comment attachment and have moved all comment handling logic into ESLint itself. This should make it easier to develop custom parsers, but it also means that AST nodes will no longer have `leadingComments` and `trailingComments` properties. Conceptually, rule authors can now think of comments in the context of tokens rather than AST nodes. + +**To address:** If you have a custom rule that depends on the `leadingComments` or `trailingComments` properties of an AST node, you can now use `sourceCode.getCommentsBefore()` and `sourceCode.getCommentsAfter()` instead, respectively. + +Additionally, the `sourceCode` object now also has `sourceCode.getCommentsInside()` (which returns all the comments inside a node), `sourceCode.getAllComments()` (which returns all the comments in the file), and allows comments to be accessed through various other token iterator methods (such as `getTokenBefore()` and `getTokenAfter()`) with the `{ includeComments: true }` option. + +For rule authors concerned about supporting ESLint v3.0 in addition to v4.0, the now deprecated `sourceCode.getComments()` is still available and will work for both versions. + +Finally, please note that the following `SourceCode` methods have been deprecated and will be removed in a future version of ESLint: + +* `getComments()` - replaced by `getCommentsBefore()`, `getCommentsAfter()`, and `getCommentsInside()` +* `getTokenOrCommentBefore()` - replaced by `getTokenBefore()` with the `{ includeComments: true }` option +* `getTokenOrCommentAfter()` - replaced by `getTokenAfter()` with the `{ includeComments: true }` option + +## `LineComment` and `BlockComment` events will no longer be emitted during AST traversal + +Starting in 4.0, `LineComment` and `BlockComments` events will not be emitted during AST traversal. There are two reasons for this: + +* This behavior was relying on comment attachment happening at the parser level, which does not happen anymore, to ensure that all comments would be accounted for +* Thinking of comments in the context of tokens is more predictable and easier to reason about than thinking about comment tokens in the context of AST nodes + +**To address:** Instead of relying on `LineComment` and `BlockComment`, rules can now use `sourceCode.getAllComments()` to get all comments in a file. To check all comments of a specific type, rules can use the following pattern: + +``` +sourceCode.getAllComments().filter(comment => comment.type === "Line"); +sourceCode.getAllComments().filter(comment => comment.type === "Block"); +``` + +## Shebangs are now returned from comment APIs + +Prior to 4.0, shebang comments in a source file would not appear in the output of `sourceCode.getAllComments()` or `sourceCode.getComments()`, but they would appear in the output of `sourceCode.getTokenOrCommentBefore` as line comments. This inconsistency led to some confusion for rule developers. + +In 4.0, shebang comments are treated as comment tokens of type `Shebang` and will be returned by any `SourceCode` method that returns comments. The goal of this change is to make working with shebang comments more consistent with how other tokens are handled. + +**To address:** If you have a custom rule that performs operations on comments, some additional logic might be required to ensure that shebang comments are correctly handled or filtered out: + +``` +sourceCode.getAllComments().filter(comment => comment.type !== "Shebang"); +``` + +--- + +## The `global` property in the `linter.verify()` API is no longer supported + +Previously, the `linter.verify()` API accepted a `global` config option, which was a synonym for the documented `globals` property. The `global` option was never documented or officially supported, and did not work in config files. It has been removed in 4.0. + +**To address:** If you were using the `global` property, please use the `globals` property instead, which does the same thing. + +## More report messages now have full location ranges + +Starting in 3.1.0, rules have been able to specify the *end* location of a reported problem, in addition to the start location, by explicitly specifying an end location in the `report` call. This is useful for tools like editor integrations, which can use the range to precisely display where a reported problem occurs. Starting in 4.0, if a *node* is reported rather than a location, the end location of the range will automatically be inferred from the end location of the node. As a result, many more reported problems will have end locations. + +This is not expected to cause breakage. However, it will likely result in larger report locations than before. For example, if a rule reports the root node of the AST, the reported problem's range will be the entire program. In some integrations, this could result in a poor user experience (e.g. if the entire program is highlighted to indicate an error). + +**To address:** If you have an integration that deals with the ranges of reported problems, make sure you handle large report ranges in a user-friendly way. + +## Some exposed APIs are now ES2015 classes + +The `CLIEngine`, `SourceCode`, and `RuleTester` modules from ESLint's Node.js API are now ES2015 classes. This will not break any documented behavior, but it does have some observable effects (for example, the methods on `CLIEngine.prototype` are now non-enumerable). + +**To address:** If you rely on enumerating the methods of ESLint's Node.js APIs, use a function that can also access non-enumerable properties such as `Object.getOwnPropertyNames`. diff --git a/eslint/docs/user-guide/migrating-to-5.0.0.md b/eslint/docs/user-guide/migrating-to-5.0.0.md new file mode 100644 index 0000000..da5c435 --- /dev/null +++ b/eslint/docs/user-guide/migrating-to-5.0.0.md @@ -0,0 +1,270 @@ +# Migrating to v5.0.0 + +ESLint v5.0.0 is the fifth major version release. We have made a few breaking changes in this release, but we expect that most users will be able to upgrade without any modifications to their build. This guide is intended to walk you through the breaking changes. + +The lists below are ordered roughly by the number of users each change is expected to affect, where the first items are expected to affect the most users. + +### Breaking changes for users + +1. [Node.js 4 is no longer supported](#drop-node-4) +1. [New rules have been added to `eslint:recommended`](#eslint-recommended-changes) +1. [The `experimentalObjectRestSpread` option has been deprecated](#experimental-object-rest-spread) +1. [Linting nonexistent files from the command line is now a fatal error](#nonexistent-files) +1. [The default options for some rules have changed](#rule-default-changes) +1. [Deprecated globals have been removed from the `node`, `browser`, and `jest` environments](#deprecated-globals) +1. [Empty files are now linted](#empty-files) +1. [Plugins in scoped packages are now resolvable in configs](#scoped-plugins) +1. [Multi-line `eslint-disable-line` directives are now reported as problems](#multiline-directives) + +### Breaking changes for plugin/custom rule developers + +1. [The `parent` property of AST nodes is now set before rules start running](#parent-before-rules) +1. [When using the default parser, spread operators now have type `SpreadElement`](#spread-operators) +1. [When using the default parser, rest operators now have type `RestElement`](#rest-operators) +1. [When using the default parser, text nodes in JSX elements now have type `JSXText`](#jsx-text-nodes) +1. [The `context.getScope()` method now returns more proper scopes](#context-get-scope) +1. [The `_linter` property on rule context objects has been removed](#no-context-linter) +1. [`RuleTester` now uses strict equality checks in its assertions](#rule-tester-equality) +1. [Rules are now required to provide messages along with reports](#required-report-messages) + +### Breaking changes for integration developers + +1. [The `source` property is no longer available on individual linting messages](#source-property) +1. [Fatal errors now result in an exit code of 2](#exit-code-two) +1. [The `eslint.linter` property is now non-enumerable](#non-enumerable-linter) + +--- + +## Node.js 4 is no longer supported + +As of April 30th, 2018, Node.js 4 will be at EOL and will no longer be receiving security updates. As a result, we have decided to drop support for it in ESLint v5. We now support the following versions of Node.js: + +* Node.js 6 (6.14.0 and above) +* Node.js 8 (8.10.0 and above) +* Anything above Node.js 9.10.0 + +**To address:** Make sure you upgrade to at least Node.js 6 when using ESLint v5. If you are unable to upgrade, we recommend continuing to use ESLint v4.x until you are able to upgrade Node.js. + +## `eslint:recommended` changes + +Two new rules have been added to the [`eslint:recommended`](https://eslint.org/docs/user-guide/configuring#using-eslintrecommended) config: + +* [`for-direction`](/docs/rules/for-direction) enforces that a `for` loop update clause moves the counter in the right direction. +* [`getter-return`](/docs/rules/getter-return) enforces that a `return` statement is present in property getters. + +**To address:** To mimic the `eslint:recommended` behavior from 4.x, you can disable these rules in a config file: + +```json +{ + "extends": "eslint:recommended", + + "rules": { + "for-direction": "off", + "getter-return": "off" + } +} +``` + +## The `experimentalObjectRestSpread` option has been deprecated + +Previously, when using the default parser it was possible to use the `experimentalObjectRestSpread` option to enable support for [rest/spread properties](https://developers.google.com/web/updates/2017/06/object-rest-spread), as follows: + +```json +{ + "parserOptions": { + "ecmaFeatures": { + "experimentalObjectRestSpread": true + } + } +} +``` + +Object rest/spread is now an official part of the JavaScript language, so our support for it is no longer experimental. In both ESLint v4 and ESLint v5, object rest/spread can now be enabled with the `"ecmaVersion": 2018` option: + +```json +{ + "parserOptions": { + "ecmaVersion": 2018 + } +} +``` + +Note that this also enables parsing for other features from ES2018, such as [async iteration](https://github.com/tc39/proposal-async-iteration). When using ESLint v5 with the default parser, it is no longer possible to toggle syntax support for object rest/spread independently of other features. + +For compatibility, ESLint v5 will treat `ecmaFeatures: { experimentalObjectRestSpread: true }` as an alias for `ecmaVersion: 2018` when the former is found in a config file. As a result, if you use object rest/spread, your code should still parse successfully with ESLint v5. However, note that this alias will be removed in ESLint v6. + +**To address:** If you use the `experimentalObjectRestSpread` option, you should be able to upgrade to ESLint v5 without any changes, but you will encounter a deprecation warning. To avoid the warning, use `ecmaVersion: 2018` in your config file rather than `ecmaFeatures: { experimentalObjectRestSpread: true }`. If you would like to disallow the use of other ES2018 features, consider using rules such as [`no-restricted-syntax`](/docs/rules/no-restricted-syntax). + +## Linting nonexistent files from the command line is now a fatal error + +Previous versions of ESLint silently ignored any nonexistent files and globs provided on the command line: + +```bash +$ eslint nonexistent-file.js 'nonexistent-folder/**/*.js' # exits without any errors in ESLint v4 +``` + +Many users found this behavior confusing, because if they made a typo in a filename, ESLint would appear to lint that file successfully while actually not linting anything. + +ESLint v5 will report a fatal error when either of the following conditions is met: + +* A file provided on the command line does not exist +* A glob or folder provided on the command line does not match any lintable files + +Note that this also affects the [`CLIEngine.executeOnFiles()`](https://eslint.org/docs/developer-guide/nodejs-api#cliengineexecuteonfiles) API. + +**To address:** If you encounter an error about missing files after upgrading to ESLint v5, you may want to double-check that there are no typos in the paths you provide to ESLint. To make the error go away, you can simply remove the given files or globs from the list of arguments provided to ESLint on the command line. + +If you use a boilerplate generator that relies on this behavior (e.g. to generate a script that runs `eslint tests/` in a new project before any test files are actually present), you can work around this issue by adding a dummy file that matches the given pattern (e.g. an empty `tests/index.js` file). + +## The default options for some rules have changed + +* The default options for the [`object-curly-newline`](/docs/rules/object-curly-newline) rule have changed from `{ multiline: true }` to `{ consistent: true }`. +* The default options object for the [`no-self-assign`](/docs/rules/no-self-assign) rule has changed from `{ props: false }` to `{ props: true }`. + +**To address:** To restore the rule behavior from ESLint v4, you can update your config file to include the previous options: + +```json +{ + "rules": { + "object-curly-newline": ["error", { "multiline": true }], + "no-self-assign": ["error", { "props": false }] + } +} +``` + +## Deprecated globals have been removed from the `node`, `browser`, and `jest` environments + +Some global variables have been deprecated or removed for code running in Node.js, browsers, and Jest. (For example, browsers used to expose an `SVGAltGlyphElement` global variable to JavaScript code, but this global has been removed from web standards and is no longer present in browsers.) As a result, we have removed these globals from the corresponding `eslint` environments, so use of these globals will trigger an error when using rules such as [`no-undef`](/docs/rules/no-undef). + +**To address:** If you use deprecated globals in the `node`, `browser`, or `jest` environments, you can add a `globals` section to your configuration to re-enable any globals you need. For example: + +```json +{ + "env": { + "browser": true + }, + "globals": { + "SVGAltGlyphElement": false + } +} +``` + +## Empty files are now linted + +ESLint v4 had a special behavior when linting files that only contain whitespace: it would skip running the parser and rules, and it would always return zero errors. This led to some confusion for users and rule authors, particularly when writing tests for rules. (When writing a stylistic rule, rule authors would occasionally write a test where the source code only contained whitespace, to ensure that the rule behaved correctly when no applicable code was found. However, a test like this would actually not run the rule at all, so an aspect of the rule would end up untested.) + +ESLint v5 treats whitespace-only files the same way as all other files: it parses them and runs enabled rules on them as appropriate. This could lead to additional linting problems if you use a custom rule that reports errors on empty files. + +**To address:** If you have an empty file in your project and you don't want it to be linted, consider adding it to an [`.eslintignore` file](/docs/user-guide/configuring#ignoring-files-and-directories). + +If you have a custom rule, you should make sure it handles empty files appropriately. (In most cases, no changes should be necessary.) + +## Plugins in scoped packages are now resolvable in configs + +When ESLint v5 encounters a plugin name in a config starting with `@`, the plugin will be resolved as a [scoped npm package](https://docs.npmjs.com/misc/scope). For example, if a config contains `"plugins": ["@foo"]`, ESLint v5 will attempt to load a package called `@foo/eslint-plugin`. (On the other hand, ESLint v4 would attempt to load a package called `eslint-plugin-@foo`.) This is a breaking change because users might have been relying on ESLint finding a package at `node_modules/eslint-plugin-@foo`. However, we think it is unlikely that many users were relying on this behavior, because packages published to npm cannot contain an `@` character in the middle. + +**To address:** If you rely on ESLint loading a package like `eslint-config-@foo`, consider renaming the package to something else. + +## Multi-line `eslint-disable-line` directives are now reported as problems + +`eslint-disable-line` and `eslint-disable-next-line` directive comments are only allowed to span a single line. For example, the following directive comment is invalid: + +```js +alert('foo'); /* eslint-disable-line + no-alert */ alert('bar'); + +// (which line is the rule disabled on?) +``` + +Previously, ESLint would ignore these malformed directive comments. ESLint v5 will report an error when it sees a problem like this, so that the issue can be more easily corrected. + +**To address:** If you see new reported errors as a result of this change, ensure that your `eslint-disable-line` directives only span a single line. Note that "block comments" (delimited by `/* */`) are still allowed to be used for directives, provided that the block comments do not contain linebreaks. + +--- + +## The `parent` property of AST nodes is now set before rules start running + +Previously, ESLint would set the `parent` property on each AST node immediately before running rule listeners for that node. This caused some confusion for rule authors, because the `parent` property would not initially be present on any nodes, and it was sometimes necessary to complicate the structure of a rule to ensure that the `parent` property of a given node would be available when needed. + +In ESLint v5, the `parent` property is set on all AST nodes before any rules have access to the AST. This makes it easier to write some rules, because the `parent` property is always available rather than being mutated behind the scenes. However, as a side-effect of having `parent` properties, the AST object has a circular structure the first time a rule sees it (previously, it only had a circular structure after the first rule listeners were called). As a result, a custom rule that enumerates all properties of a node in order to traverse the AST might now loop forever or run out of memory if it does not check for cycles properly. + +**To address:** If you have written a custom rule that enumerates all properties of an AST node, consider excluding the `parent` property or implementing cycle detection to ensure that you obtain the correct result. + +## When using the default parser, spread operators now have type `SpreadElement` + +Previously, when parsing JS code like `const foo = {...data}` with the `experimentalObjectRestSpread` option enabled, the default parser would generate an `ExperimentalSpreadProperty` node type for the `...data` spread element. + +In ESLint v5, the default parser will now always give the `...data` AST node the `SpreadElement` type, even if the (now deprecated) [`experimentalObjectRestSpread`](#experimental-object-rest-spread) option is enabled. This makes the AST compliant with the current ESTree spec. + +**To address:** If you have written a custom rule that relies on spread operators having the `ExperimentalSpreadProperty` type, you should update it to also work with spread operators that have the `SpreadElement` type. + +## When using the default parser, rest operators now have type `RestElement` + +Previously, when parsing JS code like `const {foo, ...rest} = data` with the `experimentalObjectRestSpread` option enabled, the default parser would generate an `ExperimentalRestProperty` node type for the `...data` rest element. + +In ESLint v5, the default parser will now always give the `...data` AST node the `RestElement` type, even if the (now deprecated) [`experimentalObjectRestSpread`](#experimental-object-rest-spread) option is enabled. This makes the AST compliant with the current ESTree spec. + +**To address:** If you have written a custom rule that relies on rest operators having the `ExperimentalRestProperty` type, you should update it to also work with rest operators that have the `RestElement` type. + +## When using the default parser, text nodes in JSX elements now have type `JSXText` + +When parsing JSX code like `foo`, the default parser will now give the `foo` AST node the `JSXText` type, rather than the `Literal` type. This makes the AST compliant with a recent update to the JSX spec. + +**To address:** If you have written a custom rule that relies on text nodes in JSX elements having the `Literal` type, you should update it to also work with nodes that have the `JSXText` type. + +## The `context.getScope()` method now returns more proper scopes + +Previously, the `context.getScope()` method changed its behavior based on the `parserOptions.ecmaVersion` property. However, this could cause confusing behavior when using a parser that doesn't respond to the `ecmaVersion` option, such as `babel-eslint`. + +Additionally, `context.getScope()` incorrectly returned the parent scope of the proper scope on `CatchClause` (in ES5), `ForStatement` (in ≧ES2015), `ForInStatement` (in ≧ES2015), `ForOfStatement`, and `WithStatement` nodes. + +In ESLint v5, the `context.getScope()` method has the same behavior regardless of `parserOptions.ecmaVersion` and returns the proper scope. See [the documentation](../developer-guide/working-with-rules#contextgetscope) for more details on which scopes are returned. + +**To address:** If you have written a custom rule that uses the `context.getScope()` method in node handlers, you may need to update it to account for the modified scope information. + +## The `_linter` property on rule context objects has been removed + +Previously, rule context objects had an undocumented `_linter` property, which was used internally within ESLint to process reports from rules. Some rules used this property to achieve functionality that was not intended to be possible for rules. For example, several plugins used the `_linter` property in a rule to monitor reports from other rules, for the purpose of checking for unused `/* eslint-disable */` directive comments. Although this functionality was useful for users, it could also cause stability problems for projects using ESLint. For example, an upgrade to a rule in one plugin could unexpectedly cause a rule in another plugin to start reporting errors. + +The `_linter` property has been removed in ESLint v5.0, so it is no longer possible to implement rules with this functionality. However, the [`--report-unused-disable-directives`](/docs/user-guide/command-line-interface#--report-unused-disable-directives) CLI flag can be used to flag unused directive comments. + +## `RuleTester` now uses strict equality checks in its assertions + +Previously, `RuleTester` used loose equality when making some of its assertions. For example, if a rule produced the string `"7"` as a result of autofixing, `RuleTester` would allow the number `7` in an `output` assertion, rather than the string `"7"`. In ESLint v5, comparisons from `RuleTester` use strict equality, so an assertion like this will no longer pass. + +**To address:** If you use `RuleTester` to write tests for your custom rules, make sure the expected values in your assertions are strictly equal to the actual values. + +## Rules are now required to provide messages along with reports + +Previously, it was possible for rules to report AST nodes without providing a report message. This was not intended behavior, and as a result the default formatter would crash if a rule omitted a message. However, it was possible to avoid a crash when using a non-default formatter, such as `json`. + +In ESLint v5, reporting a problem without providing a message always results in an error. + +**To address:** If you have written a custom rule that reports a problem without providing a message, update it to provide a message along with the report. + +--- + +## The `source` property is no longer available on individual linting messages + +As announced in [October 2016](/blog/2016/10/eslint-v3.8.0-released#additional-property-on-linting-results), the `source` property has been removed from individual lint message objects. + +**To address:** If you have a formatter or integration which relies on using the `source` property on individual linting messages, you should update it to use the `source` property on file results objects instead. + +## Fatal errors now result in an exit code of 2 + +When using ESLint v4, both of the following scenarios resulted in an exit code of 1 when running ESLint on the command line: + +* Linting completed successfully, but there are some linting errors +* Linting was unsuccessful due to a fatal error (e.g. an invalid config file) + +As a result, it was difficult for an integration to distinguish between the two cases to determine whether it should try to extract linting results from the output. + +In ESLint v5, an unsuccessful linting run due to a fatal error will result in an exit code of 2, rather than 1. + +**To address:** If you have an integration that detects all problems with linting runs by checking whether the exit code is equal to 1, update it to check whether the exit code is nonzero instead. + +## The `eslint.linter` property is now non-enumerable + +When using ESLint's Node.js API, the [`linter`](/docs/developer-guide/nodejs-api#linter-1) property is now non-enumerable. Note that the `linter` property was deprecated in ESLint v4 in favor of the [`Linter`](/docs/developer-guide/nodejs-api#linter) property. + +**To address:** If you rely on enumerating all the properties of the `eslint` object, use something like `Object.getOwnPropertyNames` to ensure that non-enumerable keys are captured. diff --git a/eslint/docs/user-guide/migrating-to-6.0.0.md b/eslint/docs/user-guide/migrating-to-6.0.0.md new file mode 100644 index 0000000..3baec19 --- /dev/null +++ b/eslint/docs/user-guide/migrating-to-6.0.0.md @@ -0,0 +1,330 @@ +# Migrating to v6.0.0 + +ESLint v6.0.0 is a major release of ESLint. We have made a few breaking changes in this release. This guide is intended to walk you through the breaking changes. + +The lists below are ordered roughly by the number of users each change is expected to affect, where the first items are expected to affect the most users. + +### Breaking changes for users + +1. [Node.js 6 is no longer supported](#drop-node-6) +1. [`eslint:recommended` has been updated](#eslint-recommended-changes) +1. [Plugins and shareable configs are no longer affected by ESLint's location](#package-loading-simplification) +1. [The default parser now validates options more strictly](#espree-validation) +1. [Rule configuration are validated more strictly](#rule-config-validating) +1. [The `no-redeclare` rule is now more strict by default](#no-redeclare-updates) +1. [The `comma-dangle` rule is now more strict by default](#comma-dangle-updates) +1. [The `no-confusing-arrow` rule is now more lenient by default](#no-confusing-arrow-updates) +1. [Overrides in a config file can now match dotfiles](#overrides-dotfiles) +1. [Overrides in an extended config file can now be overridden by a parent config file](#overrides-precedence) +1. [Configuration values for globals are now validated](#globals-validation) +1. [The deprecated `experimentalObjectRestSpread` option has been removed](#experimental-object-rest-spread) +1. [User-provided regular expressions in rule options are parsed with the unicode flag](#unicode-regexes) + +### Breaking changes for plugin/custom rule developers + +1. [Plugin authors may need to update installation instructions](#plugin-documentation) +1. [`RuleTester` now validates against invalid `default` keywords in rule schemas](#rule-tester-defaults) +1. [`RuleTester` now requires an absolute path on `parser` option](#rule-tester-parser) +1. [The `eslintExplicitGlobalComment` scope analysis property has been removed](#eslintExplicitGlobalComment) + +### Breaking changes for integration developers + +1. [Plugins and shareable configs are no longer affected by ESLint's location](#package-loading-simplification) +1. [`Linter` no longer tries to load missing parsers from the filesystem](#linter-parsers) + +--- + +## Node.js 6 is no longer supported + +As of April 2019, Node.js 6 will be at EOL and will no longer be receiving security updates. As a result, we have decided to drop support for it in ESLint v6. We now support the following versions of Node.js: + +* Node.js 8 (8.10.0 and above) +* Node.js 10 (10.13.0 and above) +* Anything above Node.js 11.10.1 + +**To address:** Make sure you upgrade to at least Node.js 8 when using ESLint v6. If you are unable to upgrade, we recommend continuing to use ESLint v5.x until you are able to upgrade Node.js. + +**Related issue(s):** [eslint/eslint#11546](https://github.com/eslint/eslint/issues/11456) + +## `eslint:recommended` has been updated + +The following rules have been added to the [`eslint:recommended`](https://eslint.org/docs/user-guide/configuring#using-eslintrecommended) config: + +* [`no-async-promise-executor`](https://eslint.org/docs/rules/no-async-promise-executor) disallows using an `async` function as the argument to the `Promise` constructor, which is usually a bug. +* [`no-misleading-character-class`](https://eslint.org/docs/rules/no-misleading-character-class) reports character classes in regular expressions that might not behave as expected. +* [`no-prototype-builtins`](https://eslint.org/docs/rules/no-prototype-builtins) reports method calls like `foo.hasOwnProperty("bar")` (which are a frequent source of bugs), and suggests that they be replaced with `Object.prototype.hasOwnProperty.call(foo, "bar")` instead. +* [`no-shadow-restricted-names`](https://eslint.org/docs/rules/no-shadow-restricted-names) disallows shadowing variables like `undefined` (e.g. with code like `let undefined = 5;`), since is likely to confuse readers. +* [`no-useless-catch`](https://eslint.org/docs/rules/no-useless-catch) reports `catch` clauses that are redundant and can be removed from the code without changing its behavior. +* [`no-with`](https://eslint.org/docs/rules/no-with) disallows use of the [`with` statement](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with), which can make code difficult to understand and cause compatibility problems. +* [`require-atomic-updates`](https://eslint.org/docs/rules/require-atomic-updates) reports race condition bugs that can occur when reassigning variables in async functions. + +Additionally, the following rule has been *removed* from `eslint:recommended`: + +* [`no-console`](https://eslint.org/docs/rules/no-console) disallows calling functions like `console.log`. While this rule is useful in many cases (e.g. to avoid inadvertently leaving debugging statements in production code), it is not as broadly applicable as the other rules in `eslint:recommended`, and it was a source of false positives in cases where `console.log` is acceptable (e.g. in CLI applications). + +Finally, in ESLint v5 `eslint:recommended` would explicitly disable all core rules that were not considered "recommended". This could cause confusing behavior if `eslint:recommended` was loaded after another config, since `eslint:recommended` would have the effect of turning off some rules. In ESLint v6, `eslint:recommended` has no effect on non-recommended rules. + +**To address:** To mimic the `eslint:recommended` behavior from 5.x, you can explicitly disable/enable rules in a config file as follows: + +```json +{ + "extends": "eslint:recommended", + + "rules": { + "no-async-promise-executor": "off", + "no-misleading-character-class": "off", + "no-prototype-builtins": "off", + "no-shadow-restricted-names": "off", + "no-useless-catch": "off", + "no-with": "off", + "require-atomic-updates": "off", + + "no-console": "error" + } +} +``` + +In rare cases (if you were relying on the previous behavior where `eslint:recommended` disables core rules), you might need to disable additional rules to restore the previous behavior. + +**Related issue(s):** [eslint/eslint#10768](https://github.com/eslint/eslint/issues/10768), [eslint/eslint#10873](https://github.com/eslint/eslint/issues/10873) + +## Plugins and shareable configs are no longer affected by ESLint's location + +Previously, ESLint loaded plugins relative to the location of the ESLint package itself. As a result, we suggested that users with global ESLint installations should also install plugins globally, and users with local ESLint installations should install plugins locally. However, due to a design bug, this strategy caused ESLint to randomly fail to load plugins and shareable configs under certain circumstances, particularly when using package management tools like [`lerna`](https://github.com/lerna/lerna) and [Yarn Plug n' Play](https://yarnpkg.com/lang/en/docs/pnp/). + +As a rule of thumb: With ESLint v6, plugins should always be installed locally, even if ESLint was installed globally. More precisely, ESLint v6 resolves plugins relative to the end user's project by default, and always resolves shareable configs and parsers relative to the location of the config file that imports them. + +**To address:** If you use a global installation of ESLint (e.g. installed with `npm install eslint --global`) along with plugins, you should install those plugins locally in the projects where you run ESLint. If your config file extends shareable configs and/or parsers, you should ensure that those packages are installed as dependencies of the project containing the config file. + +If you use a config file located outside of a local project (with the `--config` flag), consider installing the plugins as dependencies of that config file, and setting the [`--resolve-plugins-relative-to`](./command-line-interface#--resolve-plugins-relative-to) flag to the location of the config file. + +**Related issue(s):** [eslint/eslint#10125](https://github.com/eslint/eslint/issues/10125), [eslint/rfcs#7](https://github.com/eslint/rfcs/pull/7) + +## The default parser now validates options more strictly + +`espree`, the default parser used by ESLint, will now raise an error in the following cases: + +* The `ecmaVersion` parser option is set to something other than a number, such as the string `"2015"`. (Previously, a non-number option would simply be ignored.) +* The `sourceType: "module"` parser option is set while `ecmaVersion` is set to `5` or left unspecified. (Previously, setting `sourceType: "module"` would implicitly cause `ecmaVersion` to be set to a minimum of 2015, which could be surprising.) +* The `sourceType` is set to anything other than `"script"` or `"module"`. + +**To address:** If your config sets `ecmaVersion` to something other than a number, you can restore the previous behavior by removing `ecmaVersion`. (However, you may want to double-check that your config is actually working as expected.) If your config sets `parserOptions: { sourceType: "module" }` without also setting `parserOptions.ecmaVersion`, you should add `parserOptions: { ecmaVersion: 2015 }` to restore the previous behavior. + +**Related issue(s):** [eslint/eslint#9687](https://github.com/eslint/eslint/issues/9687), [eslint/espree#384](https://github.com/eslint/espree/issues/384) + +## Rule configuration are validated more strictly + +To catch config errors earlier, ESLint v6 will report a linting error if you are trying to configure a non-existent rule. + +config | ESLint v5 | ESLint v6 +------------- | ------------- | ------------- +`/*eslint-enable foo*/` | no error | linting error +`/*eslint-disable(-line) foo*/` | no error | linting error +`/*eslint foo: 0*/` | no error | linting error +`{rules: {foo: 0}}` | no error | no error +`{rules: {foo: 1}` | linting warning | linting error + +**To address:** You can remove the non-existent rule in your (inline) config. + +**Related issue(s):** [eslint/eslint#9505](https://github.com/eslint/eslint/issues/9505) + +## The `no-redeclare` rule is now more strict by default + +The default options for the [`no-redeclare`](https://eslint.org/docs/rules/no-redeclare) rule have changed from `{ builtinGlobals: false }` to `{ builtinGlobals: true }`. Additionally, the `no-redeclare` rule will now report an error for globals enabled by comments like `/* global foo */` if those globals were already enabled through configuration anyway. + +**To address:** + +To restore the previous options for the rule, you can configure it as follows: + +```json +{ + "rules": { + "no-redeclare": ["error", { "builtinGlobals": false }] + } +} +``` + +Additionally, if you see new errors for `global` comments in your code, you should remove those comments. + +**Related issue(s):** [eslint/eslint#11370](https://github.com/eslint/eslint/issues/11370), [eslint/eslint#11405](https://github.com/eslint/eslint/issues/11405) + +## The `comma-dangle` rule is now more strict by default + +Previously, the [`comma-dangle`](https://eslint.org/docs/rules/comma-dangle) rule would ignore trailing function arguments and parameters, unless explicitly configured to check for function commas. In ESLint v6, function commas are treated the same way as other types of trailing commas. + +**To address:** You can restore the previous default behavior of the rule with: + +```json +{ + "rules": { + "comma-dangle": ["error", { + "arrays": "never", + "objects": "never", + "imports": "never", + "exports": "never", + "functions": "ignore" + }] + } +} +``` + +To restore the previous behavior of a string option like `"always-multiline"`, replace `"never"` with `"always-multiline"` in the example above. + +**Related issue(s):** [eslint/eslint#11502](https://github.com/eslint/eslint/issues/11502) + +## The `no-confusing-arrow` rule is now more lenient by default + +The default options for the [`no-confusing-arrow`](https://eslint.org/docs/rules/no-confusing-arrow) rule have changed from `{ allowParens: false }` to `{ allowParens: true }`. + +**To address:** You can restore the previous default behavior of the rule with: + +```json +{ + "rules": { + "no-confusing-arrow": ["error", { "allowParens": false }] + } +} +``` + +**Related issue(s):** [eslint/eslint#11503](https://github.com/eslint/eslint/issues/11503) + +## Overrides in a config file can now match dotfiles + +Due to a bug, the glob patterns in a `files` list in an `overrides` section of a config file would never match dotfiles, making it impossible to have overrides apply to files starting with a dot. This bug has been fixed in ESLint v6. + +**To address:** If you don't want dotfiles to be matched by an override, consider adding something like `excludedFiles: [".*"]` to that `overrides` section. See the [documentation](https://eslint.org/docs/user-guide/configuring#configuration-based-on-glob-patterns) for more details. + +**Related issue(s):** [eslint/eslint#11201](https://github.com/eslint/eslint/issues/11201) + +## Overrides in an extended config file can now be overridden by a parent config file + +Due to a bug, it was previously the case that an `overrides` block in a shareable config had precedence over the top level of a parent config. For example, with the following config setup, the `semi` rule would end up enabled even though it was explicitly disabled in the end user's config: + +```js +// .eslintrc.js +module.exports = { + extends: ["foo"], + rules: { + semi: "off" + } +}; +``` + +```js +// eslint-config-foo/index.js +module.exports = { + overrides: { + files: ["*.js"], + rules: { + semi: "error" + } + } +}; +``` + +In ESLint v6.0.0, a parent config always has precedence over extended configs, even with `overrides` blocks. + +**To address:** We expect the impact of this issue to be very low because most shareable configs don't use `overrides` blocks. However, if you use a shareable config with `overrides` blocks, you might encounter a change in behavior due to something that is explicitly specified in your config but was inactive until now. If you would rather inherit the behavior from the shareable config, simply remove the corresponding entry from your own config. (In the example above, the previous behavior could be restored by removing `semi: "off"` from `.eslintrc.js`.) + +**Related issue(s):** [eslint/eslint#11510](https://github.com/eslint/eslint/issues/11510) + +## Configuration values for globals are now validated + +Previously, when configuring a set of global variables with an object, it was possible to use anything as the values of the object. An unknown value would be treated the same as `"writable"`. + +```js +// .eslintrc.js +module.exports = { + globals: { + foo: "readonly", + bar: "writable", + baz: "hello!" // ??? + } +}; +``` + +With this change, any unknown values in a `globals` object result in a config validation error. + +**To address:** If you see config validation errors related to globals after updating, ensure that all values configured for globals are either `readonly`, `writable`, or `off`. (ESLint also accepts some alternate spellings and variants for compatibility.) + +## The deprecated `experimentalObjectRestSpread` option has been removed + +Previously, when using the default parser, a config could use the `experimentalObjectRestSpread` option to enable parsing support for object rest/spread properties: + +```json +{ + "parserOptions": { + "ecmaFeatures": { + "experimentalObjectRestSpread": true + } + } +} +``` + +Since ESLint v5, `ecmaFeatures: { experimentalObjectRestSpread: true }` has been equivalent to `ecmaVersion: 2018`, and has also emitted a deprecation warning. In ESLint v6, the `experimentalObjectRestSpread` feature has been removed entirely and has no effect. If your config was relying on `experimentalObjectRestSpread` to enable ES2018 parsing, you might start seeing parsing errors for recent syntax. + +**To address:** If you use the `experimentalObjectRestSpread` option, you should change your config to contain this instead: + +```json +{ + "parserOptions": { + "ecmaVersion": 2018 + } +} +``` + +If you're not sure which config file needs to be updated, it may be useful to run ESLint v5 and look at what config file is mentioned in the deprecation warning. + +**Related issue(s):** [eslint/eslint#9990](https://github.com/eslint/eslint/issues/9990) + +## User-provided regular expressions in rule options are parsed with the unicode flag + +Rules like [`max-len`](/docs/rules/max-len) accept a string option which is interpreted as a regular expression. In ESLint v6.0.0, these regular expressions are interpreted with the [unicode flag](https://mathiasbynens.be/notes/es6-unicode-regex), which should exhibit more reasonable behavior when matching characters like astral symbols. Unicode regexes also validate escape sequences more strictly than non-unicode regexes. + +**To address:** If you get rule option validation errors after upgrading, ensure that any regular expressions in your rule options have no invalid escape sequences. + +**Related issue(s):** [eslint/eslint#11423](https://github.com/eslint/eslint/issues/11423) + +--- + +## Plugin authors may need to update installation instructions + +If you maintain a plugin and provide installation instructions, you should ensure that the installation instructions are up to date with the [user-facing changes to how plugins are loaded](#package-loading-simplification). In particular, if your plugin was generated with the [`generator-eslint`](https://github.com/eslint/generator-eslint) package, it likely contains outdated instructions for how to use the plugin with global ESLint installations. + +**Related issue(s):** [eslint/rfcs#7](https://github.com/eslint/rfcs/pull/7) + +## `RuleTester` now validates against invalid `default` keywords in rule schemas + +In some cases, rule schemas can use the `default` keyword to automatically specify default values for rule options. However, the `default` keyword is only effective in certain schema locations, and is ignored elsewhere, which creates a risk of bugs if a rule incorrectly expects a default value to be provided as a rule option. In ESLint v6.0.0, `RuleTester` will raise an error if a rule has an invalid `default` keyword in its schema. + +**To address:** If `RuleTester` starts reporting an error about an invalid default, you can remove the `default` property at the indicated location in your rule schema, and the rule will behave the same way. (If this happens, you might also want to verify that the rule behaves correctly when no option value is provided in that location.) + +**Related issue(s):** [eslint/eslint#11473](https://github.com/eslint/eslint/issues/11473) + +## `RuleTester` now requires an absolute path on `parser` option + +To use custom parsers in tests, we could use `parser` property with a package name or file path. However, if a package name was given, it's unclear where the tester should load the parser package from because the tester doesn't know which files are running the tester. In ESLint v6.0.0, `RuleTester` disallows `parser` property with a package name. + +**To address:** If you use `parser` property with package names in test cases, update it with `require.resolve()` function to resolve the package name to the absolute path to the package. + +**Related issue(s):** [eslint/eslint#11728](https://github.com/eslint/eslint/issues/11728), [eslint/eslint#10125](https://github.com/eslint/eslint/issues/10125), [eslint/rfcs#7](https://github.com/eslint/rfcs/pull/7) + +## The `eslintExplicitGlobalComment` scope analysis property has been removed + +Previously, ESLint would add an `eslintExplicitGlobalComment` property to `Variable` objects in scope analysis to indicate that a variable was introduced as a result of a `/* global */` comment. This property was undocumented, and the ESLint team was unable to find any usage of the property outside of ESLint core. The property has been removed in ESLint v6, and replaced with the `eslintExplicitGlobalComments` property, which can contain a list of all `/* global */` comments if a variable was declared with more than one of them. + +**To address:** If you maintain a rule that uses the `eslintExplicitGlobalComment` property, update it to use the `eslintExplicitGlobalComments` property as a list instead. + +**Related issue(s):** [eslint/rfcs#17](https://github.com/eslint/rfcs/pull/17) + +--- + +## `Linter` no longer tries to load missing parsers from the filesystem + +Previously, when linting code with a parser that had not been previously defined, the `Linter` API would attempt to load the parser from the filesystem. However, this behavior was confusing because `Linter` never access the filesystem in any other cases, and it was difficult to ensure that the correct parser would be found when loading the parser from the filesystem. + +In ESLint v6, `Linter` will no longer perform any filesystem operations, including loading parsers. + +**To address:** If you're using `Linter` with a custom parser, use [`Linter#defineParser`](https://eslint.org/docs/developer-guide/nodejs-api#linterdefineparser) to explicitly define the parser before linting any code. + +**Related issue(s):** [eslint/rfcs#7](https://github.com/eslint/rfcs/pull/7) diff --git a/eslint/docs/user-guide/rule-deprecation.md b/eslint/docs/user-guide/rule-deprecation.md new file mode 100644 index 0000000..e306916 --- /dev/null +++ b/eslint/docs/user-guide/rule-deprecation.md @@ -0,0 +1,13 @@ +# Rule Deprecation + +Balancing the trade-offs of improving a tool and the frustration these changes can cause is a difficult task. One key area in which this affects our users is in the removal of rules. + +The ESLint team is committed to making upgrading as easy and painless as possible. To that end, the team has agreed upon the following set of guidelines for deprecating rules in the future. The goal of these guidelines is to allow for improvements and changes to be made without breaking existing configurations. + +* Rules will never be removed from ESLint. +* Rules will be deprecated as needed, and marked as such in all documentation. +* After a rule has been deprecated, the team will no longer do any work on it. This includes bug fixes, enhancements, and updates to the rule's documentation. Issues and pull requests related to deprecated rule will not be accepted and will be closed. + +Since deprecated rules will never be removed, you can continue to use them indefinitely if they are working for you. However, keep in mind that deprecated rules will effectively be unmaintained. + +We hope that by following these guidelines we will be able to continue improving and working to make ESLint the best tool it can be while causing as little disruption to our users as possible during the process. diff --git a/eslint/karma.conf.js b/eslint/karma.conf.js new file mode 100644 index 0000000..dfc6bab --- /dev/null +++ b/eslint/karma.conf.js @@ -0,0 +1,101 @@ +"use strict"; + +process.env.CHROME_BIN = require("puppeteer").executablePath(); + +module.exports = function(config) { + config.set({ + + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: "", + + + /* + * frameworks to use + * available frameworks: https://npmjs.org/browse/keyword/karma-adapter + */ + frameworks: ["mocha"], + + + // list of files / patterns to load in the browser + files: [ + "build/eslint.js", + "tests/lib/linter/linter.js" + ], + + + // list of files to exclude + exclude: [ + ], + + + /* + * preprocess matching files before serving them to the browser + * available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + */ + preprocessors: { + "tests/lib/linter/linter.js": ["webpack"] + }, + webpack: { + mode: "none", + stats: "errors-only" + }, + webpackMiddleware: { + logLevel: "error" + }, + + + /* + * test results reporter to use + * possible values: "dots", "progress" + * available reporters: https://npmjs.org/browse/keyword/karma-reporter + */ + reporters: ["mocha"], + + mochaReporter: { + output: "minimal" + }, + + // web server port + port: 9876, + + + // enable / disable colors in the output (reporters and logs) + colors: true, + + + /* + * level of logging + * possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + */ + logLevel: config.LOG_INFO, + + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: false, + + + /* + * start these browsers + * available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + */ + browsers: ["HeadlessChrome"], + customLaunchers: { + HeadlessChrome: { + base: "ChromeHeadless", + flags: ["--no-sandbox"] + } + }, + + /* + * Continuous Integration mode + * if true, Karma captures browsers, runs the tests and exits + */ + singleRun: true, + + /* + * Concurrency level + * how many browser should be started simultaneous + */ + concurrency: Infinity + }); +}; diff --git a/eslint/lib/api.js b/eslint/lib/api.js new file mode 100644 index 0000000..40a5cc9 --- /dev/null +++ b/eslint/lib/api.js @@ -0,0 +1,32 @@ +/** + * @fileoverview Expose out ESLint and CLI to require. + * @author Ian Christian Myers + */ + +"use strict"; + +const { CLIEngine } = require("./cli-engine"); +const { Linter } = require("./linter"); +const { RuleTester } = require("./rule-tester"); +const { SourceCode } = require("./source-code"); + +module.exports = { + Linter, + CLIEngine, + RuleTester, + SourceCode +}; + +// DOTO: remove deprecated API. +let deprecatedLinterInstance = null; + +Object.defineProperty(module.exports, "linter", { + enumerable: false, + get() { + if (!deprecatedLinterInstance) { + deprecatedLinterInstance = new Linter(); + } + + return deprecatedLinterInstance; + } +}); diff --git a/eslint/lib/cli-engine/cascading-config-array-factory.js b/eslint/lib/cli-engine/cascading-config-array-factory.js new file mode 100644 index 0000000..b53f67b --- /dev/null +++ b/eslint/lib/cli-engine/cascading-config-array-factory.js @@ -0,0 +1,478 @@ +/** + * @fileoverview `CascadingConfigArrayFactory` class. + * + * `CascadingConfigArrayFactory` class has a responsibility: + * + * 1. Handles cascading of config files. + * + * It provides two methods: + * + * - `getConfigArrayForFile(filePath)` + * Get the corresponded configuration of a given file. This method doesn't + * throw even if the given file didn't exist. + * - `clearCache()` + * Clear the internal cache. You have to call this method when + * `additionalPluginPool` was updated if `baseConfig` or `cliConfig` depends + * on the additional plugins. (`CLIEngine#addPlugin()` method calls this.) + * + * @author Toru Nagashima + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const os = require("os"); +const path = require("path"); +const { validateConfigArray } = require("../shared/config-validator"); +const { emitDeprecationWarning } = require("../shared/deprecation-warnings"); +const { ConfigArrayFactory } = require("./config-array-factory"); +const { ConfigArray, ConfigDependency, IgnorePattern } = require("./config-array"); +const loadRules = require("./load-rules"); +const debug = require("debug")("eslint:cascading-config-array-factory"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +// Define types for VSCode IntelliSense. +/** @typedef {import("../shared/types").ConfigData} ConfigData */ +/** @typedef {import("../shared/types").Parser} Parser */ +/** @typedef {import("../shared/types").Plugin} Plugin */ +/** @typedef {ReturnType} ConfigArray */ + +/** + * @typedef {Object} CascadingConfigArrayFactoryOptions + * @property {Map} [additionalPluginPool] The map for additional plugins. + * @property {ConfigData} [baseConfig] The config by `baseConfig` option. + * @property {ConfigData} [cliConfig] The config by CLI options (`--env`, `--global`, `--ignore-pattern`, `--parser`, `--parser-options`, `--plugin`, and `--rule`). CLI options overwrite the setting in config files. + * @property {string} [cwd] The base directory to start lookup. + * @property {string} [ignorePath] The path to the alternative file of `.eslintignore`. + * @property {string[]} [rulePaths] The value of `--rulesdir` option. + * @property {string} [specificConfigPath] The value of `--config` option. + * @property {boolean} [useEslintrc] if `false` then it doesn't load config files. + */ + +/** + * @typedef {Object} CascadingConfigArrayFactoryInternalSlots + * @property {ConfigArray} baseConfigArray The config array of `baseConfig` option. + * @property {ConfigData} baseConfigData The config data of `baseConfig` option. This is used to reset `baseConfigArray`. + * @property {ConfigArray} cliConfigArray The config array of CLI options. + * @property {ConfigData} cliConfigData The config data of CLI options. This is used to reset `cliConfigArray`. + * @property {ConfigArrayFactory} configArrayFactory The factory for config arrays. + * @property {Map} configCache The cache from directory paths to config arrays. + * @property {string} cwd The base directory to start lookup. + * @property {WeakMap} finalizeCache The cache from config arrays to finalized config arrays. + * @property {string} [ignorePath] The path to the alternative file of `.eslintignore`. + * @property {string[]|null} rulePaths The value of `--rulesdir` option. This is used to reset `baseConfigArray`. + * @property {string|null} specificConfigPath The value of `--config` option. This is used to reset `cliConfigArray`. + * @property {boolean} useEslintrc if `false` then it doesn't load config files. + */ + +/** @type {WeakMap} */ +const internalSlotsMap = new WeakMap(); + +/** + * Create the config array from `baseConfig` and `rulePaths`. + * @param {CascadingConfigArrayFactoryInternalSlots} slots The slots. + * @returns {ConfigArray} The config array of the base configs. + */ +function createBaseConfigArray({ + configArrayFactory, + baseConfigData, + rulePaths, + cwd +}) { + const baseConfigArray = configArrayFactory.create( + baseConfigData, + { name: "BaseConfig" } + ); + + /* + * Create the config array element for the default ignore patterns. + * This element has `ignorePattern` property that ignores the default + * patterns in the current working directory. + */ + baseConfigArray.unshift(configArrayFactory.create( + { ignorePatterns: IgnorePattern.DefaultPatterns }, + { name: "DefaultIgnorePattern" } + )[0]); + + /* + * Load rules `--rulesdir` option as a pseudo plugin. + * Use a pseudo plugin to define rules of `--rulesdir`, so we can validate + * the rule's options with only information in the config array. + */ + if (rulePaths && rulePaths.length > 0) { + baseConfigArray.push({ + type: "config", + name: "--rulesdir", + filePath: "", + plugins: { + "": new ConfigDependency({ + definition: { + rules: rulePaths.reduce( + (map, rulesPath) => Object.assign( + map, + loadRules(rulesPath, cwd) + ), + {} + ) + }, + filePath: "", + id: "", + importerName: "--rulesdir", + importerPath: "" + }) + } + }); + } + + return baseConfigArray; +} + +/** + * Create the config array from CLI options. + * @param {CascadingConfigArrayFactoryInternalSlots} slots The slots. + * @returns {ConfigArray} The config array of the base configs. + */ +function createCLIConfigArray({ + cliConfigData, + configArrayFactory, + cwd, + ignorePath, + specificConfigPath +}) { + const cliConfigArray = configArrayFactory.create( + cliConfigData, + { name: "CLIOptions" } + ); + + cliConfigArray.unshift( + ...(ignorePath + ? configArrayFactory.loadESLintIgnore(ignorePath) + : configArrayFactory.loadDefaultESLintIgnore()) + ); + + if (specificConfigPath) { + cliConfigArray.unshift( + ...configArrayFactory.loadFile( + specificConfigPath, + { name: "--config", basePath: cwd } + ) + ); + } + + return cliConfigArray; +} + +/** + * The error type when there are files matched by a glob, but all of them have been ignored. + */ +class ConfigurationNotFoundError extends Error { + + // eslint-disable-next-line jsdoc/require-description + /** + * @param {string} directoryPath The directory path. + */ + constructor(directoryPath) { + super(`No ESLint configuration found in ${directoryPath}.`); + this.messageTemplate = "no-config-found"; + this.messageData = { directoryPath }; + } +} + +/** + * This class provides the functionality that enumerates every file which is + * matched by given glob patterns and that configuration. + */ +class CascadingConfigArrayFactory { + + /** + * Initialize this enumerator. + * @param {CascadingConfigArrayFactoryOptions} options The options. + */ + constructor({ + additionalPluginPool = new Map(), + baseConfig: baseConfigData = null, + cliConfig: cliConfigData = null, + cwd = process.cwd(), + ignorePath, + resolvePluginsRelativeTo, + rulePaths = [], + specificConfigPath = null, + useEslintrc = true + } = {}) { + const configArrayFactory = new ConfigArrayFactory({ + additionalPluginPool, + cwd, + resolvePluginsRelativeTo + }); + + internalSlotsMap.set(this, { + baseConfigArray: createBaseConfigArray({ + baseConfigData, + configArrayFactory, + cwd, + rulePaths + }), + baseConfigData, + cliConfigArray: createCLIConfigArray({ + cliConfigData, + configArrayFactory, + cwd, + ignorePath, + specificConfigPath + }), + cliConfigData, + configArrayFactory, + configCache: new Map(), + cwd, + finalizeCache: new WeakMap(), + ignorePath, + rulePaths, + specificConfigPath, + useEslintrc + }); + } + + /** + * The path to the current working directory. + * This is used by tests. + * @type {string} + */ + get cwd() { + const { cwd } = internalSlotsMap.get(this); + + return cwd; + } + + /** + * Get the config array of a given file. + * If `filePath` was not given, it returns the config which contains only + * `baseConfigData` and `cliConfigData`. + * @param {string} [filePath] The file path to a file. + * @param {Object} [options] The options. + * @param {boolean} [options.ignoreNotFoundError] If `true` then it doesn't throw `ConfigurationNotFoundError`. + * @returns {ConfigArray} The config array of the file. + */ + getConfigArrayForFile(filePath, { ignoreNotFoundError = false } = {}) { + const { + baseConfigArray, + cliConfigArray, + cwd + } = internalSlotsMap.get(this); + + if (!filePath) { + return new ConfigArray(...baseConfigArray, ...cliConfigArray); + } + + const directoryPath = path.dirname(path.resolve(cwd, filePath)); + + debug(`Load config files for ${directoryPath}.`); + + return this._finalizeConfigArray( + this._loadConfigInAncestors(directoryPath), + directoryPath, + ignoreNotFoundError + ); + } + + /** + * Clear config cache. + * @returns {void} + */ + clearCache() { + const slots = internalSlotsMap.get(this); + + slots.baseConfigArray = createBaseConfigArray(slots); + slots.cliConfigArray = createCLIConfigArray(slots); + slots.configCache.clear(); + } + + /** + * Load and normalize config files from the ancestor directories. + * @param {string} directoryPath The path to a leaf directory. + * @param {boolean} configsExistInSubdirs `true` if configurations exist in subdirectories. + * @returns {ConfigArray} The loaded config. + * @private + */ + _loadConfigInAncestors(directoryPath, configsExistInSubdirs = false) { + const { + baseConfigArray, + configArrayFactory, + configCache, + cwd, + useEslintrc + } = internalSlotsMap.get(this); + + if (!useEslintrc) { + return baseConfigArray; + } + + let configArray = configCache.get(directoryPath); + + // Hit cache. + if (configArray) { + debug(`Cache hit: ${directoryPath}.`); + return configArray; + } + debug(`No cache found: ${directoryPath}.`); + + const homePath = os.homedir(); + + // Consider this is root. + if (directoryPath === homePath && cwd !== homePath) { + debug("Stop traversing because of considered root."); + if (configsExistInSubdirs) { + const filePath = ConfigArrayFactory.getPathToConfigFileInDirectory(directoryPath); + + if (filePath) { + emitDeprecationWarning( + filePath, + "ESLINT_PERSONAL_CONFIG_SUPPRESS" + ); + } + } + return this._cacheConfig(directoryPath, baseConfigArray); + } + + // Load the config on this directory. + try { + configArray = configArrayFactory.loadInDirectory(directoryPath); + } catch (error) { + /* istanbul ignore next */ + if (error.code === "EACCES") { + debug("Stop traversing because of 'EACCES' error."); + return this._cacheConfig(directoryPath, baseConfigArray); + } + throw error; + } + + if (configArray.length > 0 && configArray.isRoot()) { + debug("Stop traversing because of 'root:true'."); + configArray.unshift(...baseConfigArray); + return this._cacheConfig(directoryPath, configArray); + } + + // Load from the ancestors and merge it. + const parentPath = path.dirname(directoryPath); + const parentConfigArray = parentPath && parentPath !== directoryPath + ? this._loadConfigInAncestors( + parentPath, + configsExistInSubdirs || configArray.length > 0 + ) + : baseConfigArray; + + if (configArray.length > 0) { + configArray.unshift(...parentConfigArray); + } else { + configArray = parentConfigArray; + } + + // Cache and return. + return this._cacheConfig(directoryPath, configArray); + } + + /** + * Freeze and cache a given config. + * @param {string} directoryPath The path to a directory as a cache key. + * @param {ConfigArray} configArray The config array as a cache value. + * @returns {ConfigArray} The `configArray` (frozen). + */ + _cacheConfig(directoryPath, configArray) { + const { configCache } = internalSlotsMap.get(this); + + Object.freeze(configArray); + configCache.set(directoryPath, configArray); + + return configArray; + } + + /** + * Finalize a given config array. + * Concatenate `--config` and other CLI options. + * @param {ConfigArray} configArray The parent config array. + * @param {string} directoryPath The path to the leaf directory to find config files. + * @param {boolean} ignoreNotFoundError If `true` then it doesn't throw `ConfigurationNotFoundError`. + * @returns {ConfigArray} The loaded config. + * @private + */ + _finalizeConfigArray(configArray, directoryPath, ignoreNotFoundError) { + const { + cliConfigArray, + configArrayFactory, + finalizeCache, + useEslintrc + } = internalSlotsMap.get(this); + + let finalConfigArray = finalizeCache.get(configArray); + + if (!finalConfigArray) { + finalConfigArray = configArray; + + // Load the personal config if there are no regular config files. + if ( + useEslintrc && + configArray.every(c => !c.filePath) && + cliConfigArray.every(c => !c.filePath) // `--config` option can be a file. + ) { + const homePath = os.homedir(); + + debug("Loading the config file of the home directory:", homePath); + + const personalConfigArray = configArrayFactory.loadInDirectory( + homePath, + { name: "PersonalConfig" } + ); + + if ( + personalConfigArray.length > 0 && + !directoryPath.startsWith(homePath) + ) { + const lastElement = + personalConfigArray[personalConfigArray.length - 1]; + + emitDeprecationWarning( + lastElement.filePath, + "ESLINT_PERSONAL_CONFIG_LOAD" + ); + } + + finalConfigArray = finalConfigArray.concat(personalConfigArray); + } + + // Apply CLI options. + if (cliConfigArray.length > 0) { + finalConfigArray = finalConfigArray.concat(cliConfigArray); + } + + // Validate rule settings and environments. + validateConfigArray(finalConfigArray); + + // Cache it. + Object.freeze(finalConfigArray); + finalizeCache.set(configArray, finalConfigArray); + + debug( + "Configuration was determined: %o on %s", + finalConfigArray, + directoryPath + ); + } + + // At least one element (the default ignore patterns) exists. + if (!ignoreNotFoundError && useEslintrc && finalConfigArray.length <= 1) { + throw new ConfigurationNotFoundError(directoryPath); + } + + return finalConfigArray; + } +} + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = { CascadingConfigArrayFactory }; diff --git a/eslint/lib/cli-engine/cli-engine.js b/eslint/lib/cli-engine/cli-engine.js new file mode 100644 index 0000000..72d1fa4 --- /dev/null +++ b/eslint/lib/cli-engine/cli-engine.js @@ -0,0 +1,1021 @@ +/** + * @fileoverview Main CLI object. + * @author Nicholas C. Zakas + */ + +"use strict"; + +/* + * The CLI object should *not* call process.exit() directly. It should only return + * exit codes. This allows other programs to use the CLI object and still control + * when the program exits. + */ + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const fs = require("fs"); +const path = require("path"); +const defaultOptions = require("../../conf/default-cli-options"); +const pkg = require("../../package.json"); +const ConfigOps = require("../shared/config-ops"); +const naming = require("../shared/naming"); +const ModuleResolver = require("../shared/relative-module-resolver"); +const { Linter } = require("../linter"); +const builtInRules = require("../rules"); +const { CascadingConfigArrayFactory } = require("./cascading-config-array-factory"); +const { IgnorePattern, getUsedExtractedConfigs } = require("./config-array"); +const { FileEnumerator } = require("./file-enumerator"); +const hash = require("./hash"); +const LintResultCache = require("./lint-result-cache"); + +const debug = require("debug")("eslint:cli-engine"); +const validFixTypes = new Set(["problem", "suggestion", "layout"]); + +//------------------------------------------------------------------------------ +// Typedefs +//------------------------------------------------------------------------------ + +// For VSCode IntelliSense +/** @typedef {import("../shared/types").ConfigData} ConfigData */ +/** @typedef {import("../shared/types").LintMessage} LintMessage */ +/** @typedef {import("../shared/types").ParserOptions} ParserOptions */ +/** @typedef {import("../shared/types").Plugin} Plugin */ +/** @typedef {import("../shared/types").RuleConf} RuleConf */ +/** @typedef {import("../shared/types").Rule} Rule */ +/** @typedef {ReturnType} ConfigArray */ +/** @typedef {ReturnType} ExtractedConfig */ + +/** + * The options to configure a CLI engine with. + * @typedef {Object} CLIEngineOptions + * @property {boolean} allowInlineConfig Enable or disable inline configuration comments. + * @property {ConfigData} baseConfig Base config object, extended by all configs used with this CLIEngine instance + * @property {boolean} cache Enable result caching. + * @property {string} cacheLocation The cache file to use instead of .eslintcache. + * @property {string} configFile The configuration file to use. + * @property {string} cwd The value to use for the current working directory. + * @property {string[]} envs An array of environments to load. + * @property {string[]|null} extensions An array of file extensions to check. + * @property {boolean|Function} fix Execute in autofix mode. If a function, should return a boolean. + * @property {string[]} fixTypes Array of rule types to apply fixes for. + * @property {string[]} globals An array of global variables to declare. + * @property {boolean} ignore False disables use of .eslintignore. + * @property {string} ignorePath The ignore file to use instead of .eslintignore. + * @property {string|string[]} ignorePattern One or more glob patterns to ignore. + * @property {boolean} useEslintrc False disables looking for .eslintrc + * @property {string} parser The name of the parser to use. + * @property {ParserOptions} parserOptions An object of parserOption settings to use. + * @property {string[]} plugins An array of plugins to load. + * @property {Record} rules An object of rules to use. + * @property {string[]} rulePaths An array of directories to load custom rules from. + * @property {boolean} reportUnusedDisableDirectives `true` adds reports for unused eslint-disable directives + * @property {boolean} globInputPaths Set to false to skip glob resolution of input file paths to lint (default: true). If false, each input file paths is assumed to be a non-glob path to an existing file. + * @property {string} resolvePluginsRelativeTo The folder where plugins should be resolved from, defaulting to the CWD + */ + +/** + * A linting result. + * @typedef {Object} LintResult + * @property {string} filePath The path to the file that was linted. + * @property {LintMessage[]} messages All of the messages for the result. + * @property {number} errorCount Number of errors for the result. + * @property {number} warningCount Number of warnings for the result. + * @property {number} fixableErrorCount Number of fixable errors for the result. + * @property {number} fixableWarningCount Number of fixable warnings for the result. + * @property {string} [source] The source code of the file that was linted. + * @property {string} [output] The source code of the file that was linted, with as many fixes applied as possible. + */ + +/** + * Information of deprecated rules. + * @typedef {Object} DeprecatedRuleInfo + * @property {string} ruleId The rule ID. + * @property {string[]} replacedBy The rule IDs that replace this deprecated rule. + */ + +/** + * Linting results. + * @typedef {Object} LintReport + * @property {LintResult[]} results All of the result. + * @property {number} errorCount Number of errors for the result. + * @property {number} warningCount Number of warnings for the result. + * @property {number} fixableErrorCount Number of fixable errors for the result. + * @property {number} fixableWarningCount Number of fixable warnings for the result. + * @property {DeprecatedRuleInfo[]} usedDeprecatedRules The list of used deprecated rules. + */ + +/** + * Private data for CLIEngine. + * @typedef {Object} CLIEngineInternalSlots + * @property {Map} additionalPluginPool The map for additional plugins. + * @property {string} cacheFilePath The path to the cache of lint results. + * @property {CascadingConfigArrayFactory} configArrayFactory The factory of configs. + * @property {(filePath: string) => boolean} defaultIgnores The default predicate function to check if a file ignored or not. + * @property {FileEnumerator} fileEnumerator The file enumerator. + * @property {ConfigArray[]} lastConfigArrays The list of config arrays that the last `executeOnFiles` or `executeOnText` used. + * @property {LintResultCache|null} lintResultCache The cache of lint results. + * @property {Linter} linter The linter instance which has loaded rules. + * @property {CLIEngineOptions} options The normalized options of this instance. + */ + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** @type {WeakMap} */ +const internalSlotsMap = new WeakMap(); + +/** + * Determines if each fix type in an array is supported by ESLint and throws + * an error if not. + * @param {string[]} fixTypes An array of fix types to check. + * @returns {void} + * @throws {Error} If an invalid fix type is found. + */ +function validateFixTypes(fixTypes) { + for (const fixType of fixTypes) { + if (!validFixTypes.has(fixType)) { + throw new Error(`Invalid fix type "${fixType}" found.`); + } + } +} + +/** + * It will calculate the error and warning count for collection of messages per file + * @param {LintMessage[]} messages Collection of messages + * @returns {Object} Contains the stats + * @private + */ +function calculateStatsPerFile(messages) { + return messages.reduce((stat, message) => { + if (message.fatal || message.severity === 2) { + stat.errorCount++; + if (message.fix) { + stat.fixableErrorCount++; + } + } else { + stat.warningCount++; + if (message.fix) { + stat.fixableWarningCount++; + } + } + return stat; + }, { + errorCount: 0, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0 + }); +} + +/** + * It will calculate the error and warning count for collection of results from all files + * @param {LintResult[]} results Collection of messages from all the files + * @returns {Object} Contains the stats + * @private + */ +function calculateStatsPerRun(results) { + return results.reduce((stat, result) => { + stat.errorCount += result.errorCount; + stat.warningCount += result.warningCount; + stat.fixableErrorCount += result.fixableErrorCount; + stat.fixableWarningCount += result.fixableWarningCount; + return stat; + }, { + errorCount: 0, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0 + }); +} + +/** + * Processes an source code using ESLint. + * @param {Object} config The config object. + * @param {string} config.text The source code to verify. + * @param {string} config.cwd The path to the current working directory. + * @param {string|undefined} config.filePath The path to the file of `text`. If this is undefined, it uses ``. + * @param {ConfigArray} config.config The config. + * @param {boolean} config.fix If `true` then it does fix. + * @param {boolean} config.allowInlineConfig If `true` then it uses directive comments. + * @param {boolean} config.reportUnusedDisableDirectives If `true` then it reports unused `eslint-disable` comments. + * @param {FileEnumerator} config.fileEnumerator The file enumerator to check if a path is a target or not. + * @param {Linter} config.linter The linter instance to verify. + * @returns {LintResult} The result of linting. + * @private + */ +function verifyText({ + text, + cwd, + filePath: providedFilePath, + config, + fix, + allowInlineConfig, + reportUnusedDisableDirectives, + fileEnumerator, + linter +}) { + const filePath = providedFilePath || ""; + + debug(`Lint ${filePath}`); + + /* + * Verify. + * `config.extractConfig(filePath)` requires an absolute path, but `linter` + * doesn't know CWD, so it gives `linter` an absolute path always. + */ + const filePathToVerify = filePath === "" ? path.join(cwd, filePath) : filePath; + const { fixed, messages, output } = linter.verifyAndFix( + text, + config, + { + allowInlineConfig, + filename: filePathToVerify, + fix, + reportUnusedDisableDirectives, + + /** + * Check if the linter should adopt a given code block or not. + * @param {string} blockFilename The virtual filename of a code block. + * @returns {boolean} `true` if the linter should adopt the code block. + */ + filterCodeBlock(blockFilename) { + return fileEnumerator.isTargetPath(blockFilename); + } + } + ); + + // Tweak and return. + const result = { + filePath, + messages, + ...calculateStatsPerFile(messages) + }; + + if (fixed) { + result.output = output; + } + if ( + result.errorCount + result.warningCount > 0 && + typeof result.output === "undefined" + ) { + result.source = text; + } + + return result; +} + +/** + * Returns result with warning by ignore settings + * @param {string} filePath File path of checked code + * @param {string} baseDir Absolute path of base directory + * @returns {LintResult} Result with single warning + * @private + */ +function createIgnoreResult(filePath, baseDir) { + let message; + const isHidden = filePath.split(path.sep) + .find(segment => /^\./u.test(segment)); + const isInNodeModules = baseDir && path.relative(baseDir, filePath).startsWith("node_modules"); + + if (isHidden) { + message = "File ignored by default. Use a negated ignore pattern (like \"--ignore-pattern '!'\") to override."; + } else if (isInNodeModules) { + message = "File ignored by default. Use \"--ignore-pattern '!node_modules/*'\" to override."; + } else { + message = "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to override."; + } + + return { + filePath: path.resolve(filePath), + messages: [ + { + fatal: false, + severity: 1, + message + } + ], + errorCount: 0, + warningCount: 1, + fixableErrorCount: 0, + fixableWarningCount: 0 + }; +} + +/** + * Get a rule. + * @param {string} ruleId The rule ID to get. + * @param {ConfigArray[]} configArrays The config arrays that have plugin rules. + * @returns {Rule|null} The rule or null. + */ +function getRule(ruleId, configArrays) { + for (const configArray of configArrays) { + const rule = configArray.pluginRules.get(ruleId); + + if (rule) { + return rule; + } + } + return builtInRules.get(ruleId) || null; +} + +/** + * Collect used deprecated rules. + * @param {ConfigArray[]} usedConfigArrays The config arrays which were used. + * @returns {IterableIterator} Used deprecated rules. + */ +function *iterateRuleDeprecationWarnings(usedConfigArrays) { + const processedRuleIds = new Set(); + + // Flatten used configs. + /** @type {ExtractedConfig[]} */ + const configs = [].concat( + ...usedConfigArrays.map(getUsedExtractedConfigs) + ); + + // Traverse rule configs. + for (const config of configs) { + for (const [ruleId, ruleConfig] of Object.entries(config.rules)) { + + // Skip if it was processed. + if (processedRuleIds.has(ruleId)) { + continue; + } + processedRuleIds.add(ruleId); + + // Skip if it's not used. + if (!ConfigOps.getRuleSeverity(ruleConfig)) { + continue; + } + const rule = getRule(ruleId, usedConfigArrays); + + // Skip if it's not deprecated. + if (!(rule && rule.meta && rule.meta.deprecated)) { + continue; + } + + // This rule was used and deprecated. + yield { + ruleId, + replacedBy: rule.meta.replacedBy || [] + }; + } + } +} + +/** + * Checks if the given message is an error message. + * @param {LintMessage} message The message to check. + * @returns {boolean} Whether or not the message is an error message. + * @private + */ +function isErrorMessage(message) { + return message.severity === 2; +} + + +/** + * return the cacheFile to be used by eslint, based on whether the provided parameter is + * a directory or looks like a directory (ends in `path.sep`), in which case the file + * name will be the `cacheFile/.cache_hashOfCWD` + * + * if cacheFile points to a file or looks like a file then in will just use that file + * @param {string} cacheFile The name of file to be used to store the cache + * @param {string} cwd Current working directory + * @returns {string} the resolved path to the cache file + */ +function getCacheFile(cacheFile, cwd) { + + /* + * make sure the path separators are normalized for the environment/os + * keeping the trailing path separator if present + */ + const normalizedCacheFile = path.normalize(cacheFile); + + const resolvedCacheFile = path.resolve(cwd, normalizedCacheFile); + const looksLikeADirectory = normalizedCacheFile.slice(-1) === path.sep; + + /** + * return the name for the cache file in case the provided parameter is a directory + * @returns {string} the resolved path to the cacheFile + */ + function getCacheFileForDirectory() { + return path.join(resolvedCacheFile, `.cache_${hash(cwd)}`); + } + + let fileStats; + + try { + fileStats = fs.lstatSync(resolvedCacheFile); + } catch (ex) { + fileStats = null; + } + + + /* + * in case the file exists we need to verify if the provided path + * is a directory or a file. If it is a directory we want to create a file + * inside that directory + */ + if (fileStats) { + + /* + * is a directory or is a file, but the original file the user provided + * looks like a directory but `path.resolve` removed the `last path.sep` + * so we need to still treat this like a directory + */ + if (fileStats.isDirectory() || looksLikeADirectory) { + return getCacheFileForDirectory(); + } + + // is file so just use that file + return resolvedCacheFile; + } + + /* + * here we known the file or directory doesn't exist, + * so we will try to infer if its a directory if it looks like a directory + * for the current operating system. + */ + + // if the last character passed is a path separator we assume is a directory + if (looksLikeADirectory) { + return getCacheFileForDirectory(); + } + + return resolvedCacheFile; +} + +/** + * Convert a string array to a boolean map. + * @param {string[]|null} keys The keys to assign true. + * @param {boolean} defaultValue The default value for each property. + * @param {string} displayName The property name which is used in error message. + * @returns {Record} The boolean map. + */ +function toBooleanMap(keys, defaultValue, displayName) { + if (keys && !Array.isArray(keys)) { + throw new Error(`${displayName} must be an array.`); + } + if (keys && keys.length > 0) { + return keys.reduce((map, def) => { + const [key, value] = def.split(":"); + + if (key !== "__proto__") { + map[key] = value === void 0 + ? defaultValue + : value === "true"; + } + + return map; + }, {}); + } + return void 0; +} + +/** + * Create a config data from CLI options. + * @param {CLIEngineOptions} options The options + * @returns {ConfigData|null} The created config data. + */ +function createConfigDataFromOptions(options) { + const { + ignorePattern, + parser, + parserOptions, + plugins, + rules + } = options; + const env = toBooleanMap(options.envs, true, "envs"); + const globals = toBooleanMap(options.globals, false, "globals"); + + if ( + env === void 0 && + globals === void 0 && + (ignorePattern === void 0 || ignorePattern.length === 0) && + parser === void 0 && + parserOptions === void 0 && + plugins === void 0 && + rules === void 0 + ) { + return null; + } + return { + env, + globals, + ignorePatterns: ignorePattern, + parser, + parserOptions, + plugins, + rules + }; +} + +/** + * Checks whether a directory exists at the given location + * @param {string} resolvedPath A path from the CWD + * @returns {boolean} `true` if a directory exists + */ +function directoryExists(resolvedPath) { + try { + return fs.statSync(resolvedPath).isDirectory(); + } catch (error) { + if (error && error.code === "ENOENT") { + return false; + } + throw error; + } +} + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +class CLIEngine { + + /** + * Creates a new instance of the core CLI engine. + * @param {CLIEngineOptions} providedOptions The options for this instance. + */ + constructor(providedOptions) { + const options = Object.assign( + Object.create(null), + defaultOptions, + { cwd: process.cwd() }, + providedOptions + ); + + if (options.fix === void 0) { + options.fix = false; + } + + const additionalPluginPool = new Map(); + const cacheFilePath = getCacheFile( + options.cacheLocation || options.cacheFile, + options.cwd + ); + const configArrayFactory = new CascadingConfigArrayFactory({ + additionalPluginPool, + baseConfig: options.baseConfig || null, + cliConfig: createConfigDataFromOptions(options), + cwd: options.cwd, + ignorePath: options.ignorePath, + resolvePluginsRelativeTo: options.resolvePluginsRelativeTo, + rulePaths: options.rulePaths, + specificConfigPath: options.configFile, + useEslintrc: options.useEslintrc + }); + const fileEnumerator = new FileEnumerator({ + configArrayFactory, + cwd: options.cwd, + extensions: options.extensions, + globInputPaths: options.globInputPaths, + errorOnUnmatchedPattern: options.errorOnUnmatchedPattern, + ignore: options.ignore + }); + const lintResultCache = + options.cache ? new LintResultCache(cacheFilePath) : null; + const linter = new Linter({ cwd: options.cwd }); + + /** @type {ConfigArray[]} */ + const lastConfigArrays = [configArrayFactory.getConfigArrayForFile()]; + + // Store private data. + internalSlotsMap.set(this, { + additionalPluginPool, + cacheFilePath, + configArrayFactory, + defaultIgnores: IgnorePattern.createDefaultIgnore(options.cwd), + fileEnumerator, + lastConfigArrays, + lintResultCache, + linter, + options + }); + + // setup special filter for fixes + if (options.fix && options.fixTypes && options.fixTypes.length > 0) { + debug(`Using fix types ${options.fixTypes}`); + + // throw an error if any invalid fix types are found + validateFixTypes(options.fixTypes); + + // convert to Set for faster lookup + const fixTypes = new Set(options.fixTypes); + + // save original value of options.fix in case it's a function + const originalFix = (typeof options.fix === "function") + ? options.fix : () => true; + + options.fix = message => { + const rule = message.ruleId && getRule(message.ruleId, lastConfigArrays); + const matches = rule && rule.meta && fixTypes.has(rule.meta.type); + + return matches && originalFix(message); + }; + } + } + + getRules() { + const { lastConfigArrays } = internalSlotsMap.get(this); + + return new Map(function *() { + yield* builtInRules; + + for (const configArray of lastConfigArrays) { + yield* configArray.pluginRules; + } + }()); + } + + /** + * Returns results that only contains errors. + * @param {LintResult[]} results The results to filter. + * @returns {LintResult[]} The filtered results. + */ + static getErrorResults(results) { + const filtered = []; + + results.forEach(result => { + const filteredMessages = result.messages.filter(isErrorMessage); + + if (filteredMessages.length > 0) { + filtered.push({ + ...result, + messages: filteredMessages, + errorCount: filteredMessages.length, + warningCount: 0, + fixableErrorCount: result.fixableErrorCount, + fixableWarningCount: 0 + }); + } + }); + + return filtered; + } + + /** + * Outputs fixes from the given results to files. + * @param {LintReport} report The report object created by CLIEngine. + * @returns {void} + */ + static outputFixes(report) { + report.results.filter(result => Object.prototype.hasOwnProperty.call(result, "output")).forEach(result => { + fs.writeFileSync(result.filePath, result.output); + }); + } + + + /** + * Add a plugin by passing its configuration + * @param {string} name Name of the plugin. + * @param {Plugin} pluginObject Plugin configuration object. + * @returns {void} + */ + addPlugin(name, pluginObject) { + const { + additionalPluginPool, + configArrayFactory, + lastConfigArrays + } = internalSlotsMap.get(this); + + additionalPluginPool.set(name, pluginObject); + configArrayFactory.clearCache(); + lastConfigArrays.length = 1; + lastConfigArrays[0] = configArrayFactory.getConfigArrayForFile(); + } + + /** + * Resolves the patterns passed into executeOnFiles() into glob-based patterns + * for easier handling. + * @param {string[]} patterns The file patterns passed on the command line. + * @returns {string[]} The equivalent glob patterns. + */ + resolveFileGlobPatterns(patterns) { + const { options } = internalSlotsMap.get(this); + + if (options.globInputPaths === false) { + return patterns.filter(Boolean); + } + + const extensions = (options.extensions || [".js"]).map(ext => ext.replace(/^\./u, "")); + const dirSuffix = `/**/*.{${extensions.join(",")}}`; + + return patterns.filter(Boolean).map(pathname => { + const resolvedPath = path.resolve(options.cwd, pathname); + const newPath = directoryExists(resolvedPath) + ? pathname.replace(/[/\\]$/u, "") + dirSuffix + : pathname; + + return path.normalize(newPath).replace(/\\/gu, "/"); + }); + } + + /** + * Executes the current configuration on an array of file and directory names. + * @param {string[]} patterns An array of file and directory names. + * @returns {LintReport} The results for all files that were linted. + */ + executeOnFiles(patterns) { + const { + cacheFilePath, + fileEnumerator, + lastConfigArrays, + lintResultCache, + linter, + options: { + allowInlineConfig, + cache, + cwd, + fix, + reportUnusedDisableDirectives + } + } = internalSlotsMap.get(this); + const results = []; + const startTime = Date.now(); + + // Clear the last used config arrays. + lastConfigArrays.length = 0; + + // Delete cache file; should this do here? + if (!cache) { + try { + fs.unlinkSync(cacheFilePath); + } catch (error) { + const errorCode = error && error.code; + + // Ignore errors when no such file exists or file system is read only (and cache file does not exist) + if (errorCode !== "ENOENT" && !(errorCode === "EROFS" && !fs.existsSync(cacheFilePath))) { + throw error; + } + } + } + + // Iterate source code files. + for (const { config, filePath, ignored } of fileEnumerator.iterateFiles(patterns)) { + if (ignored) { + results.push(createIgnoreResult(filePath, cwd)); + continue; + } + + /* + * Store used configs for: + * - this method uses to collect used deprecated rules. + * - `getRules()` method uses to collect all loaded rules. + * - `--fix-type` option uses to get the loaded rule's meta data. + */ + if (!lastConfigArrays.includes(config)) { + lastConfigArrays.push(config); + } + + // Skip if there is cached result. + if (lintResultCache) { + const cachedResult = + lintResultCache.getCachedLintResults(filePath, config); + + if (cachedResult) { + const hadMessages = + cachedResult.messages && + cachedResult.messages.length > 0; + + if (hadMessages && fix) { + debug(`Reprocessing cached file to allow autofix: ${filePath}`); + } else { + debug(`Skipping file since it hasn't changed: ${filePath}`); + results.push(cachedResult); + continue; + } + } + } + + // Do lint. + const result = verifyText({ + text: fs.readFileSync(filePath, "utf8"), + filePath, + config, + cwd, + fix, + allowInlineConfig, + reportUnusedDisableDirectives, + fileEnumerator, + linter + }); + + results.push(result); + + /* + * Store the lint result in the LintResultCache. + * NOTE: The LintResultCache will remove the file source and any + * other properties that are difficult to serialize, and will + * hydrate those properties back in on future lint runs. + */ + if (lintResultCache) { + lintResultCache.setCachedLintResults(filePath, config, result); + } + } + + // Persist the cache to disk. + if (lintResultCache) { + lintResultCache.reconcile(); + } + + // Collect used deprecated rules. + const usedDeprecatedRules = Array.from( + iterateRuleDeprecationWarnings(lastConfigArrays) + ); + + debug(`Linting complete in: ${Date.now() - startTime}ms`); + return { + results, + ...calculateStatsPerRun(results), + usedDeprecatedRules + }; + } + + /** + * Executes the current configuration on text. + * @param {string} text A string of JavaScript code to lint. + * @param {string} [filename] An optional string representing the texts filename. + * @param {boolean} [warnIgnored] Always warn when a file is ignored + * @returns {LintReport} The results for the linting. + */ + executeOnText(text, filename, warnIgnored) { + const { + configArrayFactory, + fileEnumerator, + lastConfigArrays, + linter, + options: { + allowInlineConfig, + cwd, + fix, + reportUnusedDisableDirectives + } + } = internalSlotsMap.get(this); + const results = []; + const startTime = Date.now(); + const resolvedFilename = filename && path.resolve(cwd, filename); + + // Clear the last used config arrays. + lastConfigArrays.length = 0; + + if (resolvedFilename && this.isPathIgnored(resolvedFilename)) { + if (warnIgnored) { + results.push(createIgnoreResult(resolvedFilename, cwd)); + } + } else { + const config = configArrayFactory.getConfigArrayForFile( + resolvedFilename || "__placeholder__.js" + ); + + /* + * Store used configs for: + * - this method uses to collect used deprecated rules. + * - `getRules()` method uses to collect all loaded rules. + * - `--fix-type` option uses to get the loaded rule's meta data. + */ + lastConfigArrays.push(config); + + // Do lint. + results.push(verifyText({ + text, + filePath: resolvedFilename, + config, + cwd, + fix, + allowInlineConfig, + reportUnusedDisableDirectives, + fileEnumerator, + linter + })); + } + + // Collect used deprecated rules. + const usedDeprecatedRules = Array.from( + iterateRuleDeprecationWarnings(lastConfigArrays) + ); + + debug(`Linting complete in: ${Date.now() - startTime}ms`); + return { + results, + ...calculateStatsPerRun(results), + usedDeprecatedRules + }; + } + + /** + * Returns a configuration object for the given file based on the CLI options. + * This is the same logic used by the ESLint CLI executable to determine + * configuration for each file it processes. + * @param {string} filePath The path of the file to retrieve a config object for. + * @returns {ConfigData} A configuration object for the file. + */ + getConfigForFile(filePath) { + const { configArrayFactory, options } = internalSlotsMap.get(this); + const absolutePath = path.resolve(options.cwd, filePath); + + if (directoryExists(absolutePath)) { + throw Object.assign( + new Error("'filePath' should not be a directory path."), + { messageTemplate: "print-config-with-directory-path" } + ); + } + + return configArrayFactory + .getConfigArrayForFile(absolutePath) + .extractConfig(absolutePath) + .toCompatibleObjectAsConfigFileContent(); + } + + /** + * Checks if a given path is ignored by ESLint. + * @param {string} filePath The path of the file to check. + * @returns {boolean} Whether or not the given path is ignored. + */ + isPathIgnored(filePath) { + const { + configArrayFactory, + defaultIgnores, + options: { cwd, ignore } + } = internalSlotsMap.get(this); + const absolutePath = path.resolve(cwd, filePath); + + if (ignore) { + const config = configArrayFactory + .getConfigArrayForFile(absolutePath) + .extractConfig(absolutePath); + const ignores = config.ignores || defaultIgnores; + + return ignores(absolutePath); + } + + return defaultIgnores(absolutePath); + } + + /** + * Returns the formatter representing the given format or null if no formatter + * with the given name can be found. + * @param {string} [format] The name of the format to load or the path to a + * custom formatter. + * @returns {Function} The formatter function or null if not found. + */ + getFormatter(format) { + + // default is stylish + const resolvedFormatName = format || "stylish"; + + // only strings are valid formatters + if (typeof resolvedFormatName === "string") { + + // replace \ with / for Windows compatibility + const normalizedFormatName = resolvedFormatName.replace(/\\/gu, "/"); + + const slots = internalSlotsMap.get(this); + const cwd = slots ? slots.options.cwd : process.cwd(); + const namespace = naming.getNamespaceFromTerm(normalizedFormatName); + + let formatterPath; + + // if there's a slash, then it's a file (TODO: this check seems dubious for scoped npm packages) + if (!namespace && normalizedFormatName.indexOf("/") > -1) { + formatterPath = path.resolve(cwd, normalizedFormatName); + } else { + try { + const npmFormat = naming.normalizePackageName(normalizedFormatName, "eslint-formatter"); + + formatterPath = ModuleResolver.resolve(npmFormat, path.join(cwd, "__placeholder__.js")); + } catch (e) { + formatterPath = path.resolve(__dirname, "formatters", normalizedFormatName); + } + } + + try { + return require(formatterPath); + } catch (ex) { + ex.message = `There was a problem loading formatter: ${formatterPath}\nError: ${ex.message}`; + throw ex; + } + + } else { + return null; + } + } +} + +CLIEngine.version = pkg.version; +CLIEngine.getFormatter = CLIEngine.prototype.getFormatter; + +module.exports = { + CLIEngine, + + /** + * Get the internal slots of a given CLIEngine instance for tests. + * @param {CLIEngine} instance The CLIEngine instance to get. + * @returns {CLIEngineInternalSlots} The internal slots. + */ + getCLIEngineInternalSlots(instance) { + return internalSlotsMap.get(instance); + } +}; diff --git a/eslint/lib/cli-engine/config-array-factory.js b/eslint/lib/cli-engine/config-array-factory.js new file mode 100644 index 0000000..b1429af --- /dev/null +++ b/eslint/lib/cli-engine/config-array-factory.js @@ -0,0 +1,1074 @@ +/** + * @fileoverview The factory of `ConfigArray` objects. + * + * This class provides methods to create `ConfigArray` instance. + * + * - `create(configData, options)` + * Create a `ConfigArray` instance from a config data. This is to handle CLI + * options except `--config`. + * - `loadFile(filePath, options)` + * Create a `ConfigArray` instance from a config file. This is to handle + * `--config` option. If the file was not found, throws the following error: + * - If the filename was `*.js`, a `MODULE_NOT_FOUND` error. + * - If the filename was `package.json`, an IO error or an + * `ESLINT_CONFIG_FIELD_NOT_FOUND` error. + * - Otherwise, an IO error such as `ENOENT`. + * - `loadInDirectory(directoryPath, options)` + * Create a `ConfigArray` instance from a config file which is on a given + * directory. This tries to load `.eslintrc.*` or `package.json`. If not + * found, returns an empty `ConfigArray`. + * - `loadESLintIgnore(filePath)` + * Create a `ConfigArray` instance from a config file that is `.eslintignore` + * format. This is to handle `--ignore-path` option. + * - `loadDefaultESLintIgnore()` + * Create a `ConfigArray` instance from `.eslintignore` or `package.json` in + * the current working directory. + * + * `ConfigArrayFactory` class has the responsibility that loads configuration + * files, including loading `extends`, `parser`, and `plugins`. The created + * `ConfigArray` instance has the loaded `extends`, `parser`, and `plugins`. + * + * But this class doesn't handle cascading. `CascadingConfigArrayFactory` class + * handles cascading and hierarchy. + * + * @author Toru Nagashima + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const fs = require("fs"); +const path = require("path"); +const importFresh = require("import-fresh"); +const stripComments = require("strip-json-comments"); +const { validateConfigSchema } = require("../shared/config-validator"); +const naming = require("../shared/naming"); +const ModuleResolver = require("../shared/relative-module-resolver"); +const { + ConfigArray, + ConfigDependency, + IgnorePattern, + OverrideTester +} = require("./config-array"); +const debug = require("debug")("eslint:config-array-factory"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const eslintRecommendedPath = path.resolve(__dirname, "../../conf/eslint-recommended.js"); +const eslintAllPath = path.resolve(__dirname, "../../conf/eslint-all.js"); +const configFilenames = [ + ".eslintrc.js", + ".eslintrc.cjs", + ".eslintrc.yaml", + ".eslintrc.yml", + ".eslintrc.json", + ".eslintrc", + "package.json" +]; + +// Define types for VSCode IntelliSense. +/** @typedef {import("../shared/types").ConfigData} ConfigData */ +/** @typedef {import("../shared/types").OverrideConfigData} OverrideConfigData */ +/** @typedef {import("../shared/types").Parser} Parser */ +/** @typedef {import("../shared/types").Plugin} Plugin */ +/** @typedef {import("./config-array/config-dependency").DependentParser} DependentParser */ +/** @typedef {import("./config-array/config-dependency").DependentPlugin} DependentPlugin */ +/** @typedef {ConfigArray[0]} ConfigArrayElement */ + +/** + * @typedef {Object} ConfigArrayFactoryOptions + * @property {Map} [additionalPluginPool] The map for additional plugins. + * @property {string} [cwd] The path to the current working directory. + * @property {string} [resolvePluginsRelativeTo] A path to the directory that plugins should be resolved from. Defaults to `cwd`. + */ + +/** + * @typedef {Object} ConfigArrayFactoryInternalSlots + * @property {Map} additionalPluginPool The map for additional plugins. + * @property {string} cwd The path to the current working directory. + * @property {string | undefined} resolvePluginsRelativeTo An absolute path the the directory that plugins should be resolved from. + */ + +/** + * @typedef {Object} ConfigArrayFactoryLoadingContext + * @property {string} filePath The path to the current configuration. + * @property {string} matchBasePath The base path to resolve relative paths in `overrides[].files`, `overrides[].excludedFiles`, and `ignorePatterns`. + * @property {string} name The name of the current configuration. + * @property {string} pluginBasePath The base path to resolve plugins. + * @property {"config" | "ignore" | "implicit-processor"} type The type of the current configuration. This is `"config"` in normal. This is `"ignore"` if it came from `.eslintignore`. This is `"implicit-processor"` if it came from legacy file-extension processors. + */ + +/** + * @typedef {Object} ConfigArrayFactoryLoadingContext + * @property {string} filePath The path to the current configuration. + * @property {string} matchBasePath The base path to resolve relative paths in `overrides[].files`, `overrides[].excludedFiles`, and `ignorePatterns`. + * @property {string} name The name of the current configuration. + * @property {"config" | "ignore" | "implicit-processor"} type The type of the current configuration. This is `"config"` in normal. This is `"ignore"` if it came from `.eslintignore`. This is `"implicit-processor"` if it came from legacy file-extension processors. + */ + +/** @type {WeakMap} */ +const internalSlotsMap = new WeakMap(); + +/** + * Check if a given string is a file path. + * @param {string} nameOrPath A module name or file path. + * @returns {boolean} `true` if the `nameOrPath` is a file path. + */ +function isFilePath(nameOrPath) { + return ( + /^\.{1,2}[/\\]/u.test(nameOrPath) || + path.isAbsolute(nameOrPath) + ); +} + +/** + * Convenience wrapper for synchronously reading file contents. + * @param {string} filePath The filename to read. + * @returns {string} The file contents, with the BOM removed. + * @private + */ +function readFile(filePath) { + return fs.readFileSync(filePath, "utf8").replace(/^\ufeff/u, ""); +} + +/** + * Loads a YAML configuration from a file. + * @param {string} filePath The filename to load. + * @returns {ConfigData} The configuration object from the file. + * @throws {Error} If the file cannot be read. + * @private + */ +function loadYAMLConfigFile(filePath) { + debug(`Loading YAML config file: ${filePath}`); + + // lazy load YAML to improve performance when not used + const yaml = require("js-yaml"); + + try { + + // empty YAML file can be null, so always use + return yaml.safeLoad(readFile(filePath)) || {}; + } catch (e) { + debug(`Error reading YAML file: ${filePath}`); + e.message = `Cannot read config file: ${filePath}\nError: ${e.message}`; + throw e; + } +} + +/** + * Loads a JSON configuration from a file. + * @param {string} filePath The filename to load. + * @returns {ConfigData} The configuration object from the file. + * @throws {Error} If the file cannot be read. + * @private + */ +function loadJSONConfigFile(filePath) { + debug(`Loading JSON config file: ${filePath}`); + + try { + return JSON.parse(stripComments(readFile(filePath))); + } catch (e) { + debug(`Error reading JSON file: ${filePath}`); + e.message = `Cannot read config file: ${filePath}\nError: ${e.message}`; + e.messageTemplate = "failed-to-read-json"; + e.messageData = { + path: filePath, + message: e.message + }; + throw e; + } +} + +/** + * Loads a legacy (.eslintrc) configuration from a file. + * @param {string} filePath The filename to load. + * @returns {ConfigData} The configuration object from the file. + * @throws {Error} If the file cannot be read. + * @private + */ +function loadLegacyConfigFile(filePath) { + debug(`Loading legacy config file: ${filePath}`); + + // lazy load YAML to improve performance when not used + const yaml = require("js-yaml"); + + try { + return yaml.safeLoad(stripComments(readFile(filePath))) || /* istanbul ignore next */ {}; + } catch (e) { + debug("Error reading YAML file: %s\n%o", filePath, e); + e.message = `Cannot read config file: ${filePath}\nError: ${e.message}`; + throw e; + } +} + +/** + * Loads a JavaScript configuration from a file. + * @param {string} filePath The filename to load. + * @returns {ConfigData} The configuration object from the file. + * @throws {Error} If the file cannot be read. + * @private + */ +function loadJSConfigFile(filePath) { + debug(`Loading JS config file: ${filePath}`); + try { + return importFresh(filePath); + } catch (e) { + debug(`Error reading JavaScript file: ${filePath}`); + e.message = `Cannot read config file: ${filePath}\nError: ${e.message}`; + throw e; + } +} + +/** + * Loads a configuration from a package.json file. + * @param {string} filePath The filename to load. + * @returns {ConfigData} The configuration object from the file. + * @throws {Error} If the file cannot be read. + * @private + */ +function loadPackageJSONConfigFile(filePath) { + debug(`Loading package.json config file: ${filePath}`); + try { + const packageData = loadJSONConfigFile(filePath); + + if (!Object.hasOwnProperty.call(packageData, "eslintConfig")) { + throw Object.assign( + new Error("package.json file doesn't have 'eslintConfig' field."), + { code: "ESLINT_CONFIG_FIELD_NOT_FOUND" } + ); + } + + return packageData.eslintConfig; + } catch (e) { + debug(`Error reading package.json file: ${filePath}`); + e.message = `Cannot read config file: ${filePath}\nError: ${e.message}`; + throw e; + } +} + +/** + * Loads a `.eslintignore` from a file. + * @param {string} filePath The filename to load. + * @returns {string[]} The ignore patterns from the file. + * @private + */ +function loadESLintIgnoreFile(filePath) { + debug(`Loading .eslintignore file: ${filePath}`); + + try { + return readFile(filePath) + .split(/\r?\n/gu) + .filter(line => line.trim() !== "" && !line.startsWith("#")); + } catch (e) { + debug(`Error reading .eslintignore file: ${filePath}`); + e.message = `Cannot read .eslintignore file: ${filePath}\nError: ${e.message}`; + throw e; + } +} + +/** + * Creates an error to notify about a missing config to extend from. + * @param {string} configName The name of the missing config. + * @param {string} importerName The name of the config that imported the missing config + * @returns {Error} The error object to throw + * @private + */ +function configMissingError(configName, importerName) { + return Object.assign( + new Error(`Failed to load config "${configName}" to extend from.`), + { + messageTemplate: "extend-config-missing", + messageData: { configName, importerName } + } + ); +} + +/** + * Loads a configuration file regardless of the source. Inspects the file path + * to determine the correctly way to load the config file. + * @param {string} filePath The path to the configuration. + * @returns {ConfigData|null} The configuration information. + * @private + */ +function loadConfigFile(filePath) { + switch (path.extname(filePath)) { + case ".js": + case ".cjs": + return loadJSConfigFile(filePath); + + case ".json": + if (path.basename(filePath) === "package.json") { + return loadPackageJSONConfigFile(filePath); + } + return loadJSONConfigFile(filePath); + + case ".yaml": + case ".yml": + return loadYAMLConfigFile(filePath); + + default: + return loadLegacyConfigFile(filePath); + } +} + +/** + * Write debug log. + * @param {string} request The requested module name. + * @param {string} relativeTo The file path to resolve the request relative to. + * @param {string} filePath The resolved file path. + * @returns {void} + */ +function writeDebugLogForLoading(request, relativeTo, filePath) { + /* istanbul ignore next */ + if (debug.enabled) { + let nameAndVersion = null; + + try { + const packageJsonPath = ModuleResolver.resolve( + `${request}/package.json`, + relativeTo + ); + const { version = "unknown" } = require(packageJsonPath); + + nameAndVersion = `${request}@${version}`; + } catch (error) { + debug("package.json was not found:", error.message); + nameAndVersion = request; + } + + debug("Loaded: %s (%s)", nameAndVersion, filePath); + } +} + +/** + * Create a new context with default values. + * @param {ConfigArrayFactoryInternalSlots} slots The internal slots. + * @param {"config" | "ignore" | "implicit-processor" | undefined} providedType The type of the current configuration. Default is `"config"`. + * @param {string | undefined} providedName The name of the current configuration. Default is the relative path from `cwd` to `filePath`. + * @param {string | undefined} providedFilePath The path to the current configuration. Default is empty string. + * @param {string | undefined} providedMatchBasePath The type of the current configuration. Default is the directory of `filePath` or `cwd`. + * @returns {ConfigArrayFactoryLoadingContext} The created context. + */ +function createContext( + { cwd, resolvePluginsRelativeTo }, + providedType, + providedName, + providedFilePath, + providedMatchBasePath +) { + const filePath = providedFilePath + ? path.resolve(cwd, providedFilePath) + : ""; + const matchBasePath = + (providedMatchBasePath && path.resolve(cwd, providedMatchBasePath)) || + (filePath && path.dirname(filePath)) || + cwd; + const name = + providedName || + (filePath && path.relative(cwd, filePath)) || + ""; + const pluginBasePath = + resolvePluginsRelativeTo || + (filePath && path.dirname(filePath)) || + cwd; + const type = providedType || "config"; + + return { filePath, matchBasePath, name, pluginBasePath, type }; +} + +/** + * Normalize a given plugin. + * - Ensure the object to have four properties: configs, environments, processors, and rules. + * - Ensure the object to not have other properties. + * @param {Plugin} plugin The plugin to normalize. + * @returns {Plugin} The normalized plugin. + */ +function normalizePlugin(plugin) { + return { + configs: plugin.configs || {}, + environments: plugin.environments || {}, + processors: plugin.processors || {}, + rules: plugin.rules || {} + }; +} + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +/** + * The factory of `ConfigArray` objects. + */ +class ConfigArrayFactory { + + /** + * Initialize this instance. + * @param {ConfigArrayFactoryOptions} [options] The map for additional plugins. + */ + constructor({ + additionalPluginPool = new Map(), + cwd = process.cwd(), + resolvePluginsRelativeTo + } = {}) { + internalSlotsMap.set(this, { + additionalPluginPool, + cwd, + resolvePluginsRelativeTo: + resolvePluginsRelativeTo && + path.resolve(cwd, resolvePluginsRelativeTo) + }); + } + + /** + * Create `ConfigArray` instance from a config data. + * @param {ConfigData|null} configData The config data to create. + * @param {Object} [options] The options. + * @param {string} [options.basePath] The base path to resolve relative paths in `overrides[].files`, `overrides[].excludedFiles`, and `ignorePatterns`. + * @param {string} [options.filePath] The path to this config data. + * @param {string} [options.name] The config name. + * @returns {ConfigArray} Loaded config. + */ + create(configData, { basePath, filePath, name } = {}) { + if (!configData) { + return new ConfigArray(); + } + + const slots = internalSlotsMap.get(this); + const ctx = createContext(slots, "config", name, filePath, basePath); + const elements = this._normalizeConfigData(configData, ctx); + + return new ConfigArray(...elements); + } + + /** + * Load a config file. + * @param {string} filePath The path to a config file. + * @param {Object} [options] The options. + * @param {string} [options.basePath] The base path to resolve relative paths in `overrides[].files`, `overrides[].excludedFiles`, and `ignorePatterns`. + * @param {string} [options.name] The config name. + * @returns {ConfigArray} Loaded config. + */ + loadFile(filePath, { basePath, name } = {}) { + const slots = internalSlotsMap.get(this); + const ctx = createContext(slots, "config", name, filePath, basePath); + + return new ConfigArray(...this._loadConfigData(ctx)); + } + + /** + * Load the config file on a given directory if exists. + * @param {string} directoryPath The path to a directory. + * @param {Object} [options] The options. + * @param {string} [options.basePath] The base path to resolve relative paths in `overrides[].files`, `overrides[].excludedFiles`, and `ignorePatterns`. + * @param {string} [options.name] The config name. + * @returns {ConfigArray} Loaded config. An empty `ConfigArray` if any config doesn't exist. + */ + loadInDirectory(directoryPath, { basePath, name } = {}) { + const slots = internalSlotsMap.get(this); + + for (const filename of configFilenames) { + const ctx = createContext( + slots, + "config", + name, + path.join(directoryPath, filename), + basePath + ); + + if (fs.existsSync(ctx.filePath)) { + let configData; + + try { + configData = loadConfigFile(ctx.filePath); + } catch (error) { + if (!error || error.code !== "ESLINT_CONFIG_FIELD_NOT_FOUND") { + throw error; + } + } + + if (configData) { + debug(`Config file found: ${ctx.filePath}`); + return new ConfigArray( + ...this._normalizeConfigData(configData, ctx) + ); + } + } + } + + debug(`Config file not found on ${directoryPath}`); + return new ConfigArray(); + } + + /** + * Check if a config file on a given directory exists or not. + * @param {string} directoryPath The path to a directory. + * @returns {string | null} The path to the found config file. If not found then null. + */ + static getPathToConfigFileInDirectory(directoryPath) { + for (const filename of configFilenames) { + const filePath = path.join(directoryPath, filename); + + if (fs.existsSync(filePath)) { + if (filename === "package.json") { + try { + loadPackageJSONConfigFile(filePath); + return filePath; + } catch (error) { /* ignore */ } + } else { + return filePath; + } + } + } + return null; + } + + /** + * Load `.eslintignore` file. + * @param {string} filePath The path to a `.eslintignore` file to load. + * @returns {ConfigArray} Loaded config. An empty `ConfigArray` if any config doesn't exist. + */ + loadESLintIgnore(filePath) { + const slots = internalSlotsMap.get(this); + const ctx = createContext( + slots, + "ignore", + void 0, + filePath, + slots.cwd + ); + const ignorePatterns = loadESLintIgnoreFile(ctx.filePath); + + return new ConfigArray( + ...this._normalizeESLintIgnoreData(ignorePatterns, ctx) + ); + } + + /** + * Load `.eslintignore` file in the current working directory. + * @returns {ConfigArray} Loaded config. An empty `ConfigArray` if any config doesn't exist. + */ + loadDefaultESLintIgnore() { + const slots = internalSlotsMap.get(this); + const eslintIgnorePath = path.resolve(slots.cwd, ".eslintignore"); + const packageJsonPath = path.resolve(slots.cwd, "package.json"); + + if (fs.existsSync(eslintIgnorePath)) { + return this.loadESLintIgnore(eslintIgnorePath); + } + if (fs.existsSync(packageJsonPath)) { + const data = loadJSONConfigFile(packageJsonPath); + + if (Object.hasOwnProperty.call(data, "eslintIgnore")) { + if (!Array.isArray(data.eslintIgnore)) { + throw new Error("Package.json eslintIgnore property requires an array of paths"); + } + const ctx = createContext( + slots, + "ignore", + "eslintIgnore in package.json", + packageJsonPath, + slots.cwd + ); + + return new ConfigArray( + ...this._normalizeESLintIgnoreData(data.eslintIgnore, ctx) + ); + } + } + + return new ConfigArray(); + } + + /** + * Load a given config file. + * @param {ConfigArrayFactoryLoadingContext} ctx The loading context. + * @returns {IterableIterator} Loaded config. + * @private + */ + _loadConfigData(ctx) { + return this._normalizeConfigData(loadConfigFile(ctx.filePath), ctx); + } + + /** + * Normalize a given `.eslintignore` data to config array elements. + * @param {string[]} ignorePatterns The patterns to ignore files. + * @param {ConfigArrayFactoryLoadingContext} ctx The loading context. + * @returns {IterableIterator} The normalized config. + * @private + */ + *_normalizeESLintIgnoreData(ignorePatterns, ctx) { + const elements = this._normalizeObjectConfigData( + { ignorePatterns }, + ctx + ); + + // Set `ignorePattern.loose` flag for backward compatibility. + for (const element of elements) { + if (element.ignorePattern) { + element.ignorePattern.loose = true; + } + yield element; + } + } + + /** + * Normalize a given config to an array. + * @param {ConfigData} configData The config data to normalize. + * @param {ConfigArrayFactoryLoadingContext} ctx The loading context. + * @returns {IterableIterator} The normalized config. + * @private + */ + _normalizeConfigData(configData, ctx) { + validateConfigSchema(configData, ctx.name || ctx.filePath); + return this._normalizeObjectConfigData(configData, ctx); + } + + /** + * Normalize a given config to an array. + * @param {ConfigData|OverrideConfigData} configData The config data to normalize. + * @param {ConfigArrayFactoryLoadingContext} ctx The loading context. + * @returns {IterableIterator} The normalized config. + * @private + */ + *_normalizeObjectConfigData(configData, ctx) { + const { files, excludedFiles, ...configBody } = configData; + const criteria = OverrideTester.create( + files, + excludedFiles, + ctx.matchBasePath + ); + const elements = this._normalizeObjectConfigDataBody(configBody, ctx); + + // Apply the criteria to every element. + for (const element of elements) { + + /* + * Merge the criteria. + * This is for the `overrides` entries that came from the + * configurations of `overrides[].extends`. + */ + element.criteria = OverrideTester.and(criteria, element.criteria); + + /* + * Remove `root` property to ignore `root` settings which came from + * `extends` in `overrides`. + */ + if (element.criteria) { + element.root = void 0; + } + + yield element; + } + } + + /** + * Normalize a given config to an array. + * @param {ConfigData} configData The config data to normalize. + * @param {ConfigArrayFactoryLoadingContext} ctx The loading context. + * @returns {IterableIterator} The normalized config. + * @private + */ + *_normalizeObjectConfigDataBody( + { + env, + extends: extend, + globals, + ignorePatterns, + noInlineConfig, + parser: parserName, + parserOptions, + plugins: pluginList, + processor, + reportUnusedDisableDirectives, + root, + rules, + settings, + overrides: overrideList = [] + }, + ctx + ) { + const extendList = Array.isArray(extend) ? extend : [extend]; + const ignorePattern = ignorePatterns && new IgnorePattern( + Array.isArray(ignorePatterns) ? ignorePatterns : [ignorePatterns], + ctx.matchBasePath + ); + + // Flatten `extends`. + for (const extendName of extendList.filter(Boolean)) { + yield* this._loadExtends(extendName, ctx); + } + + // Load parser & plugins. + const parser = parserName && this._loadParser(parserName, ctx); + const plugins = pluginList && this._loadPlugins(pluginList, ctx); + + // Yield pseudo config data for file extension processors. + if (plugins) { + yield* this._takeFileExtensionProcessors(plugins, ctx); + } + + // Yield the config data except `extends` and `overrides`. + yield { + + // Debug information. + type: ctx.type, + name: ctx.name, + filePath: ctx.filePath, + + // Config data. + criteria: null, + env, + globals, + ignorePattern, + noInlineConfig, + parser, + parserOptions, + plugins, + processor, + reportUnusedDisableDirectives, + root, + rules, + settings + }; + + // Flatten `overries`. + for (let i = 0; i < overrideList.length; ++i) { + yield* this._normalizeObjectConfigData( + overrideList[i], + { ...ctx, name: `${ctx.name}#overrides[${i}]` } + ); + } + } + + /** + * Load configs of an element in `extends`. + * @param {string} extendName The name of a base config. + * @param {ConfigArrayFactoryLoadingContext} ctx The loading context. + * @returns {IterableIterator} The normalized config. + * @private + */ + _loadExtends(extendName, ctx) { + debug("Loading {extends:%j} relative to %s", extendName, ctx.filePath); + try { + if (extendName.startsWith("eslint:")) { + return this._loadExtendedBuiltInConfig(extendName, ctx); + } + if (extendName.startsWith("plugin:")) { + return this._loadExtendedPluginConfig(extendName, ctx); + } + return this._loadExtendedShareableConfig(extendName, ctx); + } catch (error) { + error.message += `\nReferenced from: ${ctx.filePath || ctx.name}`; + throw error; + } + } + + /** + * Load configs of an element in `extends`. + * @param {string} extendName The name of a base config. + * @param {ConfigArrayFactoryLoadingContext} ctx The loading context. + * @returns {IterableIterator} The normalized config. + * @private + */ + _loadExtendedBuiltInConfig(extendName, ctx) { + if (extendName === "eslint:recommended") { + return this._loadConfigData({ + ...ctx, + filePath: eslintRecommendedPath, + name: `${ctx.name} » ${extendName}` + }); + } + if (extendName === "eslint:all") { + return this._loadConfigData({ + ...ctx, + filePath: eslintAllPath, + name: `${ctx.name} » ${extendName}` + }); + } + + throw configMissingError(extendName, ctx.name); + } + + /** + * Load configs of an element in `extends`. + * @param {string} extendName The name of a base config. + * @param {ConfigArrayFactoryLoadingContext} ctx The loading context. + * @returns {IterableIterator} The normalized config. + * @private + */ + _loadExtendedPluginConfig(extendName, ctx) { + const slashIndex = extendName.lastIndexOf("/"); + const pluginName = extendName.slice("plugin:".length, slashIndex); + const configName = extendName.slice(slashIndex + 1); + + if (isFilePath(pluginName)) { + throw new Error("'extends' cannot use a file path for plugins."); + } + + const plugin = this._loadPlugin(pluginName, ctx); + const configData = + plugin.definition && + plugin.definition.configs[configName]; + + if (configData) { + return this._normalizeConfigData(configData, { + ...ctx, + filePath: plugin.filePath, + name: `${ctx.name} » plugin:${plugin.id}/${configName}` + }); + } + + throw plugin.error || configMissingError(extendName, ctx.filePath); + } + + /** + * Load configs of an element in `extends`. + * @param {string} extendName The name of a base config. + * @param {ConfigArrayFactoryLoadingContext} ctx The loading context. + * @returns {IterableIterator} The normalized config. + * @private + */ + _loadExtendedShareableConfig(extendName, ctx) { + const { cwd } = internalSlotsMap.get(this); + const relativeTo = ctx.filePath || path.join(cwd, "__placeholder__.js"); + let request; + + if (isFilePath(extendName)) { + request = extendName; + } else if (extendName.startsWith(".")) { + request = `./${extendName}`; // For backward compatibility. A ton of tests depended on this behavior. + } else { + request = naming.normalizePackageName( + extendName, + "eslint-config" + ); + } + + let filePath; + + try { + filePath = ModuleResolver.resolve(request, relativeTo); + } catch (error) { + /* istanbul ignore else */ + if (error && error.code === "MODULE_NOT_FOUND") { + throw configMissingError(extendName, ctx.filePath); + } + throw error; + } + + writeDebugLogForLoading(request, relativeTo, filePath); + return this._loadConfigData({ + ...ctx, + filePath, + name: `${ctx.name} » ${request}` + }); + } + + /** + * Load given plugins. + * @param {string[]} names The plugin names to load. + * @param {ConfigArrayFactoryLoadingContext} ctx The loading context. + * @returns {Record} The loaded parser. + * @private + */ + _loadPlugins(names, ctx) { + return names.reduce((map, name) => { + if (isFilePath(name)) { + throw new Error("Plugins array cannot includes file paths."); + } + const plugin = this._loadPlugin(name, ctx); + + map[plugin.id] = plugin; + + return map; + }, {}); + } + + /** + * Load a given parser. + * @param {string} nameOrPath The package name or the path to a parser file. + * @param {ConfigArrayFactoryLoadingContext} ctx The loading context. + * @returns {DependentParser} The loaded parser. + */ + _loadParser(nameOrPath, ctx) { + debug("Loading parser %j from %s", nameOrPath, ctx.filePath); + + const { cwd } = internalSlotsMap.get(this); + const relativeTo = ctx.filePath || path.join(cwd, "__placeholder__.js"); + + try { + const filePath = ModuleResolver.resolve(nameOrPath, relativeTo); + + writeDebugLogForLoading(nameOrPath, relativeTo, filePath); + + return new ConfigDependency({ + definition: require(filePath), + filePath, + id: nameOrPath, + importerName: ctx.name, + importerPath: ctx.filePath + }); + } catch (error) { + + // If the parser name is "espree", load the espree of ESLint. + if (nameOrPath === "espree") { + debug("Fallback espree."); + return new ConfigDependency({ + definition: require("espree"), + filePath: require.resolve("espree"), + id: nameOrPath, + importerName: ctx.name, + importerPath: ctx.filePath + }); + } + + debug("Failed to load parser '%s' declared in '%s'.", nameOrPath, ctx.name); + error.message = `Failed to load parser '${nameOrPath}' declared in '${ctx.name}': ${error.message}`; + + return new ConfigDependency({ + error, + id: nameOrPath, + importerName: ctx.name, + importerPath: ctx.filePath + }); + } + } + + /** + * Load a given plugin. + * @param {string} name The plugin name to load. + * @param {ConfigArrayFactoryLoadingContext} ctx The loading context. + * @returns {DependentPlugin} The loaded plugin. + * @private + */ + _loadPlugin(name, ctx) { + debug("Loading plugin %j from %s", name, ctx.filePath); + + const { additionalPluginPool } = internalSlotsMap.get(this); + const request = naming.normalizePackageName(name, "eslint-plugin"); + const id = naming.getShorthandName(request, "eslint-plugin"); + const relativeTo = path.join(ctx.pluginBasePath, "__placeholder__.js"); + + if (name.match(/\s+/u)) { + const error = Object.assign( + new Error(`Whitespace found in plugin name '${name}'`), + { + messageTemplate: "whitespace-found", + messageData: { pluginName: request } + } + ); + + return new ConfigDependency({ + error, + id, + importerName: ctx.name, + importerPath: ctx.filePath + }); + } + + // Check for additional pool. + const plugin = + additionalPluginPool.get(request) || + additionalPluginPool.get(id); + + if (plugin) { + return new ConfigDependency({ + definition: normalizePlugin(plugin), + filePath: ctx.filePath, + id, + importerName: ctx.name, + importerPath: ctx.filePath + }); + } + + let filePath; + let error; + + try { + filePath = ModuleResolver.resolve(request, relativeTo); + } catch (resolveError) { + error = resolveError; + /* istanbul ignore else */ + if (error && error.code === "MODULE_NOT_FOUND") { + error.messageTemplate = "plugin-missing"; + error.messageData = { + pluginName: request, + resolvePluginsRelativeTo: ctx.pluginBasePath, + importerName: ctx.name + }; + } + } + + if (filePath) { + try { + writeDebugLogForLoading(request, relativeTo, filePath); + + const startTime = Date.now(); + const pluginDefinition = require(filePath); + + debug(`Plugin ${filePath} loaded in: ${Date.now() - startTime}ms`); + + return new ConfigDependency({ + definition: normalizePlugin(pluginDefinition), + filePath, + id, + importerName: ctx.name, + importerPath: ctx.filePath + }); + } catch (loadError) { + error = loadError; + } + } + + debug("Failed to load plugin '%s' declared in '%s'.", name, ctx.name); + error.message = `Failed to load plugin '${name}' declared in '${ctx.name}': ${error.message}`; + return new ConfigDependency({ + error, + id, + importerName: ctx.name, + importerPath: ctx.filePath + }); + } + + /** + * Take file expression processors as config array elements. + * @param {Record} plugins The plugin definitions. + * @param {ConfigArrayFactoryLoadingContext} ctx The loading context. + * @returns {IterableIterator} The config array elements of file expression processors. + * @private + */ + *_takeFileExtensionProcessors(plugins, ctx) { + for (const pluginId of Object.keys(plugins)) { + const processors = + plugins[pluginId] && + plugins[pluginId].definition && + plugins[pluginId].definition.processors; + + if (!processors) { + continue; + } + + for (const processorId of Object.keys(processors)) { + if (processorId.startsWith(".")) { + yield* this._normalizeObjectConfigData( + { + files: [`*${processorId}`], + processor: `${pluginId}/${processorId}` + }, + { + ...ctx, + type: "implicit-processor", + name: `${ctx.name}#processors["${pluginId}/${processorId}"]` + } + ); + } + } + } + } +} + +module.exports = { ConfigArrayFactory, createContext }; diff --git a/eslint/lib/cli-engine/config-array/config-array.js b/eslint/lib/cli-engine/config-array/config-array.js new file mode 100644 index 0000000..b343419 --- /dev/null +++ b/eslint/lib/cli-engine/config-array/config-array.js @@ -0,0 +1,524 @@ +/** + * @fileoverview `ConfigArray` class. + * + * `ConfigArray` class expresses the full of a configuration. It has the entry + * config file, base config files that were extended, loaded parsers, and loaded + * plugins. + * + * `ConfigArray` class provides three properties and two methods. + * + * - `pluginEnvironments` + * - `pluginProcessors` + * - `pluginRules` + * The `Map` objects that contain the members of all plugins that this + * config array contains. Those map objects don't have mutation methods. + * Those keys are the member ID such as `pluginId/memberName`. + * - `isRoot()` + * If `true` then this configuration has `root:true` property. + * - `extractConfig(filePath)` + * Extract the final configuration for a given file. This means merging + * every config array element which that `criteria` property matched. The + * `filePath` argument must be an absolute path. + * + * `ConfigArrayFactory` provides the loading logic of config files. + * + * @author Toru Nagashima + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const { ExtractedConfig } = require("./extracted-config"); +const { IgnorePattern } = require("./ignore-pattern"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +// Define types for VSCode IntelliSense. +/** @typedef {import("../../shared/types").Environment} Environment */ +/** @typedef {import("../../shared/types").GlobalConf} GlobalConf */ +/** @typedef {import("../../shared/types").RuleConf} RuleConf */ +/** @typedef {import("../../shared/types").Rule} Rule */ +/** @typedef {import("../../shared/types").Plugin} Plugin */ +/** @typedef {import("../../shared/types").Processor} Processor */ +/** @typedef {import("./config-dependency").DependentParser} DependentParser */ +/** @typedef {import("./config-dependency").DependentPlugin} DependentPlugin */ +/** @typedef {import("./override-tester")["OverrideTester"]} OverrideTester */ + +/** + * @typedef {Object} ConfigArrayElement + * @property {string} name The name of this config element. + * @property {string} filePath The path to the source file of this config element. + * @property {InstanceType|null} criteria The tester for the `files` and `excludedFiles` of this config element. + * @property {Record|undefined} env The environment settings. + * @property {Record|undefined} globals The global variable settings. + * @property {IgnorePattern|undefined} ignorePattern The ignore patterns. + * @property {boolean|undefined} noInlineConfig The flag that disables directive comments. + * @property {DependentParser|undefined} parser The parser loader. + * @property {Object|undefined} parserOptions The parser options. + * @property {Record|undefined} plugins The plugin loaders. + * @property {string|undefined} processor The processor name to refer plugin's processor. + * @property {boolean|undefined} reportUnusedDisableDirectives The flag to report unused `eslint-disable` comments. + * @property {boolean|undefined} root The flag to express root. + * @property {Record|undefined} rules The rule settings + * @property {Object|undefined} settings The shared settings. + * @property {"config" | "ignore" | "implicit-processor"} type The element type. + */ + +/** + * @typedef {Object} ConfigArrayInternalSlots + * @property {Map} cache The cache to extract configs. + * @property {ReadonlyMap|null} envMap The map from environment ID to environment definition. + * @property {ReadonlyMap|null} processorMap The map from processor ID to environment definition. + * @property {ReadonlyMap|null} ruleMap The map from rule ID to rule definition. + */ + +/** @type {WeakMap} */ +const internalSlotsMap = new class extends WeakMap { + get(key) { + let value = super.get(key); + + if (!value) { + value = { + cache: new Map(), + envMap: null, + processorMap: null, + ruleMap: null + }; + super.set(key, value); + } + + return value; + } +}(); + +/** + * Get the indices which are matched to a given file. + * @param {ConfigArrayElement[]} elements The elements. + * @param {string} filePath The path to a target file. + * @returns {number[]} The indices. + */ +function getMatchedIndices(elements, filePath) { + const indices = []; + + for (let i = elements.length - 1; i >= 0; --i) { + const element = elements[i]; + + if (!element.criteria || element.criteria.test(filePath)) { + indices.push(i); + } + } + + return indices; +} + +/** + * Check if a value is a non-null object. + * @param {any} x The value to check. + * @returns {boolean} `true` if the value is a non-null object. + */ +function isNonNullObject(x) { + return typeof x === "object" && x !== null; +} + +/** + * Merge two objects. + * + * Assign every property values of `y` to `x` if `x` doesn't have the property. + * If `x`'s property value is an object, it does recursive. + * @param {Object} target The destination to merge + * @param {Object|undefined} source The source to merge. + * @returns {void} + */ +function mergeWithoutOverwrite(target, source) { + if (!isNonNullObject(source)) { + return; + } + + for (const key of Object.keys(source)) { + if (key === "__proto__") { + continue; + } + + if (isNonNullObject(target[key])) { + mergeWithoutOverwrite(target[key], source[key]); + } else if (target[key] === void 0) { + if (isNonNullObject(source[key])) { + target[key] = Array.isArray(source[key]) ? [] : {}; + mergeWithoutOverwrite(target[key], source[key]); + } else if (source[key] !== void 0) { + target[key] = source[key]; + } + } + } +} + +/** + * The error for plugin conflicts. + */ +class PluginConflictError extends Error { + + /** + * Initialize this error object. + * @param {string} pluginId The plugin ID. + * @param {{filePath:string, importerName:string}[]} plugins The resolved plugins. + */ + constructor(pluginId, plugins) { + super(`Plugin "${pluginId}" was conflicted between ${plugins.map(p => `"${p.importerName}"`).join(" and ")}.`); + this.messageTemplate = "plugin-conflict"; + this.messageData = { pluginId, plugins }; + } +} + +/** + * Merge plugins. + * `target`'s definition is prior to `source`'s. + * @param {Record} target The destination to merge + * @param {Record|undefined} source The source to merge. + * @returns {void} + */ +function mergePlugins(target, source) { + if (!isNonNullObject(source)) { + return; + } + + for (const key of Object.keys(source)) { + if (key === "__proto__") { + continue; + } + const targetValue = target[key]; + const sourceValue = source[key]; + + // Adopt the plugin which was found at first. + if (targetValue === void 0) { + if (sourceValue.error) { + throw sourceValue.error; + } + target[key] = sourceValue; + } else if (sourceValue.filePath !== targetValue.filePath) { + throw new PluginConflictError(key, [ + { + filePath: targetValue.filePath, + importerName: targetValue.importerName + }, + { + filePath: sourceValue.filePath, + importerName: sourceValue.importerName + } + ]); + } + } +} + +/** + * Merge rule configs. + * `target`'s definition is prior to `source`'s. + * @param {Record} target The destination to merge + * @param {Record|undefined} source The source to merge. + * @returns {void} + */ +function mergeRuleConfigs(target, source) { + if (!isNonNullObject(source)) { + return; + } + + for (const key of Object.keys(source)) { + if (key === "__proto__") { + continue; + } + const targetDef = target[key]; + const sourceDef = source[key]; + + // Adopt the rule config which was found at first. + if (targetDef === void 0) { + if (Array.isArray(sourceDef)) { + target[key] = [...sourceDef]; + } else { + target[key] = [sourceDef]; + } + + /* + * If the first found rule config is severity only and the current rule + * config has options, merge the severity and the options. + */ + } else if ( + targetDef.length === 1 && + Array.isArray(sourceDef) && + sourceDef.length >= 2 + ) { + targetDef.push(...sourceDef.slice(1)); + } + } +} + +/** + * Create the extracted config. + * @param {ConfigArray} instance The config elements. + * @param {number[]} indices The indices to use. + * @returns {ExtractedConfig} The extracted config. + */ +function createConfig(instance, indices) { + const config = new ExtractedConfig(); + const ignorePatterns = []; + + // Merge elements. + for (const index of indices) { + const element = instance[index]; + + // Adopt the parser which was found at first. + if (!config.parser && element.parser) { + if (element.parser.error) { + throw element.parser.error; + } + config.parser = element.parser; + } + + // Adopt the processor which was found at first. + if (!config.processor && element.processor) { + config.processor = element.processor; + } + + // Adopt the noInlineConfig which was found at first. + if (config.noInlineConfig === void 0 && element.noInlineConfig !== void 0) { + config.noInlineConfig = element.noInlineConfig; + config.configNameOfNoInlineConfig = element.name; + } + + // Adopt the reportUnusedDisableDirectives which was found at first. + if (config.reportUnusedDisableDirectives === void 0 && element.reportUnusedDisableDirectives !== void 0) { + config.reportUnusedDisableDirectives = element.reportUnusedDisableDirectives; + } + + // Collect ignorePatterns + if (element.ignorePattern) { + ignorePatterns.push(element.ignorePattern); + } + + // Merge others. + mergeWithoutOverwrite(config.env, element.env); + mergeWithoutOverwrite(config.globals, element.globals); + mergeWithoutOverwrite(config.parserOptions, element.parserOptions); + mergeWithoutOverwrite(config.settings, element.settings); + mergePlugins(config.plugins, element.plugins); + mergeRuleConfigs(config.rules, element.rules); + } + + // Create the predicate function for ignore patterns. + if (ignorePatterns.length > 0) { + config.ignores = IgnorePattern.createIgnore(ignorePatterns.reverse()); + } + + return config; +} + +/** + * Collect definitions. + * @template T, U + * @param {string} pluginId The plugin ID for prefix. + * @param {Record} defs The definitions to collect. + * @param {Map} map The map to output. + * @param {function(T): U} [normalize] The normalize function for each value. + * @returns {void} + */ +function collect(pluginId, defs, map, normalize) { + if (defs) { + const prefix = pluginId && `${pluginId}/`; + + for (const [key, value] of Object.entries(defs)) { + map.set( + `${prefix}${key}`, + normalize ? normalize(value) : value + ); + } + } +} + +/** + * Normalize a rule definition. + * @param {Function|Rule} rule The rule definition to normalize. + * @returns {Rule} The normalized rule definition. + */ +function normalizePluginRule(rule) { + return typeof rule === "function" ? { create: rule } : rule; +} + +/** + * Delete the mutation methods from a given map. + * @param {Map} map The map object to delete. + * @returns {void} + */ +function deleteMutationMethods(map) { + Object.defineProperties(map, { + clear: { configurable: true, value: void 0 }, + delete: { configurable: true, value: void 0 }, + set: { configurable: true, value: void 0 } + }); +} + +/** + * Create `envMap`, `processorMap`, `ruleMap` with the plugins in the config array. + * @param {ConfigArrayElement[]} elements The config elements. + * @param {ConfigArrayInternalSlots} slots The internal slots. + * @returns {void} + */ +function initPluginMemberMaps(elements, slots) { + const processed = new Set(); + + slots.envMap = new Map(); + slots.processorMap = new Map(); + slots.ruleMap = new Map(); + + for (const element of elements) { + if (!element.plugins) { + continue; + } + + for (const [pluginId, value] of Object.entries(element.plugins)) { + const plugin = value.definition; + + if (!plugin || processed.has(pluginId)) { + continue; + } + processed.add(pluginId); + + collect(pluginId, plugin.environments, slots.envMap); + collect(pluginId, plugin.processors, slots.processorMap); + collect(pluginId, plugin.rules, slots.ruleMap, normalizePluginRule); + } + } + + deleteMutationMethods(slots.envMap); + deleteMutationMethods(slots.processorMap); + deleteMutationMethods(slots.ruleMap); +} + +/** + * Create `envMap`, `processorMap`, `ruleMap` with the plugins in the config array. + * @param {ConfigArray} instance The config elements. + * @returns {ConfigArrayInternalSlots} The extracted config. + */ +function ensurePluginMemberMaps(instance) { + const slots = internalSlotsMap.get(instance); + + if (!slots.ruleMap) { + initPluginMemberMaps(instance, slots); + } + + return slots; +} + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +/** + * The Config Array. + * + * `ConfigArray` instance contains all settings, parsers, and plugins. + * You need to call `ConfigArray#extractConfig(filePath)` method in order to + * extract, merge and get only the config data which is related to an arbitrary + * file. + * @extends {Array} + */ +class ConfigArray extends Array { + + /** + * Get the plugin environments. + * The returned map cannot be mutated. + * @type {ReadonlyMap} The plugin environments. + */ + get pluginEnvironments() { + return ensurePluginMemberMaps(this).envMap; + } + + /** + * Get the plugin processors. + * The returned map cannot be mutated. + * @type {ReadonlyMap} The plugin processors. + */ + get pluginProcessors() { + return ensurePluginMemberMaps(this).processorMap; + } + + /** + * Get the plugin rules. + * The returned map cannot be mutated. + * @returns {ReadonlyMap} The plugin rules. + */ + get pluginRules() { + return ensurePluginMemberMaps(this).ruleMap; + } + + /** + * Check if this config has `root` flag. + * @returns {boolean} `true` if this config array is root. + */ + isRoot() { + for (let i = this.length - 1; i >= 0; --i) { + const root = this[i].root; + + if (typeof root === "boolean") { + return root; + } + } + return false; + } + + /** + * Extract the config data which is related to a given file. + * @param {string} filePath The absolute path to the target file. + * @returns {ExtractedConfig} The extracted config data. + */ + extractConfig(filePath) { + const { cache } = internalSlotsMap.get(this); + const indices = getMatchedIndices(this, filePath); + const cacheKey = indices.join(","); + + if (!cache.has(cacheKey)) { + cache.set(cacheKey, createConfig(this, indices)); + } + + return cache.get(cacheKey); + } + + /** + * Check if a given path is an additional lint target. + * @param {string} filePath The absolute path to the target file. + * @returns {boolean} `true` if the file is an additional lint target. + */ + isAdditionalTargetPath(filePath) { + for (const { criteria, type } of this) { + if ( + type === "config" && + criteria && + !criteria.endsWithWildcard && + criteria.test(filePath) + ) { + return true; + } + } + return false; + } +} + +const exportObject = { + ConfigArray, + + /** + * Get the used extracted configs. + * CLIEngine will use this method to collect used deprecated rules. + * @param {ConfigArray} instance The config array object to get. + * @returns {ExtractedConfig[]} The used extracted configs. + * @private + */ + getUsedExtractedConfigs(instance) { + const { cache } = internalSlotsMap.get(instance); + + return Array.from(cache.values()); + } +}; + +module.exports = exportObject; diff --git a/eslint/lib/cli-engine/config-array/config-dependency.js b/eslint/lib/cli-engine/config-array/config-dependency.js new file mode 100644 index 0000000..0d5f6f7 --- /dev/null +++ b/eslint/lib/cli-engine/config-array/config-dependency.js @@ -0,0 +1,116 @@ +/** + * @fileoverview `ConfigDependency` class. + * + * `ConfigDependency` class expresses a loaded parser or plugin. + * + * If the parser or plugin was loaded successfully, it has `definition` property + * and `filePath` property. Otherwise, it has `error` property. + * + * When `JSON.stringify()` converted a `ConfigDependency` object to a JSON, it + * omits `definition` property. + * + * `ConfigArrayFactory` creates `ConfigDependency` objects when it loads parsers + * or plugins. + * + * @author Toru Nagashima + */ +"use strict"; + +const util = require("util"); + +/** + * The class is to store parsers or plugins. + * This class hides the loaded object from `JSON.stringify()` and `console.log`. + * @template T + */ +class ConfigDependency { + + /** + * Initialize this instance. + * @param {Object} data The dependency data. + * @param {T} [data.definition] The dependency if the loading succeeded. + * @param {Error} [data.error] The error object if the loading failed. + * @param {string} [data.filePath] The actual path to the dependency if the loading succeeded. + * @param {string} data.id The ID of this dependency. + * @param {string} data.importerName The name of the config file which loads this dependency. + * @param {string} data.importerPath The path to the config file which loads this dependency. + */ + constructor({ + definition = null, + error = null, + filePath = null, + id, + importerName, + importerPath + }) { + + /** + * The loaded dependency if the loading succeeded. + * @type {T|null} + */ + this.definition = definition; + + /** + * The error object if the loading failed. + * @type {Error|null} + */ + this.error = error; + + /** + * The loaded dependency if the loading succeeded. + * @type {string|null} + */ + this.filePath = filePath; + + /** + * The ID of this dependency. + * @type {string} + */ + this.id = id; + + /** + * The name of the config file which loads this dependency. + * @type {string} + */ + this.importerName = importerName; + + /** + * The path to the config file which loads this dependency. + * @type {string} + */ + this.importerPath = importerPath; + } + + // eslint-disable-next-line jsdoc/require-description + /** + * @returns {Object} a JSON compatible object. + */ + toJSON() { + const obj = this[util.inspect.custom](); + + // Display `error.message` (`Error#message` is unenumerable). + if (obj.error instanceof Error) { + obj.error = { ...obj.error, message: obj.error.message }; + } + + return obj; + } + + // eslint-disable-next-line jsdoc/require-description + /** + * @returns {Object} an object to display by `console.log()`. + */ + [util.inspect.custom]() { + const { + definition: _ignore, // eslint-disable-line no-unused-vars + ...obj + } = this; + + return obj; + } +} + +/** @typedef {ConfigDependency} DependentParser */ +/** @typedef {ConfigDependency} DependentPlugin */ + +module.exports = { ConfigDependency }; diff --git a/eslint/lib/cli-engine/config-array/extracted-config.js b/eslint/lib/cli-engine/config-array/extracted-config.js new file mode 100644 index 0000000..b27d6ff --- /dev/null +++ b/eslint/lib/cli-engine/config-array/extracted-config.js @@ -0,0 +1,146 @@ +/** + * @fileoverview `ExtractedConfig` class. + * + * `ExtractedConfig` class expresses a final configuration for a specific file. + * + * It provides one method. + * + * - `toCompatibleObjectAsConfigFileContent()` + * Convert this configuration to the compatible object as the content of + * config files. It converts the loaded parser and plugins to strings. + * `CLIEngine#getConfigForFile(filePath)` method uses this method. + * + * `ConfigArray#extractConfig(filePath)` creates a `ExtractedConfig` instance. + * + * @author Toru Nagashima + */ +"use strict"; + +const { IgnorePattern } = require("./ignore-pattern"); + +// For VSCode intellisense +/** @typedef {import("../../shared/types").ConfigData} ConfigData */ +/** @typedef {import("../../shared/types").GlobalConf} GlobalConf */ +/** @typedef {import("../../shared/types").SeverityConf} SeverityConf */ +/** @typedef {import("./config-dependency").DependentParser} DependentParser */ +/** @typedef {import("./config-dependency").DependentPlugin} DependentPlugin */ + +/** + * Check if `xs` starts with `ys`. + * @template T + * @param {T[]} xs The array to check. + * @param {T[]} ys The array that may be the first part of `xs`. + * @returns {boolean} `true` if `xs` starts with `ys`. + */ +function startsWith(xs, ys) { + return xs.length >= ys.length && ys.every((y, i) => y === xs[i]); +} + +/** + * The class for extracted config data. + */ +class ExtractedConfig { + constructor() { + + /** + * The config name what `noInlineConfig` setting came from. + * @type {string} + */ + this.configNameOfNoInlineConfig = ""; + + /** + * Environments. + * @type {Record} + */ + this.env = {}; + + /** + * Global variables. + * @type {Record} + */ + this.globals = {}; + + /** + * The glob patterns that ignore to lint. + * @type {(((filePath:string, dot?:boolean) => boolean) & { basePath:string; patterns:string[] }) | undefined} + */ + this.ignores = void 0; + + /** + * The flag that disables directive comments. + * @type {boolean|undefined} + */ + this.noInlineConfig = void 0; + + /** + * Parser definition. + * @type {DependentParser|null} + */ + this.parser = null; + + /** + * Options for the parser. + * @type {Object} + */ + this.parserOptions = {}; + + /** + * Plugin definitions. + * @type {Record} + */ + this.plugins = {}; + + /** + * Processor ID. + * @type {string|null} + */ + this.processor = null; + + /** + * The flag that reports unused `eslint-disable` directive comments. + * @type {boolean|undefined} + */ + this.reportUnusedDisableDirectives = void 0; + + /** + * Rule settings. + * @type {Record} + */ + this.rules = {}; + + /** + * Shared settings. + * @type {Object} + */ + this.settings = {}; + } + + /** + * Convert this config to the compatible object as a config file content. + * @returns {ConfigData} The converted object. + */ + toCompatibleObjectAsConfigFileContent() { + const { + /* eslint-disable no-unused-vars */ + configNameOfNoInlineConfig: _ignore1, + processor: _ignore2, + /* eslint-enable no-unused-vars */ + ignores, + ...config + } = this; + + config.parser = config.parser && config.parser.filePath; + config.plugins = Object.keys(config.plugins).filter(Boolean).reverse(); + config.ignorePatterns = ignores ? ignores.patterns : []; + + // Strip the default patterns from `ignorePatterns`. + if (startsWith(config.ignorePatterns, IgnorePattern.DefaultPatterns)) { + config.ignorePatterns = + config.ignorePatterns.slice(IgnorePattern.DefaultPatterns.length); + } + + return config; + } +} + +module.exports = { ExtractedConfig }; diff --git a/eslint/lib/cli-engine/config-array/ignore-pattern.js b/eslint/lib/cli-engine/config-array/ignore-pattern.js new file mode 100644 index 0000000..92690b9 --- /dev/null +++ b/eslint/lib/cli-engine/config-array/ignore-pattern.js @@ -0,0 +1,231 @@ +/** + * @fileoverview `IgnorePattern` class. + * + * `IgnorePattern` class has the set of glob patterns and the base path. + * + * It provides two static methods. + * + * - `IgnorePattern.createDefaultIgnore(cwd)` + * Create the default predicate function. + * - `IgnorePattern.createIgnore(ignorePatterns)` + * Create the predicate function from multiple `IgnorePattern` objects. + * + * It provides two properties and a method. + * + * - `patterns` + * The glob patterns that ignore to lint. + * - `basePath` + * The base path of the glob patterns. If absolute paths existed in the + * glob patterns, those are handled as relative paths to the base path. + * - `getPatternsRelativeTo(basePath)` + * Get `patterns` as modified for a given base path. It modifies the + * absolute paths in the patterns as prepending the difference of two base + * paths. + * + * `ConfigArrayFactory` creates `IgnorePattern` objects when it processes + * `ignorePatterns` properties. + * + * @author Toru Nagashima + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const assert = require("assert"); +const path = require("path"); +const ignore = require("ignore"); +const debug = require("debug")("eslint:ignore-pattern"); + +/** @typedef {ReturnType} Ignore */ + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Get the path to the common ancestor directory of given paths. + * @param {string[]} sourcePaths The paths to calculate the common ancestor. + * @returns {string} The path to the common ancestor directory. + */ +function getCommonAncestorPath(sourcePaths) { + let result = sourcePaths[0]; + + for (let i = 1; i < sourcePaths.length; ++i) { + const a = result; + const b = sourcePaths[i]; + + // Set the shorter one (it's the common ancestor if one includes the other). + result = a.length < b.length ? a : b; + + // Set the common ancestor. + for (let j = 0, lastSepPos = 0; j < a.length && j < b.length; ++j) { + if (a[j] !== b[j]) { + result = a.slice(0, lastSepPos); + break; + } + if (a[j] === path.sep) { + lastSepPos = j; + } + } + } + + return result || path.sep; +} + +/** + * Make relative path. + * @param {string} from The source path to get relative path. + * @param {string} to The destination path to get relative path. + * @returns {string} The relative path. + */ +function relative(from, to) { + const relPath = path.relative(from, to); + + if (path.sep === "/") { + return relPath; + } + return relPath.split(path.sep).join("/"); +} + +/** + * Get the trailing slash if existed. + * @param {string} filePath The path to check. + * @returns {string} The trailing slash if existed. + */ +function dirSuffix(filePath) { + const isDir = ( + filePath.endsWith(path.sep) || + (process.platform === "win32" && filePath.endsWith("/")) + ); + + return isDir ? "/" : ""; +} + +const DefaultPatterns = Object.freeze(["/**/node_modules/*"]); +const DotPatterns = Object.freeze([".*", "!.eslintrc.*", "!../"]); + +//------------------------------------------------------------------------------ +// Public +//------------------------------------------------------------------------------ + +class IgnorePattern { + + /** + * The default patterns. + * @type {string[]} + */ + static get DefaultPatterns() { + return DefaultPatterns; + } + + /** + * Create the default predicate function. + * @param {string} cwd The current working directory. + * @returns {((filePath:string, dot:boolean) => boolean) & {basePath:string; patterns:string[]}} + * The preficate function. + * The first argument is an absolute path that is checked. + * The second argument is the flag to not ignore dotfiles. + * If the predicate function returned `true`, it means the path should be ignored. + */ + static createDefaultIgnore(cwd) { + return this.createIgnore([new IgnorePattern(DefaultPatterns, cwd)]); + } + + /** + * Create the predicate function from multiple `IgnorePattern` objects. + * @param {IgnorePattern[]} ignorePatterns The list of ignore patterns. + * @returns {((filePath:string, dot?:boolean) => boolean) & {basePath:string; patterns:string[]}} + * The preficate function. + * The first argument is an absolute path that is checked. + * The second argument is the flag to not ignore dotfiles. + * If the predicate function returned `true`, it means the path should be ignored. + */ + static createIgnore(ignorePatterns) { + debug("Create with: %o", ignorePatterns); + + const basePath = getCommonAncestorPath(ignorePatterns.map(p => p.basePath)); + const patterns = [].concat( + ...ignorePatterns.map(p => p.getPatternsRelativeTo(basePath)) + ); + const ig = ignore().add([...DotPatterns, ...patterns]); + const dotIg = ignore().add(patterns); + + debug(" processed: %o", { basePath, patterns }); + + return Object.assign( + (filePath, dot = false) => { + assert(path.isAbsolute(filePath), "'filePath' should be an absolute path."); + const relPathRaw = relative(basePath, filePath); + const relPath = relPathRaw && (relPathRaw + dirSuffix(filePath)); + const adoptedIg = dot ? dotIg : ig; + const result = relPath !== "" && adoptedIg.ignores(relPath); + + debug("Check", { filePath, dot, relativePath: relPath, result }); + return result; + }, + { basePath, patterns } + ); + } + + /** + * Initialize a new `IgnorePattern` instance. + * @param {string[]} patterns The glob patterns that ignore to lint. + * @param {string} basePath The base path of `patterns`. + */ + constructor(patterns, basePath) { + assert(path.isAbsolute(basePath), "'basePath' should be an absolute path."); + + /** + * The glob patterns that ignore to lint. + * @type {string[]} + */ + this.patterns = patterns; + + /** + * The base path of `patterns`. + * @type {string} + */ + this.basePath = basePath; + + /** + * If `true` then patterns which don't start with `/` will match the paths to the outside of `basePath`. Defaults to `false`. + * + * It's set `true` for `.eslintignore`, `package.json`, and `--ignore-path` for backward compatibility. + * It's `false` as-is for `ignorePatterns` property in config files. + * @type {boolean} + */ + this.loose = false; + } + + /** + * Get `patterns` as modified for a given base path. It modifies the + * absolute paths in the patterns as prepending the difference of two base + * paths. + * @param {string} newBasePath The base path. + * @returns {string[]} Modifired patterns. + */ + getPatternsRelativeTo(newBasePath) { + assert(path.isAbsolute(newBasePath), "'newBasePath' should be an absolute path."); + const { basePath, loose, patterns } = this; + + if (newBasePath === basePath) { + return patterns; + } + const prefix = `/${relative(newBasePath, basePath)}`; + + return patterns.map(pattern => { + const negative = pattern.startsWith("!"); + const head = negative ? "!" : ""; + const body = negative ? pattern.slice(1) : pattern; + + if (body.startsWith("/") || body.startsWith("../")) { + return `${head}${prefix}${body}`; + } + return loose ? pattern : `${head}${prefix}/**/${body}`; + }); + } +} + +module.exports = { IgnorePattern }; diff --git a/eslint/lib/cli-engine/config-array/index.js b/eslint/lib/cli-engine/config-array/index.js new file mode 100644 index 0000000..928d76c --- /dev/null +++ b/eslint/lib/cli-engine/config-array/index.js @@ -0,0 +1,20 @@ +/** + * @fileoverview `ConfigArray` class. + * @author Toru Nagashima + */ +"use strict"; + +const { ConfigArray, getUsedExtractedConfigs } = require("./config-array"); +const { ConfigDependency } = require("./config-dependency"); +const { ExtractedConfig } = require("./extracted-config"); +const { IgnorePattern } = require("./ignore-pattern"); +const { OverrideTester } = require("./override-tester"); + +module.exports = { + ConfigArray, + ConfigDependency, + ExtractedConfig, + IgnorePattern, + OverrideTester, + getUsedExtractedConfigs +}; diff --git a/eslint/lib/cli-engine/config-array/override-tester.js b/eslint/lib/cli-engine/config-array/override-tester.js new file mode 100644 index 0000000..e7ba120 --- /dev/null +++ b/eslint/lib/cli-engine/config-array/override-tester.js @@ -0,0 +1,223 @@ +/** + * @fileoverview `OverrideTester` class. + * + * `OverrideTester` class handles `files` property and `excludedFiles` property + * of `overrides` config. + * + * It provides one method. + * + * - `test(filePath)` + * Test if a file path matches the pair of `files` property and + * `excludedFiles` property. The `filePath` argument must be an absolute + * path. + * + * `ConfigArrayFactory` creates `OverrideTester` objects when it processes + * `overrides` properties. + * + * @author Toru Nagashima + */ +"use strict"; + +const assert = require("assert"); +const path = require("path"); +const util = require("util"); +const { Minimatch } = require("minimatch"); +const minimatchOpts = { dot: true, matchBase: true }; + +/** + * @typedef {Object} Pattern + * @property {InstanceType[] | null} includes The positive matchers. + * @property {InstanceType[] | null} excludes The negative matchers. + */ + +/** + * Normalize a given pattern to an array. + * @param {string|string[]|undefined} patterns A glob pattern or an array of glob patterns. + * @returns {string[]|null} Normalized patterns. + * @private + */ +function normalizePatterns(patterns) { + if (Array.isArray(patterns)) { + return patterns.filter(Boolean); + } + if (typeof patterns === "string" && patterns) { + return [patterns]; + } + return []; +} + +/** + * Create the matchers of given patterns. + * @param {string[]} patterns The patterns. + * @returns {InstanceType[] | null} The matchers. + */ +function toMatcher(patterns) { + if (patterns.length === 0) { + return null; + } + return patterns.map(pattern => { + if (/^\.[/\\]/u.test(pattern)) { + return new Minimatch( + pattern.slice(2), + + // `./*.js` should not match with `subdir/foo.js` + { ...minimatchOpts, matchBase: false } + ); + } + return new Minimatch(pattern, minimatchOpts); + }); +} + +/** + * Convert a given matcher to string. + * @param {Pattern} matchers The matchers. + * @returns {string} The string expression of the matcher. + */ +function patternToJson({ includes, excludes }) { + return { + includes: includes && includes.map(m => m.pattern), + excludes: excludes && excludes.map(m => m.pattern) + }; +} + +/** + * The class to test given paths are matched by the patterns. + */ +class OverrideTester { + + /** + * Create a tester with given criteria. + * If there are no criteria, returns `null`. + * @param {string|string[]} files The glob patterns for included files. + * @param {string|string[]} excludedFiles The glob patterns for excluded files. + * @param {string} basePath The path to the base directory to test paths. + * @returns {OverrideTester|null} The created instance or `null`. + */ + static create(files, excludedFiles, basePath) { + const includePatterns = normalizePatterns(files); + const excludePatterns = normalizePatterns(excludedFiles); + let endsWithWildcard = false; + + if (includePatterns.length === 0) { + return null; + } + + // Rejects absolute paths or relative paths to parents. + for (const pattern of includePatterns) { + if (path.isAbsolute(pattern) || pattern.includes("..")) { + throw new Error(`Invalid override pattern (expected relative path not containing '..'): ${pattern}`); + } + if (pattern.endsWith("*")) { + endsWithWildcard = true; + } + } + for (const pattern of excludePatterns) { + if (path.isAbsolute(pattern) || pattern.includes("..")) { + throw new Error(`Invalid override pattern (expected relative path not containing '..'): ${pattern}`); + } + } + + const includes = toMatcher(includePatterns); + const excludes = toMatcher(excludePatterns); + + return new OverrideTester( + [{ includes, excludes }], + basePath, + endsWithWildcard + ); + } + + /** + * Combine two testers by logical and. + * If either of the testers was `null`, returns the other tester. + * The `basePath` property of the two must be the same value. + * @param {OverrideTester|null} a A tester. + * @param {OverrideTester|null} b Another tester. + * @returns {OverrideTester|null} Combined tester. + */ + static and(a, b) { + if (!b) { + return a && new OverrideTester( + a.patterns, + a.basePath, + a.endsWithWildcard + ); + } + if (!a) { + return new OverrideTester( + b.patterns, + b.basePath, + b.endsWithWildcard + ); + } + + assert.strictEqual(a.basePath, b.basePath); + return new OverrideTester( + a.patterns.concat(b.patterns), + a.basePath, + a.endsWithWildcard || b.endsWithWildcard + ); + } + + /** + * Initialize this instance. + * @param {Pattern[]} patterns The matchers. + * @param {string} basePath The base path. + * @param {boolean} endsWithWildcard If `true` then a pattern ends with `*`. + */ + constructor(patterns, basePath, endsWithWildcard = false) { + + /** @type {Pattern[]} */ + this.patterns = patterns; + + /** @type {string} */ + this.basePath = basePath; + + /** @type {boolean} */ + this.endsWithWildcard = endsWithWildcard; + } + + /** + * Test if a given path is matched or not. + * @param {string} filePath The absolute path to the target file. + * @returns {boolean} `true` if the path was matched. + */ + test(filePath) { + if (typeof filePath !== "string" || !path.isAbsolute(filePath)) { + throw new Error(`'filePath' should be an absolute path, but got ${filePath}.`); + } + const relativePath = path.relative(this.basePath, filePath); + + return this.patterns.every(({ includes, excludes }) => ( + (!includes || includes.some(m => m.match(relativePath))) && + (!excludes || !excludes.some(m => m.match(relativePath))) + )); + } + + // eslint-disable-next-line jsdoc/require-description + /** + * @returns {Object} a JSON compatible object. + */ + toJSON() { + if (this.patterns.length === 1) { + return { + ...patternToJson(this.patterns[0]), + basePath: this.basePath + }; + } + return { + AND: this.patterns.map(patternToJson), + basePath: this.basePath + }; + } + + // eslint-disable-next-line jsdoc/require-description + /** + * @returns {Object} an object to display by `console.log()`. + */ + [util.inspect.custom]() { + return this.toJSON(); + } +} + +module.exports = { OverrideTester }; diff --git a/eslint/lib/cli-engine/file-enumerator.js b/eslint/lib/cli-engine/file-enumerator.js new file mode 100644 index 0000000..7c433d3 --- /dev/null +++ b/eslint/lib/cli-engine/file-enumerator.js @@ -0,0 +1,528 @@ +/** + * @fileoverview `FileEnumerator` class. + * + * `FileEnumerator` class has two responsibilities: + * + * 1. Find target files by processing glob patterns. + * 2. Tie each target file and appropriate configuration. + * + * It provides a method: + * + * - `iterateFiles(patterns)` + * Iterate files which are matched by given patterns together with the + * corresponded configuration. This is for `CLIEngine#executeOnFiles()`. + * While iterating files, it loads the configuration file of each directory + * before iterate files on the directory, so we can use the configuration + * files to determine target files. + * + * @example + * const enumerator = new FileEnumerator(); + * const linter = new Linter(); + * + * for (const { config, filePath } of enumerator.iterateFiles(["*.js"])) { + * const code = fs.readFileSync(filePath, "utf8"); + * const messages = linter.verify(code, config, filePath); + * + * console.log(messages); + * } + * + * @author Toru Nagashima + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const fs = require("fs"); +const path = require("path"); +const getGlobParent = require("glob-parent"); +const isGlob = require("is-glob"); +const { escapeRegExp } = require("lodash"); +const { Minimatch } = require("minimatch"); +const { IgnorePattern } = require("./config-array"); +const { CascadingConfigArrayFactory } = require("./cascading-config-array-factory"); +const debug = require("debug")("eslint:file-enumerator"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const minimatchOpts = { dot: true, matchBase: true }; +const dotfilesPattern = /(?:(?:^\.)|(?:[/\\]\.))[^/\\.].*/u; +const NONE = 0; +const IGNORED_SILENTLY = 1; +const IGNORED = 2; + +// For VSCode intellisense +/** @typedef {ReturnType} ConfigArray */ + +/** + * @typedef {Object} FileEnumeratorOptions + * @property {CascadingConfigArrayFactory} [configArrayFactory] The factory for config arrays. + * @property {string} [cwd] The base directory to start lookup. + * @property {string[]} [extensions] The extensions to match files for directory patterns. + * @property {boolean} [globInputPaths] Set to false to skip glob resolution of input file paths to lint (default: true). If false, each input file paths is assumed to be a non-glob path to an existing file. + * @property {boolean} [ignore] The flag to check ignored files. + * @property {string[]} [rulePaths] The value of `--rulesdir` option. + */ + +/** + * @typedef {Object} FileAndConfig + * @property {string} filePath The path to a target file. + * @property {ConfigArray} config The config entries of that file. + * @property {boolean} ignored If `true` then this file should be ignored and warned because it was directly specified. + */ + +/** + * @typedef {Object} FileEntry + * @property {string} filePath The path to a target file. + * @property {ConfigArray} config The config entries of that file. + * @property {NONE|IGNORED_SILENTLY|IGNORED} flag The flag. + * - `NONE` means the file is a target file. + * - `IGNORED_SILENTLY` means the file should be ignored silently. + * - `IGNORED` means the file should be ignored and warned because it was directly specified. + */ + +/** + * @typedef {Object} FileEnumeratorInternalSlots + * @property {CascadingConfigArrayFactory} configArrayFactory The factory for config arrays. + * @property {string} cwd The base directory to start lookup. + * @property {RegExp|null} extensionRegExp The RegExp to test if a string ends with specific file extensions. + * @property {boolean} globInputPaths Set to false to skip glob resolution of input file paths to lint (default: true). If false, each input file paths is assumed to be a non-glob path to an existing file. + * @property {boolean} ignoreFlag The flag to check ignored files. + * @property {(filePath:string, dot:boolean) => boolean} defaultIgnores The default predicate function to ignore files. + */ + +/** @type {WeakMap} */ +const internalSlotsMap = new WeakMap(); + +/** + * Check if a string is a glob pattern or not. + * @param {string} pattern A glob pattern. + * @returns {boolean} `true` if the string is a glob pattern. + */ +function isGlobPattern(pattern) { + return isGlob(path.sep === "\\" ? pattern.replace(/\\/gu, "/") : pattern); +} + +/** + * Get stats of a given path. + * @param {string} filePath The path to target file. + * @returns {fs.Stats|null} The stats. + * @private + */ +function statSafeSync(filePath) { + try { + return fs.statSync(filePath); + } catch (error) { + /* istanbul ignore next */ + if (error.code !== "ENOENT") { + throw error; + } + return null; + } +} + +/** + * Get filenames in a given path to a directory. + * @param {string} directoryPath The path to target directory. + * @returns {import("fs").Dirent[]} The filenames. + * @private + */ +function readdirSafeSync(directoryPath) { + try { + return fs.readdirSync(directoryPath, { withFileTypes: true }); + } catch (error) { + /* istanbul ignore next */ + if (error.code !== "ENOENT") { + throw error; + } + return []; + } +} + +/** + * Create a `RegExp` object to detect extensions. + * @param {string[] | null} extensions The extensions to create. + * @returns {RegExp | null} The created `RegExp` object or null. + */ +function createExtensionRegExp(extensions) { + if (extensions) { + const normalizedExts = extensions.map(ext => escapeRegExp( + ext.startsWith(".") + ? ext.slice(1) + : ext + )); + + return new RegExp( + `.\\.(?:${normalizedExts.join("|")})$`, + "u" + ); + } + return null; +} + +/** + * The error type when no files match a glob. + */ +class NoFilesFoundError extends Error { + + // eslint-disable-next-line jsdoc/require-description + /** + * @param {string} pattern The glob pattern which was not found. + * @param {boolean} globDisabled If `true` then the pattern was a glob pattern, but glob was disabled. + */ + constructor(pattern, globDisabled) { + super(`No files matching '${pattern}' were found${globDisabled ? " (glob was disabled)" : ""}.`); + this.messageTemplate = "file-not-found"; + this.messageData = { pattern, globDisabled }; + } +} + +/** + * The error type when there are files matched by a glob, but all of them have been ignored. + */ +class AllFilesIgnoredError extends Error { + + // eslint-disable-next-line jsdoc/require-description + /** + * @param {string} pattern The glob pattern which was not found. + */ + constructor(pattern) { + super(`All files matched by '${pattern}' are ignored.`); + this.messageTemplate = "all-files-ignored"; + this.messageData = { pattern }; + } +} + +/** + * This class provides the functionality that enumerates every file which is + * matched by given glob patterns and that configuration. + */ +class FileEnumerator { + + /** + * Initialize this enumerator. + * @param {FileEnumeratorOptions} options The options. + */ + constructor({ + cwd = process.cwd(), + configArrayFactory = new CascadingConfigArrayFactory({ cwd }), + extensions = null, + globInputPaths = true, + errorOnUnmatchedPattern = true, + ignore = true + } = {}) { + internalSlotsMap.set(this, { + configArrayFactory, + cwd, + defaultIgnores: IgnorePattern.createDefaultIgnore(cwd), + extensionRegExp: createExtensionRegExp(extensions), + globInputPaths, + errorOnUnmatchedPattern, + ignoreFlag: ignore + }); + } + + /** + * Check if a given file is target or not. + * @param {string} filePath The path to a candidate file. + * @param {ConfigArray} [providedConfig] Optional. The configuration for the file. + * @returns {boolean} `true` if the file is a target. + */ + isTargetPath(filePath, providedConfig) { + const { + configArrayFactory, + extensionRegExp + } = internalSlotsMap.get(this); + + // If `--ext` option is present, use it. + if (extensionRegExp) { + return extensionRegExp.test(filePath); + } + + // `.js` file is target by default. + if (filePath.endsWith(".js")) { + return true; + } + + // use `overrides[].files` to check additional targets. + const config = + providedConfig || + configArrayFactory.getConfigArrayForFile( + filePath, + { ignoreNotFoundError: true } + ); + + return config.isAdditionalTargetPath(filePath); + } + + /** + * Iterate files which are matched by given glob patterns. + * @param {string|string[]} patternOrPatterns The glob patterns to iterate files. + * @returns {IterableIterator} The found files. + */ + *iterateFiles(patternOrPatterns) { + const { globInputPaths, errorOnUnmatchedPattern } = internalSlotsMap.get(this); + const patterns = Array.isArray(patternOrPatterns) + ? patternOrPatterns + : [patternOrPatterns]; + + debug("Start to iterate files: %o", patterns); + + // The set of paths to remove duplicate. + const set = new Set(); + + for (const pattern of patterns) { + let foundRegardlessOfIgnored = false; + let found = false; + + // Skip empty string. + if (!pattern) { + continue; + } + + // Iterate files of this pattern. + for (const { config, filePath, flag } of this._iterateFiles(pattern)) { + foundRegardlessOfIgnored = true; + if (flag === IGNORED_SILENTLY) { + continue; + } + found = true; + + // Remove duplicate paths while yielding paths. + if (!set.has(filePath)) { + set.add(filePath); + yield { + config, + filePath, + ignored: flag === IGNORED + }; + } + } + + // Raise an error if any files were not found. + if (errorOnUnmatchedPattern) { + if (!foundRegardlessOfIgnored) { + throw new NoFilesFoundError( + pattern, + !globInputPaths && isGlob(pattern) + ); + } + if (!found) { + throw new AllFilesIgnoredError(pattern); + } + } + } + + debug(`Complete iterating files: ${JSON.stringify(patterns)}`); + } + + /** + * Iterate files which are matched by a given glob pattern. + * @param {string} pattern The glob pattern to iterate files. + * @returns {IterableIterator} The found files. + */ + _iterateFiles(pattern) { + const { cwd, globInputPaths } = internalSlotsMap.get(this); + const absolutePath = path.resolve(cwd, pattern); + const isDot = dotfilesPattern.test(pattern); + const stat = statSafeSync(absolutePath); + + if (stat && stat.isDirectory()) { + return this._iterateFilesWithDirectory(absolutePath, isDot); + } + if (stat && stat.isFile()) { + return this._iterateFilesWithFile(absolutePath); + } + if (globInputPaths && isGlobPattern(pattern)) { + return this._iterateFilesWithGlob(absolutePath, isDot); + } + + return []; + } + + /** + * Iterate a file which is matched by a given path. + * @param {string} filePath The path to the target file. + * @returns {IterableIterator} The found files. + * @private + */ + _iterateFilesWithFile(filePath) { + debug(`File: ${filePath}`); + + const { configArrayFactory } = internalSlotsMap.get(this); + const config = configArrayFactory.getConfigArrayForFile(filePath); + const ignored = this._isIgnoredFile(filePath, { config, direct: true }); + const flag = ignored ? IGNORED : NONE; + + return [{ config, filePath, flag }]; + } + + /** + * Iterate files in a given path. + * @param {string} directoryPath The path to the target directory. + * @param {boolean} dotfiles If `true` then it doesn't skip dot files by default. + * @returns {IterableIterator} The found files. + * @private + */ + _iterateFilesWithDirectory(directoryPath, dotfiles) { + debug(`Directory: ${directoryPath}`); + + return this._iterateFilesRecursive( + directoryPath, + { dotfiles, recursive: true, selector: null } + ); + } + + /** + * Iterate files which are matched by a given glob pattern. + * @param {string} pattern The glob pattern to iterate files. + * @param {boolean} dotfiles If `true` then it doesn't skip dot files by default. + * @returns {IterableIterator} The found files. + * @private + */ + _iterateFilesWithGlob(pattern, dotfiles) { + debug(`Glob: ${pattern}`); + + const directoryPath = path.resolve(getGlobParent(pattern)); + const globPart = pattern.slice(directoryPath.length + 1); + + /* + * recursive if there are `**` or path separators in the glob part. + * Otherwise, patterns such as `src/*.js`, it doesn't need recursive. + */ + const recursive = /\*\*|\/|\\/u.test(globPart); + const selector = new Minimatch(pattern, minimatchOpts); + + debug(`recursive? ${recursive}`); + + return this._iterateFilesRecursive( + directoryPath, + { dotfiles, recursive, selector } + ); + } + + /** + * Iterate files in a given path. + * @param {string} directoryPath The path to the target directory. + * @param {Object} options The options to iterate files. + * @param {boolean} [options.dotfiles] If `true` then it doesn't skip dot files by default. + * @param {boolean} [options.recursive] If `true` then it dives into sub directories. + * @param {InstanceType} [options.selector] The matcher to choose files. + * @returns {IterableIterator} The found files. + * @private + */ + *_iterateFilesRecursive(directoryPath, options) { + debug(`Enter the directory: ${directoryPath}`); + const { configArrayFactory } = internalSlotsMap.get(this); + + /** @type {ConfigArray|null} */ + let config = null; + + // Enumerate the files of this directory. + for (const entry of readdirSafeSync(directoryPath)) { + const filePath = path.join(directoryPath, entry.name); + + // Check if the file is matched. + if (entry.isFile()) { + if (!config) { + config = configArrayFactory.getConfigArrayForFile( + filePath, + + /* + * We must ignore `ConfigurationNotFoundError` at this + * point because we don't know if target files exist in + * this directory. + */ + { ignoreNotFoundError: true } + ); + } + const matched = options.selector + + // Started with a glob pattern; choose by the pattern. + ? options.selector.match(filePath) + + // Started with a directory path; choose by file extensions. + : this.isTargetPath(filePath, config); + + if (matched) { + const ignored = this._isIgnoredFile(filePath, { ...options, config }); + const flag = ignored ? IGNORED_SILENTLY : NONE; + + debug(`Yield: ${entry.name}${ignored ? " but ignored" : ""}`); + yield { + config: configArrayFactory.getConfigArrayForFile(filePath), + filePath, + flag + }; + } else { + debug(`Didn't match: ${entry.name}`); + } + + // Dive into the sub directory. + } else if (options.recursive && entry.isDirectory()) { + if (!config) { + config = configArrayFactory.getConfigArrayForFile( + filePath, + { ignoreNotFoundError: true } + ); + } + const ignored = this._isIgnoredFile( + filePath + path.sep, + { ...options, config } + ); + + if (!ignored) { + yield* this._iterateFilesRecursive(filePath, options); + } + } + } + + debug(`Leave the directory: ${directoryPath}`); + } + + /** + * Check if a given file should be ignored. + * @param {string} filePath The path to a file to check. + * @param {Object} options Options + * @param {ConfigArray} [options.config] The config for this file. + * @param {boolean} [options.dotfiles] If `true` then this is not ignore dot files by default. + * @param {boolean} [options.direct] If `true` then this is a direct specified file. + * @returns {boolean} `true` if the file should be ignored. + * @private + */ + _isIgnoredFile(filePath, { + config: providedConfig, + dotfiles = false, + direct = false + }) { + const { + configArrayFactory, + defaultIgnores, + ignoreFlag + } = internalSlotsMap.get(this); + + if (ignoreFlag) { + const config = + providedConfig || + configArrayFactory.getConfigArrayForFile( + filePath, + { ignoreNotFoundError: true } + ); + const ignores = + config.extractConfig(filePath).ignores || defaultIgnores; + + return ignores(filePath, dotfiles); + } + + return !direct && defaultIgnores(filePath, dotfiles); + } +} + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = { FileEnumerator }; diff --git a/eslint/lib/cli-engine/formatters/checkstyle.js b/eslint/lib/cli-engine/formatters/checkstyle.js new file mode 100644 index 0000000..ba4d1b5 --- /dev/null +++ b/eslint/lib/cli-engine/formatters/checkstyle.js @@ -0,0 +1,60 @@ +/** + * @fileoverview CheckStyle XML reporter + * @author Ian Christian Myers + */ +"use strict"; + +const xmlEscape = require("../xml-escape"); + +//------------------------------------------------------------------------------ +// Helper Functions +//------------------------------------------------------------------------------ + +/** + * Returns the severity of warning or error + * @param {Object} message message object to examine + * @returns {string} severity level + * @private + */ +function getMessageType(message) { + if (message.fatal || message.severity === 2) { + return "error"; + } + return "warning"; + +} + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = function(results) { + + let output = ""; + + output += ""; + output += ""; + + results.forEach(result => { + const messages = result.messages; + + output += ``; + + messages.forEach(message => { + output += [ + `` + ].join(" "); + }); + + output += ""; + + }); + + output += ""; + + return output; +}; diff --git a/eslint/lib/cli-engine/formatters/codeframe.js b/eslint/lib/cli-engine/formatters/codeframe.js new file mode 100644 index 0000000..41e3ab7 --- /dev/null +++ b/eslint/lib/cli-engine/formatters/codeframe.js @@ -0,0 +1,138 @@ +/** + * @fileoverview Codeframe reporter + * @author Vitor Balocco + */ +"use strict"; + +const chalk = require("chalk"); +const { codeFrameColumns } = require("@babel/code-frame"); +const path = require("path"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Given a word and a count, append an s if count is not one. + * @param {string} word A word in its singular form. + * @param {number} count A number controlling whether word should be pluralized. + * @returns {string} The original word with an s on the end if count is not one. + */ +function pluralize(word, count) { + return (count === 1 ? word : `${word}s`); +} + +/** + * Gets a formatted relative file path from an absolute path and a line/column in the file. + * @param {string} filePath The absolute file path to format. + * @param {number} line The line from the file to use for formatting. + * @param {number} column The column from the file to use for formatting. + * @returns {string} The formatted file path. + */ +function formatFilePath(filePath, line, column) { + let relPath = path.relative(process.cwd(), filePath); + + if (line && column) { + relPath += `:${line}:${column}`; + } + + return chalk.green(relPath); +} + +/** + * Gets the formatted output for a given message. + * @param {Object} message The object that represents this message. + * @param {Object} parentResult The result object that this message belongs to. + * @returns {string} The formatted output. + */ +function formatMessage(message, parentResult) { + const type = (message.fatal || message.severity === 2) ? chalk.red("error") : chalk.yellow("warning"); + const msg = `${chalk.bold(message.message.replace(/([^ ])\.$/u, "$1"))}`; + const ruleId = message.fatal ? "" : chalk.dim(`(${message.ruleId})`); + const filePath = formatFilePath(parentResult.filePath, message.line, message.column); + const sourceCode = parentResult.output ? parentResult.output : parentResult.source; + + const firstLine = [ + `${type}:`, + `${msg}`, + ruleId ? `${ruleId}` : "", + sourceCode ? `at ${filePath}:` : `at ${filePath}` + ].filter(String).join(" "); + + const result = [firstLine]; + + if (sourceCode) { + result.push( + codeFrameColumns(sourceCode, { start: { line: message.line, column: message.column } }, { highlightCode: false }) + ); + } + + return result.join("\n"); +} + +/** + * Gets the formatted output summary for a given number of errors and warnings. + * @param {number} errors The number of errors. + * @param {number} warnings The number of warnings. + * @param {number} fixableErrors The number of fixable errors. + * @param {number} fixableWarnings The number of fixable warnings. + * @returns {string} The formatted output summary. + */ +function formatSummary(errors, warnings, fixableErrors, fixableWarnings) { + const summaryColor = errors > 0 ? "red" : "yellow"; + const summary = []; + const fixablesSummary = []; + + if (errors > 0) { + summary.push(`${errors} ${pluralize("error", errors)}`); + } + + if (warnings > 0) { + summary.push(`${warnings} ${pluralize("warning", warnings)}`); + } + + if (fixableErrors > 0) { + fixablesSummary.push(`${fixableErrors} ${pluralize("error", fixableErrors)}`); + } + + if (fixableWarnings > 0) { + fixablesSummary.push(`${fixableWarnings} ${pluralize("warning", fixableWarnings)}`); + } + + let output = chalk[summaryColor].bold(`${summary.join(" and ")} found.`); + + if (fixableErrors || fixableWarnings) { + output += chalk[summaryColor].bold(`\n${fixablesSummary.join(" and ")} potentially fixable with the \`--fix\` option.`); + } + + return output; +} + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = function(results) { + let errors = 0; + let warnings = 0; + let fixableErrors = 0; + let fixableWarnings = 0; + + const resultsWithMessages = results.filter(result => result.messages.length > 0); + + let output = resultsWithMessages.reduce((resultsOutput, result) => { + const messages = result.messages.map(message => `${formatMessage(message, result)}\n\n`); + + errors += result.errorCount; + warnings += result.warningCount; + fixableErrors += result.fixableErrorCount; + fixableWarnings += result.fixableWarningCount; + + return resultsOutput.concat(messages); + }, []).join("\n"); + + output += "\n"; + output += formatSummary(errors, warnings, fixableErrors, fixableWarnings); + + return (errors + warnings) > 0 ? output : ""; +}; diff --git a/eslint/lib/cli-engine/formatters/compact.js b/eslint/lib/cli-engine/formatters/compact.js new file mode 100644 index 0000000..2b540bd --- /dev/null +++ b/eslint/lib/cli-engine/formatters/compact.js @@ -0,0 +1,60 @@ +/** + * @fileoverview Compact reporter + * @author Nicholas C. Zakas + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Helper Functions +//------------------------------------------------------------------------------ + +/** + * Returns the severity of warning or error + * @param {Object} message message object to examine + * @returns {string} severity level + * @private + */ +function getMessageType(message) { + if (message.fatal || message.severity === 2) { + return "Error"; + } + return "Warning"; + +} + + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = function(results) { + + let output = "", + total = 0; + + results.forEach(result => { + + const messages = result.messages; + + total += messages.length; + + messages.forEach(message => { + + output += `${result.filePath}: `; + output += `line ${message.line || 0}`; + output += `, col ${message.column || 0}`; + output += `, ${getMessageType(message)}`; + output += ` - ${message.message}`; + output += message.ruleId ? ` (${message.ruleId})` : ""; + output += "\n"; + + }); + + }); + + if (total > 0) { + output += `\n${total} problem${total !== 1 ? "s" : ""}`; + } + + return output; +}; diff --git a/eslint/lib/cli-engine/formatters/html-template-message.html b/eslint/lib/cli-engine/formatters/html-template-message.html new file mode 100644 index 0000000..93795a1 --- /dev/null +++ b/eslint/lib/cli-engine/formatters/html-template-message.html @@ -0,0 +1,8 @@ + + <%= lineNumber %>:<%= columnNumber %> + <%= severityName %> + <%- message %> + + <%= ruleId %> + + diff --git a/eslint/lib/cli-engine/formatters/html-template-page.html b/eslint/lib/cli-engine/formatters/html-template-page.html new file mode 100644 index 0000000..4016576 --- /dev/null +++ b/eslint/lib/cli-engine/formatters/html-template-page.html @@ -0,0 +1,115 @@ + + + + + ESLint Report + + + +
+

ESLint Report

+
+ <%= reportSummary %> - Generated on <%= date %> +
+
+ + + <%= results %> + +
+ + + diff --git a/eslint/lib/cli-engine/formatters/html-template-result.html b/eslint/lib/cli-engine/formatters/html-template-result.html new file mode 100644 index 0000000..f4a5593 --- /dev/null +++ b/eslint/lib/cli-engine/formatters/html-template-result.html @@ -0,0 +1,6 @@ + + + [+] <%- filePath %> + <%- summary %> + + diff --git a/eslint/lib/cli-engine/formatters/html.js b/eslint/lib/cli-engine/formatters/html.js new file mode 100644 index 0000000..69f7395 --- /dev/null +++ b/eslint/lib/cli-engine/formatters/html.js @@ -0,0 +1,140 @@ +/** + * @fileoverview HTML reporter + * @author Julian Laval + */ +"use strict"; + +const lodash = require("lodash"); +const fs = require("fs"); +const path = require("path"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const pageTemplate = lodash.template(fs.readFileSync(path.join(__dirname, "html-template-page.html"), "utf-8")); +const messageTemplate = lodash.template(fs.readFileSync(path.join(__dirname, "html-template-message.html"), "utf-8")); +const resultTemplate = lodash.template(fs.readFileSync(path.join(__dirname, "html-template-result.html"), "utf-8")); + +/** + * Given a word and a count, append an s if count is not one. + * @param {string} word A word in its singular form. + * @param {int} count A number controlling whether word should be pluralized. + * @returns {string} The original word with an s on the end if count is not one. + */ +function pluralize(word, count) { + return (count === 1 ? word : `${word}s`); +} + +/** + * Renders text along the template of x problems (x errors, x warnings) + * @param {string} totalErrors Total errors + * @param {string} totalWarnings Total warnings + * @returns {string} The formatted string, pluralized where necessary + */ +function renderSummary(totalErrors, totalWarnings) { + const totalProblems = totalErrors + totalWarnings; + let renderedText = `${totalProblems} ${pluralize("problem", totalProblems)}`; + + if (totalProblems !== 0) { + renderedText += ` (${totalErrors} ${pluralize("error", totalErrors)}, ${totalWarnings} ${pluralize("warning", totalWarnings)})`; + } + return renderedText; +} + +/** + * Get the color based on whether there are errors/warnings... + * @param {string} totalErrors Total errors + * @param {string} totalWarnings Total warnings + * @returns {int} The color code (0 = green, 1 = yellow, 2 = red) + */ +function renderColor(totalErrors, totalWarnings) { + if (totalErrors !== 0) { + return 2; + } + if (totalWarnings !== 0) { + return 1; + } + return 0; +} + +/** + * Get HTML (table rows) describing the messages. + * @param {Array} messages Messages. + * @param {int} parentIndex Index of the parent HTML row. + * @param {Object} rulesMeta Dictionary containing metadata for each rule executed by the analysis. + * @returns {string} HTML (table rows) describing the messages. + */ +function renderMessages(messages, parentIndex, rulesMeta) { + + /** + * Get HTML (table row) describing a message. + * @param {Object} message Message. + * @returns {string} HTML (table row) describing a message. + */ + return lodash.map(messages, message => { + const lineNumber = message.line || 0; + const columnNumber = message.column || 0; + let ruleUrl; + + if (rulesMeta) { + const meta = rulesMeta[message.ruleId]; + + ruleUrl = lodash.get(meta, "docs.url", null); + } + + return messageTemplate({ + parentIndex, + lineNumber, + columnNumber, + severityNumber: message.severity, + severityName: message.severity === 1 ? "Warning" : "Error", + message: message.message, + ruleId: message.ruleId, + ruleUrl + }); + }).join("\n"); +} + +// eslint-disable-next-line jsdoc/require-description +/** + * @param {Array} results Test results. + * @param {Object} rulesMeta Dictionary containing metadata for each rule executed by the analysis. + * @returns {string} HTML string describing the results. + */ +function renderResults(results, rulesMeta) { + return lodash.map(results, (result, index) => resultTemplate({ + index, + color: renderColor(result.errorCount, result.warningCount), + filePath: result.filePath, + summary: renderSummary(result.errorCount, result.warningCount) + + }) + renderMessages(result.messages, index, rulesMeta)).join("\n"); +} + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = function(results, data) { + let totalErrors, + totalWarnings; + + const metaData = data ? data.rulesMeta : {}; + + totalErrors = 0; + totalWarnings = 0; + + // Iterate over results to get totals + results.forEach(result => { + totalErrors += result.errorCount; + totalWarnings += result.warningCount; + }); + + return pageTemplate({ + date: new Date(), + reportColor: renderColor(totalErrors, totalWarnings), + reportSummary: renderSummary(totalErrors, totalWarnings), + results: renderResults(results, metaData) + }); +}; diff --git a/eslint/lib/cli-engine/formatters/jslint-xml.js b/eslint/lib/cli-engine/formatters/jslint-xml.js new file mode 100644 index 0000000..0ca1cba --- /dev/null +++ b/eslint/lib/cli-engine/formatters/jslint-xml.js @@ -0,0 +1,41 @@ +/** + * @fileoverview JSLint XML reporter + * @author Ian Christian Myers + */ +"use strict"; + +const xmlEscape = require("../xml-escape"); + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = function(results) { + + let output = ""; + + output += ""; + output += ""; + + results.forEach(result => { + const messages = result.messages; + + output += ``; + + messages.forEach(message => { + output += [ + `` + ].join(" "); + }); + + output += ""; + + }); + + output += ""; + + return output; +}; diff --git a/eslint/lib/cli-engine/formatters/json-with-metadata.js b/eslint/lib/cli-engine/formatters/json-with-metadata.js new file mode 100644 index 0000000..6899471 --- /dev/null +++ b/eslint/lib/cli-engine/formatters/json-with-metadata.js @@ -0,0 +1,16 @@ +/** + * @fileoverview JSON reporter, including rules metadata + * @author Chris Meyer + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = function(results, data) { + return JSON.stringify({ + results, + metadata: data + }); +}; diff --git a/eslint/lib/cli-engine/formatters/json.js b/eslint/lib/cli-engine/formatters/json.js new file mode 100644 index 0000000..82138af --- /dev/null +++ b/eslint/lib/cli-engine/formatters/json.js @@ -0,0 +1,13 @@ +/** + * @fileoverview JSON reporter + * @author Burak Yigit Kaya aka BYK + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = function(results) { + return JSON.stringify(results); +}; diff --git a/eslint/lib/cli-engine/formatters/junit.js b/eslint/lib/cli-engine/formatters/junit.js new file mode 100644 index 0000000..a994b4b --- /dev/null +++ b/eslint/lib/cli-engine/formatters/junit.js @@ -0,0 +1,82 @@ +/** + * @fileoverview jUnit Reporter + * @author Jamund Ferguson + */ +"use strict"; + +const xmlEscape = require("../xml-escape"); +const path = require("path"); + +//------------------------------------------------------------------------------ +// Helper Functions +//------------------------------------------------------------------------------ + +/** + * Returns the severity of warning or error + * @param {Object} message message object to examine + * @returns {string} severity level + * @private + */ +function getMessageType(message) { + if (message.fatal || message.severity === 2) { + return "Error"; + } + return "Warning"; + +} + +/** + * Returns a full file path without extension + * @param {string} filePath input file path + * @returns {string} file path without extension + * @private + */ +function pathWithoutExt(filePath) { + return path.join(path.dirname(filePath), path.basename(filePath, path.extname(filePath))); +} + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = function(results) { + + let output = ""; + + output += "\n"; + output += "\n"; + + results.forEach(result => { + + const messages = result.messages; + const classname = pathWithoutExt(result.filePath); + + if (messages.length > 0) { + output += `\n`; + messages.forEach(message => { + const type = message.fatal ? "error" : "failure"; + + output += ``; + output += `<${type} message="${xmlEscape(message.message || "")}">`; + output += ""; + output += ``; + output += "\n"; + }); + output += "\n"; + } else { + output += `\n`; + output += `\n`; + output += "\n"; + } + + }); + + output += "\n"; + + return output; +}; diff --git a/eslint/lib/cli-engine/formatters/stylish.js b/eslint/lib/cli-engine/formatters/stylish.js new file mode 100644 index 0000000..a808448 --- /dev/null +++ b/eslint/lib/cli-engine/formatters/stylish.js @@ -0,0 +1,101 @@ +/** + * @fileoverview Stylish reporter + * @author Sindre Sorhus + */ +"use strict"; + +const chalk = require("chalk"), + stripAnsi = require("strip-ansi"), + table = require("text-table"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Given a word and a count, append an s if count is not one. + * @param {string} word A word in its singular form. + * @param {int} count A number controlling whether word should be pluralized. + * @returns {string} The original word with an s on the end if count is not one. + */ +function pluralize(word, count) { + return (count === 1 ? word : `${word}s`); +} + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = function(results) { + + let output = "\n", + errorCount = 0, + warningCount = 0, + fixableErrorCount = 0, + fixableWarningCount = 0, + summaryColor = "yellow"; + + results.forEach(result => { + const messages = result.messages; + + if (messages.length === 0) { + return; + } + + errorCount += result.errorCount; + warningCount += result.warningCount; + fixableErrorCount += result.fixableErrorCount; + fixableWarningCount += result.fixableWarningCount; + + output += `${chalk.underline(result.filePath)}\n`; + + output += `${table( + messages.map(message => { + let messageType; + + if (message.fatal || message.severity === 2) { + messageType = chalk.red("error"); + summaryColor = "red"; + } else { + messageType = chalk.yellow("warning"); + } + + return [ + "", + message.line || 0, + message.column || 0, + messageType, + message.message.replace(/([^ ])\.$/u, "$1"), + chalk.dim(message.ruleId || "") + ]; + }), + { + align: ["", "r", "l"], + stringLength(str) { + return stripAnsi(str).length; + } + } + ).split("\n").map(el => el.replace(/(\d+)\s+(\d+)/u, (m, p1, p2) => chalk.dim(`${p1}:${p2}`))).join("\n")}\n\n`; + }); + + const total = errorCount + warningCount; + + if (total > 0) { + output += chalk[summaryColor].bold([ + "\u2716 ", total, pluralize(" problem", total), + " (", errorCount, pluralize(" error", errorCount), ", ", + warningCount, pluralize(" warning", warningCount), ")\n" + ].join("")); + + if (fixableErrorCount > 0 || fixableWarningCount > 0) { + output += chalk[summaryColor].bold([ + " ", fixableErrorCount, pluralize(" error", fixableErrorCount), " and ", + fixableWarningCount, pluralize(" warning", fixableWarningCount), + " potentially fixable with the `--fix` option.\n" + ].join("")); + } + } + + // Resets output color, for prevent change on top level + return total > 0 ? chalk.reset(output) : ""; +}; diff --git a/eslint/lib/cli-engine/formatters/table.js b/eslint/lib/cli-engine/formatters/table.js new file mode 100644 index 0000000..a74cce0 --- /dev/null +++ b/eslint/lib/cli-engine/formatters/table.js @@ -0,0 +1,159 @@ +/** + * @fileoverview "table reporter. + * @author Gajus Kuizinas + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const chalk = require("chalk"), + table = require("table").table; + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Given a word and a count, append an "s" if count is not one. + * @param {string} word A word. + * @param {number} count Quantity. + * @returns {string} The original word with an s on the end if count is not one. + */ +function pluralize(word, count) { + return (count === 1 ? word : `${word}s`); +} + +/** + * Draws text table. + * @param {Array} messages Error messages relating to a specific file. + * @returns {string} A text table. + */ +function drawTable(messages) { + const rows = []; + + if (messages.length === 0) { + return ""; + } + + rows.push([ + chalk.bold("Line"), + chalk.bold("Column"), + chalk.bold("Type"), + chalk.bold("Message"), + chalk.bold("Rule ID") + ]); + + messages.forEach(message => { + let messageType; + + if (message.fatal || message.severity === 2) { + messageType = chalk.red("error"); + } else { + messageType = chalk.yellow("warning"); + } + + rows.push([ + message.line || 0, + message.column || 0, + messageType, + message.message, + message.ruleId || "" + ]); + }); + + return table(rows, { + columns: { + 0: { + width: 8, + wrapWord: true + }, + 1: { + width: 8, + wrapWord: true + }, + 2: { + width: 8, + wrapWord: true + }, + 3: { + paddingRight: 5, + width: 50, + wrapWord: true + }, + 4: { + width: 20, + wrapWord: true + } + }, + drawHorizontalLine(index) { + return index === 1; + } + }); +} + +/** + * Draws a report (multiple tables). + * @param {Array} results Report results for every file. + * @returns {string} A column of text tables. + */ +function drawReport(results) { + let files; + + files = results.map(result => { + if (!result.messages.length) { + return ""; + } + + return `\n${result.filePath}\n\n${drawTable(result.messages)}`; + }); + + files = files.filter(content => content.trim()); + + return files.join(""); +} + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = function(report) { + let result, + errorCount, + warningCount; + + result = ""; + errorCount = 0; + warningCount = 0; + + report.forEach(fileReport => { + errorCount += fileReport.errorCount; + warningCount += fileReport.warningCount; + }); + + if (errorCount || warningCount) { + result = drawReport(report); + } + + result += `\n${table([ + [ + chalk.red(pluralize(`${errorCount} Error`, errorCount)) + ], + [ + chalk.yellow(pluralize(`${warningCount} Warning`, warningCount)) + ] + ], { + columns: { + 0: { + width: 110, + wrapWord: true + } + }, + drawHorizontalLine() { + return true; + } + })}`; + + return result; +}; diff --git a/eslint/lib/cli-engine/formatters/tap.js b/eslint/lib/cli-engine/formatters/tap.js new file mode 100644 index 0000000..354872a --- /dev/null +++ b/eslint/lib/cli-engine/formatters/tap.js @@ -0,0 +1,95 @@ +/** + * @fileoverview TAP reporter + * @author Jonathan Kingston + */ +"use strict"; + +const yaml = require("js-yaml"); + +//------------------------------------------------------------------------------ +// Helper Functions +//------------------------------------------------------------------------------ + +/** + * Returns a canonical error level string based upon the error message passed in. + * @param {Object} message Individual error message provided by eslint + * @returns {string} Error level string + */ +function getMessageType(message) { + if (message.fatal || message.severity === 2) { + return "error"; + } + return "warning"; +} + +/** + * Takes in a JavaScript object and outputs a TAP diagnostics string + * @param {Object} diagnostic JavaScript object to be embedded as YAML into output. + * @returns {string} diagnostics string with YAML embedded - TAP version 13 compliant + */ +function outputDiagnostics(diagnostic) { + const prefix = " "; + let output = `${prefix}---\n`; + + output += prefix + yaml.safeDump(diagnostic).split("\n").join(`\n${prefix}`); + output += "...\n"; + return output; +} + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = function(results) { + let output = `TAP version 13\n1..${results.length}\n`; + + results.forEach((result, id) => { + const messages = result.messages; + let testResult = "ok"; + let diagnostics = {}; + + if (messages.length > 0) { + messages.forEach(message => { + const severity = getMessageType(message); + const diagnostic = { + message: message.message, + severity, + data: { + line: message.line || 0, + column: message.column || 0, + ruleId: message.ruleId || "" + } + }; + + // This ensures a warning message is not flagged as error + if (severity === "error") { + testResult = "not ok"; + } + + /* + * If we have multiple messages place them under a messages key + * The first error will be logged as message key + * This is to adhere to TAP 13 loosely defined specification of having a message key + */ + if ("message" in diagnostics) { + if (typeof diagnostics.messages === "undefined") { + diagnostics.messages = []; + } + diagnostics.messages.push(diagnostic); + } else { + diagnostics = diagnostic; + } + }); + } + + output += `${testResult} ${id + 1} - ${result.filePath}\n`; + + // If we have an error include diagnostics + if (messages.length > 0) { + output += outputDiagnostics(diagnostics); + } + + }); + + return output; +}; diff --git a/eslint/lib/cli-engine/formatters/unix.js b/eslint/lib/cli-engine/formatters/unix.js new file mode 100644 index 0000000..c6c4ebb --- /dev/null +++ b/eslint/lib/cli-engine/formatters/unix.js @@ -0,0 +1,58 @@ +/** + * @fileoverview unix-style formatter. + * @author oshi-shinobu + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Helper Functions +//------------------------------------------------------------------------------ + +/** + * Returns a canonical error level string based upon the error message passed in. + * @param {Object} message Individual error message provided by eslint + * @returns {string} Error level string + */ +function getMessageType(message) { + if (message.fatal || message.severity === 2) { + return "Error"; + } + return "Warning"; + +} + + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = function(results) { + + let output = "", + total = 0; + + results.forEach(result => { + + const messages = result.messages; + + total += messages.length; + + messages.forEach(message => { + + output += `${result.filePath}:`; + output += `${message.line || 0}:`; + output += `${message.column || 0}:`; + output += ` ${message.message} `; + output += `[${getMessageType(message)}${message.ruleId ? `/${message.ruleId}` : ""}]`; + output += "\n"; + + }); + + }); + + if (total > 0) { + output += `\n${total} problem${total !== 1 ? "s" : ""}`; + } + + return output; +}; diff --git a/eslint/lib/cli-engine/formatters/visualstudio.js b/eslint/lib/cli-engine/formatters/visualstudio.js new file mode 100644 index 0000000..0d49431 --- /dev/null +++ b/eslint/lib/cli-engine/formatters/visualstudio.js @@ -0,0 +1,63 @@ +/** + * @fileoverview Visual Studio compatible formatter + * @author Ronald Pijnacker + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Helper Functions +//------------------------------------------------------------------------------ + +/** + * Returns the severity of warning or error + * @param {Object} message message object to examine + * @returns {string} severity level + * @private + */ +function getMessageType(message) { + if (message.fatal || message.severity === 2) { + return "error"; + } + return "warning"; + +} + + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = function(results) { + + let output = "", + total = 0; + + results.forEach(result => { + + const messages = result.messages; + + total += messages.length; + + messages.forEach(message => { + + output += result.filePath; + output += `(${message.line || 0}`; + output += message.column ? `,${message.column}` : ""; + output += `): ${getMessageType(message)}`; + output += message.ruleId ? ` ${message.ruleId}` : ""; + output += ` : ${message.message}`; + output += "\n"; + + }); + + }); + + if (total === 0) { + output += "no problems"; + } else { + output += `\n${total} problem${total !== 1 ? "s" : ""}`; + } + + return output; +}; diff --git a/eslint/lib/cli-engine/hash.js b/eslint/lib/cli-engine/hash.js new file mode 100644 index 0000000..6d7ef8b --- /dev/null +++ b/eslint/lib/cli-engine/hash.js @@ -0,0 +1,35 @@ +/** + * @fileoverview Defining the hashing function in one place. + * @author Michael Ficarra + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const murmur = require("imurmurhash"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ +// Private +//------------------------------------------------------------------------------ + +/** + * hash the given string + * @param {string} str the string to hash + * @returns {string} the hash + */ +function hash(str) { + return murmur(str).result().toString(36); +} + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = hash; diff --git a/eslint/lib/cli-engine/index.js b/eslint/lib/cli-engine/index.js new file mode 100644 index 0000000..52e45a6 --- /dev/null +++ b/eslint/lib/cli-engine/index.js @@ -0,0 +1,7 @@ +"use strict"; + +const { CLIEngine } = require("./cli-engine"); + +module.exports = { + CLIEngine +}; diff --git a/eslint/lib/cli-engine/lint-result-cache.js b/eslint/lib/cli-engine/lint-result-cache.js new file mode 100644 index 0000000..23a1420 --- /dev/null +++ b/eslint/lib/cli-engine/lint-result-cache.js @@ -0,0 +1,142 @@ +/** + * @fileoverview Utility for caching lint results. + * @author Kevin Partington + */ +"use strict"; + +//----------------------------------------------------------------------------- +// Requirements +//----------------------------------------------------------------------------- + +const assert = require("assert"); +const fs = require("fs"); +const fileEntryCache = require("file-entry-cache"); +const stringify = require("json-stable-stringify-without-jsonify"); +const pkg = require("../../package.json"); +const hash = require("./hash"); + +//----------------------------------------------------------------------------- +// Helpers +//----------------------------------------------------------------------------- + +const configHashCache = new WeakMap(); +const nodeVersion = process && process.version; + +/** + * Calculates the hash of the config + * @param {ConfigArray} config The config. + * @returns {string} The hash of the config + */ +function hashOfConfigFor(config) { + if (!configHashCache.has(config)) { + configHashCache.set(config, hash(`${pkg.version}_${nodeVersion}_${stringify(config)}`)); + } + + return configHashCache.get(config); +} + +//----------------------------------------------------------------------------- +// Public Interface +//----------------------------------------------------------------------------- + +/** + * Lint result cache. This wraps around the file-entry-cache module, + * transparently removing properties that are difficult or expensive to + * serialize and adding them back in on retrieval. + */ +class LintResultCache { + + /** + * Creates a new LintResultCache instance. + * @param {string} cacheFileLocation The cache file location. + * configuration lookup by file path). + */ + constructor(cacheFileLocation) { + assert(cacheFileLocation, "Cache file location is required"); + + this.fileEntryCache = fileEntryCache.create(cacheFileLocation); + } + + /** + * Retrieve cached lint results for a given file path, if present in the + * cache. If the file is present and has not been changed, rebuild any + * missing result information. + * @param {string} filePath The file for which to retrieve lint results. + * @param {ConfigArray} config The config of the file. + * @returns {Object|null} The rebuilt lint results, or null if the file is + * changed or not in the filesystem. + */ + getCachedLintResults(filePath, config) { + + /* + * Cached lint results are valid if and only if: + * 1. The file is present in the filesystem + * 2. The file has not changed since the time it was previously linted + * 3. The ESLint configuration has not changed since the time the file + * was previously linted + * If any of these are not true, we will not reuse the lint results. + */ + + const fileDescriptor = this.fileEntryCache.getFileDescriptor(filePath); + const hashOfConfig = hashOfConfigFor(config); + const changed = fileDescriptor.changed || fileDescriptor.meta.hashOfConfig !== hashOfConfig; + + if (fileDescriptor.notFound || changed) { + return null; + } + + // If source is present but null, need to reread the file from the filesystem. + if (fileDescriptor.meta.results && fileDescriptor.meta.results.source === null) { + fileDescriptor.meta.results.source = fs.readFileSync(filePath, "utf-8"); + } + + return fileDescriptor.meta.results; + } + + /** + * Set the cached lint results for a given file path, after removing any + * information that will be both unnecessary and difficult to serialize. + * Avoids caching results with an "output" property (meaning fixes were + * applied), to prevent potentially incorrect results if fixes are not + * written to disk. + * @param {string} filePath The file for which to set lint results. + * @param {ConfigArray} config The config of the file. + * @param {Object} result The lint result to be set for the file. + * @returns {void} + */ + setCachedLintResults(filePath, config, result) { + if (result && Object.prototype.hasOwnProperty.call(result, "output")) { + return; + } + + const fileDescriptor = this.fileEntryCache.getFileDescriptor(filePath); + + if (fileDescriptor && !fileDescriptor.notFound) { + + // Serialize the result, except that we want to remove the file source if present. + const resultToSerialize = Object.assign({}, result); + + /* + * Set result.source to null. + * In `getCachedLintResults`, if source is explicitly null, we will + * read the file from the filesystem to set the value again. + */ + if (Object.prototype.hasOwnProperty.call(resultToSerialize, "source")) { + resultToSerialize.source = null; + } + + fileDescriptor.meta.results = resultToSerialize; + fileDescriptor.meta.hashOfConfig = hashOfConfigFor(config); + } + } + + /** + * Persists the in-memory cache to disk. + * @returns {void} + */ + reconcile() { + this.fileEntryCache.reconcile(); + } +} + +module.exports = LintResultCache; diff --git a/eslint/lib/cli-engine/load-rules.js b/eslint/lib/cli-engine/load-rules.js new file mode 100644 index 0000000..81bab63 --- /dev/null +++ b/eslint/lib/cli-engine/load-rules.js @@ -0,0 +1,46 @@ +/** + * @fileoverview Module for loading rules from files and directories. + * @author Michael Ficarra + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const fs = require("fs"), + path = require("path"); + +const rulesDirCache = {}; + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +/** + * Load all rule modules from specified directory. + * @param {string} relativeRulesDir Path to rules directory, may be relative. + * @param {string} cwd Current working directory + * @returns {Object} Loaded rule modules. + */ +module.exports = function(relativeRulesDir, cwd) { + const rulesDir = path.resolve(cwd, relativeRulesDir); + + // cache will help performance as IO operation are expensive + if (rulesDirCache[rulesDir]) { + return rulesDirCache[rulesDir]; + } + + const rules = Object.create(null); + + fs.readdirSync(rulesDir).forEach(file => { + if (path.extname(file) !== ".js") { + return; + } + rules[file.slice(0, -3)] = require(path.join(rulesDir, file)); + }); + rulesDirCache[rulesDir] = rules; + + return rules; +}; diff --git a/eslint/lib/cli-engine/xml-escape.js b/eslint/lib/cli-engine/xml-escape.js new file mode 100644 index 0000000..175c2c0 --- /dev/null +++ b/eslint/lib/cli-engine/xml-escape.js @@ -0,0 +1,34 @@ +/** + * @fileoverview XML character escaper + * @author George Chung + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +/** + * Returns the escaped value for a character + * @param {string} s string to examine + * @returns {string} severity level + * @private + */ +module.exports = function(s) { + return (`${s}`).replace(/[<>&"'\x00-\x1F\x7F\u0080-\uFFFF]/gu, c => { // eslint-disable-line no-control-regex + switch (c) { + case "<": + return "<"; + case ">": + return ">"; + case "&": + return "&"; + case "\"": + return """; + case "'": + return "'"; + default: + return `&#${c.charCodeAt(0)};`; + } + }); +}; diff --git a/eslint/lib/cli.js b/eslint/lib/cli.js new file mode 100644 index 0000000..815ce68 --- /dev/null +++ b/eslint/lib/cli.js @@ -0,0 +1,240 @@ +/** + * @fileoverview Main CLI object. + * @author Nicholas C. Zakas + */ + +"use strict"; + +/* + * The CLI object should *not* call process.exit() directly. It should only return + * exit codes. This allows other programs to use the CLI object and still control + * when the program exits. + */ + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const fs = require("fs"), + path = require("path"), + { CLIEngine } = require("./cli-engine"), + options = require("./options"), + log = require("./shared/logging"), + RuntimeInfo = require("./shared/runtime-info"); + +const debug = require("debug")("eslint:cli"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Predicate function for whether or not to apply fixes in quiet mode. + * If a message is a warning, do not apply a fix. + * @param {LintResult} lintResult The lint result. + * @returns {boolean} True if the lint message is an error (and thus should be + * autofixed), false otherwise. + */ +function quietFixPredicate(lintResult) { + return lintResult.severity === 2; +} + +/** + * Translates the CLI options into the options expected by the CLIEngine. + * @param {Object} cliOptions The CLI options to translate. + * @returns {CLIEngineOptions} The options object for the CLIEngine. + * @private + */ +function translateOptions(cliOptions) { + return { + envs: cliOptions.env, + extensions: cliOptions.ext, + rules: cliOptions.rule, + plugins: cliOptions.plugin, + globals: cliOptions.global, + ignore: cliOptions.ignore, + ignorePath: cliOptions.ignorePath, + ignorePattern: cliOptions.ignorePattern, + configFile: cliOptions.config, + rulePaths: cliOptions.rulesdir, + useEslintrc: cliOptions.eslintrc, + parser: cliOptions.parser, + parserOptions: cliOptions.parserOptions, + cache: cliOptions.cache, + cacheFile: cliOptions.cacheFile, + cacheLocation: cliOptions.cacheLocation, + fix: (cliOptions.fix || cliOptions.fixDryRun) && (cliOptions.quiet ? quietFixPredicate : true), + fixTypes: cliOptions.fixType, + allowInlineConfig: cliOptions.inlineConfig, + reportUnusedDisableDirectives: cliOptions.reportUnusedDisableDirectives, + resolvePluginsRelativeTo: cliOptions.resolvePluginsRelativeTo, + errorOnUnmatchedPattern: cliOptions.errorOnUnmatchedPattern + }; +} + +/** + * Outputs the results of the linting. + * @param {CLIEngine} engine The CLIEngine to use. + * @param {LintResult[]} results The results to print. + * @param {string} format The name of the formatter to use or the path to the formatter. + * @param {string} outputFile The path for the output file. + * @returns {boolean} True if the printing succeeds, false if not. + * @private + */ +function printResults(engine, results, format, outputFile) { + let formatter; + let rulesMeta; + + try { + formatter = engine.getFormatter(format); + } catch (e) { + log.error(e.message); + return false; + } + + const output = formatter(results, { + get rulesMeta() { + if (!rulesMeta) { + rulesMeta = {}; + for (const [ruleId, rule] of engine.getRules()) { + rulesMeta[ruleId] = rule.meta; + } + } + return rulesMeta; + } + }); + + if (output) { + if (outputFile) { + const filePath = path.resolve(process.cwd(), outputFile); + + if (fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()) { + log.error("Cannot write to output file path, it is a directory: %s", outputFile); + return false; + } + + try { + fs.mkdirSync(path.dirname(filePath), { recursive: true }); + fs.writeFileSync(filePath, output); + } catch (ex) { + log.error("There was a problem writing the output file:\n%s", ex); + return false; + } + } else { + log.info(output); + } + } + + return true; + +} + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +/** + * Encapsulates all CLI behavior for eslint. Makes it easier to test as well as + * for other Node.js programs to effectively run the CLI. + */ +const cli = { + + /** + * Executes the CLI based on an array of arguments that is passed in. + * @param {string|Array|Object} args The arguments to process. + * @param {string} [text] The text to lint (used for TTY). + * @returns {int} The exit code for the operation. + */ + execute(args, text) { + if (Array.isArray(args)) { + debug("CLI args: %o", args.slice(2)); + } + + let currentOptions; + + try { + currentOptions = options.parse(args); + } catch (error) { + log.error(error.message); + return 2; + } + + const files = currentOptions._; + const useStdin = typeof text === "string"; + + if (currentOptions.version) { + log.info(RuntimeInfo.version()); + } else if (currentOptions.envInfo) { + try { + log.info(RuntimeInfo.environment()); + return 0; + } catch (err) { + log.error(err.message); + return 2; + } + } else if (currentOptions.printConfig) { + if (files.length) { + log.error("The --print-config option must be used with exactly one file name."); + return 2; + } + if (useStdin) { + log.error("The --print-config option is not available for piped-in code."); + return 2; + } + + const engine = new CLIEngine(translateOptions(currentOptions)); + const fileConfig = engine.getConfigForFile(currentOptions.printConfig); + + log.info(JSON.stringify(fileConfig, null, " ")); + return 0; + } else if (currentOptions.help || (!files.length && !useStdin)) { + log.info(options.generateHelp()); + } else { + debug(`Running on ${useStdin ? "text" : "files"}`); + + if (currentOptions.fix && currentOptions.fixDryRun) { + log.error("The --fix option and the --fix-dry-run option cannot be used together."); + return 2; + } + + if (useStdin && currentOptions.fix) { + log.error("The --fix option is not available for piped-in code; use --fix-dry-run instead."); + return 2; + } + + if (currentOptions.fixType && !currentOptions.fix && !currentOptions.fixDryRun) { + log.error("The --fix-type option requires either --fix or --fix-dry-run."); + return 2; + } + + const engine = new CLIEngine(translateOptions(currentOptions)); + const report = useStdin ? engine.executeOnText(text, currentOptions.stdinFilename, true) : engine.executeOnFiles(files); + + if (currentOptions.fix) { + debug("Fix mode enabled - applying fixes"); + CLIEngine.outputFixes(report); + } + + if (currentOptions.quiet) { + debug("Quiet mode enabled - filtering out warnings"); + report.results = CLIEngine.getErrorResults(report.results); + } + + if (printResults(engine, report.results, currentOptions.format, currentOptions.outputFile)) { + const tooManyWarnings = currentOptions.maxWarnings >= 0 && report.warningCount > currentOptions.maxWarnings; + + if (!report.errorCount && tooManyWarnings) { + log.error("ESLint found too many warnings (maximum: %s).", currentOptions.maxWarnings); + } + + return (report.errorCount || tooManyWarnings) ? 1 : 0; + } + + return 2; + } + + return 0; + } +}; + +module.exports = cli; diff --git a/eslint/lib/init/autoconfig.js b/eslint/lib/init/autoconfig.js new file mode 100644 index 0000000..64be3d2 --- /dev/null +++ b/eslint/lib/init/autoconfig.js @@ -0,0 +1,348 @@ +/** + * @fileoverview Used for creating a suggested configuration based on project code. + * @author Ian VanSchooten + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const lodash = require("lodash"), + recConfig = require("../../conf/eslint-recommended"), + ConfigOps = require("../shared/config-ops"), + { Linter } = require("../linter"), + configRule = require("./config-rule"); + +const debug = require("debug")("eslint:autoconfig"); +const linter = new Linter(); + +//------------------------------------------------------------------------------ +// Data +//------------------------------------------------------------------------------ + +const MAX_CONFIG_COMBINATIONS = 17, // 16 combinations + 1 for severity only + RECOMMENDED_CONFIG_NAME = "eslint:recommended"; + +//------------------------------------------------------------------------------ +// Private +//------------------------------------------------------------------------------ + +/** + * Information about a rule configuration, in the context of a Registry. + * @typedef {Object} registryItem + * @param {ruleConfig} config A valid configuration for the rule + * @param {number} specificity The number of elements in the ruleConfig array + * @param {number} errorCount The number of errors encountered when linting with the config + */ + +/** + * This callback is used to measure execution status in a progress bar + * @callback progressCallback + * @param {number} The total number of times the callback will be called. + */ + +/** + * Create registryItems for rules + * @param {rulesConfig} rulesConfig Hash of rule names and arrays of ruleConfig items + * @returns {Object} registryItems for each rule in provided rulesConfig + */ +function makeRegistryItems(rulesConfig) { + return Object.keys(rulesConfig).reduce((accumulator, ruleId) => { + accumulator[ruleId] = rulesConfig[ruleId].map(config => ({ + config, + specificity: config.length || 1, + errorCount: void 0 + })); + return accumulator; + }, {}); +} + +/** + * Creates an object in which to store rule configs and error counts + * + * Unless a rulesConfig is provided at construction, the registry will not contain + * any rules, only methods. This will be useful for building up registries manually. + * + * Registry class + */ +class Registry { + + // eslint-disable-next-line jsdoc/require-description + /** + * @param {rulesConfig} [rulesConfig] Hash of rule names and arrays of possible configurations + */ + constructor(rulesConfig) { + this.rules = (rulesConfig) ? makeRegistryItems(rulesConfig) : {}; + } + + /** + * Populate the registry with core rule configs. + * + * It will set the registry's `rule` property to an object having rule names + * as keys and an array of registryItems as values. + * @returns {void} + */ + populateFromCoreRules() { + const rulesConfig = configRule.createCoreRuleConfigs(); + + this.rules = makeRegistryItems(rulesConfig); + } + + /** + * Creates sets of rule configurations which can be used for linting + * and initializes registry errors to zero for those configurations (side effect). + * + * This combines as many rules together as possible, such that the first sets + * in the array will have the highest number of rules configured, and later sets + * will have fewer and fewer, as not all rules have the same number of possible + * configurations. + * + * The length of the returned array will be <= MAX_CONFIG_COMBINATIONS. + * @returns {Object[]} "rules" configurations to use for linting + */ + buildRuleSets() { + let idx = 0; + const ruleIds = Object.keys(this.rules), + ruleSets = []; + + /** + * Add a rule configuration from the registry to the ruleSets + * + * This is broken out into its own function so that it doesn't need to be + * created inside of the while loop. + * @param {string} rule The ruleId to add. + * @returns {void} + */ + const addRuleToRuleSet = function(rule) { + + /* + * This check ensures that there is a rule configuration and that + * it has fewer than the max combinations allowed. + * If it has too many configs, we will only use the most basic of + * the possible configurations. + */ + const hasFewCombos = (this.rules[rule].length <= MAX_CONFIG_COMBINATIONS); + + if (this.rules[rule][idx] && (hasFewCombos || this.rules[rule][idx].specificity <= 2)) { + + /* + * If the rule has too many possible combinations, only take + * simple ones, avoiding objects. + */ + if (!hasFewCombos && typeof this.rules[rule][idx].config[1] === "object") { + return; + } + + ruleSets[idx] = ruleSets[idx] || {}; + ruleSets[idx][rule] = this.rules[rule][idx].config; + + /* + * Initialize errorCount to zero, since this is a config which + * will be linted. + */ + this.rules[rule][idx].errorCount = 0; + } + }.bind(this); + + while (ruleSets.length === idx) { + ruleIds.forEach(addRuleToRuleSet); + idx += 1; + } + + return ruleSets; + } + + /** + * Remove all items from the registry with a non-zero number of errors + * + * Note: this also removes rule configurations which were not linted + * (meaning, they have an undefined errorCount). + * @returns {void} + */ + stripFailingConfigs() { + const ruleIds = Object.keys(this.rules), + newRegistry = new Registry(); + + newRegistry.rules = Object.assign({}, this.rules); + ruleIds.forEach(ruleId => { + const errorFreeItems = newRegistry.rules[ruleId].filter(registryItem => (registryItem.errorCount === 0)); + + if (errorFreeItems.length > 0) { + newRegistry.rules[ruleId] = errorFreeItems; + } else { + delete newRegistry.rules[ruleId]; + } + }); + + return newRegistry; + } + + /** + * Removes rule configurations which were not included in a ruleSet + * @returns {void} + */ + stripExtraConfigs() { + const ruleIds = Object.keys(this.rules), + newRegistry = new Registry(); + + newRegistry.rules = Object.assign({}, this.rules); + ruleIds.forEach(ruleId => { + newRegistry.rules[ruleId] = newRegistry.rules[ruleId].filter(registryItem => (typeof registryItem.errorCount !== "undefined")); + }); + + return newRegistry; + } + + /** + * Creates a registry of rules which had no error-free configs. + * The new registry is intended to be analyzed to determine whether its rules + * should be disabled or set to warning. + * @returns {Registry} A registry of failing rules. + */ + getFailingRulesRegistry() { + const ruleIds = Object.keys(this.rules), + failingRegistry = new Registry(); + + ruleIds.forEach(ruleId => { + const failingConfigs = this.rules[ruleId].filter(registryItem => (registryItem.errorCount > 0)); + + if (failingConfigs && failingConfigs.length === this.rules[ruleId].length) { + failingRegistry.rules[ruleId] = failingConfigs; + } + }); + + return failingRegistry; + } + + /** + * Create an eslint config for any rules which only have one configuration + * in the registry. + * @returns {Object} An eslint config with rules section populated + */ + createConfig() { + const ruleIds = Object.keys(this.rules), + config = { rules: {} }; + + ruleIds.forEach(ruleId => { + if (this.rules[ruleId].length === 1) { + config.rules[ruleId] = this.rules[ruleId][0].config; + } + }); + + return config; + } + + /** + * Return a cloned registry containing only configs with a desired specificity + * @param {number} specificity Only keep configs with this specificity + * @returns {Registry} A registry of rules + */ + filterBySpecificity(specificity) { + const ruleIds = Object.keys(this.rules), + newRegistry = new Registry(); + + newRegistry.rules = Object.assign({}, this.rules); + ruleIds.forEach(ruleId => { + newRegistry.rules[ruleId] = this.rules[ruleId].filter(registryItem => (registryItem.specificity === specificity)); + }); + + return newRegistry; + } + + /** + * Lint SourceCodes against all configurations in the registry, and record results + * @param {Object[]} sourceCodes SourceCode objects for each filename + * @param {Object} config ESLint config object + * @param {progressCallback} [cb] Optional callback for reporting execution status + * @returns {Registry} New registry with errorCount populated + */ + lintSourceCode(sourceCodes, config, cb) { + let lintedRegistry = new Registry(); + + lintedRegistry.rules = Object.assign({}, this.rules); + + const ruleSets = lintedRegistry.buildRuleSets(); + + lintedRegistry = lintedRegistry.stripExtraConfigs(); + + debug("Linting with all possible rule combinations"); + + const filenames = Object.keys(sourceCodes); + const totalFilesLinting = filenames.length * ruleSets.length; + + filenames.forEach(filename => { + debug(`Linting file: ${filename}`); + + let ruleSetIdx = 0; + + ruleSets.forEach(ruleSet => { + const lintConfig = Object.assign({}, config, { rules: ruleSet }); + const lintResults = linter.verify(sourceCodes[filename], lintConfig); + + lintResults.forEach(result => { + + /* + * It is possible that the error is from a configuration comment + * in a linted file, in which case there may not be a config + * set in this ruleSetIdx. + * (https://github.com/eslint/eslint/issues/5992) + * (https://github.com/eslint/eslint/issues/7860) + */ + if ( + lintedRegistry.rules[result.ruleId] && + lintedRegistry.rules[result.ruleId][ruleSetIdx] + ) { + lintedRegistry.rules[result.ruleId][ruleSetIdx].errorCount += 1; + } + }); + + ruleSetIdx += 1; + + if (cb) { + cb(totalFilesLinting); // eslint-disable-line callback-return + } + }); + + // Deallocate for GC + sourceCodes[filename] = null; + }); + + return lintedRegistry; + } +} + +/** + * Extract rule configuration into eslint:recommended where possible. + * + * This will return a new config with `"extends": "eslint:recommended"` and + * only the rules which have configurations different from the recommended config. + * @param {Object} config config object + * @returns {Object} config object using `"extends": "eslint:recommended"` + */ +function extendFromRecommended(config) { + const newConfig = Object.assign({}, config); + + ConfigOps.normalizeToStrings(newConfig); + + const recRules = Object.keys(recConfig.rules).filter(ruleId => ConfigOps.isErrorSeverity(recConfig.rules[ruleId])); + + recRules.forEach(ruleId => { + if (lodash.isEqual(recConfig.rules[ruleId], newConfig.rules[ruleId])) { + delete newConfig.rules[ruleId]; + } + }); + newConfig.extends = RECOMMENDED_CONFIG_NAME; + return newConfig; +} + + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = { + Registry, + extendFromRecommended +}; diff --git a/eslint/lib/init/config-file.js b/eslint/lib/init/config-file.js new file mode 100644 index 0000000..fc62b81 --- /dev/null +++ b/eslint/lib/init/config-file.js @@ -0,0 +1,143 @@ +/** + * @fileoverview Helper to locate and load configuration files. + * @author Nicholas C. Zakas + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const fs = require("fs"), + path = require("path"), + stringify = require("json-stable-stringify-without-jsonify"); + +const debug = require("debug")("eslint:config-file"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Determines sort order for object keys for json-stable-stringify + * + * see: https://github.com/samn/json-stable-stringify#cmp + * @param {Object} a The first comparison object ({key: akey, value: avalue}) + * @param {Object} b The second comparison object ({key: bkey, value: bvalue}) + * @returns {number} 1 or -1, used in stringify cmp method + */ +function sortByKey(a, b) { + return a.key > b.key ? 1 : -1; +} + +//------------------------------------------------------------------------------ +// Private +//------------------------------------------------------------------------------ + +/** + * Writes a configuration file in JSON format. + * @param {Object} config The configuration object to write. + * @param {string} filePath The filename to write to. + * @returns {void} + * @private + */ +function writeJSONConfigFile(config, filePath) { + debug(`Writing JSON config file: ${filePath}`); + + const content = `${stringify(config, { cmp: sortByKey, space: 4 })}\n`; + + fs.writeFileSync(filePath, content, "utf8"); +} + +/** + * Writes a configuration file in YAML format. + * @param {Object} config The configuration object to write. + * @param {string} filePath The filename to write to. + * @returns {void} + * @private + */ +function writeYAMLConfigFile(config, filePath) { + debug(`Writing YAML config file: ${filePath}`); + + // lazy load YAML to improve performance when not used + const yaml = require("js-yaml"); + + const content = yaml.safeDump(config, { sortKeys: true }); + + fs.writeFileSync(filePath, content, "utf8"); +} + +/** + * Writes a configuration file in JavaScript format. + * @param {Object} config The configuration object to write. + * @param {string} filePath The filename to write to. + * @throws {Error} If an error occurs linting the config file contents. + * @returns {void} + * @private + */ +function writeJSConfigFile(config, filePath) { + debug(`Writing JS config file: ${filePath}`); + + let contentToWrite; + const stringifiedContent = `module.exports = ${stringify(config, { cmp: sortByKey, space: 4 })};\n`; + + try { + const { CLIEngine } = require("../cli-engine"); + const linter = new CLIEngine({ + baseConfig: config, + fix: true, + useEslintrc: false + }); + const report = linter.executeOnText(stringifiedContent); + + contentToWrite = report.results[0].output || stringifiedContent; + } catch (e) { + debug("Error linting JavaScript config file, writing unlinted version"); + const errorMessage = e.message; + + contentToWrite = stringifiedContent; + e.message = "An error occurred while generating your JavaScript config file. "; + e.message += "A config file was still generated, but the config file itself may not follow your linting rules."; + e.message += `\nError: ${errorMessage}`; + throw e; + } finally { + fs.writeFileSync(filePath, contentToWrite, "utf8"); + } +} + +/** + * Writes a configuration file. + * @param {Object} config The configuration object to write. + * @param {string} filePath The filename to write to. + * @returns {void} + * @throws {Error} When an unknown file type is specified. + * @private + */ +function write(config, filePath) { + switch (path.extname(filePath)) { + case ".js": + writeJSConfigFile(config, filePath); + break; + + case ".json": + writeJSONConfigFile(config, filePath); + break; + + case ".yaml": + case ".yml": + writeYAMLConfigFile(config, filePath); + break; + + default: + throw new Error("Can't write to unknown file type."); + } +} + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = { + write +}; diff --git a/eslint/lib/init/config-initializer.js b/eslint/lib/init/config-initializer.js new file mode 100644 index 0000000..28dfad1 --- /dev/null +++ b/eslint/lib/init/config-initializer.js @@ -0,0 +1,674 @@ +/** + * @fileoverview Config initialization wizard. + * @author Ilya Volodin + */ + + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const util = require("util"), + path = require("path"), + inquirer = require("inquirer"), + ProgressBar = require("progress"), + semver = require("semver"), + recConfig = require("../../conf/eslint-recommended"), + ConfigOps = require("../shared/config-ops"), + log = require("../shared/logging"), + naming = require("../shared/naming"), + ModuleResolver = require("../shared/relative-module-resolver"), + autoconfig = require("./autoconfig.js"), + ConfigFile = require("./config-file"), + npmUtils = require("./npm-utils"), + { getSourceCodeOfFiles } = require("./source-code-utils"); + +const debug = require("debug")("eslint:config-initializer"); + +//------------------------------------------------------------------------------ +// Private +//------------------------------------------------------------------------------ + +const DEFAULT_ECMA_VERSION = 2018; + +/* istanbul ignore next: hard to test fs function */ +/** + * Create .eslintrc file in the current working directory + * @param {Object} config object that contains user's answers + * @param {string} format The file format to write to. + * @returns {void} + */ +function writeFile(config, format) { + + // default is .js + let extname = ".js"; + + if (format === "YAML") { + extname = ".yml"; + } else if (format === "JSON") { + extname = ".json"; + } + + const installedESLint = config.installedESLint; + + delete config.installedESLint; + + ConfigFile.write(config, `./.eslintrc${extname}`); + log.info(`Successfully created .eslintrc${extname} file in ${process.cwd()}`); + + if (installedESLint) { + log.info("ESLint was installed locally. We recommend using this local copy instead of your globally-installed copy."); + } +} + +/** + * Get the peer dependencies of the given module. + * This adds the gotten value to cache at the first time, then reuses it. + * In a process, this function is called twice, but `npmUtils.fetchPeerDependencies` needs to access network which is relatively slow. + * @param {string} moduleName The module name to get. + * @returns {Object} The peer dependencies of the given module. + * This object is the object of `peerDependencies` field of `package.json`. + * Returns null if npm was not found. + */ +function getPeerDependencies(moduleName) { + let result = getPeerDependencies.cache.get(moduleName); + + if (!result) { + log.info(`Checking peerDependencies of ${moduleName}`); + + result = npmUtils.fetchPeerDependencies(moduleName); + getPeerDependencies.cache.set(moduleName, result); + } + + return result; +} +getPeerDependencies.cache = new Map(); + +/** + * Return necessary plugins, configs, parsers, etc. based on the config + * @param {Object} config config object + * @param {boolean} [installESLint=true] If `false` is given, it does not install eslint. + * @returns {string[]} An array of modules to be installed. + */ +function getModulesList(config, installESLint) { + const modules = {}; + + // Create a list of modules which should be installed based on config + if (config.plugins) { + for (const plugin of config.plugins) { + const moduleName = naming.normalizePackageName(plugin, "eslint-plugin"); + + modules[moduleName] = "latest"; + } + } + if (config.extends) { + const extendList = Array.isArray(config.extends) ? config.extends : [config.extends]; + + for (const extend of extendList) { + if (extend.startsWith("eslint:") || extend.startsWith("plugin:")) { + continue; + } + const moduleName = naming.normalizePackageName(extend, "eslint-config"); + + modules[moduleName] = "latest"; + Object.assign( + modules, + getPeerDependencies(`${moduleName}@latest`) + ); + } + } + + const parser = config.parser || (config.parserOptions && config.parserOptions.parser); + + if (parser) { + modules[parser] = "latest"; + } + + if (installESLint === false) { + delete modules.eslint; + } else { + const installStatus = npmUtils.checkDevDeps(["eslint"]); + + // Mark to show messages if it's new installation of eslint. + if (installStatus.eslint === false) { + log.info("Local ESLint installation not found."); + modules.eslint = modules.eslint || "latest"; + config.installedESLint = true; + } + } + + return Object.keys(modules).map(name => `${name}@${modules[name]}`); +} + +/** + * Set the `rules` of a config by examining a user's source code + * + * Note: This clones the config object and returns a new config to avoid mutating + * the original config parameter. + * @param {Object} answers answers received from inquirer + * @param {Object} config config object + * @returns {Object} config object with configured rules + */ +function configureRules(answers, config) { + const BAR_TOTAL = 20, + BAR_SOURCE_CODE_TOTAL = 4, + newConfig = Object.assign({}, config), + disabledConfigs = {}; + let sourceCodes, + registry; + + // Set up a progress bar, as this process can take a long time + const bar = new ProgressBar("Determining Config: :percent [:bar] :elapseds elapsed, eta :etas ", { + width: 30, + total: BAR_TOTAL + }); + + bar.tick(0); // Shows the progress bar + + // Get the SourceCode of all chosen files + const patterns = answers.patterns.split(/[\s]+/u); + + try { + sourceCodes = getSourceCodeOfFiles(patterns, { baseConfig: newConfig, useEslintrc: false }, total => { + bar.tick((BAR_SOURCE_CODE_TOTAL / total)); + }); + } catch (e) { + log.info("\n"); + throw e; + } + const fileQty = Object.keys(sourceCodes).length; + + if (fileQty === 0) { + log.info("\n"); + throw new Error("Automatic Configuration failed. No files were able to be parsed."); + } + + // Create a registry of rule configs + registry = new autoconfig.Registry(); + registry.populateFromCoreRules(); + + // Lint all files with each rule config in the registry + registry = registry.lintSourceCode(sourceCodes, newConfig, total => { + bar.tick((BAR_TOTAL - BAR_SOURCE_CODE_TOTAL) / total); // Subtract out ticks used at beginning + }); + debug(`\nRegistry: ${util.inspect(registry.rules, { depth: null })}`); + + // Create a list of recommended rules, because we don't want to disable them + const recRules = Object.keys(recConfig.rules).filter(ruleId => ConfigOps.isErrorSeverity(recConfig.rules[ruleId])); + + // Find and disable rules which had no error-free configuration + const failingRegistry = registry.getFailingRulesRegistry(); + + Object.keys(failingRegistry.rules).forEach(ruleId => { + + // If the rule is recommended, set it to error, otherwise disable it + disabledConfigs[ruleId] = (recRules.indexOf(ruleId) !== -1) ? 2 : 0; + }); + + // Now that we know which rules to disable, strip out configs with errors + registry = registry.stripFailingConfigs(); + + /* + * If there is only one config that results in no errors for a rule, we should use it. + * createConfig will only add rules that have one configuration in the registry. + */ + const singleConfigs = registry.createConfig().rules; + + /* + * The "sweet spot" for number of options in a config seems to be two (severity plus one option). + * Very often, a third option (usually an object) is available to address + * edge cases, exceptions, or unique situations. We will prefer to use a config with + * specificity of two. + */ + const specTwoConfigs = registry.filterBySpecificity(2).createConfig().rules; + + // Maybe a specific combination using all three options works + const specThreeConfigs = registry.filterBySpecificity(3).createConfig().rules; + + // If all else fails, try to use the default (severity only) + const defaultConfigs = registry.filterBySpecificity(1).createConfig().rules; + + // Combine configs in reverse priority order (later take precedence) + newConfig.rules = Object.assign({}, disabledConfigs, defaultConfigs, specThreeConfigs, specTwoConfigs, singleConfigs); + + // Make sure progress bar has finished (floating point rounding) + bar.update(BAR_TOTAL); + + // Log out some stats to let the user know what happened + const finalRuleIds = Object.keys(newConfig.rules); + const totalRules = finalRuleIds.length; + const enabledRules = finalRuleIds.filter(ruleId => (newConfig.rules[ruleId] !== 0)).length; + const resultMessage = [ + `\nEnabled ${enabledRules} out of ${totalRules}`, + `rules based on ${fileQty}`, + `file${(fileQty === 1) ? "." : "s."}` + ].join(" "); + + log.info(resultMessage); + + ConfigOps.normalizeToStrings(newConfig); + return newConfig; +} + +/** + * process user's answers and create config object + * @param {Object} answers answers received from inquirer + * @returns {Object} config object + */ +function processAnswers(answers) { + let config = { + rules: {}, + env: {}, + parserOptions: {}, + extends: [] + }; + + // set the latest ECMAScript version + config.parserOptions.ecmaVersion = DEFAULT_ECMA_VERSION; + config.env.es6 = true; + config.globals = { + Atomics: "readonly", + SharedArrayBuffer: "readonly" + }; + + // set the module type + if (answers.moduleType === "esm") { + config.parserOptions.sourceType = "module"; + } else if (answers.moduleType === "commonjs") { + config.env.commonjs = true; + } + + // add in browser and node environments if necessary + answers.env.forEach(env => { + config.env[env] = true; + }); + + // add in library information + if (answers.framework === "react") { + config.parserOptions.ecmaFeatures = { + jsx: true + }; + config.plugins = ["react"]; + config.extends.push("plugin:react/recommended"); + } else if (answers.framework === "vue") { + config.plugins = ["vue"]; + config.extends.push("plugin:vue/essential"); + } + + if (answers.typescript) { + if (answers.framework === "vue") { + config.parserOptions.parser = "@typescript-eslint/parser"; + } else { + config.parser = "@typescript-eslint/parser"; + } + + if (Array.isArray(config.plugins)) { + config.plugins.push("@typescript-eslint"); + } else { + config.plugins = ["@typescript-eslint"]; + } + } + + // setup rules based on problems/style enforcement preferences + if (answers.purpose === "problems") { + config.extends.unshift("eslint:recommended"); + } else if (answers.purpose === "style") { + if (answers.source === "prompt") { + config.extends.unshift("eslint:recommended"); + config.rules.indent = ["error", answers.indent]; + config.rules.quotes = ["error", answers.quotes]; + config.rules["linebreak-style"] = ["error", answers.linebreak]; + config.rules.semi = ["error", answers.semi ? "always" : "never"]; + } else if (answers.source === "auto") { + config = configureRules(answers, config); + config = autoconfig.extendFromRecommended(config); + } + } + if (answers.typescript && config.extends.includes("eslint:recommended")) { + config.extends.push("plugin:@typescript-eslint/eslint-recommended"); + } + + // normalize extends + if (config.extends.length === 0) { + delete config.extends; + } else if (config.extends.length === 1) { + config.extends = config.extends[0]; + } + + ConfigOps.normalizeToStrings(config); + return config; +} + +/** + * Get the version of the local ESLint. + * @returns {string|null} The version. If the local ESLint was not found, returns null. + */ +function getLocalESLintVersion() { + try { + const eslintPath = ModuleResolver.resolve("eslint", path.join(process.cwd(), "__placeholder__.js")); + const eslint = require(eslintPath); + + return eslint.linter.version || null; + } catch (_err) { + return null; + } +} + +/** + * Get the shareable config name of the chosen style guide. + * @param {Object} answers The answers object. + * @returns {string} The shareable config name. + */ +function getStyleGuideName(answers) { + if (answers.styleguide === "airbnb" && answers.framework !== "react") { + return "airbnb-base"; + } + return answers.styleguide; +} + +/** + * Check whether the local ESLint version conflicts with the required version of the chosen shareable config. + * @param {Object} answers The answers object. + * @returns {boolean} `true` if the local ESLint is found then it conflicts with the required version of the chosen shareable config. + */ +function hasESLintVersionConflict(answers) { + + // Get the local ESLint version. + const localESLintVersion = getLocalESLintVersion(); + + if (!localESLintVersion) { + return false; + } + + // Get the required range of ESLint version. + const configName = getStyleGuideName(answers); + const moduleName = `eslint-config-${configName}@latest`; + const peerDependencies = getPeerDependencies(moduleName) || {}; + const requiredESLintVersionRange = peerDependencies.eslint; + + if (!requiredESLintVersionRange) { + return false; + } + + answers.localESLintVersion = localESLintVersion; + answers.requiredESLintVersionRange = requiredESLintVersionRange; + + // Check the version. + if (semver.satisfies(localESLintVersion, requiredESLintVersionRange)) { + answers.installESLint = false; + return false; + } + + return true; +} + +/** + * Install modules. + * @param {string[]} modules Modules to be installed. + * @returns {void} + */ +function installModules(modules) { + log.info(`Installing ${modules.join(", ")}`); + npmUtils.installSyncSaveDev(modules); +} + +/* istanbul ignore next: no need to test inquirer */ +/** + * Ask user to install modules. + * @param {string[]} modules Array of modules to be installed. + * @param {boolean} packageJsonExists Indicates if package.json is existed. + * @returns {Promise} Answer that indicates if user wants to install. + */ +function askInstallModules(modules, packageJsonExists) { + + // If no modules, do nothing. + if (modules.length === 0) { + return Promise.resolve(); + } + + log.info("The config that you've selected requires the following dependencies:\n"); + log.info(modules.join(" ")); + return inquirer.prompt([ + { + type: "confirm", + name: "executeInstallation", + message: "Would you like to install them now with npm?", + default: true, + when() { + return modules.length && packageJsonExists; + } + } + ]).then(({ executeInstallation }) => { + if (executeInstallation) { + installModules(modules); + } + }); +} + +/* istanbul ignore next: no need to test inquirer */ +/** + * Ask use a few questions on command prompt + * @returns {Promise} The promise with the result of the prompt + */ +function promptUser() { + + return inquirer.prompt([ + { + type: "list", + name: "purpose", + message: "How would you like to use ESLint?", + default: "problems", + choices: [ + { name: "To check syntax only", value: "syntax" }, + { name: "To check syntax and find problems", value: "problems" }, + { name: "To check syntax, find problems, and enforce code style", value: "style" } + ] + }, + { + type: "list", + name: "moduleType", + message: "What type of modules does your project use?", + default: "esm", + choices: [ + { name: "JavaScript modules (import/export)", value: "esm" }, + { name: "CommonJS (require/exports)", value: "commonjs" }, + { name: "None of these", value: "none" } + ] + }, + { + type: "list", + name: "framework", + message: "Which framework does your project use?", + default: "react", + choices: [ + { name: "React", value: "react" }, + { name: "Vue.js", value: "vue" }, + { name: "None of these", value: "none" } + ] + }, + { + type: "confirm", + name: "typescript", + message: "Does your project use TypeScript?", + default: false + }, + { + type: "checkbox", + name: "env", + message: "Where does your code run?", + default: ["browser"], + choices: [ + { name: "Browser", value: "browser" }, + { name: "Node", value: "node" } + ] + }, + { + type: "list", + name: "source", + message: "How would you like to define a style for your project?", + default: "guide", + choices: [ + { name: "Use a popular style guide", value: "guide" }, + { name: "Answer questions about your style", value: "prompt" }, + { name: "Inspect your JavaScript file(s)", value: "auto" } + ], + when(answers) { + return answers.purpose === "style"; + } + }, + { + type: "list", + name: "styleguide", + message: "Which style guide do you want to follow?", + choices: [ + { name: "Airbnb: https://github.com/airbnb/javascript", value: "airbnb" }, + { name: "Standard: https://github.com/standard/standard", value: "standard" }, + { name: "Google: https://github.com/google/eslint-config-google", value: "google" } + ], + when(answers) { + answers.packageJsonExists = npmUtils.checkPackageJson(); + return answers.source === "guide" && answers.packageJsonExists; + } + }, + { + type: "input", + name: "patterns", + message: "Which file(s), path(s), or glob(s) should be examined?", + when(answers) { + return (answers.source === "auto"); + }, + validate(input) { + if (input.trim().length === 0 && input.trim() !== ",") { + return "You must tell us what code to examine. Try again."; + } + return true; + } + }, + { + type: "list", + name: "format", + message: "What format do you want your config file to be in?", + default: "JavaScript", + choices: ["JavaScript", "YAML", "JSON"] + }, + { + type: "confirm", + name: "installESLint", + message(answers) { + const verb = semver.ltr(answers.localESLintVersion, answers.requiredESLintVersionRange) + ? "upgrade" + : "downgrade"; + + return `The style guide "${answers.styleguide}" requires eslint@${answers.requiredESLintVersionRange}. You are currently using eslint@${answers.localESLintVersion}.\n Do you want to ${verb}?`; + }, + default: true, + when(answers) { + return answers.source === "guide" && answers.packageJsonExists && hasESLintVersionConflict(answers); + } + } + ]).then(earlyAnswers => { + + // early exit if no style guide is necessary + if (earlyAnswers.purpose !== "style") { + const config = processAnswers(earlyAnswers); + const modules = getModulesList(config); + + return askInstallModules(modules, earlyAnswers.packageJsonExists) + .then(() => writeFile(config, earlyAnswers.format)); + } + + // early exit if you are using a style guide + if (earlyAnswers.source === "guide") { + if (!earlyAnswers.packageJsonExists) { + log.info("A package.json is necessary to install plugins such as style guides. Run `npm init` to create a package.json file and try again."); + return void 0; + } + if (earlyAnswers.installESLint === false && !semver.satisfies(earlyAnswers.localESLintVersion, earlyAnswers.requiredESLintVersionRange)) { + log.info(`Note: it might not work since ESLint's version is mismatched with the ${earlyAnswers.styleguide} config.`); + } + if (earlyAnswers.styleguide === "airbnb" && earlyAnswers.framework !== "react") { + earlyAnswers.styleguide = "airbnb-base"; + } + + const config = processAnswers(earlyAnswers); + + if (Array.isArray(config.extends)) { + config.extends.push(earlyAnswers.styleguide); + } else if (config.extends) { + config.extends = [config.extends, earlyAnswers.styleguide]; + } else { + config.extends = [earlyAnswers.styleguide]; + } + + const modules = getModulesList(config); + + return askInstallModules(modules, earlyAnswers.packageJsonExists) + .then(() => writeFile(config, earlyAnswers.format)); + + } + + if (earlyAnswers.source === "auto") { + const combinedAnswers = Object.assign({}, earlyAnswers); + const config = processAnswers(combinedAnswers); + const modules = getModulesList(config); + + return askInstallModules(modules).then(() => writeFile(config, earlyAnswers.format)); + } + + // continue with the style questions otherwise... + return inquirer.prompt([ + { + type: "list", + name: "indent", + message: "What style of indentation do you use?", + default: "tab", + choices: [{ name: "Tabs", value: "tab" }, { name: "Spaces", value: 4 }] + }, + { + type: "list", + name: "quotes", + message: "What quotes do you use for strings?", + default: "double", + choices: [{ name: "Double", value: "double" }, { name: "Single", value: "single" }] + }, + { + type: "list", + name: "linebreak", + message: "What line endings do you use?", + default: "unix", + choices: [{ name: "Unix", value: "unix" }, { name: "Windows", value: "windows" }] + }, + { + type: "confirm", + name: "semi", + message: "Do you require semicolons?", + default: true + } + ]).then(answers => { + const totalAnswers = Object.assign({}, earlyAnswers, answers); + + const config = processAnswers(totalAnswers); + const modules = getModulesList(config); + + return askInstallModules(modules).then(() => writeFile(config, earlyAnswers.format)); + }); + }); +} + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +const init = { + getModulesList, + hasESLintVersionConflict, + installModules, + processAnswers, + /* istanbul ignore next */initializeConfig() { + return promptUser(); + } +}; + +module.exports = init; diff --git a/eslint/lib/init/config-rule.js b/eslint/lib/init/config-rule.js new file mode 100644 index 0000000..7aec89c --- /dev/null +++ b/eslint/lib/init/config-rule.js @@ -0,0 +1,317 @@ +/** + * @fileoverview Create configurations for a rule + * @author Ian VanSchooten + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const builtInRules = require("../rules"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Wrap all of the elements of an array into arrays. + * @param {*[]} xs Any array. + * @returns {Array[]} An array of arrays. + */ +function explodeArray(xs) { + return xs.reduce((accumulator, x) => { + accumulator.push([x]); + return accumulator; + }, []); +} + +/** + * Mix two arrays such that each element of the second array is concatenated + * onto each element of the first array. + * + * For example: + * combineArrays([a, [b, c]], [x, y]); // -> [[a, x], [a, y], [b, c, x], [b, c, y]] + * @param {Array} arr1 The first array to combine. + * @param {Array} arr2 The second array to combine. + * @returns {Array} A mixture of the elements of the first and second arrays. + */ +function combineArrays(arr1, arr2) { + const res = []; + + if (arr1.length === 0) { + return explodeArray(arr2); + } + if (arr2.length === 0) { + return explodeArray(arr1); + } + arr1.forEach(x1 => { + arr2.forEach(x2 => { + res.push([].concat(x1, x2)); + }); + }); + return res; +} + +/** + * Group together valid rule configurations based on object properties + * + * e.g.: + * groupByProperty([ + * {before: true}, + * {before: false}, + * {after: true}, + * {after: false} + * ]); + * + * will return: + * [ + * [{before: true}, {before: false}], + * [{after: true}, {after: false}] + * ] + * @param {Object[]} objects Array of objects, each with one property/value pair + * @returns {Array[]} Array of arrays of objects grouped by property + */ +function groupByProperty(objects) { + const groupedObj = objects.reduce((accumulator, obj) => { + const prop = Object.keys(obj)[0]; + + accumulator[prop] = accumulator[prop] ? accumulator[prop].concat(obj) : [obj]; + return accumulator; + }, {}); + + return Object.keys(groupedObj).map(prop => groupedObj[prop]); +} + + +//------------------------------------------------------------------------------ +// Private +//------------------------------------------------------------------------------ + +/** + * Configuration settings for a rule. + * + * A configuration can be a single number (severity), or an array where the first + * element in the array is the severity, and is the only required element. + * Configs may also have one or more additional elements to specify rule + * configuration or options. + * @typedef {Array|number} ruleConfig + * @param {number} 0 The rule's severity (0, 1, 2). + */ + +/** + * Object whose keys are rule names and values are arrays of valid ruleConfig items + * which should be linted against the target source code to determine error counts. + * (a ruleConfigSet.ruleConfigs). + * + * e.g. rulesConfig = { + * "comma-dangle": [2, [2, "always"], [2, "always-multiline"], [2, "never"]], + * "no-console": [2] + * } + * @typedef rulesConfig + */ + + +/** + * Create valid rule configurations by combining two arrays, + * with each array containing multiple objects each with a + * single property/value pair and matching properties. + * + * e.g.: + * combinePropertyObjects( + * [{before: true}, {before: false}], + * [{after: true}, {after: false}] + * ); + * + * will return: + * [ + * {before: true, after: true}, + * {before: true, after: false}, + * {before: false, after: true}, + * {before: false, after: false} + * ] + * @param {Object[]} objArr1 Single key/value objects, all with the same key + * @param {Object[]} objArr2 Single key/value objects, all with another key + * @returns {Object[]} Combined objects for each combination of input properties and values + */ +function combinePropertyObjects(objArr1, objArr2) { + const res = []; + + if (objArr1.length === 0) { + return objArr2; + } + if (objArr2.length === 0) { + return objArr1; + } + objArr1.forEach(obj1 => { + objArr2.forEach(obj2 => { + const combinedObj = {}; + const obj1Props = Object.keys(obj1); + const obj2Props = Object.keys(obj2); + + obj1Props.forEach(prop1 => { + combinedObj[prop1] = obj1[prop1]; + }); + obj2Props.forEach(prop2 => { + combinedObj[prop2] = obj2[prop2]; + }); + res.push(combinedObj); + }); + }); + return res; +} + +/** + * Creates a new instance of a rule configuration set + * + * A rule configuration set is an array of configurations that are valid for a + * given rule. For example, the configuration set for the "semi" rule could be: + * + * ruleConfigSet.ruleConfigs // -> [[2], [2, "always"], [2, "never"]] + * + * Rule configuration set class + */ +class RuleConfigSet { + + // eslint-disable-next-line jsdoc/require-description + /** + * @param {ruleConfig[]} configs Valid rule configurations + */ + constructor(configs) { + + /** + * Stored valid rule configurations for this instance + * @type {Array} + */ + this.ruleConfigs = configs || []; + } + + /** + * Add a severity level to the front of all configs in the instance. + * This should only be called after all configs have been added to the instance. + * @returns {void} + */ + addErrorSeverity() { + const severity = 2; + + this.ruleConfigs = this.ruleConfigs.map(config => { + config.unshift(severity); + return config; + }); + + // Add a single config at the beginning consisting of only the severity + this.ruleConfigs.unshift(severity); + } + + /** + * Add rule configs from an array of strings (schema enums) + * @param {string[]} enums Array of valid rule options (e.g. ["always", "never"]) + * @returns {void} + */ + addEnums(enums) { + this.ruleConfigs = this.ruleConfigs.concat(combineArrays(this.ruleConfigs, enums)); + } + + /** + * Add rule configurations from a schema object + * @param {Object} obj Schema item with type === "object" + * @returns {boolean} true if at least one schema for the object could be generated, false otherwise + */ + addObject(obj) { + const objectConfigSet = { + objectConfigs: [], + add(property, values) { + for (let idx = 0; idx < values.length; idx++) { + const optionObj = {}; + + optionObj[property] = values[idx]; + this.objectConfigs.push(optionObj); + } + }, + + combine() { + this.objectConfigs = groupByProperty(this.objectConfigs).reduce((accumulator, objArr) => combinePropertyObjects(accumulator, objArr), []); + } + }; + + /* + * The object schema could have multiple independent properties. + * If any contain enums or booleans, they can be added and then combined + */ + Object.keys(obj.properties).forEach(prop => { + if (obj.properties[prop].enum) { + objectConfigSet.add(prop, obj.properties[prop].enum); + } + if (obj.properties[prop].type && obj.properties[prop].type === "boolean") { + objectConfigSet.add(prop, [true, false]); + } + }); + objectConfigSet.combine(); + + if (objectConfigSet.objectConfigs.length > 0) { + this.ruleConfigs = this.ruleConfigs.concat(combineArrays(this.ruleConfigs, objectConfigSet.objectConfigs)); + return true; + } + + return false; + } +} + +/** + * Generate valid rule configurations based on a schema object + * @param {Object} schema A rule's schema object + * @returns {Array[]} Valid rule configurations + */ +function generateConfigsFromSchema(schema) { + const configSet = new RuleConfigSet(); + + if (Array.isArray(schema)) { + for (const opt of schema) { + if (opt.enum) { + configSet.addEnums(opt.enum); + } else if (opt.type && opt.type === "object") { + if (!configSet.addObject(opt)) { + break; + } + + // TODO (IanVS): support oneOf + } else { + + // If we don't know how to fill in this option, don't fill in any of the following options. + break; + } + } + } + configSet.addErrorSeverity(); + return configSet.ruleConfigs; +} + +/** + * Generate possible rule configurations for all of the core rules + * @param {boolean} noDeprecated Indicates whether ignores deprecated rules or not. + * @returns {rulesConfig} Hash of rule names and arrays of possible configurations + */ +function createCoreRuleConfigs(noDeprecated = false) { + return Array.from(builtInRules).reduce((accumulator, [id, rule]) => { + const schema = (typeof rule === "function") ? rule.schema : rule.meta.schema; + const isDeprecated = (typeof rule === "function") ? rule.deprecated : rule.meta.deprecated; + + if (noDeprecated && isDeprecated) { + return accumulator; + } + + accumulator[id] = generateConfigsFromSchema(schema); + return accumulator; + }, {}); +} + + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = { + generateConfigsFromSchema, + createCoreRuleConfigs +}; diff --git a/eslint/lib/init/npm-utils.js b/eslint/lib/init/npm-utils.js new file mode 100644 index 0000000..555ea2b --- /dev/null +++ b/eslint/lib/init/npm-utils.js @@ -0,0 +1,178 @@ +/** + * @fileoverview Utility for executing npm commands. + * @author Ian VanSchooten + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const fs = require("fs"), + spawn = require("cross-spawn"), + path = require("path"), + log = require("../shared/logging"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Find the closest package.json file, starting at process.cwd (by default), + * and working up to root. + * @param {string} [startDir=process.cwd()] Starting directory + * @returns {string} Absolute path to closest package.json file + */ +function findPackageJson(startDir) { + let dir = path.resolve(startDir || process.cwd()); + + do { + const pkgFile = path.join(dir, "package.json"); + + if (!fs.existsSync(pkgFile) || !fs.statSync(pkgFile).isFile()) { + dir = path.join(dir, ".."); + continue; + } + return pkgFile; + } while (dir !== path.resolve(dir, "..")); + return null; +} + +//------------------------------------------------------------------------------ +// Private +//------------------------------------------------------------------------------ + +/** + * Install node modules synchronously and save to devDependencies in package.json + * @param {string|string[]} packages Node module or modules to install + * @returns {void} + */ +function installSyncSaveDev(packages) { + const packageList = Array.isArray(packages) ? packages : [packages]; + const npmProcess = spawn.sync("npm", ["i", "--save-dev"].concat(packageList), + { stdio: "inherit" }); + const error = npmProcess.error; + + if (error && error.code === "ENOENT") { + const pluralS = packageList.length > 1 ? "s" : ""; + + log.error(`Could not execute npm. Please install the following package${pluralS} with a package manager of your choice: ${packageList.join(", ")}`); + } +} + +/** + * Fetch `peerDependencies` of the given package by `npm show` command. + * @param {string} packageName The package name to fetch peerDependencies. + * @returns {Object} Gotten peerDependencies. Returns null if npm was not found. + */ +function fetchPeerDependencies(packageName) { + const npmProcess = spawn.sync( + "npm", + ["show", "--json", packageName, "peerDependencies"], + { encoding: "utf8" } + ); + + const error = npmProcess.error; + + if (error && error.code === "ENOENT") { + return null; + } + const fetchedText = npmProcess.stdout.trim(); + + return JSON.parse(fetchedText || "{}"); + + +} + +/** + * Check whether node modules are include in a project's package.json. + * @param {string[]} packages Array of node module names + * @param {Object} opt Options Object + * @param {boolean} opt.dependencies Set to true to check for direct dependencies + * @param {boolean} opt.devDependencies Set to true to check for development dependencies + * @param {boolean} opt.startdir Directory to begin searching from + * @returns {Object} An object whose keys are the module names + * and values are booleans indicating installation. + */ +function check(packages, opt) { + const deps = new Set(); + const pkgJson = (opt) ? findPackageJson(opt.startDir) : findPackageJson(); + let fileJson; + + if (!pkgJson) { + throw new Error("Could not find a package.json file. Run 'npm init' to create one."); + } + + try { + fileJson = JSON.parse(fs.readFileSync(pkgJson, "utf8")); + } catch (e) { + const error = new Error(e); + + error.messageTemplate = "failed-to-read-json"; + error.messageData = { + path: pkgJson, + message: e.message + }; + throw error; + } + + ["dependencies", "devDependencies"].forEach(key => { + if (opt[key] && typeof fileJson[key] === "object") { + Object.keys(fileJson[key]).forEach(dep => deps.add(dep)); + } + }); + + return packages.reduce((status, pkg) => { + status[pkg] = deps.has(pkg); + return status; + }, {}); +} + +/** + * Check whether node modules are included in the dependencies of a project's + * package.json. + * + * Convenience wrapper around check(). + * @param {string[]} packages Array of node modules to check. + * @param {string} rootDir The directory containing a package.json + * @returns {Object} An object whose keys are the module names + * and values are booleans indicating installation. + */ +function checkDeps(packages, rootDir) { + return check(packages, { dependencies: true, startDir: rootDir }); +} + +/** + * Check whether node modules are included in the devDependencies of a project's + * package.json. + * + * Convenience wrapper around check(). + * @param {string[]} packages Array of node modules to check. + * @returns {Object} An object whose keys are the module names + * and values are booleans indicating installation. + */ +function checkDevDeps(packages) { + return check(packages, { devDependencies: true }); +} + +/** + * Check whether package.json is found in current path. + * @param {string} [startDir] Starting directory + * @returns {boolean} Whether a package.json is found in current path. + */ +function checkPackageJson(startDir) { + return !!findPackageJson(startDir); +} + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = { + installSyncSaveDev, + fetchPeerDependencies, + checkDeps, + checkDevDeps, + checkPackageJson +}; diff --git a/eslint/lib/init/source-code-utils.js b/eslint/lib/init/source-code-utils.js new file mode 100644 index 0000000..dfc170a --- /dev/null +++ b/eslint/lib/init/source-code-utils.js @@ -0,0 +1,109 @@ +/** + * @fileoverview Tools for obtaining SourceCode objects. + * @author Ian VanSchooten + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const { CLIEngine } = require("../cli-engine"); + +/* + * This is used for: + * + * 1. Enumerate target file because we have not expose such a API on `CLIEngine` + * (https://github.com/eslint/eslint/issues/11222). + * 2. Create `SourceCode` instances. Because we don't have any function which + * instantiate `SourceCode` so it needs to take the created `SourceCode` + * instance out after linting. + * + * TODO1: Expose the API that enumerates target files. + * TODO2: Extract the creation logic of `SourceCode` from `Linter` class. + */ +const { getCLIEngineInternalSlots } = require("../cli-engine/cli-engine"); // eslint-disable-line no-restricted-modules + +const debug = require("debug")("eslint:source-code-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Get the SourceCode object for a single file + * @param {string} filename The fully resolved filename to get SourceCode from. + * @param {Object} engine A CLIEngine. + * @returns {Array} Array of the SourceCode object representing the file + * and fatal error message. + */ +function getSourceCodeOfFile(filename, engine) { + debug("getting sourceCode of", filename); + const results = engine.executeOnFiles([filename]); + + if (results && results.results[0] && results.results[0].messages[0] && results.results[0].messages[0].fatal) { + const msg = results.results[0].messages[0]; + + throw new Error(`(${filename}:${msg.line}:${msg.column}) ${msg.message}`); + } + + // TODO: extract the logic that creates source code objects to `SourceCode#parse(text, options)` or something like. + const { linter } = getCLIEngineInternalSlots(engine); + const sourceCode = linter.getSourceCode(); + + return sourceCode; +} + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + + +/** + * This callback is used to measure execution status in a progress bar + * @callback progressCallback + * @param {number} The total number of times the callback will be called. + */ + +/** + * Gets the SourceCode of a single file, or set of files. + * @param {string[]|string} patterns A filename, directory name, or glob, or an array of them + * @param {Object} options A CLIEngine options object. If not provided, the default cli options will be used. + * @param {progressCallback} callback Callback for reporting execution status + * @returns {Object} The SourceCode of all processed files. + */ +function getSourceCodeOfFiles(patterns, options, callback) { + const sourceCodes = {}; + const globPatternsList = typeof patterns === "string" ? [patterns] : patterns; + const engine = new CLIEngine({ ...options, rules: {} }); + + // TODO: make file iteration as a public API and use it. + const { fileEnumerator } = getCLIEngineInternalSlots(engine); + const filenames = + Array.from(fileEnumerator.iterateFiles(globPatternsList)) + .filter(entry => !entry.ignored) + .map(entry => entry.filePath); + + if (filenames.length === 0) { + debug(`Did not find any files matching pattern(s): ${globPatternsList}`); + } + + filenames.forEach(filename => { + const sourceCode = getSourceCodeOfFile(filename, engine); + + if (sourceCode) { + debug("got sourceCode of", filename); + sourceCodes[filename] = sourceCode; + } + if (callback) { + callback(filenames.length); // eslint-disable-line callback-return + } + }); + + return sourceCodes; +} + +module.exports = { + getSourceCodeOfFiles +}; diff --git a/eslint/lib/linter/apply-disable-directives.js b/eslint/lib/linter/apply-disable-directives.js new file mode 100644 index 0000000..41d6934 --- /dev/null +++ b/eslint/lib/linter/apply-disable-directives.js @@ -0,0 +1,167 @@ +/** + * @fileoverview A module that filters reported problems based on `eslint-disable` and `eslint-enable` comments + * @author Teddy Katz + */ + +"use strict"; + +const lodash = require("lodash"); + +/** + * Compares the locations of two objects in a source file + * @param {{line: number, column: number}} itemA The first object + * @param {{line: number, column: number}} itemB The second object + * @returns {number} A value less than 1 if itemA appears before itemB in the source file, greater than 1 if + * itemA appears after itemB in the source file, or 0 if itemA and itemB have the same location. + */ +function compareLocations(itemA, itemB) { + return itemA.line - itemB.line || itemA.column - itemB.column; +} + +/** + * This is the same as the exported function, except that it + * doesn't handle disable-line and disable-next-line directives, and it always reports unused + * disable directives. + * @param {Object} options options for applying directives. This is the same as the options + * for the exported function, except that `reportUnusedDisableDirectives` is not supported + * (this function always reports unused disable directives). + * @returns {{problems: Problem[], unusedDisableDirectives: Problem[]}} An object with a list + * of filtered problems and unused eslint-disable directives + */ +function applyDirectives(options) { + const problems = []; + let nextDirectiveIndex = 0; + let currentGlobalDisableDirective = null; + const disabledRuleMap = new Map(); + + // enabledRules is only used when there is a current global disable directive. + const enabledRules = new Set(); + const usedDisableDirectives = new Set(); + + for (const problem of options.problems) { + while ( + nextDirectiveIndex < options.directives.length && + compareLocations(options.directives[nextDirectiveIndex], problem) <= 0 + ) { + const directive = options.directives[nextDirectiveIndex++]; + + switch (directive.type) { + case "disable": + if (directive.ruleId === null) { + currentGlobalDisableDirective = directive; + disabledRuleMap.clear(); + enabledRules.clear(); + } else if (currentGlobalDisableDirective) { + enabledRules.delete(directive.ruleId); + disabledRuleMap.set(directive.ruleId, directive); + } else { + disabledRuleMap.set(directive.ruleId, directive); + } + break; + + case "enable": + if (directive.ruleId === null) { + currentGlobalDisableDirective = null; + disabledRuleMap.clear(); + } else if (currentGlobalDisableDirective) { + enabledRules.add(directive.ruleId); + disabledRuleMap.delete(directive.ruleId); + } else { + disabledRuleMap.delete(directive.ruleId); + } + break; + + // no default + } + } + + if (disabledRuleMap.has(problem.ruleId)) { + usedDisableDirectives.add(disabledRuleMap.get(problem.ruleId)); + } else if (currentGlobalDisableDirective && !enabledRules.has(problem.ruleId)) { + usedDisableDirectives.add(currentGlobalDisableDirective); + } else { + problems.push(problem); + } + } + + const unusedDisableDirectives = options.directives + .filter(directive => directive.type === "disable" && !usedDisableDirectives.has(directive)) + .map(directive => ({ + ruleId: null, + message: directive.ruleId + ? `Unused eslint-disable directive (no problems were reported from '${directive.ruleId}').` + : "Unused eslint-disable directive (no problems were reported).", + line: directive.unprocessedDirective.line, + column: directive.unprocessedDirective.column, + severity: options.reportUnusedDisableDirectives === "warn" ? 1 : 2, + nodeType: null + })); + + return { problems, unusedDisableDirectives }; +} + +/** + * Given a list of directive comments (i.e. metadata about eslint-disable and eslint-enable comments) and a list + * of reported problems, determines which problems should be reported. + * @param {Object} options Information about directives and problems + * @param {{ + * type: ("disable"|"enable"|"disable-line"|"disable-next-line"), + * ruleId: (string|null), + * line: number, + * column: number + * }} options.directives Directive comments found in the file, with one-based columns. + * Two directive comments can only have the same location if they also have the same type (e.g. a single eslint-disable + * comment for two different rules is represented as two directives). + * @param {{ruleId: (string|null), line: number, column: number}[]} options.problems + * A list of problems reported by rules, sorted by increasing location in the file, with one-based columns. + * @param {"off" | "warn" | "error"} options.reportUnusedDisableDirectives If `"warn"` or `"error"`, adds additional problems for unused directives + * @returns {{ruleId: (string|null), line: number, column: number}[]} + * A list of reported problems that were not disabled by the directive comments. + */ +module.exports = ({ directives, problems, reportUnusedDisableDirectives = "off" }) => { + const blockDirectives = directives + .filter(directive => directive.type === "disable" || directive.type === "enable") + .map(directive => Object.assign({}, directive, { unprocessedDirective: directive })) + .sort(compareLocations); + + const lineDirectives = lodash.flatMap(directives, directive => { + switch (directive.type) { + case "disable": + case "enable": + return []; + + case "disable-line": + return [ + { type: "disable", line: directive.line, column: 1, ruleId: directive.ruleId, unprocessedDirective: directive }, + { type: "enable", line: directive.line + 1, column: 0, ruleId: directive.ruleId, unprocessedDirective: directive } + ]; + + case "disable-next-line": + return [ + { type: "disable", line: directive.line + 1, column: 1, ruleId: directive.ruleId, unprocessedDirective: directive }, + { type: "enable", line: directive.line + 2, column: 0, ruleId: directive.ruleId, unprocessedDirective: directive } + ]; + + default: + throw new TypeError(`Unrecognized directive type '${directive.type}'`); + } + }).sort(compareLocations); + + const blockDirectivesResult = applyDirectives({ + problems, + directives: blockDirectives, + reportUnusedDisableDirectives + }); + const lineDirectivesResult = applyDirectives({ + problems: blockDirectivesResult.problems, + directives: lineDirectives, + reportUnusedDisableDirectives + }); + + return reportUnusedDisableDirectives !== "off" + ? lineDirectivesResult.problems + .concat(blockDirectivesResult.unusedDisableDirectives) + .concat(lineDirectivesResult.unusedDisableDirectives) + .sort(compareLocations) + : lineDirectivesResult.problems; +}; diff --git a/eslint/lib/linter/code-path-analysis/code-path-analyzer.js b/eslint/lib/linter/code-path-analysis/code-path-analyzer.js new file mode 100644 index 0000000..8a623e3 --- /dev/null +++ b/eslint/lib/linter/code-path-analysis/code-path-analyzer.js @@ -0,0 +1,684 @@ +/** + * @fileoverview A class of the code path analyzer. + * @author Toru Nagashima + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const assert = require("assert"), + { breakableTypePattern } = require("../../shared/ast-utils"), + CodePath = require("./code-path"), + CodePathSegment = require("./code-path-segment"), + IdGenerator = require("./id-generator"), + debug = require("./debug-helpers"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Checks whether or not a given node is a `case` node (not `default` node). + * @param {ASTNode} node A `SwitchCase` node to check. + * @returns {boolean} `true` if the node is a `case` node (not `default` node). + */ +function isCaseNode(node) { + return Boolean(node.test); +} + +/** + * Checks whether the given logical operator is taken into account for the code + * path analysis. + * @param {string} operator The operator found in the LogicalExpression node + * @returns {boolean} `true` if the operator is "&&" or "||" + */ +function isHandledLogicalOperator(operator) { + return operator === "&&" || operator === "||"; +} + +/** + * Gets the label if the parent node of a given node is a LabeledStatement. + * @param {ASTNode} node A node to get. + * @returns {string|null} The label or `null`. + */ +function getLabel(node) { + if (node.parent.type === "LabeledStatement") { + return node.parent.label.name; + } + return null; +} + +/** + * Checks whether or not a given logical expression node goes different path + * between the `true` case and the `false` case. + * @param {ASTNode} node A node to check. + * @returns {boolean} `true` if the node is a test of a choice statement. + */ +function isForkingByTrueOrFalse(node) { + const parent = node.parent; + + switch (parent.type) { + case "ConditionalExpression": + case "IfStatement": + case "WhileStatement": + case "DoWhileStatement": + case "ForStatement": + return parent.test === node; + + case "LogicalExpression": + return isHandledLogicalOperator(parent.operator); + + default: + return false; + } +} + +/** + * Gets the boolean value of a given literal node. + * + * This is used to detect infinity loops (e.g. `while (true) {}`). + * Statements preceded by an infinity loop are unreachable if the loop didn't + * have any `break` statement. + * @param {ASTNode} node A node to get. + * @returns {boolean|undefined} a boolean value if the node is a Literal node, + * otherwise `undefined`. + */ +function getBooleanValueIfSimpleConstant(node) { + if (node.type === "Literal") { + return Boolean(node.value); + } + return void 0; +} + +/** + * Checks that a given identifier node is a reference or not. + * + * This is used to detect the first throwable node in a `try` block. + * @param {ASTNode} node An Identifier node to check. + * @returns {boolean} `true` if the node is a reference. + */ +function isIdentifierReference(node) { + const parent = node.parent; + + switch (parent.type) { + case "LabeledStatement": + case "BreakStatement": + case "ContinueStatement": + case "ArrayPattern": + case "RestElement": + case "ImportSpecifier": + case "ImportDefaultSpecifier": + case "ImportNamespaceSpecifier": + case "CatchClause": + return false; + + case "FunctionDeclaration": + case "FunctionExpression": + case "ArrowFunctionExpression": + case "ClassDeclaration": + case "ClassExpression": + case "VariableDeclarator": + return parent.id !== node; + + case "Property": + case "MethodDefinition": + return ( + parent.key !== node || + parent.computed || + parent.shorthand + ); + + case "AssignmentPattern": + return parent.key !== node; + + default: + return true; + } +} + +/** + * Updates the current segment with the head segment. + * This is similar to local branches and tracking branches of git. + * + * To separate the current and the head is in order to not make useless segments. + * + * In this process, both "onCodePathSegmentStart" and "onCodePathSegmentEnd" + * events are fired. + * @param {CodePathAnalyzer} analyzer The instance. + * @param {ASTNode} node The current AST node. + * @returns {void} + */ +function forwardCurrentToHead(analyzer, node) { + const codePath = analyzer.codePath; + const state = CodePath.getState(codePath); + const currentSegments = state.currentSegments; + const headSegments = state.headSegments; + const end = Math.max(currentSegments.length, headSegments.length); + let i, currentSegment, headSegment; + + // Fires leaving events. + for (i = 0; i < end; ++i) { + currentSegment = currentSegments[i]; + headSegment = headSegments[i]; + + if (currentSegment !== headSegment && currentSegment) { + debug.dump(`onCodePathSegmentEnd ${currentSegment.id}`); + + if (currentSegment.reachable) { + analyzer.emitter.emit( + "onCodePathSegmentEnd", + currentSegment, + node + ); + } + } + } + + // Update state. + state.currentSegments = headSegments; + + // Fires entering events. + for (i = 0; i < end; ++i) { + currentSegment = currentSegments[i]; + headSegment = headSegments[i]; + + if (currentSegment !== headSegment && headSegment) { + debug.dump(`onCodePathSegmentStart ${headSegment.id}`); + + CodePathSegment.markUsed(headSegment); + if (headSegment.reachable) { + analyzer.emitter.emit( + "onCodePathSegmentStart", + headSegment, + node + ); + } + } + } + +} + +/** + * Updates the current segment with empty. + * This is called at the last of functions or the program. + * @param {CodePathAnalyzer} analyzer The instance. + * @param {ASTNode} node The current AST node. + * @returns {void} + */ +function leaveFromCurrentSegment(analyzer, node) { + const state = CodePath.getState(analyzer.codePath); + const currentSegments = state.currentSegments; + + for (let i = 0; i < currentSegments.length; ++i) { + const currentSegment = currentSegments[i]; + + debug.dump(`onCodePathSegmentEnd ${currentSegment.id}`); + if (currentSegment.reachable) { + analyzer.emitter.emit( + "onCodePathSegmentEnd", + currentSegment, + node + ); + } + } + + state.currentSegments = []; +} + +/** + * Updates the code path due to the position of a given node in the parent node + * thereof. + * + * For example, if the node is `parent.consequent`, this creates a fork from the + * current path. + * @param {CodePathAnalyzer} analyzer The instance. + * @param {ASTNode} node The current AST node. + * @returns {void} + */ +function preprocess(analyzer, node) { + const codePath = analyzer.codePath; + const state = CodePath.getState(codePath); + const parent = node.parent; + + switch (parent.type) { + case "LogicalExpression": + if ( + parent.right === node && + isHandledLogicalOperator(parent.operator) + ) { + state.makeLogicalRight(); + } + break; + + case "ConditionalExpression": + case "IfStatement": + + /* + * Fork if this node is at `consequent`/`alternate`. + * `popForkContext()` exists at `IfStatement:exit` and + * `ConditionalExpression:exit`. + */ + if (parent.consequent === node) { + state.makeIfConsequent(); + } else if (parent.alternate === node) { + state.makeIfAlternate(); + } + break; + + case "SwitchCase": + if (parent.consequent[0] === node) { + state.makeSwitchCaseBody(false, !parent.test); + } + break; + + case "TryStatement": + if (parent.handler === node) { + state.makeCatchBlock(); + } else if (parent.finalizer === node) { + state.makeFinallyBlock(); + } + break; + + case "WhileStatement": + if (parent.test === node) { + state.makeWhileTest(getBooleanValueIfSimpleConstant(node)); + } else { + assert(parent.body === node); + state.makeWhileBody(); + } + break; + + case "DoWhileStatement": + if (parent.body === node) { + state.makeDoWhileBody(); + } else { + assert(parent.test === node); + state.makeDoWhileTest(getBooleanValueIfSimpleConstant(node)); + } + break; + + case "ForStatement": + if (parent.test === node) { + state.makeForTest(getBooleanValueIfSimpleConstant(node)); + } else if (parent.update === node) { + state.makeForUpdate(); + } else if (parent.body === node) { + state.makeForBody(); + } + break; + + case "ForInStatement": + case "ForOfStatement": + if (parent.left === node) { + state.makeForInOfLeft(); + } else if (parent.right === node) { + state.makeForInOfRight(); + } else { + assert(parent.body === node); + state.makeForInOfBody(); + } + break; + + case "AssignmentPattern": + + /* + * Fork if this node is at `right`. + * `left` is executed always, so it uses the current path. + * `popForkContext()` exists at `AssignmentPattern:exit`. + */ + if (parent.right === node) { + state.pushForkContext(); + state.forkBypassPath(); + state.forkPath(); + } + break; + + default: + break; + } +} + +/** + * Updates the code path due to the type of a given node in entering. + * @param {CodePathAnalyzer} analyzer The instance. + * @param {ASTNode} node The current AST node. + * @returns {void} + */ +function processCodePathToEnter(analyzer, node) { + let codePath = analyzer.codePath; + let state = codePath && CodePath.getState(codePath); + const parent = node.parent; + + switch (node.type) { + case "Program": + case "FunctionDeclaration": + case "FunctionExpression": + case "ArrowFunctionExpression": + if (codePath) { + + // Emits onCodePathSegmentStart events if updated. + forwardCurrentToHead(analyzer, node); + debug.dumpState(node, state, false); + } + + // Create the code path of this scope. + codePath = analyzer.codePath = new CodePath( + analyzer.idGenerator.next(), + codePath, + analyzer.onLooped + ); + state = CodePath.getState(codePath); + + // Emits onCodePathStart events. + debug.dump(`onCodePathStart ${codePath.id}`); + analyzer.emitter.emit("onCodePathStart", codePath, node); + break; + + case "LogicalExpression": + if (isHandledLogicalOperator(node.operator)) { + state.pushChoiceContext( + node.operator, + isForkingByTrueOrFalse(node) + ); + } + break; + + case "ConditionalExpression": + case "IfStatement": + state.pushChoiceContext("test", false); + break; + + case "SwitchStatement": + state.pushSwitchContext( + node.cases.some(isCaseNode), + getLabel(node) + ); + break; + + case "TryStatement": + state.pushTryContext(Boolean(node.finalizer)); + break; + + case "SwitchCase": + + /* + * Fork if this node is after the 2st node in `cases`. + * It's similar to `else` blocks. + * The next `test` node is processed in this path. + */ + if (parent.discriminant !== node && parent.cases[0] !== node) { + state.forkPath(); + } + break; + + case "WhileStatement": + case "DoWhileStatement": + case "ForStatement": + case "ForInStatement": + case "ForOfStatement": + state.pushLoopContext(node.type, getLabel(node)); + break; + + case "LabeledStatement": + if (!breakableTypePattern.test(node.body.type)) { + state.pushBreakContext(false, node.label.name); + } + break; + + default: + break; + } + + // Emits onCodePathSegmentStart events if updated. + forwardCurrentToHead(analyzer, node); + debug.dumpState(node, state, false); +} + +/** + * Updates the code path due to the type of a given node in leaving. + * @param {CodePathAnalyzer} analyzer The instance. + * @param {ASTNode} node The current AST node. + * @returns {void} + */ +function processCodePathToExit(analyzer, node) { + const codePath = analyzer.codePath; + const state = CodePath.getState(codePath); + let dontForward = false; + + switch (node.type) { + case "IfStatement": + case "ConditionalExpression": + state.popChoiceContext(); + break; + + case "LogicalExpression": + if (isHandledLogicalOperator(node.operator)) { + state.popChoiceContext(); + } + break; + + case "SwitchStatement": + state.popSwitchContext(); + break; + + case "SwitchCase": + + /* + * This is the same as the process at the 1st `consequent` node in + * `preprocess` function. + * Must do if this `consequent` is empty. + */ + if (node.consequent.length === 0) { + state.makeSwitchCaseBody(true, !node.test); + } + if (state.forkContext.reachable) { + dontForward = true; + } + break; + + case "TryStatement": + state.popTryContext(); + break; + + case "BreakStatement": + forwardCurrentToHead(analyzer, node); + state.makeBreak(node.label && node.label.name); + dontForward = true; + break; + + case "ContinueStatement": + forwardCurrentToHead(analyzer, node); + state.makeContinue(node.label && node.label.name); + dontForward = true; + break; + + case "ReturnStatement": + forwardCurrentToHead(analyzer, node); + state.makeReturn(); + dontForward = true; + break; + + case "ThrowStatement": + forwardCurrentToHead(analyzer, node); + state.makeThrow(); + dontForward = true; + break; + + case "Identifier": + if (isIdentifierReference(node)) { + state.makeFirstThrowablePathInTryBlock(); + dontForward = true; + } + break; + + case "CallExpression": + case "ImportExpression": + case "MemberExpression": + case "NewExpression": + case "YieldExpression": + state.makeFirstThrowablePathInTryBlock(); + break; + + case "WhileStatement": + case "DoWhileStatement": + case "ForStatement": + case "ForInStatement": + case "ForOfStatement": + state.popLoopContext(); + break; + + case "AssignmentPattern": + state.popForkContext(); + break; + + case "LabeledStatement": + if (!breakableTypePattern.test(node.body.type)) { + state.popBreakContext(); + } + break; + + default: + break; + } + + // Emits onCodePathSegmentStart events if updated. + if (!dontForward) { + forwardCurrentToHead(analyzer, node); + } + debug.dumpState(node, state, true); +} + +/** + * Updates the code path to finalize the current code path. + * @param {CodePathAnalyzer} analyzer The instance. + * @param {ASTNode} node The current AST node. + * @returns {void} + */ +function postprocess(analyzer, node) { + switch (node.type) { + case "Program": + case "FunctionDeclaration": + case "FunctionExpression": + case "ArrowFunctionExpression": { + let codePath = analyzer.codePath; + + // Mark the current path as the final node. + CodePath.getState(codePath).makeFinal(); + + // Emits onCodePathSegmentEnd event of the current segments. + leaveFromCurrentSegment(analyzer, node); + + // Emits onCodePathEnd event of this code path. + debug.dump(`onCodePathEnd ${codePath.id}`); + analyzer.emitter.emit("onCodePathEnd", codePath, node); + debug.dumpDot(codePath); + + codePath = analyzer.codePath = analyzer.codePath.upper; + if (codePath) { + debug.dumpState(node, CodePath.getState(codePath), true); + } + break; + } + + default: + break; + } +} + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +/** + * The class to analyze code paths. + * This class implements the EventGenerator interface. + */ +class CodePathAnalyzer { + + // eslint-disable-next-line jsdoc/require-description + /** + * @param {EventGenerator} eventGenerator An event generator to wrap. + */ + constructor(eventGenerator) { + this.original = eventGenerator; + this.emitter = eventGenerator.emitter; + this.codePath = null; + this.idGenerator = new IdGenerator("s"); + this.currentNode = null; + this.onLooped = this.onLooped.bind(this); + } + + /** + * Does the process to enter a given AST node. + * This updates state of analysis and calls `enterNode` of the wrapped. + * @param {ASTNode} node A node which is entering. + * @returns {void} + */ + enterNode(node) { + this.currentNode = node; + + // Updates the code path due to node's position in its parent node. + if (node.parent) { + preprocess(this, node); + } + + /* + * Updates the code path. + * And emits onCodePathStart/onCodePathSegmentStart events. + */ + processCodePathToEnter(this, node); + + // Emits node events. + this.original.enterNode(node); + + this.currentNode = null; + } + + /** + * Does the process to leave a given AST node. + * This updates state of analysis and calls `leaveNode` of the wrapped. + * @param {ASTNode} node A node which is leaving. + * @returns {void} + */ + leaveNode(node) { + this.currentNode = node; + + /* + * Updates the code path. + * And emits onCodePathStart/onCodePathSegmentStart events. + */ + processCodePathToExit(this, node); + + // Emits node events. + this.original.leaveNode(node); + + // Emits the last onCodePathStart/onCodePathSegmentStart events. + postprocess(this, node); + + this.currentNode = null; + } + + /** + * This is called on a code path looped. + * Then this raises a looped event. + * @param {CodePathSegment} fromSegment A segment of prev. + * @param {CodePathSegment} toSegment A segment of next. + * @returns {void} + */ + onLooped(fromSegment, toSegment) { + if (fromSegment.reachable && toSegment.reachable) { + debug.dump(`onCodePathSegmentLoop ${fromSegment.id} -> ${toSegment.id}`); + this.emitter.emit( + "onCodePathSegmentLoop", + fromSegment, + toSegment, + this.currentNode + ); + } + } +} + +module.exports = CodePathAnalyzer; diff --git a/eslint/lib/linter/code-path-analysis/code-path-segment.js b/eslint/lib/linter/code-path-analysis/code-path-segment.js new file mode 100644 index 0000000..6b17b25 --- /dev/null +++ b/eslint/lib/linter/code-path-analysis/code-path-segment.js @@ -0,0 +1,237 @@ +/** + * @fileoverview A class of the code path segment. + * @author Toru Nagashima + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const debug = require("./debug-helpers"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Checks whether or not a given segment is reachable. + * @param {CodePathSegment} segment A segment to check. + * @returns {boolean} `true` if the segment is reachable. + */ +function isReachable(segment) { + return segment.reachable; +} + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +/** + * A code path segment. + */ +class CodePathSegment { + + // eslint-disable-next-line jsdoc/require-description + /** + * @param {string} id An identifier. + * @param {CodePathSegment[]} allPrevSegments An array of the previous segments. + * This array includes unreachable segments. + * @param {boolean} reachable A flag which shows this is reachable. + */ + constructor(id, allPrevSegments, reachable) { + + /** + * The identifier of this code path. + * Rules use it to store additional information of each rule. + * @type {string} + */ + this.id = id; + + /** + * An array of the next segments. + * @type {CodePathSegment[]} + */ + this.nextSegments = []; + + /** + * An array of the previous segments. + * @type {CodePathSegment[]} + */ + this.prevSegments = allPrevSegments.filter(isReachable); + + /** + * An array of the next segments. + * This array includes unreachable segments. + * @type {CodePathSegment[]} + */ + this.allNextSegments = []; + + /** + * An array of the previous segments. + * This array includes unreachable segments. + * @type {CodePathSegment[]} + */ + this.allPrevSegments = allPrevSegments; + + /** + * A flag which shows this is reachable. + * @type {boolean} + */ + this.reachable = reachable; + + // Internal data. + Object.defineProperty(this, "internal", { + value: { + used: false, + loopedPrevSegments: [] + } + }); + + /* istanbul ignore if */ + if (debug.enabled) { + this.internal.nodes = []; + this.internal.exitNodes = []; + } + } + + /** + * Checks a given previous segment is coming from the end of a loop. + * @param {CodePathSegment} segment A previous segment to check. + * @returns {boolean} `true` if the segment is coming from the end of a loop. + */ + isLoopedPrevSegment(segment) { + return this.internal.loopedPrevSegments.indexOf(segment) !== -1; + } + + /** + * Creates the root segment. + * @param {string} id An identifier. + * @returns {CodePathSegment} The created segment. + */ + static newRoot(id) { + return new CodePathSegment(id, [], true); + } + + /** + * Creates a segment that follows given segments. + * @param {string} id An identifier. + * @param {CodePathSegment[]} allPrevSegments An array of the previous segments. + * @returns {CodePathSegment} The created segment. + */ + static newNext(id, allPrevSegments) { + return new CodePathSegment( + id, + CodePathSegment.flattenUnusedSegments(allPrevSegments), + allPrevSegments.some(isReachable) + ); + } + + /** + * Creates an unreachable segment that follows given segments. + * @param {string} id An identifier. + * @param {CodePathSegment[]} allPrevSegments An array of the previous segments. + * @returns {CodePathSegment} The created segment. + */ + static newUnreachable(id, allPrevSegments) { + const segment = new CodePathSegment(id, CodePathSegment.flattenUnusedSegments(allPrevSegments), false); + + /* + * In `if (a) return a; foo();` case, the unreachable segment preceded by + * the return statement is not used but must not be remove. + */ + CodePathSegment.markUsed(segment); + + return segment; + } + + /** + * Creates a segment that follows given segments. + * This factory method does not connect with `allPrevSegments`. + * But this inherits `reachable` flag. + * @param {string} id An identifier. + * @param {CodePathSegment[]} allPrevSegments An array of the previous segments. + * @returns {CodePathSegment} The created segment. + */ + static newDisconnected(id, allPrevSegments) { + return new CodePathSegment(id, [], allPrevSegments.some(isReachable)); + } + + /** + * Makes a given segment being used. + * + * And this function registers the segment into the previous segments as a next. + * @param {CodePathSegment} segment A segment to mark. + * @returns {void} + */ + static markUsed(segment) { + if (segment.internal.used) { + return; + } + segment.internal.used = true; + + let i; + + if (segment.reachable) { + for (i = 0; i < segment.allPrevSegments.length; ++i) { + const prevSegment = segment.allPrevSegments[i]; + + prevSegment.allNextSegments.push(segment); + prevSegment.nextSegments.push(segment); + } + } else { + for (i = 0; i < segment.allPrevSegments.length; ++i) { + segment.allPrevSegments[i].allNextSegments.push(segment); + } + } + } + + /** + * Marks a previous segment as looped. + * @param {CodePathSegment} segment A segment. + * @param {CodePathSegment} prevSegment A previous segment to mark. + * @returns {void} + */ + static markPrevSegmentAsLooped(segment, prevSegment) { + segment.internal.loopedPrevSegments.push(prevSegment); + } + + /** + * Replaces unused segments with the previous segments of each unused segment. + * @param {CodePathSegment[]} segments An array of segments to replace. + * @returns {CodePathSegment[]} The replaced array. + */ + static flattenUnusedSegments(segments) { + const done = Object.create(null); + const retv = []; + + for (let i = 0; i < segments.length; ++i) { + const segment = segments[i]; + + // Ignores duplicated. + if (done[segment.id]) { + continue; + } + + // Use previous segments if unused. + if (!segment.internal.used) { + for (let j = 0; j < segment.allPrevSegments.length; ++j) { + const prevSegment = segment.allPrevSegments[j]; + + if (!done[prevSegment.id]) { + done[prevSegment.id] = true; + retv.push(prevSegment); + } + } + } else { + done[segment.id] = true; + retv.push(segment); + } + } + + return retv; + } +} + +module.exports = CodePathSegment; diff --git a/eslint/lib/linter/code-path-analysis/code-path-state.js b/eslint/lib/linter/code-path-analysis/code-path-state.js new file mode 100644 index 0000000..75de1bc --- /dev/null +++ b/eslint/lib/linter/code-path-analysis/code-path-state.js @@ -0,0 +1,1399 @@ +/** + * @fileoverview A class to manage state of generating a code path. + * @author Toru Nagashima + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const CodePathSegment = require("./code-path-segment"), + ForkContext = require("./fork-context"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Adds given segments into the `dest` array. + * If the `others` array does not includes the given segments, adds to the `all` + * array as well. + * + * This adds only reachable and used segments. + * @param {CodePathSegment[]} dest A destination array (`returnedSegments` or `thrownSegments`). + * @param {CodePathSegment[]} others Another destination array (`returnedSegments` or `thrownSegments`). + * @param {CodePathSegment[]} all The unified destination array (`finalSegments`). + * @param {CodePathSegment[]} segments Segments to add. + * @returns {void} + */ +function addToReturnedOrThrown(dest, others, all, segments) { + for (let i = 0; i < segments.length; ++i) { + const segment = segments[i]; + + dest.push(segment); + if (others.indexOf(segment) === -1) { + all.push(segment); + } + } +} + +/** + * Gets a loop-context for a `continue` statement. + * @param {CodePathState} state A state to get. + * @param {string} label The label of a `continue` statement. + * @returns {LoopContext} A loop-context for a `continue` statement. + */ +function getContinueContext(state, label) { + if (!label) { + return state.loopContext; + } + + let context = state.loopContext; + + while (context) { + if (context.label === label) { + return context; + } + context = context.upper; + } + + /* istanbul ignore next: foolproof (syntax error) */ + return null; +} + +/** + * Gets a context for a `break` statement. + * @param {CodePathState} state A state to get. + * @param {string} label The label of a `break` statement. + * @returns {LoopContext|SwitchContext} A context for a `break` statement. + */ +function getBreakContext(state, label) { + let context = state.breakContext; + + while (context) { + if (label ? context.label === label : context.breakable) { + return context; + } + context = context.upper; + } + + /* istanbul ignore next: foolproof (syntax error) */ + return null; +} + +/** + * Gets a context for a `return` statement. + * @param {CodePathState} state A state to get. + * @returns {TryContext|CodePathState} A context for a `return` statement. + */ +function getReturnContext(state) { + let context = state.tryContext; + + while (context) { + if (context.hasFinalizer && context.position !== "finally") { + return context; + } + context = context.upper; + } + + return state; +} + +/** + * Gets a context for a `throw` statement. + * @param {CodePathState} state A state to get. + * @returns {TryContext|CodePathState} A context for a `throw` statement. + */ +function getThrowContext(state) { + let context = state.tryContext; + + while (context) { + if (context.position === "try" || + (context.hasFinalizer && context.position === "catch") + ) { + return context; + } + context = context.upper; + } + + return state; +} + +/** + * Removes a given element from a given array. + * @param {any[]} xs An array to remove the specific element. + * @param {any} x An element to be removed. + * @returns {void} + */ +function remove(xs, x) { + xs.splice(xs.indexOf(x), 1); +} + +/** + * Disconnect given segments. + * + * This is used in a process for switch statements. + * If there is the "default" chunk before other cases, the order is different + * between node's and running's. + * @param {CodePathSegment[]} prevSegments Forward segments to disconnect. + * @param {CodePathSegment[]} nextSegments Backward segments to disconnect. + * @returns {void} + */ +function removeConnection(prevSegments, nextSegments) { + for (let i = 0; i < prevSegments.length; ++i) { + const prevSegment = prevSegments[i]; + const nextSegment = nextSegments[i]; + + remove(prevSegment.nextSegments, nextSegment); + remove(prevSegment.allNextSegments, nextSegment); + remove(nextSegment.prevSegments, prevSegment); + remove(nextSegment.allPrevSegments, prevSegment); + } +} + +/** + * Creates looping path. + * @param {CodePathState} state The instance. + * @param {CodePathSegment[]} unflattenedFromSegments Segments which are source. + * @param {CodePathSegment[]} unflattenedToSegments Segments which are destination. + * @returns {void} + */ +function makeLooped(state, unflattenedFromSegments, unflattenedToSegments) { + const fromSegments = CodePathSegment.flattenUnusedSegments(unflattenedFromSegments); + const toSegments = CodePathSegment.flattenUnusedSegments(unflattenedToSegments); + + const end = Math.min(fromSegments.length, toSegments.length); + + for (let i = 0; i < end; ++i) { + const fromSegment = fromSegments[i]; + const toSegment = toSegments[i]; + + if (toSegment.reachable) { + fromSegment.nextSegments.push(toSegment); + } + if (fromSegment.reachable) { + toSegment.prevSegments.push(fromSegment); + } + fromSegment.allNextSegments.push(toSegment); + toSegment.allPrevSegments.push(fromSegment); + + if (toSegment.allPrevSegments.length >= 2) { + CodePathSegment.markPrevSegmentAsLooped(toSegment, fromSegment); + } + + state.notifyLooped(fromSegment, toSegment); + } +} + +/** + * Finalizes segments of `test` chunk of a ForStatement. + * + * - Adds `false` paths to paths which are leaving from the loop. + * - Sets `true` paths to paths which go to the body. + * @param {LoopContext} context A loop context to modify. + * @param {ChoiceContext} choiceContext A choice context of this loop. + * @param {CodePathSegment[]} head The current head paths. + * @returns {void} + */ +function finalizeTestSegmentsOfFor(context, choiceContext, head) { + if (!choiceContext.processed) { + choiceContext.trueForkContext.add(head); + choiceContext.falseForkContext.add(head); + } + + if (context.test !== true) { + context.brokenForkContext.addAll(choiceContext.falseForkContext); + } + context.endOfTestSegments = choiceContext.trueForkContext.makeNext(0, -1); +} + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +/** + * A class which manages state to analyze code paths. + */ +class CodePathState { + + // eslint-disable-next-line jsdoc/require-description + /** + * @param {IdGenerator} idGenerator An id generator to generate id for code + * path segments. + * @param {Function} onLooped A callback function to notify looping. + */ + constructor(idGenerator, onLooped) { + this.idGenerator = idGenerator; + this.notifyLooped = onLooped; + this.forkContext = ForkContext.newRoot(idGenerator); + this.choiceContext = null; + this.switchContext = null; + this.tryContext = null; + this.loopContext = null; + this.breakContext = null; + + this.currentSegments = []; + this.initialSegment = this.forkContext.head[0]; + + // returnedSegments and thrownSegments push elements into finalSegments also. + const final = this.finalSegments = []; + const returned = this.returnedForkContext = []; + const thrown = this.thrownForkContext = []; + + returned.add = addToReturnedOrThrown.bind(null, returned, thrown, final); + thrown.add = addToReturnedOrThrown.bind(null, thrown, returned, final); + } + + /** + * The head segments. + * @type {CodePathSegment[]} + */ + get headSegments() { + return this.forkContext.head; + } + + /** + * The parent forking context. + * This is used for the root of new forks. + * @type {ForkContext} + */ + get parentForkContext() { + const current = this.forkContext; + + return current && current.upper; + } + + /** + * Creates and stacks new forking context. + * @param {boolean} forkLeavingPath A flag which shows being in a + * "finally" block. + * @returns {ForkContext} The created context. + */ + pushForkContext(forkLeavingPath) { + this.forkContext = ForkContext.newEmpty( + this.forkContext, + forkLeavingPath + ); + + return this.forkContext; + } + + /** + * Pops and merges the last forking context. + * @returns {ForkContext} The last context. + */ + popForkContext() { + const lastContext = this.forkContext; + + this.forkContext = lastContext.upper; + this.forkContext.replaceHead(lastContext.makeNext(0, -1)); + + return lastContext; + } + + /** + * Creates a new path. + * @returns {void} + */ + forkPath() { + this.forkContext.add(this.parentForkContext.makeNext(-1, -1)); + } + + /** + * Creates a bypass path. + * This is used for such as IfStatement which does not have "else" chunk. + * @returns {void} + */ + forkBypassPath() { + this.forkContext.add(this.parentForkContext.head); + } + + //-------------------------------------------------------------------------- + // ConditionalExpression, LogicalExpression, IfStatement + //-------------------------------------------------------------------------- + + /** + * Creates a context for ConditionalExpression, LogicalExpression, + * IfStatement, WhileStatement, DoWhileStatement, or ForStatement. + * + * LogicalExpressions have cases that it goes different paths between the + * `true` case and the `false` case. + * + * For Example: + * + * if (a || b) { + * foo(); + * } else { + * bar(); + * } + * + * In this case, `b` is evaluated always in the code path of the `else` + * block, but it's not so in the code path of the `if` block. + * So there are 3 paths. + * + * a -> foo(); + * a -> b -> foo(); + * a -> b -> bar(); + * @param {string} kind A kind string. + * If the new context is LogicalExpression's, this is `"&&"` or `"||"`. + * If it's IfStatement's or ConditionalExpression's, this is `"test"`. + * Otherwise, this is `"loop"`. + * @param {boolean} isForkingAsResult A flag that shows that goes different + * paths between `true` and `false`. + * @returns {void} + */ + pushChoiceContext(kind, isForkingAsResult) { + this.choiceContext = { + upper: this.choiceContext, + kind, + isForkingAsResult, + trueForkContext: ForkContext.newEmpty(this.forkContext), + falseForkContext: ForkContext.newEmpty(this.forkContext), + processed: false + }; + } + + /** + * Pops the last choice context and finalizes it. + * @returns {ChoiceContext} The popped context. + */ + popChoiceContext() { + const context = this.choiceContext; + + this.choiceContext = context.upper; + + const forkContext = this.forkContext; + const headSegments = forkContext.head; + + switch (context.kind) { + case "&&": + case "||": + + /* + * If any result were not transferred from child contexts, + * this sets the head segments to both cases. + * The head segments are the path of the right-hand operand. + */ + if (!context.processed) { + context.trueForkContext.add(headSegments); + context.falseForkContext.add(headSegments); + } + + /* + * Transfers results to upper context if this context is in + * test chunk. + */ + if (context.isForkingAsResult) { + const parentContext = this.choiceContext; + + parentContext.trueForkContext.addAll(context.trueForkContext); + parentContext.falseForkContext.addAll(context.falseForkContext); + parentContext.processed = true; + + return context; + } + + break; + + case "test": + if (!context.processed) { + + /* + * The head segments are the path of the `if` block here. + * Updates the `true` path with the end of the `if` block. + */ + context.trueForkContext.clear(); + context.trueForkContext.add(headSegments); + } else { + + /* + * The head segments are the path of the `else` block here. + * Updates the `false` path with the end of the `else` + * block. + */ + context.falseForkContext.clear(); + context.falseForkContext.add(headSegments); + } + + break; + + case "loop": + + /* + * Loops are addressed in popLoopContext(). + * This is called from popLoopContext(). + */ + return context; + + /* istanbul ignore next */ + default: + throw new Error("unreachable"); + } + + // Merges all paths. + const prevForkContext = context.trueForkContext; + + prevForkContext.addAll(context.falseForkContext); + forkContext.replaceHead(prevForkContext.makeNext(0, -1)); + + return context; + } + + /** + * Makes a code path segment of the right-hand operand of a logical + * expression. + * @returns {void} + */ + makeLogicalRight() { + const context = this.choiceContext; + const forkContext = this.forkContext; + + if (context.processed) { + + /* + * This got segments already from the child choice context. + * Creates the next path from own true/false fork context. + */ + const prevForkContext = + context.kind === "&&" ? context.trueForkContext + /* kind === "||" */ : context.falseForkContext; + + forkContext.replaceHead(prevForkContext.makeNext(0, -1)); + prevForkContext.clear(); + + context.processed = false; + } else { + + /* + * This did not get segments from the child choice context. + * So addresses the head segments. + * The head segments are the path of the left-hand operand. + */ + if (context.kind === "&&") { + + // The path does short-circuit if false. + context.falseForkContext.add(forkContext.head); + } else { + + // The path does short-circuit if true. + context.trueForkContext.add(forkContext.head); + } + + forkContext.replaceHead(forkContext.makeNext(-1, -1)); + } + } + + /** + * Makes a code path segment of the `if` block. + * @returns {void} + */ + makeIfConsequent() { + const context = this.choiceContext; + const forkContext = this.forkContext; + + /* + * If any result were not transferred from child contexts, + * this sets the head segments to both cases. + * The head segments are the path of the test expression. + */ + if (!context.processed) { + context.trueForkContext.add(forkContext.head); + context.falseForkContext.add(forkContext.head); + } + + context.processed = false; + + // Creates new path from the `true` case. + forkContext.replaceHead( + context.trueForkContext.makeNext(0, -1) + ); + } + + /** + * Makes a code path segment of the `else` block. + * @returns {void} + */ + makeIfAlternate() { + const context = this.choiceContext; + const forkContext = this.forkContext; + + /* + * The head segments are the path of the `if` block. + * Updates the `true` path with the end of the `if` block. + */ + context.trueForkContext.clear(); + context.trueForkContext.add(forkContext.head); + context.processed = true; + + // Creates new path from the `false` case. + forkContext.replaceHead( + context.falseForkContext.makeNext(0, -1) + ); + } + + //-------------------------------------------------------------------------- + // SwitchStatement + //-------------------------------------------------------------------------- + + /** + * Creates a context object of SwitchStatement and stacks it. + * @param {boolean} hasCase `true` if the switch statement has one or more + * case parts. + * @param {string|null} label The label text. + * @returns {void} + */ + pushSwitchContext(hasCase, label) { + this.switchContext = { + upper: this.switchContext, + hasCase, + defaultSegments: null, + defaultBodySegments: null, + foundDefault: false, + lastIsDefault: false, + countForks: 0 + }; + + this.pushBreakContext(true, label); + } + + /** + * Pops the last context of SwitchStatement and finalizes it. + * + * - Disposes all forking stack for `case` and `default`. + * - Creates the next code path segment from `context.brokenForkContext`. + * - If the last `SwitchCase` node is not a `default` part, creates a path + * to the `default` body. + * @returns {void} + */ + popSwitchContext() { + const context = this.switchContext; + + this.switchContext = context.upper; + + const forkContext = this.forkContext; + const brokenForkContext = this.popBreakContext().brokenForkContext; + + if (context.countForks === 0) { + + /* + * When there is only one `default` chunk and there is one or more + * `break` statements, even if forks are nothing, it needs to merge + * those. + */ + if (!brokenForkContext.empty) { + brokenForkContext.add(forkContext.makeNext(-1, -1)); + forkContext.replaceHead(brokenForkContext.makeNext(0, -1)); + } + + return; + } + + const lastSegments = forkContext.head; + + this.forkBypassPath(); + const lastCaseSegments = forkContext.head; + + /* + * `brokenForkContext` is used to make the next segment. + * It must add the last segment into `brokenForkContext`. + */ + brokenForkContext.add(lastSegments); + + /* + * A path which is failed in all case test should be connected to path + * of `default` chunk. + */ + if (!context.lastIsDefault) { + if (context.defaultBodySegments) { + + /* + * Remove a link from `default` label to its chunk. + * It's false route. + */ + removeConnection(context.defaultSegments, context.defaultBodySegments); + makeLooped(this, lastCaseSegments, context.defaultBodySegments); + } else { + + /* + * It handles the last case body as broken if `default` chunk + * does not exist. + */ + brokenForkContext.add(lastCaseSegments); + } + } + + // Pops the segment context stack until the entry segment. + for (let i = 0; i < context.countForks; ++i) { + this.forkContext = this.forkContext.upper; + } + + /* + * Creates a path from all brokenForkContext paths. + * This is a path after switch statement. + */ + this.forkContext.replaceHead(brokenForkContext.makeNext(0, -1)); + } + + /** + * Makes a code path segment for a `SwitchCase` node. + * @param {boolean} isEmpty `true` if the body is empty. + * @param {boolean} isDefault `true` if the body is the default case. + * @returns {void} + */ + makeSwitchCaseBody(isEmpty, isDefault) { + const context = this.switchContext; + + if (!context.hasCase) { + return; + } + + /* + * Merge forks. + * The parent fork context has two segments. + * Those are from the current case and the body of the previous case. + */ + const parentForkContext = this.forkContext; + const forkContext = this.pushForkContext(); + + forkContext.add(parentForkContext.makeNext(0, -1)); + + /* + * Save `default` chunk info. + * If the `default` label is not at the last, we must make a path from + * the last `case` to the `default` chunk. + */ + if (isDefault) { + context.defaultSegments = parentForkContext.head; + if (isEmpty) { + context.foundDefault = true; + } else { + context.defaultBodySegments = forkContext.head; + } + } else { + if (!isEmpty && context.foundDefault) { + context.foundDefault = false; + context.defaultBodySegments = forkContext.head; + } + } + + context.lastIsDefault = isDefault; + context.countForks += 1; + } + + //-------------------------------------------------------------------------- + // TryStatement + //-------------------------------------------------------------------------- + + /** + * Creates a context object of TryStatement and stacks it. + * @param {boolean} hasFinalizer `true` if the try statement has a + * `finally` block. + * @returns {void} + */ + pushTryContext(hasFinalizer) { + this.tryContext = { + upper: this.tryContext, + position: "try", + hasFinalizer, + + returnedForkContext: hasFinalizer + ? ForkContext.newEmpty(this.forkContext) + : null, + + thrownForkContext: ForkContext.newEmpty(this.forkContext), + lastOfTryIsReachable: false, + lastOfCatchIsReachable: false + }; + } + + /** + * Pops the last context of TryStatement and finalizes it. + * @returns {void} + */ + popTryContext() { + const context = this.tryContext; + + this.tryContext = context.upper; + + if (context.position === "catch") { + + // Merges two paths from the `try` block and `catch` block merely. + this.popForkContext(); + return; + } + + /* + * The following process is executed only when there is the `finally` + * block. + */ + + const returned = context.returnedForkContext; + const thrown = context.thrownForkContext; + + if (returned.empty && thrown.empty) { + return; + } + + // Separate head to normal paths and leaving paths. + const headSegments = this.forkContext.head; + + this.forkContext = this.forkContext.upper; + const normalSegments = headSegments.slice(0, headSegments.length / 2 | 0); + const leavingSegments = headSegments.slice(headSegments.length / 2 | 0); + + // Forwards the leaving path to upper contexts. + if (!returned.empty) { + getReturnContext(this).returnedForkContext.add(leavingSegments); + } + if (!thrown.empty) { + getThrowContext(this).thrownForkContext.add(leavingSegments); + } + + // Sets the normal path as the next. + this.forkContext.replaceHead(normalSegments); + + /* + * If both paths of the `try` block and the `catch` block are + * unreachable, the next path becomes unreachable as well. + */ + if (!context.lastOfTryIsReachable && !context.lastOfCatchIsReachable) { + this.forkContext.makeUnreachable(); + } + } + + /** + * Makes a code path segment for a `catch` block. + * @returns {void} + */ + makeCatchBlock() { + const context = this.tryContext; + const forkContext = this.forkContext; + const thrown = context.thrownForkContext; + + // Update state. + context.position = "catch"; + context.thrownForkContext = ForkContext.newEmpty(forkContext); + context.lastOfTryIsReachable = forkContext.reachable; + + // Merge thrown paths. + thrown.add(forkContext.head); + const thrownSegments = thrown.makeNext(0, -1); + + // Fork to a bypass and the merged thrown path. + this.pushForkContext(); + this.forkBypassPath(); + this.forkContext.add(thrownSegments); + } + + /** + * Makes a code path segment for a `finally` block. + * + * In the `finally` block, parallel paths are created. The parallel paths + * are used as leaving-paths. The leaving-paths are paths from `return` + * statements and `throw` statements in a `try` block or a `catch` block. + * @returns {void} + */ + makeFinallyBlock() { + const context = this.tryContext; + let forkContext = this.forkContext; + const returned = context.returnedForkContext; + const thrown = context.thrownForkContext; + const headOfLeavingSegments = forkContext.head; + + // Update state. + if (context.position === "catch") { + + // Merges two paths from the `try` block and `catch` block. + this.popForkContext(); + forkContext = this.forkContext; + + context.lastOfCatchIsReachable = forkContext.reachable; + } else { + context.lastOfTryIsReachable = forkContext.reachable; + } + context.position = "finally"; + + if (returned.empty && thrown.empty) { + + // This path does not leave. + return; + } + + /* + * Create a parallel segment from merging returned and thrown. + * This segment will leave at the end of this finally block. + */ + const segments = forkContext.makeNext(-1, -1); + + for (let i = 0; i < forkContext.count; ++i) { + const prevSegsOfLeavingSegment = [headOfLeavingSegments[i]]; + + for (let j = 0; j < returned.segmentsList.length; ++j) { + prevSegsOfLeavingSegment.push(returned.segmentsList[j][i]); + } + for (let j = 0; j < thrown.segmentsList.length; ++j) { + prevSegsOfLeavingSegment.push(thrown.segmentsList[j][i]); + } + + segments.push( + CodePathSegment.newNext( + this.idGenerator.next(), + prevSegsOfLeavingSegment + ) + ); + } + + this.pushForkContext(true); + this.forkContext.add(segments); + } + + /** + * Makes a code path segment from the first throwable node to the `catch` + * block or the `finally` block. + * @returns {void} + */ + makeFirstThrowablePathInTryBlock() { + const forkContext = this.forkContext; + + if (!forkContext.reachable) { + return; + } + + const context = getThrowContext(this); + + if (context === this || + context.position !== "try" || + !context.thrownForkContext.empty + ) { + return; + } + + context.thrownForkContext.add(forkContext.head); + forkContext.replaceHead(forkContext.makeNext(-1, -1)); + } + + //-------------------------------------------------------------------------- + // Loop Statements + //-------------------------------------------------------------------------- + + /** + * Creates a context object of a loop statement and stacks it. + * @param {string} type The type of the node which was triggered. One of + * `WhileStatement`, `DoWhileStatement`, `ForStatement`, `ForInStatement`, + * and `ForStatement`. + * @param {string|null} label A label of the node which was triggered. + * @returns {void} + */ + pushLoopContext(type, label) { + const forkContext = this.forkContext; + const breakContext = this.pushBreakContext(true, label); + + switch (type) { + case "WhileStatement": + this.pushChoiceContext("loop", false); + this.loopContext = { + upper: this.loopContext, + type, + label, + test: void 0, + continueDestSegments: null, + brokenForkContext: breakContext.brokenForkContext + }; + break; + + case "DoWhileStatement": + this.pushChoiceContext("loop", false); + this.loopContext = { + upper: this.loopContext, + type, + label, + test: void 0, + entrySegments: null, + continueForkContext: ForkContext.newEmpty(forkContext), + brokenForkContext: breakContext.brokenForkContext + }; + break; + + case "ForStatement": + this.pushChoiceContext("loop", false); + this.loopContext = { + upper: this.loopContext, + type, + label, + test: void 0, + endOfInitSegments: null, + testSegments: null, + endOfTestSegments: null, + updateSegments: null, + endOfUpdateSegments: null, + continueDestSegments: null, + brokenForkContext: breakContext.brokenForkContext + }; + break; + + case "ForInStatement": + case "ForOfStatement": + this.loopContext = { + upper: this.loopContext, + type, + label, + prevSegments: null, + leftSegments: null, + endOfLeftSegments: null, + continueDestSegments: null, + brokenForkContext: breakContext.brokenForkContext + }; + break; + + /* istanbul ignore next */ + default: + throw new Error(`unknown type: "${type}"`); + } + } + + /** + * Pops the last context of a loop statement and finalizes it. + * @returns {void} + */ + popLoopContext() { + const context = this.loopContext; + + this.loopContext = context.upper; + + const forkContext = this.forkContext; + const brokenForkContext = this.popBreakContext().brokenForkContext; + + // Creates a looped path. + switch (context.type) { + case "WhileStatement": + case "ForStatement": + this.popChoiceContext(); + makeLooped( + this, + forkContext.head, + context.continueDestSegments + ); + break; + + case "DoWhileStatement": { + const choiceContext = this.popChoiceContext(); + + if (!choiceContext.processed) { + choiceContext.trueForkContext.add(forkContext.head); + choiceContext.falseForkContext.add(forkContext.head); + } + if (context.test !== true) { + brokenForkContext.addAll(choiceContext.falseForkContext); + } + + // `true` paths go to looping. + const segmentsList = choiceContext.trueForkContext.segmentsList; + + for (let i = 0; i < segmentsList.length; ++i) { + makeLooped( + this, + segmentsList[i], + context.entrySegments + ); + } + break; + } + + case "ForInStatement": + case "ForOfStatement": + brokenForkContext.add(forkContext.head); + makeLooped( + this, + forkContext.head, + context.leftSegments + ); + break; + + /* istanbul ignore next */ + default: + throw new Error("unreachable"); + } + + // Go next. + if (brokenForkContext.empty) { + forkContext.replaceHead(forkContext.makeUnreachable(-1, -1)); + } else { + forkContext.replaceHead(brokenForkContext.makeNext(0, -1)); + } + } + + /** + * Makes a code path segment for the test part of a WhileStatement. + * @param {boolean|undefined} test The test value (only when constant). + * @returns {void} + */ + makeWhileTest(test) { + const context = this.loopContext; + const forkContext = this.forkContext; + const testSegments = forkContext.makeNext(0, -1); + + // Update state. + context.test = test; + context.continueDestSegments = testSegments; + forkContext.replaceHead(testSegments); + } + + /** + * Makes a code path segment for the body part of a WhileStatement. + * @returns {void} + */ + makeWhileBody() { + const context = this.loopContext; + const choiceContext = this.choiceContext; + const forkContext = this.forkContext; + + if (!choiceContext.processed) { + choiceContext.trueForkContext.add(forkContext.head); + choiceContext.falseForkContext.add(forkContext.head); + } + + // Update state. + if (context.test !== true) { + context.brokenForkContext.addAll(choiceContext.falseForkContext); + } + forkContext.replaceHead(choiceContext.trueForkContext.makeNext(0, -1)); + } + + /** + * Makes a code path segment for the body part of a DoWhileStatement. + * @returns {void} + */ + makeDoWhileBody() { + const context = this.loopContext; + const forkContext = this.forkContext; + const bodySegments = forkContext.makeNext(-1, -1); + + // Update state. + context.entrySegments = bodySegments; + forkContext.replaceHead(bodySegments); + } + + /** + * Makes a code path segment for the test part of a DoWhileStatement. + * @param {boolean|undefined} test The test value (only when constant). + * @returns {void} + */ + makeDoWhileTest(test) { + const context = this.loopContext; + const forkContext = this.forkContext; + + context.test = test; + + // Creates paths of `continue` statements. + if (!context.continueForkContext.empty) { + context.continueForkContext.add(forkContext.head); + const testSegments = context.continueForkContext.makeNext(0, -1); + + forkContext.replaceHead(testSegments); + } + } + + /** + * Makes a code path segment for the test part of a ForStatement. + * @param {boolean|undefined} test The test value (only when constant). + * @returns {void} + */ + makeForTest(test) { + const context = this.loopContext; + const forkContext = this.forkContext; + const endOfInitSegments = forkContext.head; + const testSegments = forkContext.makeNext(-1, -1); + + // Update state. + context.test = test; + context.endOfInitSegments = endOfInitSegments; + context.continueDestSegments = context.testSegments = testSegments; + forkContext.replaceHead(testSegments); + } + + /** + * Makes a code path segment for the update part of a ForStatement. + * @returns {void} + */ + makeForUpdate() { + const context = this.loopContext; + const choiceContext = this.choiceContext; + const forkContext = this.forkContext; + + // Make the next paths of the test. + if (context.testSegments) { + finalizeTestSegmentsOfFor( + context, + choiceContext, + forkContext.head + ); + } else { + context.endOfInitSegments = forkContext.head; + } + + // Update state. + const updateSegments = forkContext.makeDisconnected(-1, -1); + + context.continueDestSegments = context.updateSegments = updateSegments; + forkContext.replaceHead(updateSegments); + } + + /** + * Makes a code path segment for the body part of a ForStatement. + * @returns {void} + */ + makeForBody() { + const context = this.loopContext; + const choiceContext = this.choiceContext; + const forkContext = this.forkContext; + + // Update state. + if (context.updateSegments) { + context.endOfUpdateSegments = forkContext.head; + + // `update` -> `test` + if (context.testSegments) { + makeLooped( + this, + context.endOfUpdateSegments, + context.testSegments + ); + } + } else if (context.testSegments) { + finalizeTestSegmentsOfFor( + context, + choiceContext, + forkContext.head + ); + } else { + context.endOfInitSegments = forkContext.head; + } + + let bodySegments = context.endOfTestSegments; + + if (!bodySegments) { + + /* + * If there is not the `test` part, the `body` path comes from the + * `init` part and the `update` part. + */ + const prevForkContext = ForkContext.newEmpty(forkContext); + + prevForkContext.add(context.endOfInitSegments); + if (context.endOfUpdateSegments) { + prevForkContext.add(context.endOfUpdateSegments); + } + + bodySegments = prevForkContext.makeNext(0, -1); + } + context.continueDestSegments = context.continueDestSegments || bodySegments; + forkContext.replaceHead(bodySegments); + } + + /** + * Makes a code path segment for the left part of a ForInStatement and a + * ForOfStatement. + * @returns {void} + */ + makeForInOfLeft() { + const context = this.loopContext; + const forkContext = this.forkContext; + const leftSegments = forkContext.makeDisconnected(-1, -1); + + // Update state. + context.prevSegments = forkContext.head; + context.leftSegments = context.continueDestSegments = leftSegments; + forkContext.replaceHead(leftSegments); + } + + /** + * Makes a code path segment for the right part of a ForInStatement and a + * ForOfStatement. + * @returns {void} + */ + makeForInOfRight() { + const context = this.loopContext; + const forkContext = this.forkContext; + const temp = ForkContext.newEmpty(forkContext); + + temp.add(context.prevSegments); + const rightSegments = temp.makeNext(-1, -1); + + // Update state. + context.endOfLeftSegments = forkContext.head; + forkContext.replaceHead(rightSegments); + } + + /** + * Makes a code path segment for the body part of a ForInStatement and a + * ForOfStatement. + * @returns {void} + */ + makeForInOfBody() { + const context = this.loopContext; + const forkContext = this.forkContext; + const temp = ForkContext.newEmpty(forkContext); + + temp.add(context.endOfLeftSegments); + const bodySegments = temp.makeNext(-1, -1); + + // Make a path: `right` -> `left`. + makeLooped(this, forkContext.head, context.leftSegments); + + // Update state. + context.brokenForkContext.add(forkContext.head); + forkContext.replaceHead(bodySegments); + } + + //-------------------------------------------------------------------------- + // Control Statements + //-------------------------------------------------------------------------- + + /** + * Creates new context for BreakStatement. + * @param {boolean} breakable The flag to indicate it can break by + * an unlabeled BreakStatement. + * @param {string|null} label The label of this context. + * @returns {Object} The new context. + */ + pushBreakContext(breakable, label) { + this.breakContext = { + upper: this.breakContext, + breakable, + label, + brokenForkContext: ForkContext.newEmpty(this.forkContext) + }; + return this.breakContext; + } + + /** + * Removes the top item of the break context stack. + * @returns {Object} The removed context. + */ + popBreakContext() { + const context = this.breakContext; + const forkContext = this.forkContext; + + this.breakContext = context.upper; + + // Process this context here for other than switches and loops. + if (!context.breakable) { + const brokenForkContext = context.brokenForkContext; + + if (!brokenForkContext.empty) { + brokenForkContext.add(forkContext.head); + forkContext.replaceHead(brokenForkContext.makeNext(0, -1)); + } + } + + return context; + } + + /** + * Makes a path for a `break` statement. + * + * It registers the head segment to a context of `break`. + * It makes new unreachable segment, then it set the head with the segment. + * @param {string} label A label of the break statement. + * @returns {void} + */ + makeBreak(label) { + const forkContext = this.forkContext; + + if (!forkContext.reachable) { + return; + } + + const context = getBreakContext(this, label); + + /* istanbul ignore else: foolproof (syntax error) */ + if (context) { + context.brokenForkContext.add(forkContext.head); + } + + forkContext.replaceHead(forkContext.makeUnreachable(-1, -1)); + } + + /** + * Makes a path for a `continue` statement. + * + * It makes a looping path. + * It makes new unreachable segment, then it set the head with the segment. + * @param {string} label A label of the continue statement. + * @returns {void} + */ + makeContinue(label) { + const forkContext = this.forkContext; + + if (!forkContext.reachable) { + return; + } + + const context = getContinueContext(this, label); + + /* istanbul ignore else: foolproof (syntax error) */ + if (context) { + if (context.continueDestSegments) { + makeLooped(this, forkContext.head, context.continueDestSegments); + + // If the context is a for-in/of loop, this effects a break also. + if (context.type === "ForInStatement" || + context.type === "ForOfStatement" + ) { + context.brokenForkContext.add(forkContext.head); + } + } else { + context.continueForkContext.add(forkContext.head); + } + } + forkContext.replaceHead(forkContext.makeUnreachable(-1, -1)); + } + + /** + * Makes a path for a `return` statement. + * + * It registers the head segment to a context of `return`. + * It makes new unreachable segment, then it set the head with the segment. + * @returns {void} + */ + makeReturn() { + const forkContext = this.forkContext; + + if (forkContext.reachable) { + getReturnContext(this).returnedForkContext.add(forkContext.head); + forkContext.replaceHead(forkContext.makeUnreachable(-1, -1)); + } + } + + /** + * Makes a path for a `throw` statement. + * + * It registers the head segment to a context of `throw`. + * It makes new unreachable segment, then it set the head with the segment. + * @returns {void} + */ + makeThrow() { + const forkContext = this.forkContext; + + if (forkContext.reachable) { + getThrowContext(this).thrownForkContext.add(forkContext.head); + forkContext.replaceHead(forkContext.makeUnreachable(-1, -1)); + } + } + + /** + * Makes the final path. + * @returns {void} + */ + makeFinal() { + const segments = this.currentSegments; + + if (segments.length > 0 && segments[0].reachable) { + this.returnedForkContext.add(segments); + } + } +} + +module.exports = CodePathState; diff --git a/eslint/lib/linter/code-path-analysis/code-path.js b/eslint/lib/linter/code-path-analysis/code-path.js new file mode 100644 index 0000000..49b37c6 --- /dev/null +++ b/eslint/lib/linter/code-path-analysis/code-path.js @@ -0,0 +1,238 @@ +/** + * @fileoverview A class of the code path. + * @author Toru Nagashima + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const CodePathState = require("./code-path-state"); +const IdGenerator = require("./id-generator"); + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +/** + * A code path. + */ +class CodePath { + + // eslint-disable-next-line jsdoc/require-description + /** + * @param {string} id An identifier. + * @param {CodePath|null} upper The code path of the upper function scope. + * @param {Function} onLooped A callback function to notify looping. + */ + constructor(id, upper, onLooped) { + + /** + * The identifier of this code path. + * Rules use it to store additional information of each rule. + * @type {string} + */ + this.id = id; + + /** + * The code path of the upper function scope. + * @type {CodePath|null} + */ + this.upper = upper; + + /** + * The code paths of nested function scopes. + * @type {CodePath[]} + */ + this.childCodePaths = []; + + // Initializes internal state. + Object.defineProperty( + this, + "internal", + { value: new CodePathState(new IdGenerator(`${id}_`), onLooped) } + ); + + // Adds this into `childCodePaths` of `upper`. + if (upper) { + upper.childCodePaths.push(this); + } + } + + /** + * Gets the state of a given code path. + * @param {CodePath} codePath A code path to get. + * @returns {CodePathState} The state of the code path. + */ + static getState(codePath) { + return codePath.internal; + } + + /** + * The initial code path segment. + * @type {CodePathSegment} + */ + get initialSegment() { + return this.internal.initialSegment; + } + + /** + * Final code path segments. + * This array is a mix of `returnedSegments` and `thrownSegments`. + * @type {CodePathSegment[]} + */ + get finalSegments() { + return this.internal.finalSegments; + } + + /** + * Final code path segments which is with `return` statements. + * This array contains the last path segment if it's reachable. + * Since the reachable last path returns `undefined`. + * @type {CodePathSegment[]} + */ + get returnedSegments() { + return this.internal.returnedForkContext; + } + + /** + * Final code path segments which is with `throw` statements. + * @type {CodePathSegment[]} + */ + get thrownSegments() { + return this.internal.thrownForkContext; + } + + /** + * Current code path segments. + * @type {CodePathSegment[]} + */ + get currentSegments() { + return this.internal.currentSegments; + } + + /** + * Traverses all segments in this code path. + * + * codePath.traverseSegments(function(segment, controller) { + * // do something. + * }); + * + * This method enumerates segments in order from the head. + * + * The `controller` object has two methods. + * + * - `controller.skip()` - Skip the following segments in this branch. + * - `controller.break()` - Skip all following segments. + * @param {Object} [options] Omittable. + * @param {CodePathSegment} [options.first] The first segment to traverse. + * @param {CodePathSegment} [options.last] The last segment to traverse. + * @param {Function} callback A callback function. + * @returns {void} + */ + traverseSegments(options, callback) { + let resolvedOptions; + let resolvedCallback; + + if (typeof options === "function") { + resolvedCallback = options; + resolvedOptions = {}; + } else { + resolvedOptions = options || {}; + resolvedCallback = callback; + } + + const startSegment = resolvedOptions.first || this.internal.initialSegment; + const lastSegment = resolvedOptions.last; + + let item = null; + let index = 0; + let end = 0; + let segment = null; + const visited = Object.create(null); + const stack = [[startSegment, 0]]; + let skippedSegment = null; + let broken = false; + const controller = { + skip() { + if (stack.length <= 1) { + broken = true; + } else { + skippedSegment = stack[stack.length - 2][0]; + } + }, + break() { + broken = true; + } + }; + + /** + * Checks a given previous segment has been visited. + * @param {CodePathSegment} prevSegment A previous segment to check. + * @returns {boolean} `true` if the segment has been visited. + */ + function isVisited(prevSegment) { + return ( + visited[prevSegment.id] || + segment.isLoopedPrevSegment(prevSegment) + ); + } + + while (stack.length > 0) { + item = stack[stack.length - 1]; + segment = item[0]; + index = item[1]; + + if (index === 0) { + + // Skip if this segment has been visited already. + if (visited[segment.id]) { + stack.pop(); + continue; + } + + // Skip if all previous segments have not been visited. + if (segment !== startSegment && + segment.prevSegments.length > 0 && + !segment.prevSegments.every(isVisited) + ) { + stack.pop(); + continue; + } + + // Reset the flag of skipping if all branches have been skipped. + if (skippedSegment && segment.prevSegments.indexOf(skippedSegment) !== -1) { + skippedSegment = null; + } + visited[segment.id] = true; + + // Call the callback when the first time. + if (!skippedSegment) { + resolvedCallback.call(this, segment, controller); + if (segment === lastSegment) { + controller.skip(); + } + if (broken) { + break; + } + } + } + + // Update the stack. + end = segment.nextSegments.length - 1; + if (index < end) { + item[1] += 1; + stack.push([segment.nextSegments[index], 0]); + } else if (index === end) { + item[0] = segment.nextSegments[index]; + item[1] = 0; + } else { + stack.pop(); + } + } + } +} + +module.exports = CodePath; diff --git a/eslint/lib/linter/code-path-analysis/debug-helpers.js b/eslint/lib/linter/code-path-analysis/debug-helpers.js new file mode 100644 index 0000000..bde4e0a --- /dev/null +++ b/eslint/lib/linter/code-path-analysis/debug-helpers.js @@ -0,0 +1,196 @@ +/** + * @fileoverview Helpers to debug for code path analysis. + * @author Toru Nagashima + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const debug = require("debug")("eslint:code-path"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Gets id of a given segment. + * @param {CodePathSegment} segment A segment to get. + * @returns {string} Id of the segment. + */ +/* istanbul ignore next */ +function getId(segment) { // eslint-disable-line jsdoc/require-jsdoc + return segment.id + (segment.reachable ? "" : "!"); +} + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = { + + /** + * A flag that debug dumping is enabled or not. + * @type {boolean} + */ + enabled: debug.enabled, + + /** + * Dumps given objects. + * @param {...any} args objects to dump. + * @returns {void} + */ + dump: debug, + + /** + * Dumps the current analyzing state. + * @param {ASTNode} node A node to dump. + * @param {CodePathState} state A state to dump. + * @param {boolean} leaving A flag whether or not it's leaving + * @returns {void} + */ + dumpState: !debug.enabled ? debug : /* istanbul ignore next */ function(node, state, leaving) { + for (let i = 0; i < state.currentSegments.length; ++i) { + const segInternal = state.currentSegments[i].internal; + + if (leaving) { + segInternal.exitNodes.push(node); + } else { + segInternal.nodes.push(node); + } + } + + debug([ + `${state.currentSegments.map(getId).join(",")})`, + `${node.type}${leaving ? ":exit" : ""}` + ].join(" ")); + }, + + /** + * Dumps a DOT code of a given code path. + * The DOT code can be visualized with Graphvis. + * @param {CodePath} codePath A code path to dump. + * @returns {void} + * @see http://www.graphviz.org + * @see http://www.webgraphviz.com + */ + dumpDot: !debug.enabled ? debug : /* istanbul ignore next */ function(codePath) { + let text = + "\n" + + "digraph {\n" + + "node[shape=box,style=\"rounded,filled\",fillcolor=white];\n" + + "initial[label=\"\",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];\n"; + + if (codePath.returnedSegments.length > 0) { + text += "final[label=\"\",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];\n"; + } + if (codePath.thrownSegments.length > 0) { + text += "thrown[label=\"✘\",shape=circle,width=0.3,height=0.3,fixedsize];\n"; + } + + const traceMap = Object.create(null); + const arrows = this.makeDotArrows(codePath, traceMap); + + for (const id in traceMap) { // eslint-disable-line guard-for-in + const segment = traceMap[id]; + + text += `${id}[`; + + if (segment.reachable) { + text += "label=\""; + } else { + text += "style=\"rounded,dashed,filled\",fillcolor=\"#FF9800\",label=\"<>\\n"; + } + + if (segment.internal.nodes.length > 0 || segment.internal.exitNodes.length > 0) { + text += [].concat( + segment.internal.nodes.map(node => { + switch (node.type) { + case "Identifier": return `${node.type} (${node.name})`; + case "Literal": return `${node.type} (${node.value})`; + default: return node.type; + } + }), + segment.internal.exitNodes.map(node => { + switch (node.type) { + case "Identifier": return `${node.type}:exit (${node.name})`; + case "Literal": return `${node.type}:exit (${node.value})`; + default: return `${node.type}:exit`; + } + }) + ).join("\\n"); + } else { + text += "????"; + } + + text += "\"];\n"; + } + + text += `${arrows}\n`; + text += "}"; + debug("DOT", text); + }, + + /** + * Makes a DOT code of a given code path. + * The DOT code can be visualized with Graphvis. + * @param {CodePath} codePath A code path to make DOT. + * @param {Object} traceMap Optional. A map to check whether or not segments had been done. + * @returns {string} A DOT code of the code path. + */ + makeDotArrows(codePath, traceMap) { + const stack = [[codePath.initialSegment, 0]]; + const done = traceMap || Object.create(null); + let lastId = codePath.initialSegment.id; + let text = `initial->${codePath.initialSegment.id}`; + + while (stack.length > 0) { + const item = stack.pop(); + const segment = item[0]; + const index = item[1]; + + if (done[segment.id] && index === 0) { + continue; + } + done[segment.id] = segment; + + const nextSegment = segment.allNextSegments[index]; + + if (!nextSegment) { + continue; + } + + if (lastId === segment.id) { + text += `->${nextSegment.id}`; + } else { + text += `;\n${segment.id}->${nextSegment.id}`; + } + lastId = nextSegment.id; + + stack.unshift([segment, 1 + index]); + stack.push([nextSegment, 0]); + } + + codePath.returnedSegments.forEach(finalSegment => { + if (lastId === finalSegment.id) { + text += "->final"; + } else { + text += `;\n${finalSegment.id}->final`; + } + lastId = null; + }); + + codePath.thrownSegments.forEach(finalSegment => { + if (lastId === finalSegment.id) { + text += "->thrown"; + } else { + text += `;\n${finalSegment.id}->thrown`; + } + lastId = null; + }); + + return `${text};`; + } +}; diff --git a/eslint/lib/linter/code-path-analysis/fork-context.js b/eslint/lib/linter/code-path-analysis/fork-context.js new file mode 100644 index 0000000..2e872b5 --- /dev/null +++ b/eslint/lib/linter/code-path-analysis/fork-context.js @@ -0,0 +1,249 @@ +/** + * @fileoverview A class to operate forking. + * + * This is state of forking. + * This has a fork list and manages it. + * + * @author Toru Nagashima + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const assert = require("assert"), + CodePathSegment = require("./code-path-segment"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Gets whether or not a given segment is reachable. + * @param {CodePathSegment} segment A segment to get. + * @returns {boolean} `true` if the segment is reachable. + */ +function isReachable(segment) { + return segment.reachable; +} + +/** + * Creates new segments from the specific range of `context.segmentsList`. + * + * When `context.segmentsList` is `[[a, b], [c, d], [e, f]]`, `begin` is `0`, and + * `end` is `-1`, this creates `[g, h]`. This `g` is from `a`, `c`, and `e`. + * This `h` is from `b`, `d`, and `f`. + * @param {ForkContext} context An instance. + * @param {number} begin The first index of the previous segments. + * @param {number} end The last index of the previous segments. + * @param {Function} create A factory function of new segments. + * @returns {CodePathSegment[]} New segments. + */ +function makeSegments(context, begin, end, create) { + const list = context.segmentsList; + + const normalizedBegin = begin >= 0 ? begin : list.length + begin; + const normalizedEnd = end >= 0 ? end : list.length + end; + + const segments = []; + + for (let i = 0; i < context.count; ++i) { + const allPrevSegments = []; + + for (let j = normalizedBegin; j <= normalizedEnd; ++j) { + allPrevSegments.push(list[j][i]); + } + + segments.push(create(context.idGenerator.next(), allPrevSegments)); + } + + return segments; +} + +/** + * `segments` becomes doubly in a `finally` block. Then if a code path exits by a + * control statement (such as `break`, `continue`) from the `finally` block, the + * destination's segments may be half of the source segments. In that case, this + * merges segments. + * @param {ForkContext} context An instance. + * @param {CodePathSegment[]} segments Segments to merge. + * @returns {CodePathSegment[]} The merged segments. + */ +function mergeExtraSegments(context, segments) { + let currentSegments = segments; + + while (currentSegments.length > context.count) { + const merged = []; + + for (let i = 0, length = currentSegments.length / 2 | 0; i < length; ++i) { + merged.push(CodePathSegment.newNext( + context.idGenerator.next(), + [currentSegments[i], currentSegments[i + length]] + )); + } + currentSegments = merged; + } + return currentSegments; +} + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +/** + * A class to manage forking. + */ +class ForkContext { + + // eslint-disable-next-line jsdoc/require-description + /** + * @param {IdGenerator} idGenerator An identifier generator for segments. + * @param {ForkContext|null} upper An upper fork context. + * @param {number} count A number of parallel segments. + */ + constructor(idGenerator, upper, count) { + this.idGenerator = idGenerator; + this.upper = upper; + this.count = count; + this.segmentsList = []; + } + + /** + * The head segments. + * @type {CodePathSegment[]} + */ + get head() { + const list = this.segmentsList; + + return list.length === 0 ? [] : list[list.length - 1]; + } + + /** + * A flag which shows empty. + * @type {boolean} + */ + get empty() { + return this.segmentsList.length === 0; + } + + /** + * A flag which shows reachable. + * @type {boolean} + */ + get reachable() { + const segments = this.head; + + return segments.length > 0 && segments.some(isReachable); + } + + /** + * Creates new segments from this context. + * @param {number} begin The first index of previous segments. + * @param {number} end The last index of previous segments. + * @returns {CodePathSegment[]} New segments. + */ + makeNext(begin, end) { + return makeSegments(this, begin, end, CodePathSegment.newNext); + } + + /** + * Creates new segments from this context. + * The new segments is always unreachable. + * @param {number} begin The first index of previous segments. + * @param {number} end The last index of previous segments. + * @returns {CodePathSegment[]} New segments. + */ + makeUnreachable(begin, end) { + return makeSegments(this, begin, end, CodePathSegment.newUnreachable); + } + + /** + * Creates new segments from this context. + * The new segments don't have connections for previous segments. + * But these inherit the reachable flag from this context. + * @param {number} begin The first index of previous segments. + * @param {number} end The last index of previous segments. + * @returns {CodePathSegment[]} New segments. + */ + makeDisconnected(begin, end) { + return makeSegments(this, begin, end, CodePathSegment.newDisconnected); + } + + /** + * Adds segments into this context. + * The added segments become the head. + * @param {CodePathSegment[]} segments Segments to add. + * @returns {void} + */ + add(segments) { + assert(segments.length >= this.count, `${segments.length} >= ${this.count}`); + + this.segmentsList.push(mergeExtraSegments(this, segments)); + } + + /** + * Replaces the head segments with given segments. + * The current head segments are removed. + * @param {CodePathSegment[]} segments Segments to add. + * @returns {void} + */ + replaceHead(segments) { + assert(segments.length >= this.count, `${segments.length} >= ${this.count}`); + + this.segmentsList.splice(-1, 1, mergeExtraSegments(this, segments)); + } + + /** + * Adds all segments of a given fork context into this context. + * @param {ForkContext} context A fork context to add. + * @returns {void} + */ + addAll(context) { + assert(context.count === this.count); + + const source = context.segmentsList; + + for (let i = 0; i < source.length; ++i) { + this.segmentsList.push(source[i]); + } + } + + /** + * Clears all segments in this context. + * @returns {void} + */ + clear() { + this.segmentsList = []; + } + + /** + * Creates the root fork context. + * @param {IdGenerator} idGenerator An identifier generator for segments. + * @returns {ForkContext} New fork context. + */ + static newRoot(idGenerator) { + const context = new ForkContext(idGenerator, null, 1); + + context.add([CodePathSegment.newRoot(idGenerator.next())]); + + return context; + } + + /** + * Creates an empty fork context preceded by a given context. + * @param {ForkContext} parentContext The parent fork context. + * @param {boolean} forkLeavingPath A flag which shows inside of `finally` block. + * @returns {ForkContext} New fork context. + */ + static newEmpty(parentContext, forkLeavingPath) { + return new ForkContext( + parentContext.idGenerator, + parentContext, + (forkLeavingPath ? 2 : 1) * parentContext.count + ); + } +} + +module.exports = ForkContext; diff --git a/eslint/lib/linter/code-path-analysis/id-generator.js b/eslint/lib/linter/code-path-analysis/id-generator.js new file mode 100644 index 0000000..4cb2e0e --- /dev/null +++ b/eslint/lib/linter/code-path-analysis/id-generator.js @@ -0,0 +1,46 @@ +/** + * @fileoverview A class of identifiers generator for code path segments. + * + * Each rule uses the identifier of code path segments to store additional + * information of the code path. + * + * @author Toru Nagashima + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +/** + * A generator for unique ids. + */ +class IdGenerator { + + // eslint-disable-next-line jsdoc/require-description + /** + * @param {string} prefix Optional. A prefix of generated ids. + */ + constructor(prefix) { + this.prefix = String(prefix); + this.n = 0; + } + + /** + * Generates id. + * @returns {string} A generated id. + */ + next() { + this.n = 1 + this.n | 0; + + /* istanbul ignore if */ + if (this.n < 0) { + this.n = 1; + } + + return this.prefix + this.n; + } +} + +module.exports = IdGenerator; diff --git a/eslint/lib/linter/config-comment-parser.js b/eslint/lib/linter/config-comment-parser.js new file mode 100644 index 0000000..3586231 --- /dev/null +++ b/eslint/lib/linter/config-comment-parser.js @@ -0,0 +1,141 @@ +/** + * @fileoverview Config Comment Parser + * @author Nicholas C. Zakas + */ + +/* eslint-disable class-methods-use-this*/ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const levn = require("levn"), + ConfigOps = require("../shared/config-ops"); + +const debug = require("debug")("eslint:config-comment-parser"); + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +/** + * Object to parse ESLint configuration comments inside JavaScript files. + * @name ConfigCommentParser + */ +module.exports = class ConfigCommentParser { + + /** + * Parses a list of "name:string_value" or/and "name" options divided by comma or + * whitespace. Used for "global" and "exported" comments. + * @param {string} string The string to parse. + * @param {Comment} comment The comment node which has the string. + * @returns {Object} Result map object of names and string values, or null values if no value was provided + */ + parseStringConfig(string, comment) { + debug("Parsing String config"); + + const items = {}; + + // Collapse whitespace around `:` and `,` to make parsing easier + const trimmedString = string.replace(/\s*([:,])\s*/gu, "$1"); + + trimmedString.split(/\s|,+/u).forEach(name => { + if (!name) { + return; + } + + // value defaults to null (if not provided), e.g: "foo" => ["foo", null] + const [key, value = null] = name.split(":"); + + items[key] = { value, comment }; + }); + return items; + } + + /** + * Parses a JSON-like config. + * @param {string} string The string to parse. + * @param {Object} location Start line and column of comments for potential error message. + * @returns {({success: true, config: Object}|{success: false, error: Problem})} Result map object + */ + parseJsonConfig(string, location) { + debug("Parsing JSON config"); + + let items = {}; + + // Parses a JSON-like comment by the same way as parsing CLI option. + try { + items = levn.parse("Object", string) || {}; + + // Some tests say that it should ignore invalid comments such as `/*eslint no-alert:abc*/`. + // Also, commaless notations have invalid severity: + // "no-alert: 2 no-console: 2" --> {"no-alert": "2 no-console: 2"} + // Should ignore that case as well. + if (ConfigOps.isEverySeverityValid(items)) { + return { + success: true, + config: items + }; + } + } catch (ex) { + + debug("Levn parsing failed; falling back to manual parsing."); + + // ignore to parse the string by a fallback. + } + + /* + * Optionator cannot parse commaless notations. + * But we are supporting that. So this is a fallback for that. + */ + items = {}; + const normalizedString = string.replace(/([-a-zA-Z0-9/]+):/gu, "\"$1\":").replace(/(\]|[0-9])\s+(?=")/u, "$1,"); + + try { + items = JSON.parse(`{${normalizedString}}`); + } catch (ex) { + debug("Manual parsing failed."); + + return { + success: false, + error: { + ruleId: null, + fatal: true, + severity: 2, + message: `Failed to parse JSON from '${normalizedString}': ${ex.message}`, + line: location.start.line, + column: location.start.column + 1 + } + }; + + } + + return { + success: true, + config: items + }; + } + + /** + * Parses a config of values separated by comma. + * @param {string} string The string to parse. + * @returns {Object} Result map of values and true values + */ + parseListConfig(string) { + debug("Parsing list config"); + + const items = {}; + + // Collapse whitespace around commas + string.replace(/\s*,\s*/gu, ",").split(/,+/u).forEach(name => { + const trimmedName = name.trim(); + + if (trimmedName) { + items[trimmedName] = true; + } + }); + return items; + } + +}; diff --git a/eslint/lib/linter/index.js b/eslint/lib/linter/index.js new file mode 100644 index 0000000..25fd769 --- /dev/null +++ b/eslint/lib/linter/index.js @@ -0,0 +1,13 @@ +"use strict"; + +const { Linter } = require("./linter"); +const interpolate = require("./interpolate"); +const SourceCodeFixer = require("./source-code-fixer"); + +module.exports = { + Linter, + + // For testers. + SourceCodeFixer, + interpolate +}; diff --git a/eslint/lib/linter/interpolate.js b/eslint/lib/linter/interpolate.js new file mode 100644 index 0000000..87e06a0 --- /dev/null +++ b/eslint/lib/linter/interpolate.js @@ -0,0 +1,28 @@ +/** + * @fileoverview Interpolate keys from an object into a string with {{ }} markers. + * @author Jed Fox + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = (text, data) => { + if (!data) { + return text; + } + + // Substitution content for any {{ }} markers. + return text.replace(/\{\{([^{}]+?)\}\}/gu, (fullMatch, termWithWhitespace) => { + const term = termWithWhitespace.trim(); + + if (term in data) { + return data[term]; + } + + // Preserve old behavior: If parameter name not provided, don't replace it. + return fullMatch; + }); +}; diff --git a/eslint/lib/linter/linter.js b/eslint/lib/linter/linter.js new file mode 100644 index 0000000..1d021d1 --- /dev/null +++ b/eslint/lib/linter/linter.js @@ -0,0 +1,1463 @@ +/** + * @fileoverview Main Linter Class + * @author Gyandeep Singh + * @author aladdin-add + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const + path = require("path"), + eslintScope = require("eslint-scope"), + evk = require("eslint-visitor-keys"), + espree = require("espree"), + lodash = require("lodash"), + BuiltInEnvironments = require("../../conf/environments"), + pkg = require("../../package.json"), + astUtils = require("../shared/ast-utils"), + ConfigOps = require("../shared/config-ops"), + validator = require("../shared/config-validator"), + Traverser = require("../shared/traverser"), + { SourceCode } = require("../source-code"), + CodePathAnalyzer = require("./code-path-analysis/code-path-analyzer"), + applyDisableDirectives = require("./apply-disable-directives"), + ConfigCommentParser = require("./config-comment-parser"), + NodeEventGenerator = require("./node-event-generator"), + createReportTranslator = require("./report-translator"), + Rules = require("./rules"), + createEmitter = require("./safe-emitter"), + SourceCodeFixer = require("./source-code-fixer"), + timing = require("./timing"), + ruleReplacements = require("../../conf/replacements.json"); + +const debug = require("debug")("eslint:linter"); +const MAX_AUTOFIX_PASSES = 10; +const DEFAULT_PARSER_NAME = "espree"; +const commentParser = new ConfigCommentParser(); +const DEFAULT_ERROR_LOC = { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } }; + +//------------------------------------------------------------------------------ +// Typedefs +//------------------------------------------------------------------------------ + +/** @typedef {InstanceType} ConfigArray */ +/** @typedef {InstanceType} ExtractedConfig */ +/** @typedef {import("../shared/types").ConfigData} ConfigData */ +/** @typedef {import("../shared/types").Environment} Environment */ +/** @typedef {import("../shared/types").GlobalConf} GlobalConf */ +/** @typedef {import("../shared/types").LintMessage} LintMessage */ +/** @typedef {import("../shared/types").ParserOptions} ParserOptions */ +/** @typedef {import("../shared/types").Processor} Processor */ +/** @typedef {import("../shared/types").Rule} Rule */ + +/** + * @template T + * @typedef {{ [P in keyof T]-?: T[P] }} Required + */ + +/** + * @typedef {Object} DisableDirective + * @property {("disable"|"enable"|"disable-line"|"disable-next-line")} type + * @property {number} line + * @property {number} column + * @property {(string|null)} ruleId + */ + +/** + * The private data for `Linter` instance. + * @typedef {Object} LinterInternalSlots + * @property {ConfigArray|null} lastConfigArray The `ConfigArray` instance that the last `verify()` call used. + * @property {SourceCode|null} lastSourceCode The `SourceCode` instance that the last `verify()` call used. + * @property {Map} parserMap The loaded parsers. + * @property {Rules} ruleMap The loaded rules. + */ + +/** + * @typedef {Object} VerifyOptions + * @property {boolean} [allowInlineConfig] Allow/disallow inline comments' ability + * to change config once it is set. Defaults to true if not supplied. + * Useful if you want to validate JS without comments overriding rules. + * @property {boolean} [disableFixes] if `true` then the linter doesn't make `fix` + * properties into the lint result. + * @property {string} [filename] the filename of the source code. + * @property {boolean | "off" | "warn" | "error"} [reportUnusedDisableDirectives] Adds reported errors for + * unused `eslint-disable` directives. + */ + +/** + * @typedef {Object} ProcessorOptions + * @property {(filename:string, text:string) => boolean} [filterCodeBlock] the + * predicate function that selects adopt code blocks. + * @property {Processor["postprocess"]} [postprocess] postprocessor for report + * messages. If provided, this should accept an array of the message lists + * for each code block returned from the preprocessor, apply a mapping to + * the messages as appropriate, and return a one-dimensional array of + * messages. + * @property {Processor["preprocess"]} [preprocess] preprocessor for source text. + * If provided, this should accept a string of source text, and return an + * array of code blocks to lint. + */ + +/** + * @typedef {Object} FixOptions + * @property {boolean | ((message: LintMessage) => boolean)} [fix] Determines + * whether fixes should be applied. + */ + +/** + * @typedef {Object} InternalOptions + * @property {string | null} warnInlineConfig The config name what `noInlineConfig` setting came from. If `noInlineConfig` setting didn't exist, this is null. If this is a config name, then the linter warns directive comments. + * @property {"off" | "warn" | "error"} reportUnusedDisableDirectives (boolean values were normalized) + */ + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Ensures that variables representing built-in properties of the Global Object, + * and any globals declared by special block comments, are present in the global + * scope. + * @param {Scope} globalScope The global scope. + * @param {Object} configGlobals The globals declared in configuration + * @param {{exportedVariables: Object, enabledGlobals: Object}} commentDirectives Directives from comment configuration + * @returns {void} + */ +function addDeclaredGlobals(globalScope, configGlobals, { exportedVariables, enabledGlobals }) { + + // Define configured global variables. + for (const id of new Set([...Object.keys(configGlobals), ...Object.keys(enabledGlobals)])) { + + /* + * `ConfigOps.normalizeConfigGlobal` will throw an error if a configured global value is invalid. However, these errors would + * typically be caught when validating a config anyway (validity for inline global comments is checked separately). + */ + const configValue = configGlobals[id] === void 0 ? void 0 : ConfigOps.normalizeConfigGlobal(configGlobals[id]); + const commentValue = enabledGlobals[id] && enabledGlobals[id].value; + const value = commentValue || configValue; + const sourceComments = enabledGlobals[id] && enabledGlobals[id].comments; + + if (value === "off") { + continue; + } + + let variable = globalScope.set.get(id); + + if (!variable) { + variable = new eslintScope.Variable(id, globalScope); + + globalScope.variables.push(variable); + globalScope.set.set(id, variable); + } + + variable.eslintImplicitGlobalSetting = configValue; + variable.eslintExplicitGlobal = sourceComments !== void 0; + variable.eslintExplicitGlobalComments = sourceComments; + variable.writeable = (value === "writable"); + } + + // mark all exported variables as such + Object.keys(exportedVariables).forEach(name => { + const variable = globalScope.set.get(name); + + if (variable) { + variable.eslintUsed = true; + } + }); + + /* + * "through" contains all references which definitions cannot be found. + * Since we augment the global scope using configuration, we need to update + * references and remove the ones that were added by configuration. + */ + globalScope.through = globalScope.through.filter(reference => { + const name = reference.identifier.name; + const variable = globalScope.set.get(name); + + if (variable) { + + /* + * Links the variable and the reference. + * And this reference is removed from `Scope#through`. + */ + reference.resolved = variable; + variable.references.push(reference); + + return false; + } + + return true; + }); +} + +/** + * creates a missing-rule message. + * @param {string} ruleId the ruleId to create + * @returns {string} created error message + * @private + */ +function createMissingRuleMessage(ruleId) { + return Object.prototype.hasOwnProperty.call(ruleReplacements.rules, ruleId) + ? `Rule '${ruleId}' was removed and replaced by: ${ruleReplacements.rules[ruleId].join(", ")}` + : `Definition for rule '${ruleId}' was not found.`; +} + +/** + * creates a linting problem + * @param {Object} options to create linting error + * @param {string} [options.ruleId] the ruleId to report + * @param {Object} [options.loc] the loc to report + * @param {string} [options.message] the error message to report + * @param {string} [options.severity] the error message to report + * @returns {LintMessage} created problem, returns a missing-rule problem if only provided ruleId. + * @private + */ +function createLintingProblem(options) { + const { + ruleId = null, + loc = DEFAULT_ERROR_LOC, + message = createMissingRuleMessage(options.ruleId), + severity = 2 + } = options; + + return { + ruleId, + message, + line: loc.start.line, + column: loc.start.column + 1, + endLine: loc.end.line, + endColumn: loc.end.column + 1, + severity, + nodeType: null + }; +} + +/** + * Creates a collection of disable directives from a comment + * @param {Object} options to create disable directives + * @param {("disable"|"enable"|"disable-line"|"disable-next-line")} options.type The type of directive comment + * @param {{line: number, column: number}} options.loc The 0-based location of the comment token + * @param {string} options.value The value after the directive in the comment + * comment specified no specific rules, so it applies to all rules (e.g. `eslint-disable`) + * @param {function(string): {create: Function}} options.ruleMapper A map from rule IDs to defined rules + * @returns {Object} Directives and problems from the comment + */ +function createDisableDirectives(options) { + const { type, loc, value, ruleMapper } = options; + const ruleIds = Object.keys(commentParser.parseListConfig(value)); + const directiveRules = ruleIds.length ? ruleIds : [null]; + const result = { + directives: [], // valid disable directives + directiveProblems: [] // problems in directives + }; + + for (const ruleId of directiveRules) { + + // push to directives, if the rule is defined(including null, e.g. /*eslint enable*/) + if (ruleId === null || ruleMapper(ruleId) !== null) { + result.directives.push({ type, line: loc.start.line, column: loc.start.column + 1, ruleId }); + } else { + result.directiveProblems.push(createLintingProblem({ ruleId, loc })); + } + } + return result; +} + +/** + * Remove the ignored part from a given directive comment and trim it. + * @param {string} value The comment text to strip. + * @returns {string} The stripped text. + */ +function stripDirectiveComment(value) { + return value.split(/\s-{2,}\s/u)[0].trim(); +} + +/** + * Parses comments in file to extract file-specific config of rules, globals + * and environments and merges them with global config; also code blocks + * where reporting is disabled or enabled and merges them with reporting config. + * @param {string} filename The file being checked. + * @param {ASTNode} ast The top node of the AST. + * @param {function(string): {create: Function}} ruleMapper A map from rule IDs to defined rules + * @param {string|null} warnInlineConfig If a string then it should warn directive comments as disabled. The string value is the config name what the setting came from. + * @returns {{configuredRules: Object, enabledGlobals: {value:string,comment:Token}[], exportedVariables: Object, problems: Problem[], disableDirectives: DisableDirective[]}} + * A collection of the directive comments that were found, along with any problems that occurred when parsing + */ +function getDirectiveComments(filename, ast, ruleMapper, warnInlineConfig) { + const configuredRules = {}; + const enabledGlobals = Object.create(null); + const exportedVariables = {}; + const problems = []; + const disableDirectives = []; + + ast.comments.filter(token => token.type !== "Shebang").forEach(comment => { + const trimmedCommentText = stripDirectiveComment(comment.value); + const match = /^(eslint(?:-env|-enable|-disable(?:(?:-next)?-line)?)?|exported|globals?)(?:\s|$)/u.exec(trimmedCommentText); + + if (!match) { + return; + } + const directiveText = match[1]; + const lineCommentSupported = /^eslint-disable-(next-)?line$/u.test(directiveText); + + if (comment.type === "Line" && !lineCommentSupported) { + return; + } + + if (warnInlineConfig) { + const kind = comment.type === "Block" ? `/*${directiveText}*/` : `//${directiveText}`; + + problems.push(createLintingProblem({ + ruleId: null, + message: `'${kind}' has no effect because you have 'noInlineConfig' setting in ${warnInlineConfig}.`, + loc: comment.loc, + severity: 1 + })); + return; + } + + if (lineCommentSupported && comment.loc.start.line !== comment.loc.end.line) { + const message = `${directiveText} comment should not span multiple lines.`; + + problems.push(createLintingProblem({ + ruleId: null, + message, + loc: comment.loc + })); + return; + } + + const directiveValue = trimmedCommentText.slice(match.index + directiveText.length); + + switch (directiveText) { + case "eslint-disable": + case "eslint-enable": + case "eslint-disable-next-line": + case "eslint-disable-line": { + const directiveType = directiveText.slice("eslint-".length); + const options = { type: directiveType, loc: comment.loc, value: directiveValue, ruleMapper }; + const { directives, directiveProblems } = createDisableDirectives(options); + + disableDirectives.push(...directives); + problems.push(...directiveProblems); + break; + } + + case "exported": + Object.assign(exportedVariables, commentParser.parseStringConfig(directiveValue, comment)); + break; + + case "globals": + case "global": + for (const [id, { value }] of Object.entries(commentParser.parseStringConfig(directiveValue, comment))) { + let normalizedValue; + + try { + normalizedValue = ConfigOps.normalizeConfigGlobal(value); + } catch (err) { + problems.push(createLintingProblem({ + ruleId: null, + loc: comment.loc, + message: err.message + })); + continue; + } + + if (enabledGlobals[id]) { + enabledGlobals[id].comments.push(comment); + enabledGlobals[id].value = normalizedValue; + } else { + enabledGlobals[id] = { + comments: [comment], + value: normalizedValue + }; + } + } + break; + + case "eslint": { + const parseResult = commentParser.parseJsonConfig(directiveValue, comment.loc); + + if (parseResult.success) { + Object.keys(parseResult.config).forEach(name => { + const rule = ruleMapper(name); + const ruleValue = parseResult.config[name]; + + if (rule === null) { + problems.push(createLintingProblem({ ruleId: name, loc: comment.loc })); + return; + } + + try { + validator.validateRuleOptions(rule, name, ruleValue); + } catch (err) { + problems.push(createLintingProblem({ + ruleId: name, + message: err.message, + loc: comment.loc + })); + + // do not apply the config, if found invalid options. + return; + } + + configuredRules[name] = ruleValue; + }); + } else { + problems.push(parseResult.error); + } + + break; + } + + // no default + } + }); + + return { + configuredRules, + enabledGlobals, + exportedVariables, + problems, + disableDirectives + }; +} + +/** + * Normalize ECMAScript version from the initial config + * @param {number} ecmaVersion ECMAScript version from the initial config + * @returns {number} normalized ECMAScript version + */ +function normalizeEcmaVersion(ecmaVersion) { + + /* + * Calculate ECMAScript edition number from official year version starting with + * ES2015, which corresponds with ES6 (or a difference of 2009). + */ + return ecmaVersion >= 2015 ? ecmaVersion - 2009 : ecmaVersion; +} + +const eslintEnvPattern = /\/\*\s*eslint-env\s(.+?)\*\//gu; + +/** + * Checks whether or not there is a comment which has "eslint-env *" in a given text. + * @param {string} text A source code text to check. + * @returns {Object|null} A result of parseListConfig() with "eslint-env *" comment. + */ +function findEslintEnv(text) { + let match, retv; + + eslintEnvPattern.lastIndex = 0; + + while ((match = eslintEnvPattern.exec(text)) !== null) { + retv = Object.assign( + retv || {}, + commentParser.parseListConfig(stripDirectiveComment(match[1])) + ); + } + + return retv; +} + +/** + * Convert "/path/to/" to "". + * `CLIEngine#executeOnText()` method gives "/path/to/" if the filename + * was omitted because `configArray.extractConfig()` requires an absolute path. + * But the linter should pass `` to `RuleContext#getFilename()` in that + * case. + * Also, code blocks can have their virtual filename. If the parent filename was + * ``, the virtual filename is `/0_foo.js` or something like (i.e., + * it's not an absolute path). + * @param {string} filename The filename to normalize. + * @returns {string} The normalized filename. + */ +function normalizeFilename(filename) { + const parts = filename.split(path.sep); + const index = parts.lastIndexOf(""); + + return index === -1 ? filename : parts.slice(index).join(path.sep); +} + +/** + * Normalizes the possible options for `linter.verify` and `linter.verifyAndFix` to a + * consistent shape. + * @param {VerifyOptions} providedOptions Options + * @param {ConfigData} config Config. + * @returns {Required & InternalOptions} Normalized options + */ +function normalizeVerifyOptions(providedOptions, config) { + const disableInlineConfig = config.noInlineConfig === true; + const ignoreInlineConfig = providedOptions.allowInlineConfig === false; + const configNameOfNoInlineConfig = config.configNameOfNoInlineConfig + ? ` (${config.configNameOfNoInlineConfig})` + : ""; + + let reportUnusedDisableDirectives = providedOptions.reportUnusedDisableDirectives; + + if (typeof reportUnusedDisableDirectives === "boolean") { + reportUnusedDisableDirectives = reportUnusedDisableDirectives ? "error" : "off"; + } + if (typeof reportUnusedDisableDirectives !== "string") { + reportUnusedDisableDirectives = config.reportUnusedDisableDirectives ? "warn" : "off"; + } + + return { + filename: normalizeFilename(providedOptions.filename || ""), + allowInlineConfig: !ignoreInlineConfig, + warnInlineConfig: disableInlineConfig && !ignoreInlineConfig + ? `your config${configNameOfNoInlineConfig}` + : null, + reportUnusedDisableDirectives, + disableFixes: Boolean(providedOptions.disableFixes) + }; +} + +/** + * Combines the provided parserOptions with the options from environments + * @param {string} parserName The parser name which uses this options. + * @param {ParserOptions} providedOptions The provided 'parserOptions' key in a config + * @param {Environment[]} enabledEnvironments The environments enabled in configuration and with inline comments + * @returns {ParserOptions} Resulting parser options after merge + */ +function resolveParserOptions(parserName, providedOptions, enabledEnvironments) { + const parserOptionsFromEnv = enabledEnvironments + .filter(env => env.parserOptions) + .reduce((parserOptions, env) => lodash.merge(parserOptions, env.parserOptions), {}); + const mergedParserOptions = lodash.merge(parserOptionsFromEnv, providedOptions || {}); + const isModule = mergedParserOptions.sourceType === "module"; + + if (isModule) { + + /* + * can't have global return inside of modules + * TODO: espree validate parserOptions.globalReturn when sourceType is setting to module.(@aladdin-add) + */ + mergedParserOptions.ecmaFeatures = Object.assign({}, mergedParserOptions.ecmaFeatures, { globalReturn: false }); + } + + /* + * TODO: @aladdin-add + * 1. for a 3rd-party parser, do not normalize parserOptions + * 2. for espree, no need to do this (espree will do it) + */ + mergedParserOptions.ecmaVersion = normalizeEcmaVersion(mergedParserOptions.ecmaVersion); + + return mergedParserOptions; +} + +/** + * Combines the provided globals object with the globals from environments + * @param {Record} providedGlobals The 'globals' key in a config + * @param {Environment[]} enabledEnvironments The environments enabled in configuration and with inline comments + * @returns {Record} The resolved globals object + */ +function resolveGlobals(providedGlobals, enabledEnvironments) { + return Object.assign( + {}, + ...enabledEnvironments.filter(env => env.globals).map(env => env.globals), + providedGlobals + ); +} + +/** + * Strips Unicode BOM from a given text. + * @param {string} text A text to strip. + * @returns {string} The stripped text. + */ +function stripUnicodeBOM(text) { + + /* + * Check Unicode BOM. + * In JavaScript, string data is stored as UTF-16, so BOM is 0xFEFF. + * http://www.ecma-international.org/ecma-262/6.0/#sec-unicode-format-control-characters + */ + if (text.charCodeAt(0) === 0xFEFF) { + return text.slice(1); + } + return text; +} + +/** + * Get the options for a rule (not including severity), if any + * @param {Array|number} ruleConfig rule configuration + * @returns {Array} of rule options, empty Array if none + */ +function getRuleOptions(ruleConfig) { + if (Array.isArray(ruleConfig)) { + return ruleConfig.slice(1); + } + return []; + +} + +/** + * Analyze scope of the given AST. + * @param {ASTNode} ast The `Program` node to analyze. + * @param {ParserOptions} parserOptions The parser options. + * @param {Record} visitorKeys The visitor keys. + * @returns {ScopeManager} The analysis result. + */ +function analyzeScope(ast, parserOptions, visitorKeys) { + const ecmaFeatures = parserOptions.ecmaFeatures || {}; + const ecmaVersion = parserOptions.ecmaVersion || 5; + + return eslintScope.analyze(ast, { + ignoreEval: true, + nodejsScope: ecmaFeatures.globalReturn, + impliedStrict: ecmaFeatures.impliedStrict, + ecmaVersion, + sourceType: parserOptions.sourceType || "script", + childVisitorKeys: visitorKeys || evk.KEYS, + fallback: Traverser.getKeys + }); +} + +/** + * Parses text into an AST. Moved out here because the try-catch prevents + * optimization of functions, so it's best to keep the try-catch as isolated + * as possible + * @param {string} text The text to parse. + * @param {Parser} parser The parser to parse. + * @param {ParserOptions} providedParserOptions Options to pass to the parser + * @param {string} filePath The path to the file being parsed. + * @returns {{success: false, error: Problem}|{success: true, sourceCode: SourceCode}} + * An object containing the AST and parser services if parsing was successful, or the error if parsing failed + * @private + */ +function parse(text, parser, providedParserOptions, filePath) { + const textToParse = stripUnicodeBOM(text).replace(astUtils.shebangPattern, (match, captured) => `//${captured}`); + const parserOptions = Object.assign({}, providedParserOptions, { + loc: true, + range: true, + raw: true, + tokens: true, + comment: true, + eslintVisitorKeys: true, + eslintScopeManager: true, + filePath + }); + + /* + * Check for parsing errors first. If there's a parsing error, nothing + * else can happen. However, a parsing error does not throw an error + * from this method - it's just considered a fatal error message, a + * problem that ESLint identified just like any other. + */ + try { + const parseResult = (typeof parser.parseForESLint === "function") + ? parser.parseForESLint(textToParse, parserOptions) + : { ast: parser.parse(textToParse, parserOptions) }; + const ast = parseResult.ast; + const parserServices = parseResult.services || {}; + const visitorKeys = parseResult.visitorKeys || evk.KEYS; + const scopeManager = parseResult.scopeManager || analyzeScope(ast, parserOptions, visitorKeys); + + return { + success: true, + + /* + * Save all values that `parseForESLint()` returned. + * If a `SourceCode` object is given as the first parameter instead of source code text, + * linter skips the parsing process and reuses the source code object. + * In that case, linter needs all the values that `parseForESLint()` returned. + */ + sourceCode: new SourceCode({ + text, + ast, + parserServices, + scopeManager, + visitorKeys + }) + }; + } catch (ex) { + + // If the message includes a leading line number, strip it: + const message = `Parsing error: ${ex.message.replace(/^line \d+:/iu, "").trim()}`; + + debug("%s\n%s", message, ex.stack); + + return { + success: false, + error: { + ruleId: null, + fatal: true, + severity: 2, + message, + line: ex.lineNumber, + column: ex.column + } + }; + } +} + +/** + * Gets the scope for the current node + * @param {ScopeManager} scopeManager The scope manager for this AST + * @param {ASTNode} currentNode The node to get the scope of + * @returns {eslint-scope.Scope} The scope information for this node + */ +function getScope(scopeManager, currentNode) { + + // On Program node, get the outermost scope to avoid return Node.js special function scope or ES modules scope. + const inner = currentNode.type !== "Program"; + + for (let node = currentNode; node; node = node.parent) { + const scope = scopeManager.acquire(node, inner); + + if (scope) { + if (scope.type === "function-expression-name") { + return scope.childScopes[0]; + } + return scope; + } + } + + return scopeManager.scopes[0]; +} + +/** + * Marks a variable as used in the current scope + * @param {ScopeManager} scopeManager The scope manager for this AST. The scope may be mutated by this function. + * @param {ASTNode} currentNode The node currently being traversed + * @param {Object} parserOptions The options used to parse this text + * @param {string} name The name of the variable that should be marked as used. + * @returns {boolean} True if the variable was found and marked as used, false if not. + */ +function markVariableAsUsed(scopeManager, currentNode, parserOptions, name) { + const hasGlobalReturn = parserOptions.ecmaFeatures && parserOptions.ecmaFeatures.globalReturn; + const specialScope = hasGlobalReturn || parserOptions.sourceType === "module"; + const currentScope = getScope(scopeManager, currentNode); + + // Special Node.js scope means we need to start one level deeper + const initialScope = currentScope.type === "global" && specialScope ? currentScope.childScopes[0] : currentScope; + + for (let scope = initialScope; scope; scope = scope.upper) { + const variable = scope.variables.find(scopeVar => scopeVar.name === name); + + if (variable) { + variable.eslintUsed = true; + return true; + } + } + + return false; +} + +/** + * Runs a rule, and gets its listeners + * @param {Rule} rule A normalized rule with a `create` method + * @param {Context} ruleContext The context that should be passed to the rule + * @returns {Object} A map of selector listeners provided by the rule + */ +function createRuleListeners(rule, ruleContext) { + try { + return rule.create(ruleContext); + } catch (ex) { + ex.message = `Error while loading rule '${ruleContext.id}': ${ex.message}`; + throw ex; + } +} + +/** + * Gets all the ancestors of a given node + * @param {ASTNode} node The node + * @returns {ASTNode[]} All the ancestor nodes in the AST, not including the provided node, starting + * from the root node and going inwards to the parent node. + */ +function getAncestors(node) { + const ancestorsStartingAtParent = []; + + for (let ancestor = node.parent; ancestor; ancestor = ancestor.parent) { + ancestorsStartingAtParent.push(ancestor); + } + + return ancestorsStartingAtParent.reverse(); +} + +// methods that exist on SourceCode object +const DEPRECATED_SOURCECODE_PASSTHROUGHS = { + getSource: "getText", + getSourceLines: "getLines", + getAllComments: "getAllComments", + getNodeByRangeIndex: "getNodeByRangeIndex", + getComments: "getComments", + getCommentsBefore: "getCommentsBefore", + getCommentsAfter: "getCommentsAfter", + getCommentsInside: "getCommentsInside", + getJSDocComment: "getJSDocComment", + getFirstToken: "getFirstToken", + getFirstTokens: "getFirstTokens", + getLastToken: "getLastToken", + getLastTokens: "getLastTokens", + getTokenAfter: "getTokenAfter", + getTokenBefore: "getTokenBefore", + getTokenByRangeStart: "getTokenByRangeStart", + getTokens: "getTokens", + getTokensAfter: "getTokensAfter", + getTokensBefore: "getTokensBefore", + getTokensBetween: "getTokensBetween" +}; + +const BASE_TRAVERSAL_CONTEXT = Object.freeze( + Object.keys(DEPRECATED_SOURCECODE_PASSTHROUGHS).reduce( + (contextInfo, methodName) => + Object.assign(contextInfo, { + [methodName](...args) { + return this.getSourceCode()[DEPRECATED_SOURCECODE_PASSTHROUGHS[methodName]](...args); + } + }), + {} + ) +); + +/** + * Runs the given rules on the given SourceCode object + * @param {SourceCode} sourceCode A SourceCode object for the given text + * @param {Object} configuredRules The rules configuration + * @param {function(string): Rule} ruleMapper A mapper function from rule names to rules + * @param {Object} parserOptions The options that were passed to the parser + * @param {string} parserName The name of the parser in the config + * @param {Object} settings The settings that were enabled in the config + * @param {string} filename The reported filename of the code + * @param {boolean} disableFixes If true, it doesn't make `fix` properties. + * @param {string | undefined} cwd cwd of the cli + * @returns {Problem[]} An array of reported problems + */ +function runRules(sourceCode, configuredRules, ruleMapper, parserOptions, parserName, settings, filename, disableFixes, cwd) { + const emitter = createEmitter(); + const nodeQueue = []; + let currentNode = sourceCode.ast; + + Traverser.traverse(sourceCode.ast, { + enter(node, parent) { + node.parent = parent; + nodeQueue.push({ isEntering: true, node }); + }, + leave(node) { + nodeQueue.push({ isEntering: false, node }); + }, + visitorKeys: sourceCode.visitorKeys + }); + + /* + * Create a frozen object with the ruleContext properties and methods that are shared by all rules. + * All rule contexts will inherit from this object. This avoids the performance penalty of copying all the + * properties once for each rule. + */ + const sharedTraversalContext = Object.freeze( + Object.assign( + Object.create(BASE_TRAVERSAL_CONTEXT), + { + getAncestors: () => getAncestors(currentNode), + getDeclaredVariables: sourceCode.scopeManager.getDeclaredVariables.bind(sourceCode.scopeManager), + getCwd: () => cwd, + getFilename: () => filename, + getScope: () => getScope(sourceCode.scopeManager, currentNode), + getSourceCode: () => sourceCode, + markVariableAsUsed: name => markVariableAsUsed(sourceCode.scopeManager, currentNode, parserOptions, name), + parserOptions, + parserPath: parserName, + parserServices: sourceCode.parserServices, + settings + } + ) + ); + + + const lintingProblems = []; + + Object.keys(configuredRules).forEach(ruleId => { + const severity = ConfigOps.getRuleSeverity(configuredRules[ruleId]); + + // not load disabled rules + if (severity === 0) { + return; + } + + const rule = ruleMapper(ruleId); + + if (rule === null) { + lintingProblems.push(createLintingProblem({ ruleId })); + return; + } + + const messageIds = rule.meta && rule.meta.messages; + let reportTranslator = null; + const ruleContext = Object.freeze( + Object.assign( + Object.create(sharedTraversalContext), + { + id: ruleId, + options: getRuleOptions(configuredRules[ruleId]), + report(...args) { + + /* + * Create a report translator lazily. + * In a vast majority of cases, any given rule reports zero errors on a given + * piece of code. Creating a translator lazily avoids the performance cost of + * creating a new translator function for each rule that usually doesn't get + * called. + * + * Using lazy report translators improves end-to-end performance by about 3% + * with Node 8.4.0. + */ + if (reportTranslator === null) { + reportTranslator = createReportTranslator({ + ruleId, + severity, + sourceCode, + messageIds, + disableFixes + }); + } + const problem = reportTranslator(...args); + + if (problem.fix && rule.meta && !rule.meta.fixable) { + throw new Error("Fixable rules should export a `meta.fixable` property."); + } + lintingProblems.push(problem); + } + } + ) + ); + + const ruleListeners = createRuleListeners(rule, ruleContext); + + // add all the selectors from the rule as listeners + Object.keys(ruleListeners).forEach(selector => { + emitter.on( + selector, + timing.enabled + ? timing.time(ruleId, ruleListeners[selector]) + : ruleListeners[selector] + ); + }); + }); + + const eventGenerator = new CodePathAnalyzer(new NodeEventGenerator(emitter)); + + nodeQueue.forEach(traversalInfo => { + currentNode = traversalInfo.node; + + try { + if (traversalInfo.isEntering) { + eventGenerator.enterNode(currentNode); + } else { + eventGenerator.leaveNode(currentNode); + } + } catch (err) { + err.currentNode = currentNode; + throw err; + } + }); + + return lintingProblems; +} + +/** + * Ensure the source code to be a string. + * @param {string|SourceCode} textOrSourceCode The text or source code object. + * @returns {string} The source code text. + */ +function ensureText(textOrSourceCode) { + if (typeof textOrSourceCode === "object") { + const { hasBOM, text } = textOrSourceCode; + const bom = hasBOM ? "\uFEFF" : ""; + + return bom + text; + } + + return String(textOrSourceCode); +} + +/** + * Get an environment. + * @param {LinterInternalSlots} slots The internal slots of Linter. + * @param {string} envId The environment ID to get. + * @returns {Environment|null} The environment. + */ +function getEnv(slots, envId) { + return ( + (slots.lastConfigArray && slots.lastConfigArray.pluginEnvironments.get(envId)) || + BuiltInEnvironments.get(envId) || + null + ); +} + +/** + * Get a rule. + * @param {LinterInternalSlots} slots The internal slots of Linter. + * @param {string} ruleId The rule ID to get. + * @returns {Rule|null} The rule. + */ +function getRule(slots, ruleId) { + return ( + (slots.lastConfigArray && slots.lastConfigArray.pluginRules.get(ruleId)) || + slots.ruleMap.get(ruleId) + ); +} + +/** + * Normalize the value of the cwd + * @param {string | undefined} cwd raw value of the cwd, path to a directory that should be considered as the current working directory, can be undefined. + * @returns {string | undefined} normalized cwd + */ +function normalizeCwd(cwd) { + if (cwd) { + return cwd; + } + if (typeof process === "object") { + return process.cwd(); + } + + // It's more explicit to assign the undefined + // eslint-disable-next-line no-undefined + return undefined; +} + +/** + * The map to store private data. + * @type {WeakMap} + */ +const internalSlotsMap = new WeakMap(); + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +/** + * Object that is responsible for verifying JavaScript text + * @name eslint + */ +class Linter { + + /** + * Initialize the Linter. + * @param {Object} [config] the config object + * @param {string} [config.cwd] path to a directory that should be considered as the current working directory, can be undefined. + */ + constructor({ cwd } = {}) { + internalSlotsMap.set(this, { + cwd: normalizeCwd(cwd), + lastConfigArray: null, + lastSourceCode: null, + parserMap: new Map([["espree", espree]]), + ruleMap: new Rules() + }); + + this.version = pkg.version; + } + + /** + * Getter for package version. + * @static + * @returns {string} The version from package.json. + */ + static get version() { + return pkg.version; + } + + /** + * Same as linter.verify, except without support for processors. + * @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object. + * @param {ConfigData} providedConfig An ESLintConfig instance to configure everything. + * @param {VerifyOptions} [providedOptions] The optional filename of the file being checked. + * @returns {LintMessage[]} The results as an array of messages or an empty array if no messages. + */ + _verifyWithoutProcessors(textOrSourceCode, providedConfig, providedOptions) { + const slots = internalSlotsMap.get(this); + const config = providedConfig || {}; + const options = normalizeVerifyOptions(providedOptions, config); + let text; + + // evaluate arguments + if (typeof textOrSourceCode === "string") { + slots.lastSourceCode = null; + text = textOrSourceCode; + } else { + slots.lastSourceCode = textOrSourceCode; + text = textOrSourceCode.text; + } + + // Resolve parser. + let parserName = DEFAULT_PARSER_NAME; + let parser = espree; + + if (typeof config.parser === "object" && config.parser !== null) { + parserName = config.parser.filePath; + parser = config.parser.definition; + } else if (typeof config.parser === "string") { + if (!slots.parserMap.has(config.parser)) { + return [{ + ruleId: null, + fatal: true, + severity: 2, + message: `Configured parser '${config.parser}' was not found.`, + line: 0, + column: 0 + }]; + } + parserName = config.parser; + parser = slots.parserMap.get(config.parser); + } + + // search and apply "eslint-env *". + const envInFile = options.allowInlineConfig && !options.warnInlineConfig + ? findEslintEnv(text) + : {}; + const resolvedEnvConfig = Object.assign({ builtin: true }, config.env, envInFile); + const enabledEnvs = Object.keys(resolvedEnvConfig) + .filter(envName => resolvedEnvConfig[envName]) + .map(envName => getEnv(slots, envName)) + .filter(env => env); + + const parserOptions = resolveParserOptions(parserName, config.parserOptions || {}, enabledEnvs); + const configuredGlobals = resolveGlobals(config.globals || {}, enabledEnvs); + const settings = config.settings || {}; + + if (!slots.lastSourceCode) { + const parseResult = parse( + text, + parser, + parserOptions, + options.filename + ); + + if (!parseResult.success) { + return [parseResult.error]; + } + + slots.lastSourceCode = parseResult.sourceCode; + } else { + + /* + * If the given source code object as the first argument does not have scopeManager, analyze the scope. + * This is for backward compatibility (SourceCode is frozen so it cannot rebind). + */ + if (!slots.lastSourceCode.scopeManager) { + slots.lastSourceCode = new SourceCode({ + text: slots.lastSourceCode.text, + ast: slots.lastSourceCode.ast, + parserServices: slots.lastSourceCode.parserServices, + visitorKeys: slots.lastSourceCode.visitorKeys, + scopeManager: analyzeScope(slots.lastSourceCode.ast, parserOptions) + }); + } + } + + const sourceCode = slots.lastSourceCode; + const commentDirectives = options.allowInlineConfig + ? getDirectiveComments(options.filename, sourceCode.ast, ruleId => getRule(slots, ruleId), options.warnInlineConfig) + : { configuredRules: {}, enabledGlobals: {}, exportedVariables: {}, problems: [], disableDirectives: [] }; + + // augment global scope with declared global variables + addDeclaredGlobals( + sourceCode.scopeManager.scopes[0], + configuredGlobals, + { exportedVariables: commentDirectives.exportedVariables, enabledGlobals: commentDirectives.enabledGlobals } + ); + + const configuredRules = Object.assign({}, config.rules, commentDirectives.configuredRules); + + let lintingProblems; + + try { + lintingProblems = runRules( + sourceCode, + configuredRules, + ruleId => getRule(slots, ruleId), + parserOptions, + parserName, + settings, + options.filename, + options.disableFixes, + slots.cwd + ); + } catch (err) { + err.message += `\nOccurred while linting ${options.filename}`; + debug("An error occurred while traversing"); + debug("Filename:", options.filename); + if (err.currentNode) { + const { line } = err.currentNode.loc.start; + + debug("Line:", line); + err.message += `:${line}`; + } + debug("Parser Options:", parserOptions); + debug("Parser Path:", parserName); + debug("Settings:", settings); + throw err; + } + + return applyDisableDirectives({ + directives: commentDirectives.disableDirectives, + problems: lintingProblems + .concat(commentDirectives.problems) + .sort((problemA, problemB) => problemA.line - problemB.line || problemA.column - problemB.column), + reportUnusedDisableDirectives: options.reportUnusedDisableDirectives + }); + } + + /** + * Verifies the text against the rules specified by the second argument. + * @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object. + * @param {ConfigData|ConfigArray} config An ESLintConfig instance to configure everything. + * @param {(string|(VerifyOptions&ProcessorOptions))} [filenameOrOptions] The optional filename of the file being checked. + * If this is not set, the filename will default to '' in the rule context. If + * an object, then it has "filename", "allowInlineConfig", and some properties. + * @returns {LintMessage[]} The results as an array of messages or an empty array if no messages. + */ + verify(textOrSourceCode, config, filenameOrOptions) { + debug("Verify"); + const options = typeof filenameOrOptions === "string" + ? { filename: filenameOrOptions } + : filenameOrOptions || {}; + + // CLIEngine passes a `ConfigArray` object. + if (config && typeof config.extractConfig === "function") { + return this._verifyWithConfigArray(textOrSourceCode, config, options); + } + + /* + * `Linter` doesn't support `overrides` property in configuration. + * So we cannot apply multiple processors. + */ + if (options.preprocess || options.postprocess) { + return this._verifyWithProcessor(textOrSourceCode, config, options); + } + return this._verifyWithoutProcessors(textOrSourceCode, config, options); + } + + /** + * Verify a given code with `ConfigArray`. + * @param {string|SourceCode} textOrSourceCode The source code. + * @param {ConfigArray} configArray The config array. + * @param {VerifyOptions&ProcessorOptions} options The options. + * @returns {LintMessage[]} The found problems. + */ + _verifyWithConfigArray(textOrSourceCode, configArray, options) { + debug("With ConfigArray: %s", options.filename); + + // Store the config array in order to get plugin envs and rules later. + internalSlotsMap.get(this).lastConfigArray = configArray; + + // Extract the final config for this file. + const config = configArray.extractConfig(options.filename); + const processor = + config.processor && + configArray.pluginProcessors.get(config.processor); + + // Verify. + if (processor) { + debug("Apply the processor: %o", config.processor); + const { preprocess, postprocess, supportsAutofix } = processor; + const disableFixes = options.disableFixes || !supportsAutofix; + + return this._verifyWithProcessor( + textOrSourceCode, + config, + { ...options, disableFixes, postprocess, preprocess }, + configArray + ); + } + return this._verifyWithoutProcessors(textOrSourceCode, config, options); + } + + /** + * Verify with a processor. + * @param {string|SourceCode} textOrSourceCode The source code. + * @param {ConfigData|ExtractedConfig} config The config array. + * @param {VerifyOptions&ProcessorOptions} options The options. + * @param {ConfigArray} [configForRecursive] The `ConfigArray` object to apply multiple processors recursively. + * @returns {LintMessage[]} The found problems. + */ + _verifyWithProcessor(textOrSourceCode, config, options, configForRecursive) { + const filename = options.filename || ""; + const filenameToExpose = normalizeFilename(filename); + const text = ensureText(textOrSourceCode); + const preprocess = options.preprocess || (rawText => [rawText]); + const postprocess = options.postprocess || lodash.flatten; + const filterCodeBlock = + options.filterCodeBlock || + (blockFilename => blockFilename.endsWith(".js")); + const originalExtname = path.extname(filename); + const messageLists = preprocess(text, filenameToExpose).map((block, i) => { + debug("A code block was found: %o", block.filename || "(unnamed)"); + + // Keep the legacy behavior. + if (typeof block === "string") { + return this._verifyWithoutProcessors(block, config, options); + } + + const blockText = block.text; + const blockName = path.join(filename, `${i}_${block.filename}`); + + // Skip this block if filtered. + if (!filterCodeBlock(blockName, blockText)) { + debug("This code block was skipped."); + return []; + } + + // Resolve configuration again if the file extension was changed. + if (configForRecursive && path.extname(blockName) !== originalExtname) { + debug("Resolving configuration again because the file extension was changed."); + return this._verifyWithConfigArray( + blockText, + configForRecursive, + { ...options, filename: blockName } + ); + } + + // Does lint. + return this._verifyWithoutProcessors( + blockText, + config, + { ...options, filename: blockName } + ); + }); + + return postprocess(messageLists, filenameToExpose); + } + + /** + * Gets the SourceCode object representing the parsed source. + * @returns {SourceCode} The SourceCode object. + */ + getSourceCode() { + return internalSlotsMap.get(this).lastSourceCode; + } + + /** + * Defines a new linting rule. + * @param {string} ruleId A unique rule identifier + * @param {Function | Rule} ruleModule Function from context to object mapping AST node types to event handlers + * @returns {void} + */ + defineRule(ruleId, ruleModule) { + internalSlotsMap.get(this).ruleMap.define(ruleId, ruleModule); + } + + /** + * Defines many new linting rules. + * @param {Record} rulesToDefine map from unique rule identifier to rule + * @returns {void} + */ + defineRules(rulesToDefine) { + Object.getOwnPropertyNames(rulesToDefine).forEach(ruleId => { + this.defineRule(ruleId, rulesToDefine[ruleId]); + }); + } + + /** + * Gets an object with all loaded rules. + * @returns {Map} All loaded rules + */ + getRules() { + const { lastConfigArray, ruleMap } = internalSlotsMap.get(this); + + return new Map(function *() { + yield* ruleMap; + + if (lastConfigArray) { + yield* lastConfigArray.pluginRules; + } + }()); + } + + /** + * Define a new parser module + * @param {string} parserId Name of the parser + * @param {Parser} parserModule The parser object + * @returns {void} + */ + defineParser(parserId, parserModule) { + internalSlotsMap.get(this).parserMap.set(parserId, parserModule); + } + + /** + * Performs multiple autofix passes over the text until as many fixes as possible + * have been applied. + * @param {string} text The source text to apply fixes to. + * @param {ConfigData|ConfigArray} config The ESLint config object to use. + * @param {VerifyOptions&ProcessorOptions&FixOptions} options The ESLint options object to use. + * @returns {{fixed:boolean,messages:LintMessage[],output:string}} The result of the fix operation as returned from the + * SourceCodeFixer. + */ + verifyAndFix(text, config, options) { + let messages = [], + fixedResult, + fixed = false, + passNumber = 0, + currentText = text; + const debugTextDescription = options && options.filename || `${text.slice(0, 10)}...`; + const shouldFix = options && typeof options.fix !== "undefined" ? options.fix : true; + + /** + * This loop continues until one of the following is true: + * + * 1. No more fixes have been applied. + * 2. Ten passes have been made. + * + * That means anytime a fix is successfully applied, there will be another pass. + * Essentially, guaranteeing a minimum of two passes. + */ + do { + passNumber++; + + debug(`Linting code for ${debugTextDescription} (pass ${passNumber})`); + messages = this.verify(currentText, config, options); + + debug(`Generating fixed text for ${debugTextDescription} (pass ${passNumber})`); + fixedResult = SourceCodeFixer.applyFixes(currentText, messages, shouldFix); + + /* + * stop if there are any syntax errors. + * 'fixedResult.output' is a empty string. + */ + if (messages.length === 1 && messages[0].fatal) { + break; + } + + // keep track if any fixes were ever applied - important for return value + fixed = fixed || fixedResult.fixed; + + // update to use the fixed output instead of the original text + currentText = fixedResult.output; + + } while ( + fixedResult.fixed && + passNumber < MAX_AUTOFIX_PASSES + ); + + /* + * If the last result had fixes, we need to lint again to be sure we have + * the most up-to-date information. + */ + if (fixedResult.fixed) { + fixedResult.messages = this.verify(currentText, config, options); + } + + // ensure the last result properly reflects if fixes were done + fixedResult.fixed = fixed; + fixedResult.output = currentText; + + return fixedResult; + } +} + +module.exports = { + Linter, + + /** + * Get the internal slots of a given Linter instance for tests. + * @param {Linter} instance The Linter instance to get. + * @returns {LinterInternalSlots} The internal slots. + */ + getLinterInternalSlots(instance) { + return internalSlotsMap.get(instance); + } +}; diff --git a/eslint/lib/linter/node-event-generator.js b/eslint/lib/linter/node-event-generator.js new file mode 100644 index 0000000..6f3b251 --- /dev/null +++ b/eslint/lib/linter/node-event-generator.js @@ -0,0 +1,311 @@ +/** + * @fileoverview The event generator for AST nodes. + * @author Toru Nagashima + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const esquery = require("esquery"); +const lodash = require("lodash"); + +//------------------------------------------------------------------------------ +// Typedefs +//------------------------------------------------------------------------------ + +/** + * An object describing an AST selector + * @typedef {Object} ASTSelector + * @property {string} rawSelector The string that was parsed into this selector + * @property {boolean} isExit `true` if this should be emitted when exiting the node rather than when entering + * @property {Object} parsedSelector An object (from esquery) describing the matching behavior of the selector + * @property {string[]|null} listenerTypes A list of node types that could possibly cause the selector to match, + * or `null` if all node types could cause a match + * @property {number} attributeCount The total number of classes, pseudo-classes, and attribute queries in this selector + * @property {number} identifierCount The total number of identifier queries in this selector + */ + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Gets the possible types of a selector + * @param {Object} parsedSelector An object (from esquery) describing the matching behavior of the selector + * @returns {string[]|null} The node types that could possibly trigger this selector, or `null` if all node types could trigger it + */ +function getPossibleTypes(parsedSelector) { + switch (parsedSelector.type) { + case "identifier": + return [parsedSelector.value]; + + case "matches": { + const typesForComponents = parsedSelector.selectors.map(getPossibleTypes); + + if (typesForComponents.every(Boolean)) { + return lodash.union(...typesForComponents); + } + return null; + } + + case "compound": { + const typesForComponents = parsedSelector.selectors.map(getPossibleTypes).filter(typesForComponent => typesForComponent); + + // If all of the components could match any type, then the compound could also match any type. + if (!typesForComponents.length) { + return null; + } + + /* + * If at least one of the components could only match a particular type, the compound could only match + * the intersection of those types. + */ + return lodash.intersection(...typesForComponents); + } + + case "child": + case "descendant": + case "sibling": + case "adjacent": + return getPossibleTypes(parsedSelector.right); + + default: + return null; + + } +} + +/** + * Counts the number of class, pseudo-class, and attribute queries in this selector + * @param {Object} parsedSelector An object (from esquery) describing the selector's matching behavior + * @returns {number} The number of class, pseudo-class, and attribute queries in this selector + */ +function countClassAttributes(parsedSelector) { + switch (parsedSelector.type) { + case "child": + case "descendant": + case "sibling": + case "adjacent": + return countClassAttributes(parsedSelector.left) + countClassAttributes(parsedSelector.right); + + case "compound": + case "not": + case "matches": + return parsedSelector.selectors.reduce((sum, childSelector) => sum + countClassAttributes(childSelector), 0); + + case "attribute": + case "field": + case "nth-child": + case "nth-last-child": + return 1; + + default: + return 0; + } +} + +/** + * Counts the number of identifier queries in this selector + * @param {Object} parsedSelector An object (from esquery) describing the selector's matching behavior + * @returns {number} The number of identifier queries + */ +function countIdentifiers(parsedSelector) { + switch (parsedSelector.type) { + case "child": + case "descendant": + case "sibling": + case "adjacent": + return countIdentifiers(parsedSelector.left) + countIdentifiers(parsedSelector.right); + + case "compound": + case "not": + case "matches": + return parsedSelector.selectors.reduce((sum, childSelector) => sum + countIdentifiers(childSelector), 0); + + case "identifier": + return 1; + + default: + return 0; + } +} + +/** + * Compares the specificity of two selector objects, with CSS-like rules. + * @param {ASTSelector} selectorA An AST selector descriptor + * @param {ASTSelector} selectorB Another AST selector descriptor + * @returns {number} + * a value less than 0 if selectorA is less specific than selectorB + * a value greater than 0 if selectorA is more specific than selectorB + * a value less than 0 if selectorA and selectorB have the same specificity, and selectorA <= selectorB alphabetically + * a value greater than 0 if selectorA and selectorB have the same specificity, and selectorA > selectorB alphabetically + */ +function compareSpecificity(selectorA, selectorB) { + return selectorA.attributeCount - selectorB.attributeCount || + selectorA.identifierCount - selectorB.identifierCount || + (selectorA.rawSelector <= selectorB.rawSelector ? -1 : 1); +} + +/** + * Parses a raw selector string, and throws a useful error if parsing fails. + * @param {string} rawSelector A raw AST selector + * @returns {Object} An object (from esquery) describing the matching behavior of this selector + * @throws {Error} An error if the selector is invalid + */ +function tryParseSelector(rawSelector) { + try { + return esquery.parse(rawSelector.replace(/:exit$/u, "")); + } catch (err) { + if (err.location && err.location.start && typeof err.location.start.offset === "number") { + throw new SyntaxError(`Syntax error in selector "${rawSelector}" at position ${err.location.start.offset}: ${err.message}`); + } + throw err; + } +} + +/** + * Parses a raw selector string, and returns the parsed selector along with specificity and type information. + * @param {string} rawSelector A raw AST selector + * @returns {ASTSelector} A selector descriptor + */ +const parseSelector = lodash.memoize(rawSelector => { + const parsedSelector = tryParseSelector(rawSelector); + + return { + rawSelector, + isExit: rawSelector.endsWith(":exit"), + parsedSelector, + listenerTypes: getPossibleTypes(parsedSelector), + attributeCount: countClassAttributes(parsedSelector), + identifierCount: countIdentifiers(parsedSelector) + }; +}); + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +/** + * The event generator for AST nodes. + * This implements below interface. + * + * ```ts + * interface EventGenerator { + * emitter: SafeEmitter; + * enterNode(node: ASTNode): void; + * leaveNode(node: ASTNode): void; + * } + * ``` + */ +class NodeEventGenerator { + + // eslint-disable-next-line jsdoc/require-description + /** + * @param {SafeEmitter} emitter + * An SafeEmitter which is the destination of events. This emitter must already + * have registered listeners for all of the events that it needs to listen for. + * (See lib/linter/safe-emitter.js for more details on `SafeEmitter`.) + * @returns {NodeEventGenerator} new instance + */ + constructor(emitter) { + this.emitter = emitter; + this.currentAncestry = []; + this.enterSelectorsByNodeType = new Map(); + this.exitSelectorsByNodeType = new Map(); + this.anyTypeEnterSelectors = []; + this.anyTypeExitSelectors = []; + + emitter.eventNames().forEach(rawSelector => { + const selector = parseSelector(rawSelector); + + if (selector.listenerTypes) { + const typeMap = selector.isExit ? this.exitSelectorsByNodeType : this.enterSelectorsByNodeType; + + selector.listenerTypes.forEach(nodeType => { + if (!typeMap.has(nodeType)) { + typeMap.set(nodeType, []); + } + typeMap.get(nodeType).push(selector); + }); + return; + } + const selectors = selector.isExit ? this.anyTypeExitSelectors : this.anyTypeEnterSelectors; + + selectors.push(selector); + }); + + this.anyTypeEnterSelectors.sort(compareSpecificity); + this.anyTypeExitSelectors.sort(compareSpecificity); + this.enterSelectorsByNodeType.forEach(selectorList => selectorList.sort(compareSpecificity)); + this.exitSelectorsByNodeType.forEach(selectorList => selectorList.sort(compareSpecificity)); + } + + /** + * Checks a selector against a node, and emits it if it matches + * @param {ASTNode} node The node to check + * @param {ASTSelector} selector An AST selector descriptor + * @returns {void} + */ + applySelector(node, selector) { + if (esquery.matches(node, selector.parsedSelector, this.currentAncestry)) { + this.emitter.emit(selector.rawSelector, node); + } + } + + /** + * Applies all appropriate selectors to a node, in specificity order + * @param {ASTNode} node The node to check + * @param {boolean} isExit `false` if the node is currently being entered, `true` if it's currently being exited + * @returns {void} + */ + applySelectors(node, isExit) { + const selectorsByNodeType = (isExit ? this.exitSelectorsByNodeType : this.enterSelectorsByNodeType).get(node.type) || []; + const anyTypeSelectors = isExit ? this.anyTypeExitSelectors : this.anyTypeEnterSelectors; + + /* + * selectorsByNodeType and anyTypeSelectors were already sorted by specificity in the constructor. + * Iterate through each of them, applying selectors in the right order. + */ + let selectorsByTypeIndex = 0; + let anyTypeSelectorsIndex = 0; + + while (selectorsByTypeIndex < selectorsByNodeType.length || anyTypeSelectorsIndex < anyTypeSelectors.length) { + if ( + selectorsByTypeIndex >= selectorsByNodeType.length || + anyTypeSelectorsIndex < anyTypeSelectors.length && + compareSpecificity(anyTypeSelectors[anyTypeSelectorsIndex], selectorsByNodeType[selectorsByTypeIndex]) < 0 + ) { + this.applySelector(node, anyTypeSelectors[anyTypeSelectorsIndex++]); + } else { + this.applySelector(node, selectorsByNodeType[selectorsByTypeIndex++]); + } + } + } + + /** + * Emits an event of entering AST node. + * @param {ASTNode} node A node which was entered. + * @returns {void} + */ + enterNode(node) { + if (node.parent) { + this.currentAncestry.unshift(node.parent); + } + this.applySelectors(node, false); + } + + /** + * Emits an event of leaving AST node. + * @param {ASTNode} node A node which was left. + * @returns {void} + */ + leaveNode(node) { + this.applySelectors(node, true); + this.currentAncestry.shift(); + } +} + +module.exports = NodeEventGenerator; diff --git a/eslint/lib/linter/report-translator.js b/eslint/lib/linter/report-translator.js new file mode 100644 index 0000000..eef5165 --- /dev/null +++ b/eslint/lib/linter/report-translator.js @@ -0,0 +1,347 @@ +/** + * @fileoverview A helper that translates context.report() calls from the rule API into generic problem objects + * @author Teddy Katz + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const assert = require("assert"); +const ruleFixer = require("./rule-fixer"); +const interpolate = require("./interpolate"); + +//------------------------------------------------------------------------------ +// Typedefs +//------------------------------------------------------------------------------ + +/** + * An error message description + * @typedef {Object} MessageDescriptor + * @property {ASTNode} [node] The reported node + * @property {Location} loc The location of the problem. + * @property {string} message The problem message. + * @property {Object} [data] Optional data to use to fill in placeholders in the + * message. + * @property {Function} [fix] The function to call that creates a fix command. + * @property {Array<{desc?: string, messageId?: string, fix: Function}>} suggest Suggestion descriptions and functions to create a the associated fixes. + */ + +/** + * Information about the report + * @typedef {Object} ReportInfo + * @property {string} ruleId + * @property {(0|1|2)} severity + * @property {(string|undefined)} message + * @property {(string|undefined)} [messageId] + * @property {number} line + * @property {number} column + * @property {(number|undefined)} [endLine] + * @property {(number|undefined)} [endColumn] + * @property {(string|null)} nodeType + * @property {string} source + * @property {({text: string, range: (number[]|null)}|null)} [fix] + * @property {Array<{text: string, range: (number[]|null)}|null>} [suggestions] + */ + +//------------------------------------------------------------------------------ +// Module Definition +//------------------------------------------------------------------------------ + + +/** + * Translates a multi-argument context.report() call into a single object argument call + * @param {...*} args A list of arguments passed to `context.report` + * @returns {MessageDescriptor} A normalized object containing report information + */ +function normalizeMultiArgReportCall(...args) { + + // If there is one argument, it is considered to be a new-style call already. + if (args.length === 1) { + + // Shallow clone the object to avoid surprises if reusing the descriptor + return Object.assign({}, args[0]); + } + + // If the second argument is a string, the arguments are interpreted as [node, message, data, fix]. + if (typeof args[1] === "string") { + return { + node: args[0], + message: args[1], + data: args[2], + fix: args[3] + }; + } + + // Otherwise, the arguments are interpreted as [node, loc, message, data, fix]. + return { + node: args[0], + loc: args[1], + message: args[2], + data: args[3], + fix: args[4] + }; +} + +/** + * Asserts that either a loc or a node was provided, and the node is valid if it was provided. + * @param {MessageDescriptor} descriptor A descriptor to validate + * @returns {void} + * @throws AssertionError if neither a node nor a loc was provided, or if the node is not an object + */ +function assertValidNodeInfo(descriptor) { + if (descriptor.node) { + assert(typeof descriptor.node === "object", "Node must be an object"); + } else { + assert(descriptor.loc, "Node must be provided when reporting error if location is not provided"); + } +} + +/** + * Normalizes a MessageDescriptor to always have a `loc` with `start` and `end` properties + * @param {MessageDescriptor} descriptor A descriptor for the report from a rule. + * @returns {{start: Location, end: (Location|null)}} An updated location that infers the `start` and `end` properties + * from the `node` of the original descriptor, or infers the `start` from the `loc` of the original descriptor. + */ +function normalizeReportLoc(descriptor) { + if (descriptor.loc) { + if (descriptor.loc.start) { + return descriptor.loc; + } + return { start: descriptor.loc, end: null }; + } + return descriptor.node.loc; +} + +/** + * Compares items in a fixes array by range. + * @param {Fix} a The first message. + * @param {Fix} b The second message. + * @returns {int} -1 if a comes before b, 1 if a comes after b, 0 if equal. + * @private + */ +function compareFixesByRange(a, b) { + return a.range[0] - b.range[0] || a.range[1] - b.range[1]; +} + +/** + * Merges the given fixes array into one. + * @param {Fix[]} fixes The fixes to merge. + * @param {SourceCode} sourceCode The source code object to get the text between fixes. + * @returns {{text: string, range: number[]}} The merged fixes + */ +function mergeFixes(fixes, sourceCode) { + if (fixes.length === 0) { + return null; + } + if (fixes.length === 1) { + return fixes[0]; + } + + fixes.sort(compareFixesByRange); + + const originalText = sourceCode.text; + const start = fixes[0].range[0]; + const end = fixes[fixes.length - 1].range[1]; + let text = ""; + let lastPos = Number.MIN_SAFE_INTEGER; + + for (const fix of fixes) { + assert(fix.range[0] >= lastPos, "Fix objects must not be overlapped in a report."); + + if (fix.range[0] >= 0) { + text += originalText.slice(Math.max(0, start, lastPos), fix.range[0]); + } + text += fix.text; + lastPos = fix.range[1]; + } + text += originalText.slice(Math.max(0, start, lastPos), end); + + return { range: [start, end], text }; +} + +/** + * Gets one fix object from the given descriptor. + * If the descriptor retrieves multiple fixes, this merges those to one. + * @param {MessageDescriptor} descriptor The report descriptor. + * @param {SourceCode} sourceCode The source code object to get text between fixes. + * @returns {({text: string, range: number[]}|null)} The fix for the descriptor + */ +function normalizeFixes(descriptor, sourceCode) { + if (typeof descriptor.fix !== "function") { + return null; + } + + // @type {null | Fix | Fix[] | IterableIterator} + const fix = descriptor.fix(ruleFixer); + + // Merge to one. + if (fix && Symbol.iterator in fix) { + return mergeFixes(Array.from(fix), sourceCode); + } + return fix; +} + +/** + * Gets an array of suggestion objects from the given descriptor. + * @param {MessageDescriptor} descriptor The report descriptor. + * @param {SourceCode} sourceCode The source code object to get text between fixes. + * @param {Object} messages Object of meta messages for the rule. + * @returns {Array} The suggestions for the descriptor + */ +function mapSuggestions(descriptor, sourceCode, messages) { + if (!descriptor.suggest || !Array.isArray(descriptor.suggest)) { + return []; + } + + return descriptor.suggest.map(suggestInfo => { + const computedDesc = suggestInfo.desc || messages[suggestInfo.messageId]; + + return { + ...suggestInfo, + desc: interpolate(computedDesc, suggestInfo.data), + fix: normalizeFixes(suggestInfo, sourceCode) + }; + }); +} + +/** + * Creates information about the report from a descriptor + * @param {Object} options Information about the problem + * @param {string} options.ruleId Rule ID + * @param {(0|1|2)} options.severity Rule severity + * @param {(ASTNode|null)} options.node Node + * @param {string} options.message Error message + * @param {string} [options.messageId] The error message ID. + * @param {{start: SourceLocation, end: (SourceLocation|null)}} options.loc Start and end location + * @param {{text: string, range: (number[]|null)}} options.fix The fix object + * @param {Array<{text: string, range: (number[]|null)}>} options.suggestions The array of suggestions objects + * @returns {function(...args): ReportInfo} Function that returns information about the report + */ +function createProblem(options) { + const problem = { + ruleId: options.ruleId, + severity: options.severity, + message: options.message, + line: options.loc.start.line, + column: options.loc.start.column + 1, + nodeType: options.node && options.node.type || null + }; + + /* + * If this isn’t in the conditional, some of the tests fail + * because `messageId` is present in the problem object + */ + if (options.messageId) { + problem.messageId = options.messageId; + } + + if (options.loc.end) { + problem.endLine = options.loc.end.line; + problem.endColumn = options.loc.end.column + 1; + } + + if (options.fix) { + problem.fix = options.fix; + } + + if (options.suggestions && options.suggestions.length > 0) { + problem.suggestions = options.suggestions; + } + + return problem; +} + +/** + * Validates that suggestions are properly defined. Throws if an error is detected. + * @param {Array<{ desc?: string, messageId?: string }>} suggest The incoming suggest data. + * @param {Object} messages Object of meta messages for the rule. + * @returns {void} + */ +function validateSuggestions(suggest, messages) { + if (suggest && Array.isArray(suggest)) { + suggest.forEach(suggestion => { + if (suggestion.messageId) { + const { messageId } = suggestion; + + if (!messages) { + throw new TypeError(`context.report() called with a suggest option with a messageId '${messageId}', but no messages were present in the rule metadata.`); + } + + if (!messages[messageId]) { + throw new TypeError(`context.report() called with a suggest option with a messageId '${messageId}' which is not present in the 'messages' config: ${JSON.stringify(messages, null, 2)}`); + } + + if (suggestion.desc) { + throw new TypeError("context.report() called with a suggest option that defines both a 'messageId' and an 'desc'. Please only pass one."); + } + } else if (!suggestion.desc) { + throw new TypeError("context.report() called with a suggest option that doesn't have either a `desc` or `messageId`"); + } + + if (typeof suggestion.fix !== "function") { + throw new TypeError(`context.report() called with a suggest option without a fix function. See: ${suggestion}`); + } + }); + } +} + +/** + * Returns a function that converts the arguments of a `context.report` call from a rule into a reported + * problem for the Node.js API. + * @param {{ruleId: string, severity: number, sourceCode: SourceCode, messageIds: Object, disableFixes: boolean}} metadata Metadata for the reported problem + * @param {SourceCode} sourceCode The `SourceCode` instance for the text being linted + * @returns {function(...args): ReportInfo} Function that returns information about the report + */ + +module.exports = function createReportTranslator(metadata) { + + /* + * `createReportTranslator` gets called once per enabled rule per file. It needs to be very performant. + * The report translator itself (i.e. the function that `createReportTranslator` returns) gets + * called every time a rule reports a problem, which happens much less frequently (usually, the vast + * majority of rules don't report any problems for a given file). + */ + return (...args) => { + const descriptor = normalizeMultiArgReportCall(...args); + const messages = metadata.messageIds; + + assertValidNodeInfo(descriptor); + + let computedMessage; + + if (descriptor.messageId) { + if (!messages) { + throw new TypeError("context.report() called with a messageId, but no messages were present in the rule metadata."); + } + const id = descriptor.messageId; + + if (descriptor.message) { + throw new TypeError("context.report() called with a message and a messageId. Please only pass one."); + } + if (!messages || !Object.prototype.hasOwnProperty.call(messages, id)) { + throw new TypeError(`context.report() called with a messageId of '${id}' which is not present in the 'messages' config: ${JSON.stringify(messages, null, 2)}`); + } + computedMessage = messages[id]; + } else if (descriptor.message) { + computedMessage = descriptor.message; + } else { + throw new TypeError("Missing `message` property in report() call; add a message that describes the linting problem."); + } + + validateSuggestions(descriptor.suggest, messages); + + return createProblem({ + ruleId: metadata.ruleId, + severity: metadata.severity, + node: descriptor.node, + message: interpolate(computedMessage, descriptor.data), + messageId: descriptor.messageId, + loc: normalizeReportLoc(descriptor), + fix: metadata.disableFixes ? null : normalizeFixes(descriptor, metadata.sourceCode), + suggestions: metadata.disableFixes ? [] : mapSuggestions(descriptor, metadata.sourceCode, messages) + }); + }; +}; diff --git a/eslint/lib/linter/rule-fixer.js b/eslint/lib/linter/rule-fixer.js new file mode 100644 index 0000000..bdd80d1 --- /dev/null +++ b/eslint/lib/linter/rule-fixer.js @@ -0,0 +1,140 @@ +/** + * @fileoverview An object that creates fix commands for rules. + * @author Nicholas C. Zakas + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +// none! + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Creates a fix command that inserts text at the specified index in the source text. + * @param {int} index The 0-based index at which to insert the new text. + * @param {string} text The text to insert. + * @returns {Object} The fix command. + * @private + */ +function insertTextAt(index, text) { + return { + range: [index, index], + text + }; +} + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +/** + * Creates code fixing commands for rules. + */ + +const ruleFixer = Object.freeze({ + + /** + * Creates a fix command that inserts text after the given node or token. + * The fix is not applied until applyFixes() is called. + * @param {ASTNode|Token} nodeOrToken The node or token to insert after. + * @param {string} text The text to insert. + * @returns {Object} The fix command. + */ + insertTextAfter(nodeOrToken, text) { + return this.insertTextAfterRange(nodeOrToken.range, text); + }, + + /** + * Creates a fix command that inserts text after the specified range in the source text. + * The fix is not applied until applyFixes() is called. + * @param {int[]} range The range to replace, first item is start of range, second + * is end of range. + * @param {string} text The text to insert. + * @returns {Object} The fix command. + */ + insertTextAfterRange(range, text) { + return insertTextAt(range[1], text); + }, + + /** + * Creates a fix command that inserts text before the given node or token. + * The fix is not applied until applyFixes() is called. + * @param {ASTNode|Token} nodeOrToken The node or token to insert before. + * @param {string} text The text to insert. + * @returns {Object} The fix command. + */ + insertTextBefore(nodeOrToken, text) { + return this.insertTextBeforeRange(nodeOrToken.range, text); + }, + + /** + * Creates a fix command that inserts text before the specified range in the source text. + * The fix is not applied until applyFixes() is called. + * @param {int[]} range The range to replace, first item is start of range, second + * is end of range. + * @param {string} text The text to insert. + * @returns {Object} The fix command. + */ + insertTextBeforeRange(range, text) { + return insertTextAt(range[0], text); + }, + + /** + * Creates a fix command that replaces text at the node or token. + * The fix is not applied until applyFixes() is called. + * @param {ASTNode|Token} nodeOrToken The node or token to remove. + * @param {string} text The text to insert. + * @returns {Object} The fix command. + */ + replaceText(nodeOrToken, text) { + return this.replaceTextRange(nodeOrToken.range, text); + }, + + /** + * Creates a fix command that replaces text at the specified range in the source text. + * The fix is not applied until applyFixes() is called. + * @param {int[]} range The range to replace, first item is start of range, second + * is end of range. + * @param {string} text The text to insert. + * @returns {Object} The fix command. + */ + replaceTextRange(range, text) { + return { + range, + text + }; + }, + + /** + * Creates a fix command that removes the node or token from the source. + * The fix is not applied until applyFixes() is called. + * @param {ASTNode|Token} nodeOrToken The node or token to remove. + * @returns {Object} The fix command. + */ + remove(nodeOrToken) { + return this.removeRange(nodeOrToken.range); + }, + + /** + * Creates a fix command that removes the specified range of text from the source. + * The fix is not applied until applyFixes() is called. + * @param {int[]} range The range to remove, first item is start of range, second + * is end of range. + * @returns {Object} The fix command. + */ + removeRange(range) { + return { + range, + text: "" + }; + } + +}); + + +module.exports = ruleFixer; diff --git a/eslint/lib/linter/rules.js b/eslint/lib/linter/rules.js new file mode 100644 index 0000000..a153266 --- /dev/null +++ b/eslint/lib/linter/rules.js @@ -0,0 +1,77 @@ +/** + * @fileoverview Defines a storage for rules. + * @author Nicholas C. Zakas + * @author aladdin-add + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const builtInRules = require("../rules"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Normalizes a rule module to the new-style API + * @param {(Function|{create: Function})} rule A rule object, which can either be a function + * ("old-style") or an object with a `create` method ("new-style") + * @returns {{create: Function}} A new-style rule. + */ +function normalizeRule(rule) { + return typeof rule === "function" ? Object.assign({ create: rule }, rule) : rule; +} + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +class Rules { + constructor() { + this._rules = Object.create(null); + } + + /** + * Registers a rule module for rule id in storage. + * @param {string} ruleId Rule id (file name). + * @param {Function} ruleModule Rule handler. + * @returns {void} + */ + define(ruleId, ruleModule) { + this._rules[ruleId] = normalizeRule(ruleModule); + } + + /** + * Access rule handler by id (file name). + * @param {string} ruleId Rule id (file name). + * @returns {{create: Function, schema: JsonSchema[]}} + * A rule. This is normalized to always have the new-style shape with a `create` method. + */ + get(ruleId) { + if (typeof this._rules[ruleId] === "string") { + this.define(ruleId, require(this._rules[ruleId])); + } + if (this._rules[ruleId]) { + return this._rules[ruleId]; + } + if (builtInRules.has(ruleId)) { + return builtInRules.get(ruleId); + } + + return null; + } + + *[Symbol.iterator]() { + yield* builtInRules; + + for (const ruleId of Object.keys(this._rules)) { + yield [ruleId, this.get(ruleId)]; + } + } +} + +module.exports = Rules; diff --git a/eslint/lib/linter/safe-emitter.js b/eslint/lib/linter/safe-emitter.js new file mode 100644 index 0000000..ab21223 --- /dev/null +++ b/eslint/lib/linter/safe-emitter.js @@ -0,0 +1,52 @@ +/** + * @fileoverview A variant of EventEmitter which does not give listeners information about each other + * @author Teddy Katz + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Typedefs +//------------------------------------------------------------------------------ + +/** + * An event emitter + * @typedef {Object} SafeEmitter + * @property {function(eventName: string, listenerFunc: Function): void} on Adds a listener for a given event name + * @property {function(eventName: string, arg1?: any, arg2?: any, arg3?: any)} emit Emits an event with a given name. + * This calls all the listeners that were listening for that name, with `arg1`, `arg2`, and `arg3` as arguments. + * @property {function(): string[]} eventNames Gets the list of event names that have registered listeners. + */ + +/** + * Creates an object which can listen for and emit events. + * This is similar to the EventEmitter API in Node's standard library, but it has a few differences. + * The goal is to allow multiple modules to attach arbitrary listeners to the same emitter, without + * letting the modules know about each other at all. + * 1. It has no special keys like `error` and `newListener`, which would allow modules to detect when + * another module throws an error or registers a listener. + * 2. It calls listener functions without any `this` value. (`EventEmitter` calls listeners with a + * `this` value of the emitter instance, which would give listeners access to other listeners.) + * @returns {SafeEmitter} An emitter + */ +module.exports = () => { + const listeners = Object.create(null); + + return Object.freeze({ + on(eventName, listener) { + if (eventName in listeners) { + listeners[eventName].push(listener); + } else { + listeners[eventName] = [listener]; + } + }, + emit(eventName, ...args) { + if (eventName in listeners) { + listeners[eventName].forEach(listener => listener(...args)); + } + }, + eventNames() { + return Object.keys(listeners); + } + }); +}; diff --git a/eslint/lib/linter/source-code-fixer.js b/eslint/lib/linter/source-code-fixer.js new file mode 100644 index 0000000..53dc1dc --- /dev/null +++ b/eslint/lib/linter/source-code-fixer.js @@ -0,0 +1,152 @@ +/** + * @fileoverview An object that caches and applies source code fixes. + * @author Nicholas C. Zakas + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const debug = require("debug")("eslint:source-code-fixer"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const BOM = "\uFEFF"; + +/** + * Compares items in a messages array by range. + * @param {Message} a The first message. + * @param {Message} b The second message. + * @returns {int} -1 if a comes before b, 1 if a comes after b, 0 if equal. + * @private + */ +function compareMessagesByFixRange(a, b) { + return a.fix.range[0] - b.fix.range[0] || a.fix.range[1] - b.fix.range[1]; +} + +/** + * Compares items in a messages array by line and column. + * @param {Message} a The first message. + * @param {Message} b The second message. + * @returns {int} -1 if a comes before b, 1 if a comes after b, 0 if equal. + * @private + */ +function compareMessagesByLocation(a, b) { + return a.line - b.line || a.column - b.column; +} + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +/** + * Utility for apply fixes to source code. + * @constructor + */ +function SourceCodeFixer() { + Object.freeze(this); +} + +/** + * Applies the fixes specified by the messages to the given text. Tries to be + * smart about the fixes and won't apply fixes over the same area in the text. + * @param {string} sourceText The text to apply the changes to. + * @param {Message[]} messages The array of messages reported by ESLint. + * @param {boolean|Function} [shouldFix=true] Determines whether each message should be fixed + * @returns {Object} An object containing the fixed text and any unfixed messages. + */ +SourceCodeFixer.applyFixes = function(sourceText, messages, shouldFix) { + debug("Applying fixes"); + + if (shouldFix === false) { + debug("shouldFix parameter was false, not attempting fixes"); + return { + fixed: false, + messages, + output: sourceText + }; + } + + // clone the array + const remainingMessages = [], + fixes = [], + bom = sourceText.startsWith(BOM) ? BOM : "", + text = bom ? sourceText.slice(1) : sourceText; + let lastPos = Number.NEGATIVE_INFINITY, + output = bom; + + /** + * Try to use the 'fix' from a problem. + * @param {Message} problem The message object to apply fixes from + * @returns {boolean} Whether fix was successfully applied + */ + function attemptFix(problem) { + const fix = problem.fix; + const start = fix.range[0]; + const end = fix.range[1]; + + // Remain it as a problem if it's overlapped or it's a negative range + if (lastPos >= start || start > end) { + remainingMessages.push(problem); + return false; + } + + // Remove BOM. + if ((start < 0 && end >= 0) || (start === 0 && fix.text.startsWith(BOM))) { + output = ""; + } + + // Make output to this fix. + output += text.slice(Math.max(0, lastPos), Math.max(0, start)); + output += fix.text; + lastPos = end; + return true; + } + + messages.forEach(problem => { + if (Object.prototype.hasOwnProperty.call(problem, "fix")) { + fixes.push(problem); + } else { + remainingMessages.push(problem); + } + }); + + if (fixes.length) { + debug("Found fixes to apply"); + let fixesWereApplied = false; + + for (const problem of fixes.sort(compareMessagesByFixRange)) { + if (typeof shouldFix !== "function" || shouldFix(problem)) { + attemptFix(problem); + + /* + * The only time attemptFix will fail is if a previous fix was + * applied which conflicts with it. So we can mark this as true. + */ + fixesWereApplied = true; + } else { + remainingMessages.push(problem); + } + } + output += text.slice(Math.max(0, lastPos)); + + return { + fixed: fixesWereApplied, + messages: remainingMessages.sort(compareMessagesByLocation), + output + }; + } + + debug("No fixes to apply"); + return { + fixed: false, + messages, + output: bom + text + }; + +}; + +module.exports = SourceCodeFixer; diff --git a/eslint/lib/linter/timing.js b/eslint/lib/linter/timing.js new file mode 100644 index 0000000..8396d92 --- /dev/null +++ b/eslint/lib/linter/timing.js @@ -0,0 +1,139 @@ +/** + * @fileoverview Tracks performance of individual rules. + * @author Brandon Mills + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/* istanbul ignore next */ +/** + * Align the string to left + * @param {string} str string to evaluate + * @param {int} len length of the string + * @param {string} ch delimiter character + * @returns {string} modified string + * @private + */ +function alignLeft(str, len, ch) { + return str + new Array(len - str.length + 1).join(ch || " "); +} + +/* istanbul ignore next */ +/** + * Align the string to right + * @param {string} str string to evaluate + * @param {int} len length of the string + * @param {string} ch delimiter character + * @returns {string} modified string + * @private + */ +function alignRight(str, len, ch) { + return new Array(len - str.length + 1).join(ch || " ") + str; +} + +//------------------------------------------------------------------------------ +// Module definition +//------------------------------------------------------------------------------ + +const enabled = !!process.env.TIMING; + +const HEADERS = ["Rule", "Time (ms)", "Relative"]; +const ALIGN = [alignLeft, alignRight, alignRight]; + +/* istanbul ignore next */ +/** + * display the data + * @param {Object} data Data object to be displayed + * @returns {void} prints modified string with console.log + * @private + */ +function display(data) { + let total = 0; + const rows = Object.keys(data) + .map(key => { + const time = data[key]; + + total += time; + return [key, time]; + }) + .sort((a, b) => b[1] - a[1]) + .slice(0, 10); + + rows.forEach(row => { + row.push(`${(row[1] * 100 / total).toFixed(1)}%`); + row[1] = row[1].toFixed(3); + }); + + rows.unshift(HEADERS); + + const widths = []; + + rows.forEach(row => { + const len = row.length; + + for (let i = 0; i < len; i++) { + const n = row[i].length; + + if (!widths[i] || n > widths[i]) { + widths[i] = n; + } + } + }); + + const table = rows.map(row => ( + row + .map((cell, index) => ALIGN[index](cell, widths[index])) + .join(" | ") + )); + + table.splice(1, 0, widths.map((width, index) => { + const extraAlignment = index !== 0 && index !== widths.length - 1 ? 2 : 1; + + return ALIGN[index](":", width + extraAlignment, "-"); + }).join("|")); + + console.log(table.join("\n")); // eslint-disable-line no-console +} + +/* istanbul ignore next */ +module.exports = (function() { + + const data = Object.create(null); + + /** + * Time the run + * @param {*} key key from the data object + * @param {Function} fn function to be called + * @returns {Function} function to be executed + * @private + */ + function time(key, fn) { + if (typeof data[key] === "undefined") { + data[key] = 0; + } + + return function(...args) { + let t = process.hrtime(); + + fn(...args); + t = process.hrtime(t); + data[key] += t[0] * 1e3 + t[1] / 1e6; + }; + } + + if (enabled) { + process.on("exit", () => { + display(data); + }); + } + + return { + time, + enabled + }; + +}()); diff --git a/eslint/lib/options.js b/eslint/lib/options.js new file mode 100644 index 0000000..98dc04b --- /dev/null +++ b/eslint/lib/options.js @@ -0,0 +1,263 @@ +/** + * @fileoverview Options configuration for optionator. + * @author George Zahariev + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const optionator = require("optionator"); + +//------------------------------------------------------------------------------ +// Initialization and Public Interface +//------------------------------------------------------------------------------ + +// exports "parse(args)", "generateHelp()", and "generateHelpForOption(optionName)" +module.exports = optionator({ + prepend: "eslint [options] file.js [file.js] [dir]", + defaults: { + concatRepeatedArrays: true, + mergeRepeatedObjects: true + }, + options: [ + { + heading: "Basic configuration" + }, + { + option: "eslintrc", + type: "Boolean", + default: "true", + description: "Disable use of configuration from .eslintrc.*" + }, + { + option: "config", + alias: "c", + type: "path::String", + description: "Use this configuration, overriding .eslintrc.* config options if present" + }, + { + option: "env", + type: "[String]", + description: "Specify environments" + }, + { + option: "ext", + type: "[String]", + default: ".js", + description: "Specify JavaScript file extensions" + }, + { + option: "global", + type: "[String]", + description: "Define global variables" + }, + { + option: "parser", + type: "String", + description: "Specify the parser to be used" + }, + { + option: "parser-options", + type: "Object", + description: "Specify parser options" + }, + { + option: "resolve-plugins-relative-to", + type: "path::String", + description: "A folder where plugins should be resolved from, CWD by default" + }, + { + heading: "Specifying rules and plugins" + }, + { + option: "rulesdir", + type: "[path::String]", + description: "Use additional rules from this directory" + }, + { + option: "plugin", + type: "[String]", + description: "Specify plugins" + }, + { + option: "rule", + type: "Object", + description: "Specify rules" + }, + { + heading: "Fixing problems" + }, + { + option: "fix", + type: "Boolean", + default: false, + description: "Automatically fix problems" + }, + { + option: "fix-dry-run", + type: "Boolean", + default: false, + description: "Automatically fix problems without saving the changes to the file system" + }, + { + option: "fix-type", + type: "Array", + description: "Specify the types of fixes to apply (problem, suggestion, layout)" + }, + { + heading: "Ignoring files" + }, + { + option: "ignore-path", + type: "path::String", + description: "Specify path of ignore file" + }, + { + option: "ignore", + type: "Boolean", + default: "true", + description: "Disable use of ignore files and patterns" + }, + { + option: "ignore-pattern", + type: "[String]", + description: "Pattern of files to ignore (in addition to those in .eslintignore)", + concatRepeatedArrays: [true, { + oneValuePerFlag: true + }] + }, + { + heading: "Using stdin" + }, + { + option: "stdin", + type: "Boolean", + default: "false", + description: "Lint code provided on " + }, + { + option: "stdin-filename", + type: "String", + description: "Specify filename to process STDIN as" + }, + { + heading: "Handling warnings" + }, + { + option: "quiet", + type: "Boolean", + default: "false", + description: "Report errors only" + }, + { + option: "max-warnings", + type: "Int", + default: "-1", + description: "Number of warnings to trigger nonzero exit code" + }, + { + heading: "Output" + }, + { + option: "output-file", + alias: "o", + type: "path::String", + description: "Specify file to write report to" + }, + { + option: "format", + alias: "f", + type: "String", + default: "stylish", + description: "Use a specific output format" + }, + { + option: "color", + type: "Boolean", + alias: "no-color", + description: "Force enabling/disabling of color" + }, + { + heading: "Inline configuration comments" + }, + { + option: "inline-config", + type: "Boolean", + default: "true", + description: "Prevent comments from changing config or rules" + }, + { + option: "report-unused-disable-directives", + type: "Boolean", + default: void 0, + description: "Adds reported errors for unused eslint-disable directives" + }, + { + heading: "Caching" + }, + { + option: "cache", + type: "Boolean", + default: "false", + description: "Only check changed files" + }, + { + option: "cache-file", + type: "path::String", + default: ".eslintcache", + description: "Path to the cache file. Deprecated: use --cache-location" + }, + { + option: "cache-location", + type: "path::String", + description: "Path to the cache file or directory" + }, + { + heading: "Miscellaneous" + }, + { + option: "init", + type: "Boolean", + default: "false", + description: "Run config initialization wizard" + }, + { + option: "env-info", + type: "Boolean", + default: "false", + description: "Output execution environment information" + }, + { + option: "error-on-unmatched-pattern", + type: "Boolean", + default: "true", + description: "Prevent errors when pattern is unmatched" + }, + { + option: "debug", + type: "Boolean", + default: false, + description: "Output debugging information" + }, + { + option: "help", + alias: "h", + type: "Boolean", + description: "Show help" + }, + { + option: "version", + alias: "v", + type: "Boolean", + description: "Output the version number" + }, + { + option: "print-config", + type: "path::String", + description: "Print the configuration for the given file" + } + ] +}); diff --git a/eslint/lib/rule-tester/index.js b/eslint/lib/rule-tester/index.js new file mode 100644 index 0000000..f52d140 --- /dev/null +++ b/eslint/lib/rule-tester/index.js @@ -0,0 +1,5 @@ +"use strict"; + +module.exports = { + RuleTester: require("./rule-tester") +}; diff --git a/eslint/lib/rule-tester/rule-tester.js b/eslint/lib/rule-tester/rule-tester.js new file mode 100644 index 0000000..1c17371 --- /dev/null +++ b/eslint/lib/rule-tester/rule-tester.js @@ -0,0 +1,879 @@ +/** + * @fileoverview Mocha test wrapper + * @author Ilya Volodin + */ +"use strict"; + +/* global describe, it */ + +/* + * This is a wrapper around mocha to allow for DRY unittests for eslint + * Format: + * RuleTester.run("{ruleName}", { + * valid: [ + * "{code}", + * { code: "{code}", options: {options}, globals: {globals}, parser: "{parser}", settings: {settings} } + * ], + * invalid: [ + * { code: "{code}", errors: {numErrors} }, + * { code: "{code}", errors: ["{errorMessage}"] }, + * { code: "{code}", options: {options}, globals: {globals}, parser: "{parser}", settings: {settings}, errors: [{ message: "{errorMessage}", type: "{errorNodeType}"}] } + * ] + * }); + * + * Variables: + * {code} - String that represents the code to be tested + * {options} - Arguments that are passed to the configurable rules. + * {globals} - An object representing a list of variables that are + * registered as globals + * {parser} - String representing the parser to use + * {settings} - An object representing global settings for all rules + * {numErrors} - If failing case doesn't need to check error message, + * this integer will specify how many errors should be + * received + * {errorMessage} - Message that is returned by the rule on failure + * {errorNodeType} - AST node type that is returned by they rule as + * a cause of the failure. + */ + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const + assert = require("assert"), + path = require("path"), + util = require("util"), + lodash = require("lodash"), + Traverser = require("../../lib/shared/traverser"), + { getRuleOptionsSchema, validate } = require("../shared/config-validator"), + { Linter, SourceCodeFixer, interpolate } = require("../linter"); + +const ajv = require("../shared/ajv")({ strictDefaults: true }); + +const espreePath = require.resolve("espree"); + +//------------------------------------------------------------------------------ +// Typedefs +//------------------------------------------------------------------------------ + +/** @typedef {import("../shared/types").Parser} Parser */ + +/** + * A test case that is expected to pass lint. + * @typedef {Object} ValidTestCase + * @property {string} code Code for the test case. + * @property {any[]} [options] Options for the test case. + * @property {{ [name: string]: any }} [settings] Settings for the test case. + * @property {string} [filename] The fake filename for the test case. Useful for rules that make assertion about filenames. + * @property {string} [parser] The absolute path for the parser. + * @property {{ [name: string]: any }} [parserOptions] Options for the parser. + * @property {{ [name: string]: "readonly" | "writable" | "off" }} [globals] The additional global variables. + * @property {{ [name: string]: boolean }} [env] Environments for the test case. + */ + +/** + * A test case that is expected to fail lint. + * @typedef {Object} InvalidTestCase + * @property {string} code Code for the test case. + * @property {number | Array} errors Expected errors. + * @property {string | null} [output] The expected code after autofixes are applied. If set to `null`, the test runner will assert that no autofix is suggested. + * @property {any[]} [options] Options for the test case. + * @property {{ [name: string]: any }} [settings] Settings for the test case. + * @property {string} [filename] The fake filename for the test case. Useful for rules that make assertion about filenames. + * @property {string} [parser] The absolute path for the parser. + * @property {{ [name: string]: any }} [parserOptions] Options for the parser. + * @property {{ [name: string]: "readonly" | "writable" | "off" }} [globals] The additional global variables. + * @property {{ [name: string]: boolean }} [env] Environments for the test case. + */ + +/** + * A description of a reported error used in a rule tester test. + * @typedef {Object} TestCaseError + * @property {string | RegExp} [message] Message. + * @property {string} [messageId] Message ID. + * @property {string} [type] The type of the reported AST node. + * @property {{ [name: string]: string }} [data] The data used to fill the message template. + * @property {number} [line] The 1-based line number of the reported start location. + * @property {number} [column] The 1-based column number of the reported start location. + * @property {number} [endLine] The 1-based line number of the reported end location. + * @property {number} [endColumn] The 1-based column number of the reported end location. + */ + +//------------------------------------------------------------------------------ +// Private Members +//------------------------------------------------------------------------------ + +/* + * testerDefaultConfig must not be modified as it allows to reset the tester to + * the initial default configuration + */ +const testerDefaultConfig = { rules: {} }; +let defaultConfig = { rules: {} }; + +/* + * List every parameters possible on a test case that are not related to eslint + * configuration + */ +const RuleTesterParameters = [ + "code", + "filename", + "options", + "errors", + "output" +]; + +/* + * All allowed property names in error objects. + */ +const errorObjectParameters = new Set([ + "message", + "messageId", + "data", + "type", + "line", + "column", + "endLine", + "endColumn", + "suggestions" +]); +const friendlyErrorObjectParameterList = `[${[...errorObjectParameters].map(key => `'${key}'`).join(", ")}]`; + +/* + * All allowed property names in suggestion objects. + */ +const suggestionObjectParameters = new Set([ + "desc", + "messageId", + "data", + "output" +]); +const friendlySuggestionObjectParameterList = `[${[...suggestionObjectParameters].map(key => `'${key}'`).join(", ")}]`; + +const hasOwnProperty = Function.call.bind(Object.hasOwnProperty); + +/** + * Clones a given value deeply. + * Note: This ignores `parent` property. + * @param {any} x A value to clone. + * @returns {any} A cloned value. + */ +function cloneDeeplyExcludesParent(x) { + if (typeof x === "object" && x !== null) { + if (Array.isArray(x)) { + return x.map(cloneDeeplyExcludesParent); + } + + const retv = {}; + + for (const key in x) { + if (key !== "parent" && hasOwnProperty(x, key)) { + retv[key] = cloneDeeplyExcludesParent(x[key]); + } + } + + return retv; + } + + return x; +} + +/** + * Freezes a given value deeply. + * @param {any} x A value to freeze. + * @returns {void} + */ +function freezeDeeply(x) { + if (typeof x === "object" && x !== null) { + if (Array.isArray(x)) { + x.forEach(freezeDeeply); + } else { + for (const key in x) { + if (key !== "parent" && hasOwnProperty(x, key)) { + freezeDeeply(x[key]); + } + } + } + Object.freeze(x); + } +} + +/** + * Replace control characters by `\u00xx` form. + * @param {string} text The text to sanitize. + * @returns {string} The sanitized text. + */ +function sanitize(text) { + return text.replace( + /[\u0000-\u0009\u000b-\u001a]/gu, // eslint-disable-line no-control-regex + c => `\\u${c.codePointAt(0).toString(16).padStart(4, "0")}` + ); +} + +/** + * Define `start`/`end` properties as throwing error. + * @param {string} objName Object name used for error messages. + * @param {ASTNode} node The node to define. + * @returns {void} + */ +function defineStartEndAsError(objName, node) { + Object.defineProperties(node, { + start: { + get() { + throw new Error(`Use ${objName}.range[0] instead of ${objName}.start`); + }, + configurable: true, + enumerable: false + }, + end: { + get() { + throw new Error(`Use ${objName}.range[1] instead of ${objName}.end`); + }, + configurable: true, + enumerable: false + } + }); +} + +/** + * Define `start`/`end` properties of all nodes of the given AST as throwing error. + * @param {ASTNode} ast The root node to errorize `start`/`end` properties. + * @param {Object} [visitorKeys] Visitor keys to be used for traversing the given ast. + * @returns {void} + */ +function defineStartEndAsErrorInTree(ast, visitorKeys) { + Traverser.traverse(ast, { visitorKeys, enter: defineStartEndAsError.bind(null, "node") }); + ast.tokens.forEach(defineStartEndAsError.bind(null, "token")); + ast.comments.forEach(defineStartEndAsError.bind(null, "token")); +} + +/** + * Wraps the given parser in order to intercept and modify return values from the `parse` and `parseForESLint` methods, for test purposes. + * In particular, to modify ast nodes, tokens and comments to throw on access to their `start` and `end` properties. + * @param {Parser} parser Parser object. + * @returns {Parser} Wrapped parser object. + */ +function wrapParser(parser) { + if (typeof parser.parseForESLint === "function") { + return { + parseForESLint(...args) { + const ret = parser.parseForESLint(...args); + + defineStartEndAsErrorInTree(ret.ast, ret.visitorKeys); + return ret; + } + }; + } + return { + parse(...args) { + const ast = parser.parse(...args); + + defineStartEndAsErrorInTree(ast); + return ast; + } + }; +} + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +// default separators for testing +const DESCRIBE = Symbol("describe"); +const IT = Symbol("it"); + +/** + * This is `it` default handler if `it` don't exist. + * @this {Mocha} + * @param {string} text The description of the test case. + * @param {Function} method The logic of the test case. + * @returns {any} Returned value of `method`. + */ +function itDefaultHandler(text, method) { + try { + return method.call(this); + } catch (err) { + if (err instanceof assert.AssertionError) { + err.message += ` (${util.inspect(err.actual)} ${err.operator} ${util.inspect(err.expected)})`; + } + throw err; + } +} + +/** + * This is `describe` default handler if `describe` don't exist. + * @this {Mocha} + * @param {string} text The description of the test case. + * @param {Function} method The logic of the test case. + * @returns {any} Returned value of `method`. + */ +function describeDefaultHandler(text, method) { + return method.call(this); +} + +class RuleTester { + + /** + * Creates a new instance of RuleTester. + * @param {Object} [testerConfig] Optional, extra configuration for the tester + */ + constructor(testerConfig) { + + /** + * The configuration to use for this tester. Combination of the tester + * configuration and the default configuration. + * @type {Object} + */ + this.testerConfig = lodash.merge( + + // we have to clone because merge uses the first argument for recipient + lodash.cloneDeep(defaultConfig), + testerConfig, + { rules: { "rule-tester/validate-ast": "error" } } + ); + + /** + * Rule definitions to define before tests. + * @type {Object} + */ + this.rules = {}; + this.linter = new Linter(); + } + + /** + * Set the configuration to use for all future tests + * @param {Object} config the configuration to use. + * @returns {void} + */ + static setDefaultConfig(config) { + if (typeof config !== "object") { + throw new TypeError("RuleTester.setDefaultConfig: config must be an object"); + } + defaultConfig = config; + + // Make sure the rules object exists since it is assumed to exist later + defaultConfig.rules = defaultConfig.rules || {}; + } + + /** + * Get the current configuration used for all tests + * @returns {Object} the current configuration + */ + static getDefaultConfig() { + return defaultConfig; + } + + /** + * Reset the configuration to the initial configuration of the tester removing + * any changes made until now. + * @returns {void} + */ + static resetDefaultConfig() { + defaultConfig = lodash.cloneDeep(testerDefaultConfig); + } + + + /* + * If people use `mocha test.js --watch` command, `describe` and `it` function + * instances are different for each execution. So `describe` and `it` should get fresh instance + * always. + */ + static get describe() { + return ( + this[DESCRIBE] || + (typeof describe === "function" ? describe : describeDefaultHandler) + ); + } + + static set describe(value) { + this[DESCRIBE] = value; + } + + static get it() { + return ( + this[IT] || + (typeof it === "function" ? it : itDefaultHandler) + ); + } + + static set it(value) { + this[IT] = value; + } + + /** + * Define a rule for one particular run of tests. + * @param {string} name The name of the rule to define. + * @param {Function} rule The rule definition. + * @returns {void} + */ + defineRule(name, rule) { + this.rules[name] = rule; + } + + /** + * Adds a new rule test to execute. + * @param {string} ruleName The name of the rule to run. + * @param {Function} rule The rule to test. + * @param {{ + * valid: (ValidTestCase | string)[], + * invalid: InvalidTestCase[] + * }} test The collection of tests to run. + * @returns {void} + */ + run(ruleName, rule, test) { + + const testerConfig = this.testerConfig, + requiredScenarios = ["valid", "invalid"], + scenarioErrors = [], + linter = this.linter; + + if (lodash.isNil(test) || typeof test !== "object") { + throw new TypeError(`Test Scenarios for rule ${ruleName} : Could not find test scenario object`); + } + + requiredScenarios.forEach(scenarioType => { + if (lodash.isNil(test[scenarioType])) { + scenarioErrors.push(`Could not find any ${scenarioType} test scenarios`); + } + }); + + if (scenarioErrors.length > 0) { + throw new Error([ + `Test Scenarios for rule ${ruleName} is invalid:` + ].concat(scenarioErrors).join("\n")); + } + + + linter.defineRule(ruleName, Object.assign({}, rule, { + + // Create a wrapper rule that freezes the `context` properties. + create(context) { + freezeDeeply(context.options); + freezeDeeply(context.settings); + freezeDeeply(context.parserOptions); + + return (typeof rule === "function" ? rule : rule.create)(context); + } + })); + + linter.defineRules(this.rules); + + /** + * Run the rule for the given item + * @param {string|Object} item Item to run the rule against + * @returns {Object} Eslint run result + * @private + */ + function runRuleForItem(item) { + let config = lodash.cloneDeep(testerConfig), + code, filename, output, beforeAST, afterAST; + + if (typeof item === "string") { + code = item; + } else { + code = item.code; + + /* + * Assumes everything on the item is a config except for the + * parameters used by this tester + */ + const itemConfig = lodash.omit(item, RuleTesterParameters); + + /* + * Create the config object from the tester config and this item + * specific configurations. + */ + config = lodash.merge( + config, + itemConfig + ); + } + + if (item.filename) { + filename = item.filename; + } + + if (hasOwnProperty(item, "options")) { + assert(Array.isArray(item.options), "options must be an array"); + config.rules[ruleName] = [1].concat(item.options); + } else { + config.rules[ruleName] = 1; + } + + const schema = getRuleOptionsSchema(rule); + + /* + * Setup AST getters. + * The goal is to check whether or not AST was modified when + * running the rule under test. + */ + linter.defineRule("rule-tester/validate-ast", () => ({ + Program(node) { + beforeAST = cloneDeeplyExcludesParent(node); + }, + "Program:exit"(node) { + afterAST = node; + } + })); + + if (typeof config.parser === "string") { + assert(path.isAbsolute(config.parser), "Parsers provided as strings to RuleTester must be absolute paths"); + } else { + config.parser = espreePath; + } + + linter.defineParser(config.parser, wrapParser(require(config.parser))); + + if (schema) { + ajv.validateSchema(schema); + + if (ajv.errors) { + const errors = ajv.errors.map(error => { + const field = error.dataPath[0] === "." ? error.dataPath.slice(1) : error.dataPath; + + return `\t${field}: ${error.message}`; + }).join("\n"); + + throw new Error([`Schema for rule ${ruleName} is invalid:`, errors]); + } + + /* + * `ajv.validateSchema` checks for errors in the structure of the schema (by comparing the schema against a "meta-schema"), + * and it reports those errors individually. However, there are other types of schema errors that only occur when compiling + * the schema (e.g. using invalid defaults in a schema), and only one of these errors can be reported at a time. As a result, + * the schema is compiled here separately from checking for `validateSchema` errors. + */ + try { + ajv.compile(schema); + } catch (err) { + throw new Error(`Schema for rule ${ruleName} is invalid: ${err.message}`); + } + } + + validate(config, "rule-tester", id => (id === ruleName ? rule : null)); + + // Verify the code. + const messages = linter.verify(code, config, filename); + const fatalErrorMessage = messages.find(m => m.fatal); + + assert(!fatalErrorMessage, `A fatal parsing error occurred: ${fatalErrorMessage && fatalErrorMessage.message}`); + + // Verify if autofix makes a syntax error or not. + if (messages.some(m => m.fix)) { + output = SourceCodeFixer.applyFixes(code, messages).output; + const errorMessageInFix = linter.verify(output, config, filename).find(m => m.fatal); + + assert(!errorMessageInFix, `A fatal parsing error occurred in autofix: ${errorMessageInFix && errorMessageInFix.message}`); + } else { + output = code; + } + + return { + messages, + output, + beforeAST, + afterAST: cloneDeeplyExcludesParent(afterAST) + }; + } + + /** + * Check if the AST was changed + * @param {ASTNode} beforeAST AST node before running + * @param {ASTNode} afterAST AST node after running + * @returns {void} + * @private + */ + function assertASTDidntChange(beforeAST, afterAST) { + if (!lodash.isEqual(beforeAST, afterAST)) { + assert.fail("Rule should not modify AST."); + } + } + + /** + * Check if the template is valid or not + * all valid cases go through this + * @param {string|Object} item Item to run the rule against + * @returns {void} + * @private + */ + function testValidTemplate(item) { + const result = runRuleForItem(item); + const messages = result.messages; + + assert.strictEqual(messages.length, 0, util.format("Should have no errors but had %d: %s", + messages.length, util.inspect(messages))); + + assertASTDidntChange(result.beforeAST, result.afterAST); + } + + /** + * Asserts that the message matches its expected value. If the expected + * value is a regular expression, it is checked against the actual + * value. + * @param {string} actual Actual value + * @param {string|RegExp} expected Expected value + * @returns {void} + * @private + */ + function assertMessageMatches(actual, expected) { + if (expected instanceof RegExp) { + + // assert.js doesn't have a built-in RegExp match function + assert.ok( + expected.test(actual), + `Expected '${actual}' to match ${expected}` + ); + } else { + assert.strictEqual(actual, expected); + } + } + + /** + * Check if the template is invalid or not + * all invalid cases go through this. + * @param {string|Object} item Item to run the rule against + * @returns {void} + * @private + */ + function testInvalidTemplate(item) { + assert.ok(item.errors || item.errors === 0, + `Did not specify errors for an invalid test of ${ruleName}`); + + const ruleHasMetaMessages = hasOwnProperty(rule, "meta") && hasOwnProperty(rule.meta, "messages"); + const friendlyIDList = ruleHasMetaMessages ? `[${Object.keys(rule.meta.messages).map(key => `'${key}'`).join(", ")}]` : null; + + const result = runRuleForItem(item); + const messages = result.messages; + + if (typeof item.errors === "number") { + assert.strictEqual(messages.length, item.errors, util.format("Should have %d error%s but had %d: %s", + item.errors, item.errors === 1 ? "" : "s", messages.length, util.inspect(messages))); + } else { + assert.strictEqual( + messages.length, item.errors.length, + util.format( + "Should have %d error%s but had %d: %s", + item.errors.length, item.errors.length === 1 ? "" : "s", messages.length, util.inspect(messages) + ) + ); + + const hasMessageOfThisRule = messages.some(m => m.ruleId === ruleName); + + for (let i = 0, l = item.errors.length; i < l; i++) { + const error = item.errors[i]; + const message = messages[i]; + + assert(hasMessageOfThisRule, "Error rule name should be the same as the name of the rule being tested"); + + if (typeof error === "string" || error instanceof RegExp) { + + // Just an error message. + assertMessageMatches(message.message, error); + } else if (typeof error === "object" && error !== null) { + + /* + * Error object. + * This may have a message, messageId, data, node type, line, and/or + * column. + */ + + Object.keys(error).forEach(propertyName => { + assert.ok( + errorObjectParameters.has(propertyName), + `Invalid error property name '${propertyName}'. Expected one of ${friendlyErrorObjectParameterList}.` + ); + }); + + if (hasOwnProperty(error, "message")) { + assert.ok(!hasOwnProperty(error, "messageId"), "Error should not specify both 'message' and a 'messageId'."); + assert.ok(!hasOwnProperty(error, "data"), "Error should not specify both 'data' and 'message'."); + assertMessageMatches(message.message, error.message); + } else if (hasOwnProperty(error, "messageId")) { + assert.ok( + ruleHasMetaMessages, + "Error can not use 'messageId' if rule under test doesn't define 'meta.messages'." + ); + if (!hasOwnProperty(rule.meta.messages, error.messageId)) { + assert(false, `Invalid messageId '${error.messageId}'. Expected one of ${friendlyIDList}.`); + } + assert.strictEqual( + message.messageId, + error.messageId, + `messageId '${message.messageId}' does not match expected messageId '${error.messageId}'.` + ); + if (hasOwnProperty(error, "data")) { + + /* + * if data was provided, then directly compare the returned message to a synthetic + * interpolated message using the same message ID and data provided in the test. + * See https://github.com/eslint/eslint/issues/9890 for context. + */ + const unformattedOriginalMessage = rule.meta.messages[error.messageId]; + const rehydratedMessage = interpolate(unformattedOriginalMessage, error.data); + + assert.strictEqual( + message.message, + rehydratedMessage, + `Hydrated message "${rehydratedMessage}" does not match "${message.message}"` + ); + } + } + + assert.ok( + hasOwnProperty(error, "data") ? hasOwnProperty(error, "messageId") : true, + "Error must specify 'messageId' if 'data' is used." + ); + + if (error.type) { + assert.strictEqual(message.nodeType, error.type, `Error type should be ${error.type}, found ${message.nodeType}`); + } + + if (hasOwnProperty(error, "line")) { + assert.strictEqual(message.line, error.line, `Error line should be ${error.line}`); + } + + if (hasOwnProperty(error, "column")) { + assert.strictEqual(message.column, error.column, `Error column should be ${error.column}`); + } + + if (hasOwnProperty(error, "endLine")) { + assert.strictEqual(message.endLine, error.endLine, `Error endLine should be ${error.endLine}`); + } + + if (hasOwnProperty(error, "endColumn")) { + assert.strictEqual(message.endColumn, error.endColumn, `Error endColumn should be ${error.endColumn}`); + } + + if (hasOwnProperty(error, "suggestions")) { + + // Support asserting there are no suggestions + if (!error.suggestions || (Array.isArray(error.suggestions) && error.suggestions.length === 0)) { + if (Array.isArray(message.suggestions) && message.suggestions.length > 0) { + assert.fail(`Error should have no suggestions on error with message: "${message.message}"`); + } + } else { + assert.strictEqual(Array.isArray(message.suggestions), true, `Error should have an array of suggestions. Instead received "${message.suggestions}" on error with message: "${message.message}"`); + assert.strictEqual(message.suggestions.length, error.suggestions.length, `Error should have ${error.suggestions.length} suggestions. Instead found ${message.suggestions.length} suggestions`); + + error.suggestions.forEach((expectedSuggestion, index) => { + assert.ok( + typeof expectedSuggestion === "object" && expectedSuggestion !== null, + "Test suggestion in 'suggestions' array must be an object." + ); + Object.keys(expectedSuggestion).forEach(propertyName => { + assert.ok( + suggestionObjectParameters.has(propertyName), + `Invalid suggestion property name '${propertyName}'. Expected one of ${friendlySuggestionObjectParameterList}.` + ); + }); + + const actualSuggestion = message.suggestions[index]; + const suggestionPrefix = `Error Suggestion at index ${index} :`; + + if (hasOwnProperty(expectedSuggestion, "desc")) { + assert.ok( + !hasOwnProperty(expectedSuggestion, "data"), + `${suggestionPrefix} Test should not specify both 'desc' and 'data'.` + ); + assert.strictEqual( + actualSuggestion.desc, + expectedSuggestion.desc, + `${suggestionPrefix} desc should be "${expectedSuggestion.desc}" but got "${actualSuggestion.desc}" instead.` + ); + } + + if (hasOwnProperty(expectedSuggestion, "messageId")) { + assert.ok( + ruleHasMetaMessages, + `${suggestionPrefix} Test can not use 'messageId' if rule under test doesn't define 'meta.messages'.` + ); + assert.ok( + hasOwnProperty(rule.meta.messages, expectedSuggestion.messageId), + `${suggestionPrefix} Test has invalid messageId '${expectedSuggestion.messageId}', the rule under test allows only one of ${friendlyIDList}.` + ); + assert.strictEqual( + actualSuggestion.messageId, + expectedSuggestion.messageId, + `${suggestionPrefix} messageId should be '${expectedSuggestion.messageId}' but got '${actualSuggestion.messageId}' instead.` + ); + if (hasOwnProperty(expectedSuggestion, "data")) { + const unformattedMetaMessage = rule.meta.messages[expectedSuggestion.messageId]; + const rehydratedDesc = interpolate(unformattedMetaMessage, expectedSuggestion.data); + + assert.strictEqual( + actualSuggestion.desc, + rehydratedDesc, + `${suggestionPrefix} Hydrated test desc "${rehydratedDesc}" does not match received desc "${actualSuggestion.desc}".` + ); + } + } else { + assert.ok( + !hasOwnProperty(expectedSuggestion, "data"), + `${suggestionPrefix} Test must specify 'messageId' if 'data' is used.` + ); + } + + if (hasOwnProperty(expectedSuggestion, "output")) { + const codeWithAppliedSuggestion = SourceCodeFixer.applyFixes(item.code, [actualSuggestion]).output; + + assert.strictEqual(codeWithAppliedSuggestion, expectedSuggestion.output, `Expected the applied suggestion fix to match the test suggestion output for suggestion at index: ${index} on error with message: "${message.message}"`); + } + }); + } + } + } else { + + // Message was an unexpected type + assert.fail(`Error should be a string, object, or RegExp, but found (${util.inspect(message)})`); + } + } + } + + if (hasOwnProperty(item, "output")) { + if (item.output === null) { + assert.strictEqual( + result.output, + item.code, + "Expected no autofixes to be suggested" + ); + } else { + assert.strictEqual(result.output, item.output, "Output is incorrect."); + } + } else { + assert.strictEqual( + result.output, + item.code, + "The rule fixed the code. Please add 'output' property." + ); + } + + assertASTDidntChange(result.beforeAST, result.afterAST); + } + + /* + * This creates a mocha test suite and pipes all supplied info through + * one of the templates above. + */ + RuleTester.describe(ruleName, () => { + RuleTester.describe("valid", () => { + test.valid.forEach(valid => { + RuleTester.it(sanitize(typeof valid === "object" ? valid.code : valid), () => { + testValidTemplate(valid); + }); + }); + }); + + RuleTester.describe("invalid", () => { + test.invalid.forEach(invalid => { + RuleTester.it(sanitize(invalid.code), () => { + testInvalidTemplate(invalid); + }); + }); + }); + }); + } +} + +RuleTester[DESCRIBE] = RuleTester[IT] = null; + +module.exports = RuleTester; diff --git a/eslint/lib/rules/accessor-pairs.js b/eslint/lib/rules/accessor-pairs.js new file mode 100644 index 0000000..0254825 --- /dev/null +++ b/eslint/lib/rules/accessor-pairs.js @@ -0,0 +1,367 @@ +/** + * @fileoverview Rule to flag wrapping non-iife in parens + * @author Gyandeep Singh + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Typedefs +//------------------------------------------------------------------------------ + +/** + * Property name if it can be computed statically, otherwise the list of the tokens of the key node. + * @typedef {string|Token[]} Key + */ + +/** + * Accessor nodes with the same key. + * @typedef {Object} AccessorData + * @property {Key} key Accessor's key + * @property {ASTNode[]} getters List of getter nodes. + * @property {ASTNode[]} setters List of setter nodes. + */ + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Checks whether or not the given lists represent the equal tokens in the same order. + * Tokens are compared by their properties, not by instance. + * @param {Token[]} left First list of tokens. + * @param {Token[]} right Second list of tokens. + * @returns {boolean} `true` if the lists have same tokens. + */ +function areEqualTokenLists(left, right) { + if (left.length !== right.length) { + return false; + } + + for (let i = 0; i < left.length; i++) { + const leftToken = left[i], + rightToken = right[i]; + + if (leftToken.type !== rightToken.type || leftToken.value !== rightToken.value) { + return false; + } + } + + return true; +} + +/** + * Checks whether or not the given keys are equal. + * @param {Key} left First key. + * @param {Key} right Second key. + * @returns {boolean} `true` if the keys are equal. + */ +function areEqualKeys(left, right) { + if (typeof left === "string" && typeof right === "string") { + + // Statically computed names. + return left === right; + } + if (Array.isArray(left) && Array.isArray(right)) { + + // Token lists. + return areEqualTokenLists(left, right); + } + + return false; +} + +/** + * Checks whether or not a given node is of an accessor kind ('get' or 'set'). + * @param {ASTNode} node A node to check. + * @returns {boolean} `true` if the node is of an accessor kind. + */ +function isAccessorKind(node) { + return node.kind === "get" || node.kind === "set"; +} + +/** + * Checks whether or not a given node is an `Identifier` node which was named a given name. + * @param {ASTNode} node A node to check. + * @param {string} name An expected name of the node. + * @returns {boolean} `true` if the node is an `Identifier` node which was named as expected. + */ +function isIdentifier(node, name) { + return node.type === "Identifier" && node.name === name; +} + +/** + * Checks whether or not a given node is an argument of a specified method call. + * @param {ASTNode} node A node to check. + * @param {number} index An expected index of the node in arguments. + * @param {string} object An expected name of the object of the method. + * @param {string} property An expected name of the method. + * @returns {boolean} `true` if the node is an argument of the specified method call. + */ +function isArgumentOfMethodCall(node, index, object, property) { + const parent = node.parent; + + return ( + parent.type === "CallExpression" && + parent.callee.type === "MemberExpression" && + parent.callee.computed === false && + isIdentifier(parent.callee.object, object) && + isIdentifier(parent.callee.property, property) && + parent.arguments[index] === node + ); +} + +/** + * Checks whether or not a given node is a property descriptor. + * @param {ASTNode} node A node to check. + * @returns {boolean} `true` if the node is a property descriptor. + */ +function isPropertyDescriptor(node) { + + // Object.defineProperty(obj, "foo", {set: ...}) + if (isArgumentOfMethodCall(node, 2, "Object", "defineProperty") || + isArgumentOfMethodCall(node, 2, "Reflect", "defineProperty") + ) { + return true; + } + + /* + * Object.defineProperties(obj, {foo: {set: ...}}) + * Object.create(proto, {foo: {set: ...}}) + */ + const grandparent = node.parent.parent; + + return grandparent.type === "ObjectExpression" && ( + isArgumentOfMethodCall(grandparent, 1, "Object", "create") || + isArgumentOfMethodCall(grandparent, 1, "Object", "defineProperties") + ); +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "enforce getter and setter pairs in objects and classes", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/accessor-pairs" + }, + + schema: [{ + type: "object", + properties: { + getWithoutSet: { + type: "boolean", + default: false + }, + setWithoutGet: { + type: "boolean", + default: true + }, + enforceForClassMembers: { + type: "boolean", + default: true + } + }, + additionalProperties: false + }], + + messages: { + missingGetterInPropertyDescriptor: "Getter is not present in property descriptor.", + missingSetterInPropertyDescriptor: "Setter is not present in property descriptor.", + missingGetterInObjectLiteral: "Getter is not present for {{ name }}.", + missingSetterInObjectLiteral: "Setter is not present for {{ name }}.", + missingGetterInClass: "Getter is not present for class {{ name }}.", + missingSetterInClass: "Setter is not present for class {{ name }}." + } + }, + create(context) { + const config = context.options[0] || {}; + const checkGetWithoutSet = config.getWithoutSet === true; + const checkSetWithoutGet = config.setWithoutGet !== false; + const enforceForClassMembers = config.enforceForClassMembers !== false; + const sourceCode = context.getSourceCode(); + + /** + * Reports the given node. + * @param {ASTNode} node The node to report. + * @param {string} messageKind "missingGetter" or "missingSetter". + * @returns {void} + * @private + */ + function report(node, messageKind) { + if (node.type === "Property") { + context.report({ + node, + messageId: `${messageKind}InObjectLiteral`, + loc: astUtils.getFunctionHeadLoc(node.value, sourceCode), + data: { name: astUtils.getFunctionNameWithKind(node.value) } + }); + } else if (node.type === "MethodDefinition") { + context.report({ + node, + messageId: `${messageKind}InClass`, + loc: astUtils.getFunctionHeadLoc(node.value, sourceCode), + data: { name: astUtils.getFunctionNameWithKind(node.value) } + }); + } else { + context.report({ + node, + messageId: `${messageKind}InPropertyDescriptor` + }); + } + } + + /** + * Reports each of the nodes in the given list using the same messageId. + * @param {ASTNode[]} nodes Nodes to report. + * @param {string} messageKind "missingGetter" or "missingSetter". + * @returns {void} + * @private + */ + function reportList(nodes, messageKind) { + for (const node of nodes) { + report(node, messageKind); + } + } + + /** + * Creates a new `AccessorData` object for the given getter or setter node. + * @param {ASTNode} node A getter or setter node. + * @returns {AccessorData} New `AccessorData` object that contains the given node. + * @private + */ + function createAccessorData(node) { + const name = astUtils.getStaticPropertyName(node); + const key = (name !== null) ? name : sourceCode.getTokens(node.key); + + return { + key, + getters: node.kind === "get" ? [node] : [], + setters: node.kind === "set" ? [node] : [] + }; + } + + /** + * Merges the given `AccessorData` object into the given accessors list. + * @param {AccessorData[]} accessors The list to merge into. + * @param {AccessorData} accessorData The object to merge. + * @returns {AccessorData[]} The same instance with the merged object. + * @private + */ + function mergeAccessorData(accessors, accessorData) { + const equalKeyElement = accessors.find(a => areEqualKeys(a.key, accessorData.key)); + + if (equalKeyElement) { + equalKeyElement.getters.push(...accessorData.getters); + equalKeyElement.setters.push(...accessorData.setters); + } else { + accessors.push(accessorData); + } + + return accessors; + } + + /** + * Checks accessor pairs in the given list of nodes. + * @param {ASTNode[]} nodes The list to check. + * @returns {void} + * @private + */ + function checkList(nodes) { + const accessors = nodes + .filter(isAccessorKind) + .map(createAccessorData) + .reduce(mergeAccessorData, []); + + for (const { getters, setters } of accessors) { + if (checkSetWithoutGet && setters.length && !getters.length) { + reportList(setters, "missingGetter"); + } + if (checkGetWithoutSet && getters.length && !setters.length) { + reportList(getters, "missingSetter"); + } + } + } + + /** + * Checks accessor pairs in an object literal. + * @param {ASTNode} node `ObjectExpression` node to check. + * @returns {void} + * @private + */ + function checkObjectLiteral(node) { + checkList(node.properties.filter(p => p.type === "Property")); + } + + /** + * Checks accessor pairs in a property descriptor. + * @param {ASTNode} node Property descriptor `ObjectExpression` node to check. + * @returns {void} + * @private + */ + function checkPropertyDescriptor(node) { + const namesToCheck = node.properties + .filter(p => p.type === "Property" && p.kind === "init" && !p.computed) + .map(({ key }) => key.name); + + const hasGetter = namesToCheck.includes("get"); + const hasSetter = namesToCheck.includes("set"); + + if (checkSetWithoutGet && hasSetter && !hasGetter) { + report(node, "missingGetter"); + } + if (checkGetWithoutSet && hasGetter && !hasSetter) { + report(node, "missingSetter"); + } + } + + /** + * Checks the given object expression as an object literal and as a possible property descriptor. + * @param {ASTNode} node `ObjectExpression` node to check. + * @returns {void} + * @private + */ + function checkObjectExpression(node) { + checkObjectLiteral(node); + if (isPropertyDescriptor(node)) { + checkPropertyDescriptor(node); + } + } + + /** + * Checks the given class body. + * @param {ASTNode} node `ClassBody` node to check. + * @returns {void} + * @private + */ + function checkClassBody(node) { + const methodDefinitions = node.body.filter(m => m.type === "MethodDefinition"); + + checkList(methodDefinitions.filter(m => m.static)); + checkList(methodDefinitions.filter(m => !m.static)); + } + + const listeners = {}; + + if (checkSetWithoutGet || checkGetWithoutSet) { + listeners.ObjectExpression = checkObjectExpression; + if (enforceForClassMembers) { + listeners.ClassBody = checkClassBody; + } + } + + return listeners; + } +}; diff --git a/eslint/lib/rules/array-bracket-newline.js b/eslint/lib/rules/array-bracket-newline.js new file mode 100644 index 0000000..b4b4dd4 --- /dev/null +++ b/eslint/lib/rules/array-bracket-newline.js @@ -0,0 +1,258 @@ +/** + * @fileoverview Rule to enforce linebreaks after open and before close array brackets + * @author Jan Peer Stöcklmair + */ + +"use strict"; + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "enforce linebreaks after opening and before closing array brackets", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/array-bracket-newline" + }, + + fixable: "whitespace", + + schema: [ + { + oneOf: [ + { + enum: ["always", "never", "consistent"] + }, + { + type: "object", + properties: { + multiline: { + type: "boolean" + }, + minItems: { + type: ["integer", "null"], + minimum: 0 + } + }, + additionalProperties: false + } + ] + } + ], + + messages: { + unexpectedOpeningLinebreak: "There should be no linebreak after '['.", + unexpectedClosingLinebreak: "There should be no linebreak before ']'.", + missingOpeningLinebreak: "A linebreak is required after '['.", + missingClosingLinebreak: "A linebreak is required before ']'." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + + //---------------------------------------------------------------------- + // Helpers + //---------------------------------------------------------------------- + + /** + * Normalizes a given option value. + * @param {string|Object|undefined} option An option value to parse. + * @returns {{multiline: boolean, minItems: number}} Normalized option object. + */ + function normalizeOptionValue(option) { + let consistent = false; + let multiline = false; + let minItems = 0; + + if (option) { + if (option === "consistent") { + consistent = true; + minItems = Number.POSITIVE_INFINITY; + } else if (option === "always" || option.minItems === 0) { + minItems = 0; + } else if (option === "never") { + minItems = Number.POSITIVE_INFINITY; + } else { + multiline = Boolean(option.multiline); + minItems = option.minItems || Number.POSITIVE_INFINITY; + } + } else { + consistent = false; + multiline = true; + minItems = Number.POSITIVE_INFINITY; + } + + return { consistent, multiline, minItems }; + } + + /** + * Normalizes a given option value. + * @param {string|Object|undefined} options An option value to parse. + * @returns {{ArrayExpression: {multiline: boolean, minItems: number}, ArrayPattern: {multiline: boolean, minItems: number}}} Normalized option object. + */ + function normalizeOptions(options) { + const value = normalizeOptionValue(options); + + return { ArrayExpression: value, ArrayPattern: value }; + } + + /** + * Reports that there shouldn't be a linebreak after the first token + * @param {ASTNode} node The node to report in the event of an error. + * @param {Token} token The token to use for the report. + * @returns {void} + */ + function reportNoBeginningLinebreak(node, token) { + context.report({ + node, + loc: token.loc, + messageId: "unexpectedOpeningLinebreak", + fix(fixer) { + const nextToken = sourceCode.getTokenAfter(token, { includeComments: true }); + + if (astUtils.isCommentToken(nextToken)) { + return null; + } + + return fixer.removeRange([token.range[1], nextToken.range[0]]); + } + }); + } + + /** + * Reports that there shouldn't be a linebreak before the last token + * @param {ASTNode} node The node to report in the event of an error. + * @param {Token} token The token to use for the report. + * @returns {void} + */ + function reportNoEndingLinebreak(node, token) { + context.report({ + node, + loc: token.loc, + messageId: "unexpectedClosingLinebreak", + fix(fixer) { + const previousToken = sourceCode.getTokenBefore(token, { includeComments: true }); + + if (astUtils.isCommentToken(previousToken)) { + return null; + } + + return fixer.removeRange([previousToken.range[1], token.range[0]]); + } + }); + } + + /** + * Reports that there should be a linebreak after the first token + * @param {ASTNode} node The node to report in the event of an error. + * @param {Token} token The token to use for the report. + * @returns {void} + */ + function reportRequiredBeginningLinebreak(node, token) { + context.report({ + node, + loc: token.loc, + messageId: "missingOpeningLinebreak", + fix(fixer) { + return fixer.insertTextAfter(token, "\n"); + } + }); + } + + /** + * Reports that there should be a linebreak before the last token + * @param {ASTNode} node The node to report in the event of an error. + * @param {Token} token The token to use for the report. + * @returns {void} + */ + function reportRequiredEndingLinebreak(node, token) { + context.report({ + node, + loc: token.loc, + messageId: "missingClosingLinebreak", + fix(fixer) { + return fixer.insertTextBefore(token, "\n"); + } + }); + } + + /** + * Reports a given node if it violated this rule. + * @param {ASTNode} node A node to check. This is an ArrayExpression node or an ArrayPattern node. + * @returns {void} + */ + function check(node) { + const elements = node.elements; + const normalizedOptions = normalizeOptions(context.options[0]); + const options = normalizedOptions[node.type]; + const openBracket = sourceCode.getFirstToken(node); + const closeBracket = sourceCode.getLastToken(node); + const firstIncComment = sourceCode.getTokenAfter(openBracket, { includeComments: true }); + const lastIncComment = sourceCode.getTokenBefore(closeBracket, { includeComments: true }); + const first = sourceCode.getTokenAfter(openBracket); + const last = sourceCode.getTokenBefore(closeBracket); + + const needsLinebreaks = ( + elements.length >= options.minItems || + ( + options.multiline && + elements.length > 0 && + firstIncComment.loc.start.line !== lastIncComment.loc.end.line + ) || + ( + elements.length === 0 && + firstIncComment.type === "Block" && + firstIncComment.loc.start.line !== lastIncComment.loc.end.line && + firstIncComment === lastIncComment + ) || + ( + options.consistent && + openBracket.loc.end.line !== first.loc.start.line + ) + ); + + /* + * Use tokens or comments to check multiline or not. + * But use only tokens to check whether linebreaks are needed. + * This allows: + * var arr = [ // eslint-disable-line foo + * 'a' + * ] + */ + + if (needsLinebreaks) { + if (astUtils.isTokenOnSameLine(openBracket, first)) { + reportRequiredBeginningLinebreak(node, openBracket); + } + if (astUtils.isTokenOnSameLine(last, closeBracket)) { + reportRequiredEndingLinebreak(node, closeBracket); + } + } else { + if (!astUtils.isTokenOnSameLine(openBracket, first)) { + reportNoBeginningLinebreak(node, openBracket); + } + if (!astUtils.isTokenOnSameLine(last, closeBracket)) { + reportNoEndingLinebreak(node, closeBracket); + } + } + } + + //---------------------------------------------------------------------- + // Public + //---------------------------------------------------------------------- + + return { + ArrayPattern: check, + ArrayExpression: check + }; + } +}; diff --git a/eslint/lib/rules/array-bracket-spacing.js b/eslint/lib/rules/array-bracket-spacing.js new file mode 100644 index 0000000..c2b77a6 --- /dev/null +++ b/eslint/lib/rules/array-bracket-spacing.js @@ -0,0 +1,241 @@ +/** + * @fileoverview Disallows or enforces spaces inside of array brackets. + * @author Jamund Ferguson + */ +"use strict"; + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "enforce consistent spacing inside array brackets", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/array-bracket-spacing" + }, + + fixable: "whitespace", + + schema: [ + { + enum: ["always", "never"] + }, + { + type: "object", + properties: { + singleValue: { + type: "boolean" + }, + objectsInArrays: { + type: "boolean" + }, + arraysInArrays: { + type: "boolean" + } + }, + additionalProperties: false + } + ], + + messages: { + unexpectedSpaceAfter: "There should be no space after '{{tokenValue}}'.", + unexpectedSpaceBefore: "There should be no space before '{{tokenValue}}'.", + missingSpaceAfter: "A space is required after '{{tokenValue}}'.", + missingSpaceBefore: "A space is required before '{{tokenValue}}'." + } + }, + create(context) { + const spaced = context.options[0] === "always", + sourceCode = context.getSourceCode(); + + /** + * Determines whether an option is set, relative to the spacing option. + * If spaced is "always", then check whether option is set to false. + * If spaced is "never", then check whether option is set to true. + * @param {Object} option The option to exclude. + * @returns {boolean} Whether or not the property is excluded. + */ + function isOptionSet(option) { + return context.options[1] ? context.options[1][option] === !spaced : false; + } + + const options = { + spaced, + singleElementException: isOptionSet("singleValue"), + objectsInArraysException: isOptionSet("objectsInArrays"), + arraysInArraysException: isOptionSet("arraysInArrays") + }; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Reports that there shouldn't be a space after the first token + * @param {ASTNode} node The node to report in the event of an error. + * @param {Token} token The token to use for the report. + * @returns {void} + */ + function reportNoBeginningSpace(node, token) { + const nextToken = sourceCode.getTokenAfter(token); + + context.report({ + node, + loc: { start: token.loc.end, end: nextToken.loc.start }, + messageId: "unexpectedSpaceAfter", + data: { + tokenValue: token.value + }, + fix(fixer) { + return fixer.removeRange([token.range[1], nextToken.range[0]]); + } + }); + } + + /** + * Reports that there shouldn't be a space before the last token + * @param {ASTNode} node The node to report in the event of an error. + * @param {Token} token The token to use for the report. + * @returns {void} + */ + function reportNoEndingSpace(node, token) { + const previousToken = sourceCode.getTokenBefore(token); + + context.report({ + node, + loc: { start: previousToken.loc.end, end: token.loc.start }, + messageId: "unexpectedSpaceBefore", + data: { + tokenValue: token.value + }, + fix(fixer) { + return fixer.removeRange([previousToken.range[1], token.range[0]]); + } + }); + } + + /** + * Reports that there should be a space after the first token + * @param {ASTNode} node The node to report in the event of an error. + * @param {Token} token The token to use for the report. + * @returns {void} + */ + function reportRequiredBeginningSpace(node, token) { + context.report({ + node, + loc: token.loc, + messageId: "missingSpaceAfter", + data: { + tokenValue: token.value + }, + fix(fixer) { + return fixer.insertTextAfter(token, " "); + } + }); + } + + /** + * Reports that there should be a space before the last token + * @param {ASTNode} node The node to report in the event of an error. + * @param {Token} token The token to use for the report. + * @returns {void} + */ + function reportRequiredEndingSpace(node, token) { + context.report({ + node, + loc: token.loc, + messageId: "missingSpaceBefore", + data: { + tokenValue: token.value + }, + fix(fixer) { + return fixer.insertTextBefore(token, " "); + } + }); + } + + /** + * Determines if a node is an object type + * @param {ASTNode} node The node to check. + * @returns {boolean} Whether or not the node is an object type. + */ + function isObjectType(node) { + return node && (node.type === "ObjectExpression" || node.type === "ObjectPattern"); + } + + /** + * Determines if a node is an array type + * @param {ASTNode} node The node to check. + * @returns {boolean} Whether or not the node is an array type. + */ + function isArrayType(node) { + return node && (node.type === "ArrayExpression" || node.type === "ArrayPattern"); + } + + /** + * Validates the spacing around array brackets + * @param {ASTNode} node The node we're checking for spacing + * @returns {void} + */ + function validateArraySpacing(node) { + if (options.spaced && node.elements.length === 0) { + return; + } + + const first = sourceCode.getFirstToken(node), + second = sourceCode.getFirstToken(node, 1), + last = node.typeAnnotation + ? sourceCode.getTokenBefore(node.typeAnnotation) + : sourceCode.getLastToken(node), + penultimate = sourceCode.getTokenBefore(last), + firstElement = node.elements[0], + lastElement = node.elements[node.elements.length - 1]; + + const openingBracketMustBeSpaced = + options.objectsInArraysException && isObjectType(firstElement) || + options.arraysInArraysException && isArrayType(firstElement) || + options.singleElementException && node.elements.length === 1 + ? !options.spaced : options.spaced; + + const closingBracketMustBeSpaced = + options.objectsInArraysException && isObjectType(lastElement) || + options.arraysInArraysException && isArrayType(lastElement) || + options.singleElementException && node.elements.length === 1 + ? !options.spaced : options.spaced; + + if (astUtils.isTokenOnSameLine(first, second)) { + if (openingBracketMustBeSpaced && !sourceCode.isSpaceBetweenTokens(first, second)) { + reportRequiredBeginningSpace(node, first); + } + if (!openingBracketMustBeSpaced && sourceCode.isSpaceBetweenTokens(first, second)) { + reportNoBeginningSpace(node, first); + } + } + + if (first !== penultimate && astUtils.isTokenOnSameLine(penultimate, last)) { + if (closingBracketMustBeSpaced && !sourceCode.isSpaceBetweenTokens(penultimate, last)) { + reportRequiredEndingSpace(node, last); + } + if (!closingBracketMustBeSpaced && sourceCode.isSpaceBetweenTokens(penultimate, last)) { + reportNoEndingSpace(node, last); + } + } + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + ArrayPattern: validateArraySpacing, + ArrayExpression: validateArraySpacing + }; + } +}; diff --git a/eslint/lib/rules/array-callback-return.js b/eslint/lib/rules/array-callback-return.js new file mode 100644 index 0000000..eb38965 --- /dev/null +++ b/eslint/lib/rules/array-callback-return.js @@ -0,0 +1,302 @@ +/** + * @fileoverview Rule to enforce return statements in callbacks of array's methods + * @author Toru Nagashima + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const lodash = require("lodash"); + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/u; +const TARGET_METHODS = /^(?:every|filter|find(?:Index)?|flatMap|forEach|map|reduce(?:Right)?|some|sort)$/u; + +/** + * Checks a given code path segment is reachable. + * @param {CodePathSegment} segment A segment to check. + * @returns {boolean} `true` if the segment is reachable. + */ +function isReachable(segment) { + return segment.reachable; +} + +/** + * Gets a readable location. + * + * - FunctionExpression -> the function name or `function` keyword. + * - ArrowFunctionExpression -> `=>` token. + * @param {ASTNode} node A function node to get. + * @param {SourceCode} sourceCode A source code to get tokens. + * @returns {ASTNode|Token} The node or the token of a location. + */ +function getLocation(node, sourceCode) { + if (node.type === "ArrowFunctionExpression") { + return sourceCode.getTokenBefore(node.body); + } + return node.id || node; +} + +/** + * Checks a given node is a MemberExpression node which has the specified name's + * property. + * @param {ASTNode} node A node to check. + * @returns {boolean} `true` if the node is a MemberExpression node which has + * the specified name's property + */ +function isTargetMethod(node) { + return ( + node.type === "MemberExpression" && + TARGET_METHODS.test(astUtils.getStaticPropertyName(node) || "") + ); +} + +/** + * Checks whether or not a given node is a function expression which is the + * callback of an array method, returning the method name. + * @param {ASTNode} node A node to check. This is one of + * FunctionExpression or ArrowFunctionExpression. + * @returns {string} The method name if the node is a callback method, + * null otherwise. + */ +function getArrayMethodName(node) { + let currentNode = node; + + while (currentNode) { + const parent = currentNode.parent; + + switch (parent.type) { + + /* + * Looks up the destination. e.g., + * foo.every(nativeFoo || function foo() { ... }); + */ + case "LogicalExpression": + case "ConditionalExpression": + currentNode = parent; + break; + + /* + * If the upper function is IIFE, checks the destination of the return value. + * e.g. + * foo.every((function() { + * // setup... + * return function callback() { ... }; + * })()); + */ + case "ReturnStatement": { + const func = astUtils.getUpperFunction(parent); + + if (func === null || !astUtils.isCallee(func)) { + return null; + } + currentNode = func.parent; + break; + } + + /* + * e.g. + * Array.from([], function() {}); + * list.every(function() {}); + */ + case "CallExpression": + if (astUtils.isArrayFromMethod(parent.callee)) { + if ( + parent.arguments.length >= 2 && + parent.arguments[1] === currentNode + ) { + return "from"; + } + } + if (isTargetMethod(parent.callee)) { + if ( + parent.arguments.length >= 1 && + parent.arguments[0] === currentNode + ) { + return astUtils.getStaticPropertyName(parent.callee); + } + } + return null; + + // Otherwise this node is not target. + default: + return null; + } + } + + /* istanbul ignore next: unreachable */ + return null; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "enforce `return` statements in callbacks of array methods", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/array-callback-return" + }, + + schema: [ + { + type: "object", + properties: { + allowImplicit: { + type: "boolean", + default: false + }, + checkForEach: { + type: "boolean", + default: false + } + }, + additionalProperties: false + } + ], + + messages: { + expectedAtEnd: "Expected to return a value at the end of {{name}}.", + expectedInside: "Expected to return a value in {{name}}.", + expectedReturnValue: "{{name}} expected a return value.", + expectedNoReturnValue: "{{name}} did not expect a return value." + } + }, + + create(context) { + + const options = context.options[0] || { allowImplicit: false, checkForEach: false }; + + let funcInfo = { + arrayMethodName: null, + upper: null, + codePath: null, + hasReturn: false, + shouldCheck: false, + node: null + }; + + /** + * Checks whether or not the last code path segment is reachable. + * Then reports this function if the segment is reachable. + * + * If the last code path segment is reachable, there are paths which are not + * returned or thrown. + * @param {ASTNode} node A node to check. + * @returns {void} + */ + function checkLastSegment(node) { + + if (!funcInfo.shouldCheck) { + return; + } + + let messageId = null; + + if (funcInfo.arrayMethodName === "forEach") { + if (options.checkForEach && node.type === "ArrowFunctionExpression" && node.expression) { + messageId = "expectedNoReturnValue"; + } + } else { + if (node.body.type === "BlockStatement" && funcInfo.codePath.currentSegments.some(isReachable)) { + messageId = funcInfo.hasReturn ? "expectedAtEnd" : "expectedInside"; + } + } + + if (messageId) { + let name = astUtils.getFunctionNameWithKind(funcInfo.node); + + name = messageId === "expectedNoReturnValue" ? lodash.upperFirst(name) : name; + context.report({ + node, + loc: getLocation(node, context.getSourceCode()).loc.start, + messageId, + data: { name } + }); + } + } + + return { + + // Stacks this function's information. + onCodePathStart(codePath, node) { + + let methodName = null; + + if (TARGET_NODE_TYPE.test(node.type)) { + methodName = getArrayMethodName(node); + } + + funcInfo = { + arrayMethodName: methodName, + upper: funcInfo, + codePath, + hasReturn: false, + shouldCheck: + methodName && + !node.async && + !node.generator, + node + }; + }, + + // Pops this function's information. + onCodePathEnd() { + funcInfo = funcInfo.upper; + }, + + // Checks the return statement is valid. + ReturnStatement(node) { + + if (!funcInfo.shouldCheck) { + return; + } + + funcInfo.hasReturn = true; + + let messageId = null; + + if (funcInfo.arrayMethodName === "forEach") { + + // if checkForEach: true, returning a value at any path inside a forEach is not allowed + if (options.checkForEach && node.argument) { + messageId = "expectedNoReturnValue"; + } + } else { + + // if allowImplicit: false, should also check node.argument + if (!options.allowImplicit && !node.argument) { + messageId = "expectedReturnValue"; + } + } + + if (messageId) { + context.report({ + node, + messageId, + data: { + name: lodash.upperFirst(astUtils.getFunctionNameWithKind(funcInfo.node)) + } + }); + } + }, + + // Reports a given function if the last path is reachable. + "FunctionExpression:exit": checkLastSegment, + "ArrowFunctionExpression:exit": checkLastSegment + }; + } +}; diff --git a/eslint/lib/rules/array-element-newline.js b/eslint/lib/rules/array-element-newline.js new file mode 100644 index 0000000..b7a9678 --- /dev/null +++ b/eslint/lib/rules/array-element-newline.js @@ -0,0 +1,301 @@ +/** + * @fileoverview Rule to enforce line breaks after each array element + * @author Jan Peer Stöcklmair + */ + +"use strict"; + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "enforce line breaks after each array element", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/array-element-newline" + }, + + fixable: "whitespace", + + schema: { + definitions: { + basicConfig: { + oneOf: [ + { + enum: ["always", "never", "consistent"] + }, + { + type: "object", + properties: { + multiline: { + type: "boolean" + }, + minItems: { + type: ["integer", "null"], + minimum: 0 + } + }, + additionalProperties: false + } + ] + } + }, + items: [ + { + oneOf: [ + { + $ref: "#/definitions/basicConfig" + }, + { + type: "object", + properties: { + ArrayExpression: { + $ref: "#/definitions/basicConfig" + }, + ArrayPattern: { + $ref: "#/definitions/basicConfig" + } + }, + additionalProperties: false, + minProperties: 1 + } + ] + } + ] + }, + + messages: { + unexpectedLineBreak: "There should be no linebreak here.", + missingLineBreak: "There should be a linebreak after this element." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + //---------------------------------------------------------------------- + // Helpers + //---------------------------------------------------------------------- + + /** + * Normalizes a given option value. + * @param {string|Object|undefined} providedOption An option value to parse. + * @returns {{multiline: boolean, minItems: number}} Normalized option object. + */ + function normalizeOptionValue(providedOption) { + let consistent = false; + let multiline = false; + let minItems; + + const option = providedOption || "always"; + + if (!option || option === "always" || option.minItems === 0) { + minItems = 0; + } else if (option === "never") { + minItems = Number.POSITIVE_INFINITY; + } else if (option === "consistent") { + consistent = true; + minItems = Number.POSITIVE_INFINITY; + } else { + multiline = Boolean(option.multiline); + minItems = option.minItems || Number.POSITIVE_INFINITY; + } + + return { consistent, multiline, minItems }; + } + + /** + * Normalizes a given option value. + * @param {string|Object|undefined} options An option value to parse. + * @returns {{ArrayExpression: {multiline: boolean, minItems: number}, ArrayPattern: {multiline: boolean, minItems: number}}} Normalized option object. + */ + function normalizeOptions(options) { + if (options && (options.ArrayExpression || options.ArrayPattern)) { + let expressionOptions, patternOptions; + + if (options.ArrayExpression) { + expressionOptions = normalizeOptionValue(options.ArrayExpression); + } + + if (options.ArrayPattern) { + patternOptions = normalizeOptionValue(options.ArrayPattern); + } + + return { ArrayExpression: expressionOptions, ArrayPattern: patternOptions }; + } + + const value = normalizeOptionValue(options); + + return { ArrayExpression: value, ArrayPattern: value }; + } + + /** + * Reports that there shouldn't be a line break after the first token + * @param {Token} token The token to use for the report. + * @returns {void} + */ + function reportNoLineBreak(token) { + const tokenBefore = sourceCode.getTokenBefore(token, { includeComments: true }); + + context.report({ + loc: { + start: tokenBefore.loc.end, + end: token.loc.start + }, + messageId: "unexpectedLineBreak", + fix(fixer) { + if (astUtils.isCommentToken(tokenBefore)) { + return null; + } + + if (!astUtils.isTokenOnSameLine(tokenBefore, token)) { + return fixer.replaceTextRange([tokenBefore.range[1], token.range[0]], " "); + } + + /* + * This will check if the comma is on the same line as the next element + * Following array: + * [ + * 1 + * , 2 + * , 3 + * ] + * + * will be fixed to: + * [ + * 1, 2, 3 + * ] + */ + const twoTokensBefore = sourceCode.getTokenBefore(tokenBefore, { includeComments: true }); + + if (astUtils.isCommentToken(twoTokensBefore)) { + return null; + } + + return fixer.replaceTextRange([twoTokensBefore.range[1], tokenBefore.range[0]], ""); + + } + }); + } + + /** + * Reports that there should be a line break after the first token + * @param {Token} token The token to use for the report. + * @returns {void} + */ + function reportRequiredLineBreak(token) { + const tokenBefore = sourceCode.getTokenBefore(token, { includeComments: true }); + + context.report({ + loc: { + start: tokenBefore.loc.end, + end: token.loc.start + }, + messageId: "missingLineBreak", + fix(fixer) { + return fixer.replaceTextRange([tokenBefore.range[1], token.range[0]], "\n"); + } + }); + } + + /** + * Reports a given node if it violated this rule. + * @param {ASTNode} node A node to check. This is an ObjectExpression node or an ObjectPattern node. + * @returns {void} + */ + function check(node) { + const elements = node.elements; + const normalizedOptions = normalizeOptions(context.options[0]); + const options = normalizedOptions[node.type]; + + if (!options) { + return; + } + + let elementBreak = false; + + /* + * MULTILINE: true + * loop through every element and check + * if at least one element has linebreaks inside + * this ensures that following is not valid (due to elements are on the same line): + * + * [ + * 1, + * 2, + * 3 + * ] + */ + if (options.multiline) { + elementBreak = elements + .filter(element => element !== null) + .some(element => element.loc.start.line !== element.loc.end.line); + } + + const linebreaksCount = node.elements.map((element, i) => { + const previousElement = elements[i - 1]; + + if (i === 0 || element === null || previousElement === null) { + return false; + } + + const commaToken = sourceCode.getFirstTokenBetween(previousElement, element, astUtils.isCommaToken); + const lastTokenOfPreviousElement = sourceCode.getTokenBefore(commaToken); + const firstTokenOfCurrentElement = sourceCode.getTokenAfter(commaToken); + + return !astUtils.isTokenOnSameLine(lastTokenOfPreviousElement, firstTokenOfCurrentElement); + }).filter(isBreak => isBreak === true).length; + + const needsLinebreaks = ( + elements.length >= options.minItems || + ( + options.multiline && + elementBreak + ) || + ( + options.consistent && + linebreaksCount > 0 && + linebreaksCount < node.elements.length + ) + ); + + elements.forEach((element, i) => { + const previousElement = elements[i - 1]; + + if (i === 0 || element === null || previousElement === null) { + return; + } + + const commaToken = sourceCode.getFirstTokenBetween(previousElement, element, astUtils.isCommaToken); + const lastTokenOfPreviousElement = sourceCode.getTokenBefore(commaToken); + const firstTokenOfCurrentElement = sourceCode.getTokenAfter(commaToken); + + if (needsLinebreaks) { + if (astUtils.isTokenOnSameLine(lastTokenOfPreviousElement, firstTokenOfCurrentElement)) { + reportRequiredLineBreak(firstTokenOfCurrentElement); + } + } else { + if (!astUtils.isTokenOnSameLine(lastTokenOfPreviousElement, firstTokenOfCurrentElement)) { + reportNoLineBreak(firstTokenOfCurrentElement); + } + } + }); + } + + //---------------------------------------------------------------------- + // Public + //---------------------------------------------------------------------- + + return { + ArrayPattern: check, + ArrayExpression: check + }; + } +}; diff --git a/eslint/lib/rules/arrow-body-style.js b/eslint/lib/rules/arrow-body-style.js new file mode 100644 index 0000000..9d5c77d --- /dev/null +++ b/eslint/lib/rules/arrow-body-style.js @@ -0,0 +1,251 @@ +/** + * @fileoverview Rule to require braces in arrow function body. + * @author Alberto Rodríguez + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "require braces around arrow function bodies", + category: "ECMAScript 6", + recommended: false, + url: "https://eslint.org/docs/rules/arrow-body-style" + }, + + schema: { + anyOf: [ + { + type: "array", + items: [ + { + enum: ["always", "never"] + } + ], + minItems: 0, + maxItems: 1 + }, + { + type: "array", + items: [ + { + enum: ["as-needed"] + }, + { + type: "object", + properties: { + requireReturnForObjectLiteral: { type: "boolean" } + }, + additionalProperties: false + } + ], + minItems: 0, + maxItems: 2 + } + ] + }, + + fixable: "code", + + messages: { + unexpectedOtherBlock: "Unexpected block statement surrounding arrow body.", + unexpectedEmptyBlock: "Unexpected block statement surrounding arrow body; put a value of `undefined` immediately after the `=>`.", + unexpectedObjectBlock: "Unexpected block statement surrounding arrow body; parenthesize the returned value and move it immediately after the `=>`.", + unexpectedSingleBlock: "Unexpected block statement surrounding arrow body; move the returned value immediately after the `=>`.", + expectedBlock: "Expected block statement surrounding arrow body." + } + }, + + create(context) { + const options = context.options; + const always = options[0] === "always"; + const asNeeded = !options[0] || options[0] === "as-needed"; + const never = options[0] === "never"; + const requireReturnForObjectLiteral = options[1] && options[1].requireReturnForObjectLiteral; + const sourceCode = context.getSourceCode(); + + /** + * Checks whether the given node has ASI problem or not. + * @param {Token} token The token to check. + * @returns {boolean} `true` if it changes semantics if `;` or `}` followed by the token are removed. + */ + function hasASIProblem(token) { + return token && token.type === "Punctuator" && /^[([/`+-]/u.test(token.value); + } + + /** + * Gets the closing parenthesis which is the pair of the given opening parenthesis. + * @param {Token} token The opening parenthesis token to get. + * @returns {Token} The found closing parenthesis token. + */ + function findClosingParen(token) { + let node = sourceCode.getNodeByRangeIndex(token.range[0]); + + while (!astUtils.isParenthesised(sourceCode, node)) { + node = node.parent; + } + return sourceCode.getTokenAfter(node); + } + + /** + * Determines whether a arrow function body needs braces + * @param {ASTNode} node The arrow function node. + * @returns {void} + */ + function validate(node) { + const arrowBody = node.body; + + if (arrowBody.type === "BlockStatement") { + const blockBody = arrowBody.body; + + if (blockBody.length !== 1 && !never) { + return; + } + + if (asNeeded && requireReturnForObjectLiteral && blockBody[0].type === "ReturnStatement" && + blockBody[0].argument && blockBody[0].argument.type === "ObjectExpression") { + return; + } + + if (never || asNeeded && blockBody[0].type === "ReturnStatement") { + let messageId; + + if (blockBody.length === 0) { + messageId = "unexpectedEmptyBlock"; + } else if (blockBody.length > 1) { + messageId = "unexpectedOtherBlock"; + } else if (blockBody[0].argument === null) { + messageId = "unexpectedSingleBlock"; + } else if (astUtils.isOpeningBraceToken(sourceCode.getFirstToken(blockBody[0], { skip: 1 }))) { + messageId = "unexpectedObjectBlock"; + } else { + messageId = "unexpectedSingleBlock"; + } + + context.report({ + node, + loc: arrowBody.loc.start, + messageId, + fix(fixer) { + const fixes = []; + + if (blockBody.length !== 1 || + blockBody[0].type !== "ReturnStatement" || + !blockBody[0].argument || + hasASIProblem(sourceCode.getTokenAfter(arrowBody)) + ) { + return fixes; + } + + const openingBrace = sourceCode.getFirstToken(arrowBody); + const closingBrace = sourceCode.getLastToken(arrowBody); + const firstValueToken = sourceCode.getFirstToken(blockBody[0], 1); + const lastValueToken = sourceCode.getLastToken(blockBody[0]); + const commentsExist = + sourceCode.commentsExistBetween(openingBrace, firstValueToken) || + sourceCode.commentsExistBetween(lastValueToken, closingBrace); + + /* + * Remove tokens around the return value. + * If comments don't exist, remove extra spaces as well. + */ + if (commentsExist) { + fixes.push( + fixer.remove(openingBrace), + fixer.remove(closingBrace), + fixer.remove(sourceCode.getTokenAfter(openingBrace)) // return keyword + ); + } else { + fixes.push( + fixer.removeRange([openingBrace.range[0], firstValueToken.range[0]]), + fixer.removeRange([lastValueToken.range[1], closingBrace.range[1]]) + ); + } + + /* + * If the first token of the reutrn value is `{` or the return value is a sequence expression, + * enclose the return value by parentheses to avoid syntax error. + */ + if (astUtils.isOpeningBraceToken(firstValueToken) || blockBody[0].argument.type === "SequenceExpression") { + fixes.push( + fixer.insertTextBefore(firstValueToken, "("), + fixer.insertTextAfter(lastValueToken, ")") + ); + } + + /* + * If the last token of the return statement is semicolon, remove it. + * Non-block arrow body is an expression, not a statement. + */ + if (astUtils.isSemicolonToken(lastValueToken)) { + fixes.push(fixer.remove(lastValueToken)); + } + + return fixes; + } + }); + } + } else { + if (always || (asNeeded && requireReturnForObjectLiteral && arrowBody.type === "ObjectExpression")) { + context.report({ + node, + loc: arrowBody.loc.start, + messageId: "expectedBlock", + fix(fixer) { + const fixes = []; + const arrowToken = sourceCode.getTokenBefore(arrowBody, astUtils.isArrowToken); + const [firstTokenAfterArrow, secondTokenAfterArrow] = sourceCode.getTokensAfter(arrowToken, { count: 2 }); + const lastToken = sourceCode.getLastToken(node); + const isParenthesisedObjectLiteral = + astUtils.isOpeningParenToken(firstTokenAfterArrow) && + astUtils.isOpeningBraceToken(secondTokenAfterArrow); + + // If the value is object literal, remove parentheses which were forced by syntax. + if (isParenthesisedObjectLiteral) { + const openingParenToken = firstTokenAfterArrow; + const openingBraceToken = secondTokenAfterArrow; + + if (astUtils.isTokenOnSameLine(openingParenToken, openingBraceToken)) { + fixes.push(fixer.replaceText(openingParenToken, "{return ")); + } else { + + // Avoid ASI + fixes.push( + fixer.replaceText(openingParenToken, "{"), + fixer.insertTextBefore(openingBraceToken, "return ") + ); + } + + // Closing paren for the object doesn't have to be lastToken, e.g.: () => ({}).foo() + fixes.push(fixer.remove(findClosingParen(openingBraceToken))); + fixes.push(fixer.insertTextAfter(lastToken, "}")); + + } else { + fixes.push(fixer.insertTextBefore(firstTokenAfterArrow, "{return ")); + fixes.push(fixer.insertTextAfter(lastToken, "}")); + } + + return fixes; + } + }); + } + } + } + + return { + "ArrowFunctionExpression:exit": validate + }; + } +}; diff --git a/eslint/lib/rules/arrow-parens.js b/eslint/lib/rules/arrow-parens.js new file mode 100644 index 0000000..dc3c382 --- /dev/null +++ b/eslint/lib/rules/arrow-parens.js @@ -0,0 +1,184 @@ +/** + * @fileoverview Rule to require parens in arrow function arguments. + * @author Jxck + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Get location should be reported by AST node. + * @param {ASTNode} node AST Node. + * @returns {Location} Location information. + */ +function getLocation(node) { + return { + start: node.params[0].loc.start, + end: node.params[node.params.length - 1].loc.end + }; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "require parentheses around arrow function arguments", + category: "ECMAScript 6", + recommended: false, + url: "https://eslint.org/docs/rules/arrow-parens" + }, + + fixable: "code", + + schema: [ + { + enum: ["always", "as-needed"] + }, + { + type: "object", + properties: { + requireForBlockBody: { + type: "boolean", + default: false + } + }, + additionalProperties: false + } + ], + + messages: { + unexpectedParens: "Unexpected parentheses around single function argument.", + expectedParens: "Expected parentheses around arrow function argument.", + + unexpectedParensInline: "Unexpected parentheses around single function argument having a body with no curly braces.", + expectedParensBlock: "Expected parentheses around arrow function argument having a body with curly braces." + } + }, + + create(context) { + const asNeeded = context.options[0] === "as-needed"; + const requireForBlockBody = asNeeded && context.options[1] && context.options[1].requireForBlockBody === true; + + const sourceCode = context.getSourceCode(); + + /** + * Determines whether a arrow function argument end with `)` + * @param {ASTNode} node The arrow function node. + * @returns {void} + */ + function parens(node) { + const isAsync = node.async; + const firstTokenOfParam = sourceCode.getFirstToken(node, isAsync ? 1 : 0); + + /** + * Remove the parenthesis around a parameter + * @param {Fixer} fixer Fixer + * @returns {string} fixed parameter + */ + function fixParamsWithParenthesis(fixer) { + const paramToken = sourceCode.getTokenAfter(firstTokenOfParam); + + /* + * ES8 allows Trailing commas in function parameter lists and calls + * https://github.com/eslint/eslint/issues/8834 + */ + const closingParenToken = sourceCode.getTokenAfter(paramToken, astUtils.isClosingParenToken); + const asyncToken = isAsync ? sourceCode.getTokenBefore(firstTokenOfParam) : null; + const shouldAddSpaceForAsync = asyncToken && (asyncToken.range[1] === firstTokenOfParam.range[0]); + + return fixer.replaceTextRange([ + firstTokenOfParam.range[0], + closingParenToken.range[1] + ], `${shouldAddSpaceForAsync ? " " : ""}${paramToken.value}`); + } + + // "as-needed", { "requireForBlockBody": true }: x => x + if ( + requireForBlockBody && + node.params.length === 1 && + node.params[0].type === "Identifier" && + !node.params[0].typeAnnotation && + node.body.type !== "BlockStatement" && + !node.returnType + ) { + if (astUtils.isOpeningParenToken(firstTokenOfParam)) { + context.report({ + node, + messageId: "unexpectedParensInline", + loc: getLocation(node), + fix: fixParamsWithParenthesis + }); + } + return; + } + + if ( + requireForBlockBody && + node.body.type === "BlockStatement" + ) { + if (!astUtils.isOpeningParenToken(firstTokenOfParam)) { + context.report({ + node, + messageId: "expectedParensBlock", + loc: getLocation(node), + fix(fixer) { + return fixer.replaceText(firstTokenOfParam, `(${firstTokenOfParam.value})`); + } + }); + } + return; + } + + // "as-needed": x => x + if (asNeeded && + node.params.length === 1 && + node.params[0].type === "Identifier" && + !node.params[0].typeAnnotation && + !node.returnType + ) { + if (astUtils.isOpeningParenToken(firstTokenOfParam)) { + context.report({ + node, + messageId: "unexpectedParens", + loc: getLocation(node), + fix: fixParamsWithParenthesis + }); + } + return; + } + + if (firstTokenOfParam.type === "Identifier") { + const after = sourceCode.getTokenAfter(firstTokenOfParam); + + // (x) => x + if (after.value !== ")") { + context.report({ + node, + messageId: "expectedParens", + loc: getLocation(node), + fix(fixer) { + return fixer.replaceText(firstTokenOfParam, `(${firstTokenOfParam.value})`); + } + }); + } + } + } + + return { + ArrowFunctionExpression: parens + }; + } +}; diff --git a/eslint/lib/rules/arrow-spacing.js b/eslint/lib/rules/arrow-spacing.js new file mode 100644 index 0000000..e5110c6 --- /dev/null +++ b/eslint/lib/rules/arrow-spacing.js @@ -0,0 +1,161 @@ +/** + * @fileoverview Rule to define spacing before/after arrow function's arrow. + * @author Jxck + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "enforce consistent spacing before and after the arrow in arrow functions", + category: "ECMAScript 6", + recommended: false, + url: "https://eslint.org/docs/rules/arrow-spacing" + }, + + fixable: "whitespace", + + schema: [ + { + type: "object", + properties: { + before: { + type: "boolean", + default: true + }, + after: { + type: "boolean", + default: true + } + }, + additionalProperties: false + } + ], + + messages: { + expectedBefore: "Missing space before =>.", + unexpectedBefore: "Unexpected space before =>.", + + expectedAfter: "Missing space after =>.", + unexpectedAfter: "Unexpected space after =>." + } + }, + + create(context) { + + // merge rules with default + const rule = Object.assign({}, context.options[0]); + + rule.before = rule.before !== false; + rule.after = rule.after !== false; + + const sourceCode = context.getSourceCode(); + + /** + * Get tokens of arrow(`=>`) and before/after arrow. + * @param {ASTNode} node The arrow function node. + * @returns {Object} Tokens of arrow and before/after arrow. + */ + function getTokens(node) { + const arrow = sourceCode.getTokenBefore(node.body, astUtils.isArrowToken); + + return { + before: sourceCode.getTokenBefore(arrow), + arrow, + after: sourceCode.getTokenAfter(arrow) + }; + } + + /** + * Count spaces before/after arrow(`=>`) token. + * @param {Object} tokens Tokens before/after arrow. + * @returns {Object} count of space before/after arrow. + */ + function countSpaces(tokens) { + const before = tokens.arrow.range[0] - tokens.before.range[1]; + const after = tokens.after.range[0] - tokens.arrow.range[1]; + + return { before, after }; + } + + /** + * Determines whether space(s) before after arrow(`=>`) is satisfy rule. + * if before/after value is `true`, there should be space(s). + * if before/after value is `false`, there should be no space. + * @param {ASTNode} node The arrow function node. + * @returns {void} + */ + function spaces(node) { + const tokens = getTokens(node); + const countSpace = countSpaces(tokens); + + if (rule.before) { + + // should be space(s) before arrow + if (countSpace.before === 0) { + context.report({ + node: tokens.before, + messageId: "expectedBefore", + fix(fixer) { + return fixer.insertTextBefore(tokens.arrow, " "); + } + }); + } + } else { + + // should be no space before arrow + if (countSpace.before > 0) { + context.report({ + node: tokens.before, + messageId: "unexpectedBefore", + fix(fixer) { + return fixer.removeRange([tokens.before.range[1], tokens.arrow.range[0]]); + } + }); + } + } + + if (rule.after) { + + // should be space(s) after arrow + if (countSpace.after === 0) { + context.report({ + node: tokens.after, + messageId: "expectedAfter", + fix(fixer) { + return fixer.insertTextAfter(tokens.arrow, " "); + } + }); + } + } else { + + // should be no space after arrow + if (countSpace.after > 0) { + context.report({ + node: tokens.after, + messageId: "unexpectedAfter", + fix(fixer) { + return fixer.removeRange([tokens.arrow.range[1], tokens.after.range[0]]); + } + }); + } + } + } + + return { + ArrowFunctionExpression: spaces + }; + } +}; diff --git a/eslint/lib/rules/block-scoped-var.js b/eslint/lib/rules/block-scoped-var.js new file mode 100644 index 0000000..481057b --- /dev/null +++ b/eslint/lib/rules/block-scoped-var.js @@ -0,0 +1,122 @@ +/** + * @fileoverview Rule to check for "block scoped" variables by binding context + * @author Matt DuVall + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "enforce the use of variables within the scope they are defined", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/block-scoped-var" + }, + + schema: [], + + messages: { + outOfScope: "'{{name}}' used outside of binding context." + } + }, + + create(context) { + let stack = []; + + /** + * Makes a block scope. + * @param {ASTNode} node A node of a scope. + * @returns {void} + */ + function enterScope(node) { + stack.push(node.range); + } + + /** + * Pops the last block scope. + * @returns {void} + */ + function exitScope() { + stack.pop(); + } + + /** + * Reports a given reference. + * @param {eslint-scope.Reference} reference A reference to report. + * @returns {void} + */ + function report(reference) { + const identifier = reference.identifier; + + context.report({ node: identifier, messageId: "outOfScope", data: { name: identifier.name } }); + } + + /** + * Finds and reports references which are outside of valid scopes. + * @param {ASTNode} node A node to get variables. + * @returns {void} + */ + function checkForVariables(node) { + if (node.kind !== "var") { + return; + } + + // Defines a predicate to check whether or not a given reference is outside of valid scope. + const scopeRange = stack[stack.length - 1]; + + /** + * Check if a reference is out of scope + * @param {ASTNode} reference node to examine + * @returns {boolean} True is its outside the scope + * @private + */ + function isOutsideOfScope(reference) { + const idRange = reference.identifier.range; + + return idRange[0] < scopeRange[0] || idRange[1] > scopeRange[1]; + } + + // Gets declared variables, and checks its references. + const variables = context.getDeclaredVariables(node); + + for (let i = 0; i < variables.length; ++i) { + + // Reports. + variables[i] + .references + .filter(isOutsideOfScope) + .forEach(report); + } + } + + return { + Program(node) { + stack = [node.range]; + }, + + // Manages scopes. + BlockStatement: enterScope, + "BlockStatement:exit": exitScope, + ForStatement: enterScope, + "ForStatement:exit": exitScope, + ForInStatement: enterScope, + "ForInStatement:exit": exitScope, + ForOfStatement: enterScope, + "ForOfStatement:exit": exitScope, + SwitchStatement: enterScope, + "SwitchStatement:exit": exitScope, + CatchClause: enterScope, + "CatchClause:exit": exitScope, + + // Finds and reports references which are outside of valid scope. + VariableDeclaration: checkForVariables + }; + + } +}; diff --git a/eslint/lib/rules/block-spacing.js b/eslint/lib/rules/block-spacing.js new file mode 100644 index 0000000..c6ed44a --- /dev/null +++ b/eslint/lib/rules/block-spacing.js @@ -0,0 +1,147 @@ +/** + * @fileoverview A rule to disallow or enforce spaces inside of single line blocks. + * @author Toru Nagashima + */ + +"use strict"; + +const util = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "disallow or enforce spaces inside of blocks after opening block and before closing block", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/block-spacing" + }, + + fixable: "whitespace", + + schema: [ + { enum: ["always", "never"] } + ], + + messages: { + missing: "Requires a space {{location}} '{{token}}'.", + extra: "Unexpected space(s) {{location}} '{{token}}'." + } + }, + + create(context) { + const always = (context.options[0] !== "never"), + messageId = always ? "missing" : "extra", + sourceCode = context.getSourceCode(); + + /** + * Gets the open brace token from a given node. + * @param {ASTNode} node A BlockStatement/SwitchStatement node to get. + * @returns {Token} The token of the open brace. + */ + function getOpenBrace(node) { + if (node.type === "SwitchStatement") { + if (node.cases.length > 0) { + return sourceCode.getTokenBefore(node.cases[0]); + } + return sourceCode.getLastToken(node, 1); + } + return sourceCode.getFirstToken(node); + } + + /** + * Checks whether or not: + * - given tokens are on same line. + * - there is/isn't a space between given tokens. + * @param {Token} left A token to check. + * @param {Token} right The token which is next to `left`. + * @returns {boolean} + * When the option is `"always"`, `true` if there are one or more spaces between given tokens. + * When the option is `"never"`, `true` if there are not any spaces between given tokens. + * If given tokens are not on same line, it's always `true`. + */ + function isValid(left, right) { + return ( + !util.isTokenOnSameLine(left, right) || + sourceCode.isSpaceBetweenTokens(left, right) === always + ); + } + + /** + * Reports invalid spacing style inside braces. + * @param {ASTNode} node A BlockStatement/SwitchStatement node to get. + * @returns {void} + */ + function checkSpacingInsideBraces(node) { + + // Gets braces and the first/last token of content. + const openBrace = getOpenBrace(node); + const closeBrace = sourceCode.getLastToken(node); + const firstToken = sourceCode.getTokenAfter(openBrace, { includeComments: true }); + const lastToken = sourceCode.getTokenBefore(closeBrace, { includeComments: true }); + + // Skip if the node is invalid or empty. + if (openBrace.type !== "Punctuator" || + openBrace.value !== "{" || + closeBrace.type !== "Punctuator" || + closeBrace.value !== "}" || + firstToken === closeBrace + ) { + return; + } + + // Skip line comments for option never + if (!always && firstToken.type === "Line") { + return; + } + + // Check. + if (!isValid(openBrace, firstToken)) { + context.report({ + node, + loc: openBrace.loc.start, + messageId, + data: { + location: "after", + token: openBrace.value + }, + fix(fixer) { + if (always) { + return fixer.insertTextBefore(firstToken, " "); + } + + return fixer.removeRange([openBrace.range[1], firstToken.range[0]]); + } + }); + } + if (!isValid(lastToken, closeBrace)) { + context.report({ + node, + loc: closeBrace.loc.start, + messageId, + data: { + location: "before", + token: closeBrace.value + }, + fix(fixer) { + if (always) { + return fixer.insertTextAfter(lastToken, " "); + } + + return fixer.removeRange([lastToken.range[1], closeBrace.range[0]]); + } + }); + } + } + + return { + BlockStatement: checkSpacingInsideBraces, + SwitchStatement: checkSpacingInsideBraces + }; + } +}; diff --git a/eslint/lib/rules/brace-style.js b/eslint/lib/rules/brace-style.js new file mode 100644 index 0000000..07223d1 --- /dev/null +++ b/eslint/lib/rules/brace-style.js @@ -0,0 +1,188 @@ +/** + * @fileoverview Rule to flag block statements that do not use the one true brace style + * @author Ian Christian Myers + */ + +"use strict"; + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "enforce consistent brace style for blocks", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/brace-style" + }, + + schema: [ + { + enum: ["1tbs", "stroustrup", "allman"] + }, + { + type: "object", + properties: { + allowSingleLine: { + type: "boolean", + default: false + } + }, + additionalProperties: false + } + ], + + fixable: "whitespace", + + messages: { + nextLineOpen: "Opening curly brace does not appear on the same line as controlling statement.", + sameLineOpen: "Opening curly brace appears on the same line as controlling statement.", + blockSameLine: "Statement inside of curly braces should be on next line.", + nextLineClose: "Closing curly brace does not appear on the same line as the subsequent block.", + singleLineClose: "Closing curly brace should be on the same line as opening curly brace or on the line after the previous block.", + sameLineClose: "Closing curly brace appears on the same line as the subsequent block." + } + }, + + create(context) { + const style = context.options[0] || "1tbs", + params = context.options[1] || {}, + sourceCode = context.getSourceCode(); + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Fixes a place where a newline unexpectedly appears + * @param {Token} firstToken The token before the unexpected newline + * @param {Token} secondToken The token after the unexpected newline + * @returns {Function} A fixer function to remove the newlines between the tokens + */ + function removeNewlineBetween(firstToken, secondToken) { + const textRange = [firstToken.range[1], secondToken.range[0]]; + const textBetween = sourceCode.text.slice(textRange[0], textRange[1]); + + // Don't do a fix if there is a comment between the tokens + if (textBetween.trim()) { + return null; + } + return fixer => fixer.replaceTextRange(textRange, " "); + } + + /** + * Validates a pair of curly brackets based on the user's config + * @param {Token} openingCurly The opening curly bracket + * @param {Token} closingCurly The closing curly bracket + * @returns {void} + */ + function validateCurlyPair(openingCurly, closingCurly) { + const tokenBeforeOpeningCurly = sourceCode.getTokenBefore(openingCurly); + const tokenAfterOpeningCurly = sourceCode.getTokenAfter(openingCurly); + const tokenBeforeClosingCurly = sourceCode.getTokenBefore(closingCurly); + const singleLineException = params.allowSingleLine && astUtils.isTokenOnSameLine(openingCurly, closingCurly); + + if (style !== "allman" && !astUtils.isTokenOnSameLine(tokenBeforeOpeningCurly, openingCurly)) { + context.report({ + node: openingCurly, + messageId: "nextLineOpen", + fix: removeNewlineBetween(tokenBeforeOpeningCurly, openingCurly) + }); + } + + if (style === "allman" && astUtils.isTokenOnSameLine(tokenBeforeOpeningCurly, openingCurly) && !singleLineException) { + context.report({ + node: openingCurly, + messageId: "sameLineOpen", + fix: fixer => fixer.insertTextBefore(openingCurly, "\n") + }); + } + + if (astUtils.isTokenOnSameLine(openingCurly, tokenAfterOpeningCurly) && tokenAfterOpeningCurly !== closingCurly && !singleLineException) { + context.report({ + node: openingCurly, + messageId: "blockSameLine", + fix: fixer => fixer.insertTextAfter(openingCurly, "\n") + }); + } + + if (tokenBeforeClosingCurly !== openingCurly && !singleLineException && astUtils.isTokenOnSameLine(tokenBeforeClosingCurly, closingCurly)) { + context.report({ + node: closingCurly, + messageId: "singleLineClose", + fix: fixer => fixer.insertTextBefore(closingCurly, "\n") + }); + } + } + + /** + * Validates the location of a token that appears before a keyword (e.g. a newline before `else`) + * @param {Token} curlyToken The closing curly token. This is assumed to precede a keyword token (such as `else` or `finally`). + * @returns {void} + */ + function validateCurlyBeforeKeyword(curlyToken) { + const keywordToken = sourceCode.getTokenAfter(curlyToken); + + if (style === "1tbs" && !astUtils.isTokenOnSameLine(curlyToken, keywordToken)) { + context.report({ + node: curlyToken, + messageId: "nextLineClose", + fix: removeNewlineBetween(curlyToken, keywordToken) + }); + } + + if (style !== "1tbs" && astUtils.isTokenOnSameLine(curlyToken, keywordToken)) { + context.report({ + node: curlyToken, + messageId: "sameLineClose", + fix: fixer => fixer.insertTextAfter(curlyToken, "\n") + }); + } + } + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + BlockStatement(node) { + if (!astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type)) { + validateCurlyPair(sourceCode.getFirstToken(node), sourceCode.getLastToken(node)); + } + }, + ClassBody(node) { + validateCurlyPair(sourceCode.getFirstToken(node), sourceCode.getLastToken(node)); + }, + SwitchStatement(node) { + const closingCurly = sourceCode.getLastToken(node); + const openingCurly = sourceCode.getTokenBefore(node.cases.length ? node.cases[0] : closingCurly); + + validateCurlyPair(openingCurly, closingCurly); + }, + IfStatement(node) { + if (node.consequent.type === "BlockStatement" && node.alternate) { + + // Handle the keyword after the `if` block (before `else`) + validateCurlyBeforeKeyword(sourceCode.getLastToken(node.consequent)); + } + }, + TryStatement(node) { + + // Handle the keyword after the `try` block (before `catch` or `finally`) + validateCurlyBeforeKeyword(sourceCode.getLastToken(node.block)); + + if (node.handler && node.finalizer) { + + // Handle the keyword after the `catch` block (before `finally`) + validateCurlyBeforeKeyword(sourceCode.getLastToken(node.handler.body)); + } + } + }; + } +}; diff --git a/eslint/lib/rules/callback-return.js b/eslint/lib/rules/callback-return.js new file mode 100644 index 0000000..c5263cd --- /dev/null +++ b/eslint/lib/rules/callback-return.js @@ -0,0 +1,182 @@ +/** + * @fileoverview Enforce return after a callback. + * @author Jamund Ferguson + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "require `return` statements after callbacks", + category: "Node.js and CommonJS", + recommended: false, + url: "https://eslint.org/docs/rules/callback-return" + }, + + schema: [{ + type: "array", + items: { type: "string" } + }], + + messages: { + missingReturn: "Expected return with your callback function." + } + }, + + create(context) { + + const callbacks = context.options[0] || ["callback", "cb", "next"], + sourceCode = context.getSourceCode(); + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Find the closest parent matching a list of types. + * @param {ASTNode} node The node whose parents we are searching + * @param {Array} types The node types to match + * @returns {ASTNode} The matched node or undefined. + */ + function findClosestParentOfType(node, types) { + if (!node.parent) { + return null; + } + if (types.indexOf(node.parent.type) === -1) { + return findClosestParentOfType(node.parent, types); + } + return node.parent; + } + + /** + * Check to see if a node contains only identifers + * @param {ASTNode} node The node to check + * @returns {boolean} Whether or not the node contains only identifers + */ + function containsOnlyIdentifiers(node) { + if (node.type === "Identifier") { + return true; + } + + if (node.type === "MemberExpression") { + if (node.object.type === "Identifier") { + return true; + } + if (node.object.type === "MemberExpression") { + return containsOnlyIdentifiers(node.object); + } + } + + return false; + } + + /** + * Check to see if a CallExpression is in our callback list. + * @param {ASTNode} node The node to check against our callback names list. + * @returns {boolean} Whether or not this function matches our callback name. + */ + function isCallback(node) { + return containsOnlyIdentifiers(node.callee) && callbacks.indexOf(sourceCode.getText(node.callee)) > -1; + } + + /** + * Determines whether or not the callback is part of a callback expression. + * @param {ASTNode} node The callback node + * @param {ASTNode} parentNode The expression node + * @returns {boolean} Whether or not this is part of a callback expression + */ + function isCallbackExpression(node, parentNode) { + + // ensure the parent node exists and is an expression + if (!parentNode || parentNode.type !== "ExpressionStatement") { + return false; + } + + // cb() + if (parentNode.expression === node) { + return true; + } + + // special case for cb && cb() and similar + if (parentNode.expression.type === "BinaryExpression" || parentNode.expression.type === "LogicalExpression") { + if (parentNode.expression.right === node) { + return true; + } + } + + return false; + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + CallExpression(node) { + + // if we're not a callback we can return + if (!isCallback(node)) { + return; + } + + // find the closest block, return or loop + const closestBlock = findClosestParentOfType(node, ["BlockStatement", "ReturnStatement", "ArrowFunctionExpression"]) || {}; + + // if our parent is a return we know we're ok + if (closestBlock.type === "ReturnStatement") { + return; + } + + // arrow functions don't always have blocks and implicitly return + if (closestBlock.type === "ArrowFunctionExpression") { + return; + } + + // block statements are part of functions and most if statements + if (closestBlock.type === "BlockStatement") { + + // find the last item in the block + const lastItem = closestBlock.body[closestBlock.body.length - 1]; + + // if the callback is the last thing in a block that might be ok + if (isCallbackExpression(node, lastItem)) { + + const parentType = closestBlock.parent.type; + + // but only if the block is part of a function + if (parentType === "FunctionExpression" || + parentType === "FunctionDeclaration" || + parentType === "ArrowFunctionExpression" + ) { + return; + } + + } + + // ending a block with a return is also ok + if (lastItem.type === "ReturnStatement") { + + // but only if the callback is immediately before + if (isCallbackExpression(node, closestBlock.body[closestBlock.body.length - 2])) { + return; + } + } + + } + + // as long as you're the child of a function at this point you should be asked to return + if (findClosestParentOfType(node, ["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"])) { + context.report({ node, messageId: "missingReturn" }); + } + + } + + }; + } +}; diff --git a/eslint/lib/rules/camelcase.js b/eslint/lib/rules/camelcase.js new file mode 100644 index 0000000..0436083 --- /dev/null +++ b/eslint/lib/rules/camelcase.js @@ -0,0 +1,278 @@ +/** + * @fileoverview Rule to flag non-camelcased identifiers + * @author Nicholas C. Zakas + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "enforce camelcase naming convention", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/camelcase" + }, + + schema: [ + { + type: "object", + properties: { + ignoreDestructuring: { + type: "boolean", + default: false + }, + ignoreImports: { + type: "boolean", + default: false + }, + properties: { + enum: ["always", "never"] + }, + allow: { + type: "array", + items: [ + { + type: "string" + } + ], + minItems: 0, + uniqueItems: true + } + }, + additionalProperties: false + } + ], + + messages: { + notCamelCase: "Identifier '{{name}}' is not in camel case." + } + }, + + create(context) { + + const options = context.options[0] || {}; + let properties = options.properties || ""; + const ignoreDestructuring = options.ignoreDestructuring; + const ignoreImports = options.ignoreImports; + const allow = options.allow || []; + + if (properties !== "always" && properties !== "never") { + properties = "always"; + } + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + // contains reported nodes to avoid reporting twice on destructuring with shorthand notation + const reported = []; + const ALLOWED_PARENT_TYPES = new Set(["CallExpression", "NewExpression"]); + + /** + * Checks if a string contains an underscore and isn't all upper-case + * @param {string} name The string to check. + * @returns {boolean} if the string is underscored + * @private + */ + function isUnderscored(name) { + + // if there's an underscore, it might be A_CONSTANT, which is okay + return name.includes("_") && name !== name.toUpperCase(); + } + + /** + * Checks if a string match the ignore list + * @param {string} name The string to check. + * @returns {boolean} if the string is ignored + * @private + */ + function isAllowed(name) { + return allow.some( + entry => name === entry || name.match(new RegExp(entry, "u")) + ); + } + + /** + * Checks if a parent of a node is an ObjectPattern. + * @param {ASTNode} node The node to check. + * @returns {boolean} if the node is inside an ObjectPattern + * @private + */ + function isInsideObjectPattern(node) { + let current = node; + + while (current) { + const parent = current.parent; + + if (parent && parent.type === "Property" && parent.computed && parent.key === current) { + return false; + } + + if (current.type === "ObjectPattern") { + return true; + } + + current = parent; + } + + return false; + } + + /** + * Checks whether the given node represents assignment target property in destructuring. + * + * For examples: + * ({a: b.foo} = c); // => true for `foo` + * ([a.foo] = b); // => true for `foo` + * ([a.foo = 1] = b); // => true for `foo` + * ({...a.foo} = b); // => true for `foo` + * @param {ASTNode} node An Identifier node to check + * @returns {boolean} True if the node is an assignment target property in destructuring. + */ + function isAssignmentTargetPropertyInDestructuring(node) { + if ( + node.parent.type === "MemberExpression" && + node.parent.property === node && + !node.parent.computed + ) { + const effectiveParent = node.parent.parent; + + return ( + effectiveParent.type === "Property" && + effectiveParent.value === node.parent && + effectiveParent.parent.type === "ObjectPattern" || + effectiveParent.type === "ArrayPattern" || + effectiveParent.type === "RestElement" || + ( + effectiveParent.type === "AssignmentPattern" && + effectiveParent.left === node.parent + ) + ); + } + return false; + } + + /** + * Reports an AST node as a rule violation. + * @param {ASTNode} node The node to report. + * @returns {void} + * @private + */ + function report(node) { + if (!reported.includes(node)) { + reported.push(node); + context.report({ node, messageId: "notCamelCase", data: { name: node.name } }); + } + } + + return { + + Identifier(node) { + + /* + * Leading and trailing underscores are commonly used to flag + * private/protected identifiers, strip them before checking if underscored + */ + const name = node.name, + nameIsUnderscored = isUnderscored(name.replace(/^_+|_+$/gu, "")), + effectiveParent = (node.parent.type === "MemberExpression") ? node.parent.parent : node.parent; + + // First, we ignore the node if it match the ignore list + if (isAllowed(name)) { + return; + } + + // MemberExpressions get special rules + if (node.parent.type === "MemberExpression") { + + // "never" check properties + if (properties === "never") { + return; + } + + // Always report underscored object names + if (node.parent.object.type === "Identifier" && node.parent.object.name === node.name && nameIsUnderscored) { + report(node); + + // Report AssignmentExpressions only if they are the left side of the assignment + } else if (effectiveParent.type === "AssignmentExpression" && nameIsUnderscored && (effectiveParent.right.type !== "MemberExpression" || effectiveParent.left.type === "MemberExpression" && effectiveParent.left.property.name === node.name)) { + report(node); + + } else if (isAssignmentTargetPropertyInDestructuring(node) && nameIsUnderscored) { + report(node); + } + + /* + * Properties have their own rules, and + * AssignmentPattern nodes can be treated like Properties: + * e.g.: const { no_camelcased = false } = bar; + */ + } else if (node.parent.type === "Property" || node.parent.type === "AssignmentPattern") { + + if (node.parent.parent && node.parent.parent.type === "ObjectPattern") { + if (node.parent.shorthand && node.parent.value.left && nameIsUnderscored) { + report(node); + } + + const assignmentKeyEqualsValue = node.parent.key.name === node.parent.value.name; + + if (nameIsUnderscored && node.parent.computed) { + report(node); + } + + // prevent checking righthand side of destructured object + if (node.parent.key === node && node.parent.value !== node) { + return; + } + + const valueIsUnderscored = node.parent.value.name && nameIsUnderscored; + + // ignore destructuring if the option is set, unless a new identifier is created + if (valueIsUnderscored && !(assignmentKeyEqualsValue && ignoreDestructuring)) { + report(node); + } + } + + // "never" check properties or always ignore destructuring + if (properties === "never" || (ignoreDestructuring && isInsideObjectPattern(node))) { + return; + } + + // don't check right hand side of AssignmentExpression to prevent duplicate warnings + if (nameIsUnderscored && !ALLOWED_PARENT_TYPES.has(effectiveParent.type) && !(node.parent.right === node)) { + report(node); + } + + // Check if it's an import specifier + } else if (["ImportSpecifier", "ImportNamespaceSpecifier", "ImportDefaultSpecifier"].includes(node.parent.type)) { + + if (node.parent.type === "ImportSpecifier" && ignoreImports) { + return; + } + + // Report only if the local imported identifier is underscored + if ( + node.parent.local && + node.parent.local.name === node.name && + nameIsUnderscored + ) { + report(node); + } + + // Report anything that is underscored that isn't a CallExpression + } else if (nameIsUnderscored && !ALLOWED_PARENT_TYPES.has(effectiveParent.type)) { + report(node); + } + } + + }; + + } +}; diff --git a/eslint/lib/rules/capitalized-comments.js b/eslint/lib/rules/capitalized-comments.js new file mode 100644 index 0000000..d7524b8 --- /dev/null +++ b/eslint/lib/rules/capitalized-comments.js @@ -0,0 +1,300 @@ +/** + * @fileoverview enforce or disallow capitalization of the first letter of a comment + * @author Kevin Partington + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const LETTER_PATTERN = require("./utils/patterns/letters"); +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const DEFAULT_IGNORE_PATTERN = astUtils.COMMENTS_IGNORE_PATTERN, + WHITESPACE = /\s/gu, + MAYBE_URL = /^\s*[^:/?#\s]+:\/\/[^?#]/u; // TODO: Combine w/ max-len pattern? + +/* + * Base schema body for defining the basic capitalization rule, ignorePattern, + * and ignoreInlineComments values. + * This can be used in a few different ways in the actual schema. + */ +const SCHEMA_BODY = { + type: "object", + properties: { + ignorePattern: { + type: "string" + }, + ignoreInlineComments: { + type: "boolean" + }, + ignoreConsecutiveComments: { + type: "boolean" + } + }, + additionalProperties: false +}; +const DEFAULTS = { + ignorePattern: "", + ignoreInlineComments: false, + ignoreConsecutiveComments: false +}; + +/** + * Get normalized options for either block or line comments from the given + * user-provided options. + * - If the user-provided options is just a string, returns a normalized + * set of options using default values for all other options. + * - If the user-provided options is an object, then a normalized option + * set is returned. Options specified in overrides will take priority + * over options specified in the main options object, which will in + * turn take priority over the rule's defaults. + * @param {Object|string} rawOptions The user-provided options. + * @param {string} which Either "line" or "block". + * @returns {Object} The normalized options. + */ +function getNormalizedOptions(rawOptions, which) { + return Object.assign({}, DEFAULTS, rawOptions[which] || rawOptions); +} + +/** + * Get normalized options for block and line comments. + * @param {Object|string} rawOptions The user-provided options. + * @returns {Object} An object with "Line" and "Block" keys and corresponding + * normalized options objects. + */ +function getAllNormalizedOptions(rawOptions = {}) { + return { + Line: getNormalizedOptions(rawOptions, "line"), + Block: getNormalizedOptions(rawOptions, "block") + }; +} + +/** + * Creates a regular expression for each ignorePattern defined in the rule + * options. + * + * This is done in order to avoid invoking the RegExp constructor repeatedly. + * @param {Object} normalizedOptions The normalized rule options. + * @returns {void} + */ +function createRegExpForIgnorePatterns(normalizedOptions) { + Object.keys(normalizedOptions).forEach(key => { + const ignorePatternStr = normalizedOptions[key].ignorePattern; + + if (ignorePatternStr) { + const regExp = RegExp(`^\\s*(?:${ignorePatternStr})`, "u"); + + normalizedOptions[key].ignorePatternRegExp = regExp; + } + }); +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "enforce or disallow capitalization of the first letter of a comment", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/capitalized-comments" + }, + + fixable: "code", + + schema: [ + { enum: ["always", "never"] }, + { + oneOf: [ + SCHEMA_BODY, + { + type: "object", + properties: { + line: SCHEMA_BODY, + block: SCHEMA_BODY + }, + additionalProperties: false + } + ] + } + ], + + messages: { + unexpectedLowercaseComment: "Comments should not begin with a lowercase character.", + unexpectedUppercaseComment: "Comments should not begin with an uppercase character." + } + }, + + create(context) { + + const capitalize = context.options[0] || "always", + normalizedOptions = getAllNormalizedOptions(context.options[1]), + sourceCode = context.getSourceCode(); + + createRegExpForIgnorePatterns(normalizedOptions); + + //---------------------------------------------------------------------- + // Helpers + //---------------------------------------------------------------------- + + /** + * Checks whether a comment is an inline comment. + * + * For the purpose of this rule, a comment is inline if: + * 1. The comment is preceded by a token on the same line; and + * 2. The command is followed by a token on the same line. + * + * Note that the comment itself need not be single-line! + * + * Also, it follows from this definition that only block comments can + * be considered as possibly inline. This is because line comments + * would consume any following tokens on the same line as the comment. + * @param {ASTNode} comment The comment node to check. + * @returns {boolean} True if the comment is an inline comment, false + * otherwise. + */ + function isInlineComment(comment) { + const previousToken = sourceCode.getTokenBefore(comment, { includeComments: true }), + nextToken = sourceCode.getTokenAfter(comment, { includeComments: true }); + + return Boolean( + previousToken && + nextToken && + comment.loc.start.line === previousToken.loc.end.line && + comment.loc.end.line === nextToken.loc.start.line + ); + } + + /** + * Determine if a comment follows another comment. + * @param {ASTNode} comment The comment to check. + * @returns {boolean} True if the comment follows a valid comment. + */ + function isConsecutiveComment(comment) { + const previousTokenOrComment = sourceCode.getTokenBefore(comment, { includeComments: true }); + + return Boolean( + previousTokenOrComment && + ["Block", "Line"].indexOf(previousTokenOrComment.type) !== -1 + ); + } + + /** + * Check a comment to determine if it is valid for this rule. + * @param {ASTNode} comment The comment node to process. + * @param {Object} options The options for checking this comment. + * @returns {boolean} True if the comment is valid, false otherwise. + */ + function isCommentValid(comment, options) { + + // 1. Check for default ignore pattern. + if (DEFAULT_IGNORE_PATTERN.test(comment.value)) { + return true; + } + + // 2. Check for custom ignore pattern. + const commentWithoutAsterisks = comment.value + .replace(/\*/gu, ""); + + if (options.ignorePatternRegExp && options.ignorePatternRegExp.test(commentWithoutAsterisks)) { + return true; + } + + // 3. Check for inline comments. + if (options.ignoreInlineComments && isInlineComment(comment)) { + return true; + } + + // 4. Is this a consecutive comment (and are we tolerating those)? + if (options.ignoreConsecutiveComments && isConsecutiveComment(comment)) { + return true; + } + + // 5. Does the comment start with a possible URL? + if (MAYBE_URL.test(commentWithoutAsterisks)) { + return true; + } + + // 6. Is the initial word character a letter? + const commentWordCharsOnly = commentWithoutAsterisks + .replace(WHITESPACE, ""); + + if (commentWordCharsOnly.length === 0) { + return true; + } + + const firstWordChar = commentWordCharsOnly[0]; + + if (!LETTER_PATTERN.test(firstWordChar)) { + return true; + } + + // 7. Check the case of the initial word character. + const isUppercase = firstWordChar !== firstWordChar.toLocaleLowerCase(), + isLowercase = firstWordChar !== firstWordChar.toLocaleUpperCase(); + + if (capitalize === "always" && isLowercase) { + return false; + } + if (capitalize === "never" && isUppercase) { + return false; + } + + return true; + } + + /** + * Process a comment to determine if it needs to be reported. + * @param {ASTNode} comment The comment node to process. + * @returns {void} + */ + function processComment(comment) { + const options = normalizedOptions[comment.type], + commentValid = isCommentValid(comment, options); + + if (!commentValid) { + const messageId = capitalize === "always" + ? "unexpectedLowercaseComment" + : "unexpectedUppercaseComment"; + + context.report({ + node: null, // Intentionally using loc instead + loc: comment.loc, + messageId, + fix(fixer) { + const match = comment.value.match(LETTER_PATTERN); + + return fixer.replaceTextRange( + + // Offset match.index by 2 to account for the first 2 characters that start the comment (// or /*) + [comment.range[0] + match.index + 2, comment.range[0] + match.index + 3], + capitalize === "always" ? match[0].toLocaleUpperCase() : match[0].toLocaleLowerCase() + ); + } + }); + } + } + + //---------------------------------------------------------------------- + // Public + //---------------------------------------------------------------------- + + return { + Program() { + const comments = sourceCode.getAllComments(); + + comments.filter(token => token.type !== "Shebang").forEach(processComment); + } + }; + } +}; diff --git a/eslint/lib/rules/class-methods-use-this.js b/eslint/lib/rules/class-methods-use-this.js new file mode 100644 index 0000000..2cc5cc4 --- /dev/null +++ b/eslint/lib/rules/class-methods-use-this.js @@ -0,0 +1,125 @@ +/** + * @fileoverview Rule to enforce that all class methods use 'this'. + * @author Patrick Williams + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "enforce that class methods utilize `this`", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/class-methods-use-this" + }, + + schema: [{ + type: "object", + properties: { + exceptMethods: { + type: "array", + items: { + type: "string" + } + } + }, + additionalProperties: false + }], + + messages: { + missingThis: "Expected 'this' to be used by class {{name}}." + } + }, + create(context) { + const config = Object.assign({}, context.options[0]); + const exceptMethods = new Set(config.exceptMethods || []); + + const stack = []; + + /** + * Initializes the current context to false and pushes it onto the stack. + * These booleans represent whether 'this' has been used in the context. + * @returns {void} + * @private + */ + function enterFunction() { + stack.push(false); + } + + /** + * Check if the node is an instance method + * @param {ASTNode} node node to check + * @returns {boolean} True if its an instance method + * @private + */ + function isInstanceMethod(node) { + return !node.static && node.kind !== "constructor" && node.type === "MethodDefinition"; + } + + /** + * Check if the node is an instance method not excluded by config + * @param {ASTNode} node node to check + * @returns {boolean} True if it is an instance method, and not excluded by config + * @private + */ + function isIncludedInstanceMethod(node) { + return isInstanceMethod(node) && + (node.computed || !exceptMethods.has(node.key.name)); + } + + /** + * Checks if we are leaving a function that is a method, and reports if 'this' has not been used. + * Static methods and the constructor are exempt. + * Then pops the context off the stack. + * @param {ASTNode} node A function node that was entered. + * @returns {void} + * @private + */ + function exitFunction(node) { + const methodUsesThis = stack.pop(); + + if (isIncludedInstanceMethod(node.parent) && !methodUsesThis) { + context.report({ + node, + messageId: "missingThis", + data: { + name: astUtils.getFunctionNameWithKind(node) + } + }); + } + } + + /** + * Mark the current context as having used 'this'. + * @returns {void} + * @private + */ + function markThisUsed() { + if (stack.length) { + stack[stack.length - 1] = true; + } + } + + return { + FunctionDeclaration: enterFunction, + "FunctionDeclaration:exit": exitFunction, + FunctionExpression: enterFunction, + "FunctionExpression:exit": exitFunction, + ThisExpression: markThisUsed, + Super: markThisUsed + }; + } +}; diff --git a/eslint/lib/rules/comma-dangle.js b/eslint/lib/rules/comma-dangle.js new file mode 100644 index 0000000..e22b7f3 --- /dev/null +++ b/eslint/lib/rules/comma-dangle.js @@ -0,0 +1,340 @@ +/** + * @fileoverview Rule to forbid or enforce dangling commas. + * @author Ian Christian Myers + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const lodash = require("lodash"); +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const DEFAULT_OPTIONS = Object.freeze({ + arrays: "never", + objects: "never", + imports: "never", + exports: "never", + functions: "never" +}); + +/** + * Checks whether or not a trailing comma is allowed in a given node. + * If the `lastItem` is `RestElement` or `RestProperty`, it disallows trailing commas. + * @param {ASTNode} lastItem The node of the last element in the given node. + * @returns {boolean} `true` if a trailing comma is allowed. + */ +function isTrailingCommaAllowed(lastItem) { + return !( + lastItem.type === "RestElement" || + lastItem.type === "RestProperty" || + lastItem.type === "ExperimentalRestProperty" + ); +} + +/** + * Normalize option value. + * @param {string|Object|undefined} optionValue The 1st option value to normalize. + * @param {number} ecmaVersion The normalized ECMAScript version. + * @returns {Object} The normalized option value. + */ +function normalizeOptions(optionValue, ecmaVersion) { + if (typeof optionValue === "string") { + return { + arrays: optionValue, + objects: optionValue, + imports: optionValue, + exports: optionValue, + functions: (!ecmaVersion || ecmaVersion < 8) ? "ignore" : optionValue + }; + } + if (typeof optionValue === "object" && optionValue !== null) { + return { + arrays: optionValue.arrays || DEFAULT_OPTIONS.arrays, + objects: optionValue.objects || DEFAULT_OPTIONS.objects, + imports: optionValue.imports || DEFAULT_OPTIONS.imports, + exports: optionValue.exports || DEFAULT_OPTIONS.exports, + functions: optionValue.functions || DEFAULT_OPTIONS.functions + }; + } + + return DEFAULT_OPTIONS; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "require or disallow trailing commas", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/comma-dangle" + }, + + fixable: "code", + + schema: { + definitions: { + value: { + enum: [ + "always-multiline", + "always", + "never", + "only-multiline" + ] + }, + valueWithIgnore: { + enum: [ + "always-multiline", + "always", + "ignore", + "never", + "only-multiline" + ] + } + }, + type: "array", + items: [ + { + oneOf: [ + { + $ref: "#/definitions/value" + }, + { + type: "object", + properties: { + arrays: { $ref: "#/definitions/valueWithIgnore" }, + objects: { $ref: "#/definitions/valueWithIgnore" }, + imports: { $ref: "#/definitions/valueWithIgnore" }, + exports: { $ref: "#/definitions/valueWithIgnore" }, + functions: { $ref: "#/definitions/valueWithIgnore" } + }, + additionalProperties: false + } + ] + } + ] + }, + + messages: { + unexpected: "Unexpected trailing comma.", + missing: "Missing trailing comma." + } + }, + + create(context) { + const options = normalizeOptions(context.options[0], context.parserOptions.ecmaVersion); + + const sourceCode = context.getSourceCode(); + + /** + * Gets the last item of the given node. + * @param {ASTNode} node The node to get. + * @returns {ASTNode|null} The last node or null. + */ + function getLastItem(node) { + switch (node.type) { + case "ObjectExpression": + case "ObjectPattern": + return lodash.last(node.properties); + case "ArrayExpression": + case "ArrayPattern": + return lodash.last(node.elements); + case "ImportDeclaration": + case "ExportNamedDeclaration": + return lodash.last(node.specifiers); + case "FunctionDeclaration": + case "FunctionExpression": + case "ArrowFunctionExpression": + return lodash.last(node.params); + case "CallExpression": + case "NewExpression": + return lodash.last(node.arguments); + default: + return null; + } + } + + /** + * Gets the trailing comma token of the given node. + * If the trailing comma does not exist, this returns the token which is + * the insertion point of the trailing comma token. + * @param {ASTNode} node The node to get. + * @param {ASTNode} lastItem The last item of the node. + * @returns {Token} The trailing comma token or the insertion point. + */ + function getTrailingToken(node, lastItem) { + switch (node.type) { + case "ObjectExpression": + case "ArrayExpression": + case "CallExpression": + case "NewExpression": + return sourceCode.getLastToken(node, 1); + default: { + const nextToken = sourceCode.getTokenAfter(lastItem); + + if (astUtils.isCommaToken(nextToken)) { + return nextToken; + } + return sourceCode.getLastToken(lastItem); + } + } + } + + /** + * Checks whether or not a given node is multiline. + * This rule handles a given node as multiline when the closing parenthesis + * and the last element are not on the same line. + * @param {ASTNode} node A node to check. + * @returns {boolean} `true` if the node is multiline. + */ + function isMultiline(node) { + const lastItem = getLastItem(node); + + if (!lastItem) { + return false; + } + + const penultimateToken = getTrailingToken(node, lastItem); + const lastToken = sourceCode.getTokenAfter(penultimateToken); + + return lastToken.loc.end.line !== penultimateToken.loc.end.line; + } + + /** + * Reports a trailing comma if it exists. + * @param {ASTNode} node A node to check. Its type is one of + * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern, + * ImportDeclaration, and ExportNamedDeclaration. + * @returns {void} + */ + function forbidTrailingComma(node) { + const lastItem = getLastItem(node); + + if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) { + return; + } + + const trailingToken = getTrailingToken(node, lastItem); + + if (astUtils.isCommaToken(trailingToken)) { + context.report({ + node: lastItem, + loc: trailingToken.loc, + messageId: "unexpected", + fix(fixer) { + return fixer.remove(trailingToken); + } + }); + } + } + + /** + * Reports the last element of a given node if it does not have a trailing + * comma. + * + * If a given node is `ArrayPattern` which has `RestElement`, the trailing + * comma is disallowed, so report if it exists. + * @param {ASTNode} node A node to check. Its type is one of + * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern, + * ImportDeclaration, and ExportNamedDeclaration. + * @returns {void} + */ + function forceTrailingComma(node) { + const lastItem = getLastItem(node); + + if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) { + return; + } + if (!isTrailingCommaAllowed(lastItem)) { + forbidTrailingComma(node); + return; + } + + const trailingToken = getTrailingToken(node, lastItem); + + if (trailingToken.value !== ",") { + context.report({ + node: lastItem, + loc: { + start: trailingToken.loc.end, + end: astUtils.getNextLocation(sourceCode, trailingToken.loc.end) + }, + messageId: "missing", + fix(fixer) { + return fixer.insertTextAfter(trailingToken, ","); + } + }); + } + } + + /** + * If a given node is multiline, reports the last element of a given node + * when it does not have a trailing comma. + * Otherwise, reports a trailing comma if it exists. + * @param {ASTNode} node A node to check. Its type is one of + * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern, + * ImportDeclaration, and ExportNamedDeclaration. + * @returns {void} + */ + function forceTrailingCommaIfMultiline(node) { + if (isMultiline(node)) { + forceTrailingComma(node); + } else { + forbidTrailingComma(node); + } + } + + /** + * Only if a given node is not multiline, reports the last element of a given node + * when it does not have a trailing comma. + * Otherwise, reports a trailing comma if it exists. + * @param {ASTNode} node A node to check. Its type is one of + * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern, + * ImportDeclaration, and ExportNamedDeclaration. + * @returns {void} + */ + function allowTrailingCommaIfMultiline(node) { + if (!isMultiline(node)) { + forbidTrailingComma(node); + } + } + + const predicate = { + always: forceTrailingComma, + "always-multiline": forceTrailingCommaIfMultiline, + "only-multiline": allowTrailingCommaIfMultiline, + never: forbidTrailingComma, + ignore: lodash.noop + }; + + return { + ObjectExpression: predicate[options.objects], + ObjectPattern: predicate[options.objects], + + ArrayExpression: predicate[options.arrays], + ArrayPattern: predicate[options.arrays], + + ImportDeclaration: predicate[options.imports], + + ExportNamedDeclaration: predicate[options.exports], + + FunctionDeclaration: predicate[options.functions], + FunctionExpression: predicate[options.functions], + ArrowFunctionExpression: predicate[options.functions], + CallExpression: predicate[options.functions], + NewExpression: predicate[options.functions] + }; + } +}; diff --git a/eslint/lib/rules/comma-spacing.js b/eslint/lib/rules/comma-spacing.js new file mode 100644 index 0000000..73c10a7 --- /dev/null +++ b/eslint/lib/rules/comma-spacing.js @@ -0,0 +1,195 @@ +/** + * @fileoverview Comma spacing - validates spacing before and after comma + * @author Vignesh Anand aka vegetableman. + */ +"use strict"; + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "enforce consistent spacing before and after commas", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/comma-spacing" + }, + + fixable: "whitespace", + + schema: [ + { + type: "object", + properties: { + before: { + type: "boolean", + default: false + }, + after: { + type: "boolean", + default: true + } + }, + additionalProperties: false + } + ], + + messages: { + missing: "A space is required {{loc}} ','.", + unexpected: "There should be no space {{loc}} ','." + } + }, + + create(context) { + + const sourceCode = context.getSourceCode(); + const tokensAndComments = sourceCode.tokensAndComments; + + const options = { + before: context.options[0] ? context.options[0].before : false, + after: context.options[0] ? context.options[0].after : true + }; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + // list of comma tokens to ignore for the check of leading whitespace + const commaTokensToIgnore = []; + + /** + * Reports a spacing error with an appropriate message. + * @param {ASTNode} node The binary expression node to report. + * @param {string} loc Is the error "before" or "after" the comma? + * @param {ASTNode} otherNode The node at the left or right of `node` + * @returns {void} + * @private + */ + function report(node, loc, otherNode) { + context.report({ + node, + fix(fixer) { + if (options[loc]) { + if (loc === "before") { + return fixer.insertTextBefore(node, " "); + } + return fixer.insertTextAfter(node, " "); + + } + let start, end; + const newText = ""; + + if (loc === "before") { + start = otherNode.range[1]; + end = node.range[0]; + } else { + start = node.range[1]; + end = otherNode.range[0]; + } + + return fixer.replaceTextRange([start, end], newText); + + }, + messageId: options[loc] ? "missing" : "unexpected", + data: { + loc + } + }); + } + + /** + * Validates the spacing around a comma token. + * @param {Object} tokens The tokens to be validated. + * @param {Token} tokens.comma The token representing the comma. + * @param {Token} [tokens.left] The last token before the comma. + * @param {Token} [tokens.right] The first token after the comma. + * @param {Token|ASTNode} reportItem The item to use when reporting an error. + * @returns {void} + * @private + */ + function validateCommaItemSpacing(tokens, reportItem) { + if (tokens.left && astUtils.isTokenOnSameLine(tokens.left, tokens.comma) && + (options.before !== sourceCode.isSpaceBetweenTokens(tokens.left, tokens.comma)) + ) { + report(reportItem, "before", tokens.left); + } + + if (tokens.right && astUtils.isClosingParenToken(tokens.right)) { + return; + } + + if (tokens.right && !options.after && tokens.right.type === "Line") { + return; + } + + if (tokens.right && astUtils.isTokenOnSameLine(tokens.comma, tokens.right) && + (options.after !== sourceCode.isSpaceBetweenTokens(tokens.comma, tokens.right)) + ) { + report(reportItem, "after", tokens.right); + } + } + + /** + * Adds null elements of the given ArrayExpression or ArrayPattern node to the ignore list. + * @param {ASTNode} node An ArrayExpression or ArrayPattern node. + * @returns {void} + */ + function addNullElementsToIgnoreList(node) { + let previousToken = sourceCode.getFirstToken(node); + + node.elements.forEach(element => { + let token; + + if (element === null) { + token = sourceCode.getTokenAfter(previousToken); + + if (astUtils.isCommaToken(token)) { + commaTokensToIgnore.push(token); + } + } else { + token = sourceCode.getTokenAfter(element); + } + + previousToken = token; + }); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + "Program:exit"() { + tokensAndComments.forEach((token, i) => { + + if (!astUtils.isCommaToken(token)) { + return; + } + + if (token && token.type === "JSXText") { + return; + } + + const previousToken = tokensAndComments[i - 1]; + const nextToken = tokensAndComments[i + 1]; + + validateCommaItemSpacing({ + comma: token, + left: astUtils.isCommaToken(previousToken) || commaTokensToIgnore.indexOf(token) > -1 ? null : previousToken, + right: astUtils.isCommaToken(nextToken) ? null : nextToken + }, token); + }); + }, + ArrayExpression: addNullElementsToIgnoreList, + ArrayPattern: addNullElementsToIgnoreList + + }; + + } +}; diff --git a/eslint/lib/rules/comma-style.js b/eslint/lib/rules/comma-style.js new file mode 100644 index 0000000..bc22f05 --- /dev/null +++ b/eslint/lib/rules/comma-style.js @@ -0,0 +1,315 @@ +/** + * @fileoverview Comma style - enforces comma styles of two types: last and first + * @author Vignesh Anand aka vegetableman + */ + +"use strict"; + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "enforce consistent comma style", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/comma-style" + }, + + fixable: "code", + + schema: [ + { + enum: ["first", "last"] + }, + { + type: "object", + properties: { + exceptions: { + type: "object", + additionalProperties: { + type: "boolean" + } + } + }, + additionalProperties: false + } + ], + + messages: { + unexpectedLineBeforeAndAfterComma: "Bad line breaking before and after ','.", + expectedCommaFirst: "',' should be placed first.", + expectedCommaLast: "',' should be placed last." + } + }, + + create(context) { + const style = context.options[0] || "last", + sourceCode = context.getSourceCode(); + const exceptions = { + ArrayPattern: true, + ArrowFunctionExpression: true, + CallExpression: true, + FunctionDeclaration: true, + FunctionExpression: true, + ImportDeclaration: true, + ObjectPattern: true, + NewExpression: true + }; + + if (context.options.length === 2 && Object.prototype.hasOwnProperty.call(context.options[1], "exceptions")) { + const keys = Object.keys(context.options[1].exceptions); + + for (let i = 0; i < keys.length; i++) { + exceptions[keys[i]] = context.options[1].exceptions[keys[i]]; + } + } + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Modified text based on the style + * @param {string} styleType Style type + * @param {string} text Source code text + * @returns {string} modified text + * @private + */ + function getReplacedText(styleType, text) { + switch (styleType) { + case "between": + return `,${text.replace(astUtils.LINEBREAK_MATCHER, "")}`; + + case "first": + return `${text},`; + + case "last": + return `,${text}`; + + default: + return ""; + } + } + + /** + * Determines the fixer function for a given style. + * @param {string} styleType comma style + * @param {ASTNode} previousItemToken The token to check. + * @param {ASTNode} commaToken The token to check. + * @param {ASTNode} currentItemToken The token to check. + * @returns {Function} Fixer function + * @private + */ + function getFixerFunction(styleType, previousItemToken, commaToken, currentItemToken) { + const text = + sourceCode.text.slice(previousItemToken.range[1], commaToken.range[0]) + + sourceCode.text.slice(commaToken.range[1], currentItemToken.range[0]); + const range = [previousItemToken.range[1], currentItemToken.range[0]]; + + return function(fixer) { + return fixer.replaceTextRange(range, getReplacedText(styleType, text)); + }; + } + + /** + * Validates the spacing around single items in lists. + * @param {Token} previousItemToken The last token from the previous item. + * @param {Token} commaToken The token representing the comma. + * @param {Token} currentItemToken The first token of the current item. + * @param {Token} reportItem The item to use when reporting an error. + * @returns {void} + * @private + */ + function validateCommaItemSpacing(previousItemToken, commaToken, currentItemToken, reportItem) { + + // if single line + if (astUtils.isTokenOnSameLine(commaToken, currentItemToken) && + astUtils.isTokenOnSameLine(previousItemToken, commaToken)) { + + // do nothing. + + } else if (!astUtils.isTokenOnSameLine(commaToken, currentItemToken) && + !astUtils.isTokenOnSameLine(previousItemToken, commaToken)) { + + const comment = sourceCode.getCommentsAfter(commaToken)[0]; + const styleType = comment && comment.type === "Block" && astUtils.isTokenOnSameLine(commaToken, comment) + ? style + : "between"; + + // lone comma + context.report({ + node: reportItem, + loc: { + line: commaToken.loc.end.line, + column: commaToken.loc.start.column + }, + messageId: "unexpectedLineBeforeAndAfterComma", + fix: getFixerFunction(styleType, previousItemToken, commaToken, currentItemToken) + }); + + } else if (style === "first" && !astUtils.isTokenOnSameLine(commaToken, currentItemToken)) { + + context.report({ + node: reportItem, + messageId: "expectedCommaFirst", + fix: getFixerFunction(style, previousItemToken, commaToken, currentItemToken) + }); + + } else if (style === "last" && astUtils.isTokenOnSameLine(commaToken, currentItemToken)) { + + context.report({ + node: reportItem, + loc: { + line: commaToken.loc.end.line, + column: commaToken.loc.end.column + }, + messageId: "expectedCommaLast", + fix: getFixerFunction(style, previousItemToken, commaToken, currentItemToken) + }); + } + } + + /** + * Checks the comma placement with regards to a declaration/property/element + * @param {ASTNode} node The binary expression node to check + * @param {string} property The property of the node containing child nodes. + * @private + * @returns {void} + */ + function validateComma(node, property) { + const items = node[property], + arrayLiteral = (node.type === "ArrayExpression" || node.type === "ArrayPattern"); + + if (items.length > 1 || arrayLiteral) { + + // seed as opening [ + let previousItemToken = sourceCode.getFirstToken(node); + + items.forEach(item => { + const commaToken = item ? sourceCode.getTokenBefore(item) : previousItemToken, + currentItemToken = item ? sourceCode.getFirstToken(item) : sourceCode.getTokenAfter(commaToken), + reportItem = item || currentItemToken; + + /* + * This works by comparing three token locations: + * - previousItemToken is the last token of the previous item + * - commaToken is the location of the comma before the current item + * - currentItemToken is the first token of the current item + * + * These values get switched around if item is undefined. + * previousItemToken will refer to the last token not belonging + * to the current item, which could be a comma or an opening + * square bracket. currentItemToken could be a comma. + * + * All comparisons are done based on these tokens directly, so + * they are always valid regardless of an undefined item. + */ + if (astUtils.isCommaToken(commaToken)) { + validateCommaItemSpacing(previousItemToken, commaToken, + currentItemToken, reportItem); + } + + if (item) { + const tokenAfterItem = sourceCode.getTokenAfter(item, astUtils.isNotClosingParenToken); + + previousItemToken = tokenAfterItem + ? sourceCode.getTokenBefore(tokenAfterItem) + : sourceCode.ast.tokens[sourceCode.ast.tokens.length - 1]; + } + }); + + /* + * Special case for array literals that have empty last items, such + * as [ 1, 2, ]. These arrays only have two items show up in the + * AST, so we need to look at the token to verify that there's no + * dangling comma. + */ + if (arrayLiteral) { + + const lastToken = sourceCode.getLastToken(node), + nextToLastToken = sourceCode.getTokenBefore(lastToken); + + if (astUtils.isCommaToken(nextToLastToken)) { + validateCommaItemSpacing( + sourceCode.getTokenBefore(nextToLastToken), + nextToLastToken, + lastToken, + lastToken + ); + } + } + } + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + const nodes = {}; + + if (!exceptions.VariableDeclaration) { + nodes.VariableDeclaration = function(node) { + validateComma(node, "declarations"); + }; + } + if (!exceptions.ObjectExpression) { + nodes.ObjectExpression = function(node) { + validateComma(node, "properties"); + }; + } + if (!exceptions.ObjectPattern) { + nodes.ObjectPattern = function(node) { + validateComma(node, "properties"); + }; + } + if (!exceptions.ArrayExpression) { + nodes.ArrayExpression = function(node) { + validateComma(node, "elements"); + }; + } + if (!exceptions.ArrayPattern) { + nodes.ArrayPattern = function(node) { + validateComma(node, "elements"); + }; + } + if (!exceptions.FunctionDeclaration) { + nodes.FunctionDeclaration = function(node) { + validateComma(node, "params"); + }; + } + if (!exceptions.FunctionExpression) { + nodes.FunctionExpression = function(node) { + validateComma(node, "params"); + }; + } + if (!exceptions.ArrowFunctionExpression) { + nodes.ArrowFunctionExpression = function(node) { + validateComma(node, "params"); + }; + } + if (!exceptions.CallExpression) { + nodes.CallExpression = function(node) { + validateComma(node, "arguments"); + }; + } + if (!exceptions.ImportDeclaration) { + nodes.ImportDeclaration = function(node) { + validateComma(node, "specifiers"); + }; + } + if (!exceptions.NewExpression) { + nodes.NewExpression = function(node) { + validateComma(node, "arguments"); + }; + } + + return nodes; + } +}; diff --git a/eslint/lib/rules/complexity.js b/eslint/lib/rules/complexity.js new file mode 100644 index 0000000..7fc8bf9 --- /dev/null +++ b/eslint/lib/rules/complexity.js @@ -0,0 +1,160 @@ +/** + * @fileoverview Counts the cyclomatic complexity of each function of the script. See http://en.wikipedia.org/wiki/Cyclomatic_complexity. + * Counts the number of if, conditional, for, while, try, switch/case, + * @author Patrick Brosset + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const lodash = require("lodash"); + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "enforce a maximum cyclomatic complexity allowed in a program", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/complexity" + }, + + schema: [ + { + oneOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "object", + properties: { + maximum: { + type: "integer", + minimum: 0 + }, + max: { + type: "integer", + minimum: 0 + } + }, + additionalProperties: false + } + ] + } + ], + + messages: { + complex: "{{name}} has a complexity of {{complexity}}. Maximum allowed is {{max}}." + } + }, + + create(context) { + const option = context.options[0]; + let THRESHOLD = 20; + + if ( + typeof option === "object" && + (Object.prototype.hasOwnProperty.call(option, "maximum") || Object.prototype.hasOwnProperty.call(option, "max")) + ) { + THRESHOLD = option.maximum || option.max; + } else if (typeof option === "number") { + THRESHOLD = option; + } + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + // Using a stack to store complexity (handling nested functions) + const fns = []; + + /** + * When parsing a new function, store it in our function stack + * @returns {void} + * @private + */ + function startFunction() { + fns.push(1); + } + + /** + * Evaluate the node at the end of function + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function endFunction(node) { + const name = lodash.upperFirst(astUtils.getFunctionNameWithKind(node)); + const complexity = fns.pop(); + + if (complexity > THRESHOLD) { + context.report({ + node, + messageId: "complex", + data: { name, complexity, max: THRESHOLD } + }); + } + } + + /** + * Increase the complexity of the function in context + * @returns {void} + * @private + */ + function increaseComplexity() { + if (fns.length) { + fns[fns.length - 1]++; + } + } + + /** + * Increase the switch complexity in context + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function increaseSwitchComplexity(node) { + + // Avoiding `default` + if (node.test) { + increaseComplexity(); + } + } + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + FunctionDeclaration: startFunction, + FunctionExpression: startFunction, + ArrowFunctionExpression: startFunction, + "FunctionDeclaration:exit": endFunction, + "FunctionExpression:exit": endFunction, + "ArrowFunctionExpression:exit": endFunction, + + CatchClause: increaseComplexity, + ConditionalExpression: increaseComplexity, + LogicalExpression: increaseComplexity, + ForStatement: increaseComplexity, + ForInStatement: increaseComplexity, + ForOfStatement: increaseComplexity, + IfStatement: increaseComplexity, + SwitchCase: increaseSwitchComplexity, + WhileStatement: increaseComplexity, + DoWhileStatement: increaseComplexity + }; + + } +}; diff --git a/eslint/lib/rules/computed-property-spacing.js b/eslint/lib/rules/computed-property-spacing.js new file mode 100644 index 0000000..53fdb8f --- /dev/null +++ b/eslint/lib/rules/computed-property-spacing.js @@ -0,0 +1,204 @@ +/** + * @fileoverview Disallows or enforces spaces inside computed properties. + * @author Jamund Ferguson + */ +"use strict"; + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "enforce consistent spacing inside computed property brackets", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/computed-property-spacing" + }, + + fixable: "whitespace", + + schema: [ + { + enum: ["always", "never"] + }, + { + type: "object", + properties: { + enforceForClassMembers: { + type: "boolean", + default: true + } + }, + additionalProperties: false + } + ], + + messages: { + unexpectedSpaceBefore: "There should be no space before '{{tokenValue}}'.", + unexpectedSpaceAfter: "There should be no space after '{{tokenValue}}'.", + + missingSpaceBefore: "A space is required before '{{tokenValue}}'.", + missingSpaceAfter: "A space is required after '{{tokenValue}}'." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + const propertyNameMustBeSpaced = context.options[0] === "always"; // default is "never" + const enforceForClassMembers = !context.options[1] || context.options[1].enforceForClassMembers; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Reports that there shouldn't be a space after the first token + * @param {ASTNode} node The node to report in the event of an error. + * @param {Token} token The token to use for the report. + * @param {Token} tokenAfter The token after `token`. + * @returns {void} + */ + function reportNoBeginningSpace(node, token, tokenAfter) { + context.report({ + node, + loc: { start: token.loc.end, end: tokenAfter.loc.start }, + messageId: "unexpectedSpaceAfter", + data: { + tokenValue: token.value + }, + fix(fixer) { + return fixer.removeRange([token.range[1], tokenAfter.range[0]]); + } + }); + } + + /** + * Reports that there shouldn't be a space before the last token + * @param {ASTNode} node The node to report in the event of an error. + * @param {Token} token The token to use for the report. + * @param {Token} tokenBefore The token before `token`. + * @returns {void} + */ + function reportNoEndingSpace(node, token, tokenBefore) { + context.report({ + node, + loc: { start: tokenBefore.loc.end, end: token.loc.start }, + messageId: "unexpectedSpaceBefore", + data: { + tokenValue: token.value + }, + fix(fixer) { + return fixer.removeRange([tokenBefore.range[1], token.range[0]]); + } + }); + } + + /** + * Reports that there should be a space after the first token + * @param {ASTNode} node The node to report in the event of an error. + * @param {Token} token The token to use for the report. + * @returns {void} + */ + function reportRequiredBeginningSpace(node, token) { + context.report({ + node, + loc: token.loc, + messageId: "missingSpaceAfter", + data: { + tokenValue: token.value + }, + fix(fixer) { + return fixer.insertTextAfter(token, " "); + } + }); + } + + /** + * Reports that there should be a space before the last token + * @param {ASTNode} node The node to report in the event of an error. + * @param {Token} token The token to use for the report. + * @returns {void} + */ + function reportRequiredEndingSpace(node, token) { + context.report({ + node, + loc: token.loc, + messageId: "missingSpaceBefore", + data: { + tokenValue: token.value + }, + fix(fixer) { + return fixer.insertTextBefore(token, " "); + } + }); + } + + /** + * Returns a function that checks the spacing of a node on the property name + * that was passed in. + * @param {string} propertyName The property on the node to check for spacing + * @returns {Function} A function that will check spacing on a node + */ + function checkSpacing(propertyName) { + return function(node) { + if (!node.computed) { + return; + } + + const property = node[propertyName]; + + const before = sourceCode.getTokenBefore(property, astUtils.isOpeningBracketToken), + first = sourceCode.getTokenAfter(before, { includeComments: true }), + after = sourceCode.getTokenAfter(property, astUtils.isClosingBracketToken), + last = sourceCode.getTokenBefore(after, { includeComments: true }); + + if (astUtils.isTokenOnSameLine(before, first)) { + if (propertyNameMustBeSpaced) { + if (!sourceCode.isSpaceBetweenTokens(before, first) && astUtils.isTokenOnSameLine(before, first)) { + reportRequiredBeginningSpace(node, before); + } + } else { + if (sourceCode.isSpaceBetweenTokens(before, first)) { + reportNoBeginningSpace(node, before, first); + } + } + } + + if (astUtils.isTokenOnSameLine(last, after)) { + if (propertyNameMustBeSpaced) { + if (!sourceCode.isSpaceBetweenTokens(last, after) && astUtils.isTokenOnSameLine(last, after)) { + reportRequiredEndingSpace(node, after); + } + } else { + if (sourceCode.isSpaceBetweenTokens(last, after)) { + reportNoEndingSpace(node, after, last); + } + } + } + }; + } + + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + const listeners = { + Property: checkSpacing("key"), + MemberExpression: checkSpacing("property") + }; + + if (enforceForClassMembers) { + listeners.MethodDefinition = checkSpacing("key"); + } + + return listeners; + + } +}; diff --git a/eslint/lib/rules/consistent-return.js b/eslint/lib/rules/consistent-return.js new file mode 100644 index 0000000..22667fa --- /dev/null +++ b/eslint/lib/rules/consistent-return.js @@ -0,0 +1,196 @@ +/** + * @fileoverview Rule to flag consistent return values + * @author Nicholas C. Zakas + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const lodash = require("lodash"); + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Checks whether or not a given node is an `Identifier` node which was named a given name. + * @param {ASTNode} node A node to check. + * @param {string} name An expected name of the node. + * @returns {boolean} `true` if the node is an `Identifier` node which was named as expected. + */ +function isIdentifier(node, name) { + return node.type === "Identifier" && node.name === name; +} + +/** + * Checks whether or not a given code path segment is unreachable. + * @param {CodePathSegment} segment A CodePathSegment to check. + * @returns {boolean} `true` if the segment is unreachable. + */ +function isUnreachable(segment) { + return !segment.reachable; +} + +/** + * Checks whether a given node is a `constructor` method in an ES6 class + * @param {ASTNode} node A node to check + * @returns {boolean} `true` if the node is a `constructor` method + */ +function isClassConstructor(node) { + return node.type === "FunctionExpression" && + node.parent && + node.parent.type === "MethodDefinition" && + node.parent.kind === "constructor"; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "require `return` statements to either always or never specify values", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/consistent-return" + }, + + schema: [{ + type: "object", + properties: { + treatUndefinedAsUnspecified: { + type: "boolean", + default: false + } + }, + additionalProperties: false + }], + + messages: { + missingReturn: "Expected to return a value at the end of {{name}}.", + missingReturnValue: "{{name}} expected a return value.", + unexpectedReturnValue: "{{name}} expected no return value." + } + }, + + create(context) { + const options = context.options[0] || {}; + const treatUndefinedAsUnspecified = options.treatUndefinedAsUnspecified === true; + let funcInfo = null; + + /** + * Checks whether of not the implicit returning is consistent if the last + * code path segment is reachable. + * @param {ASTNode} node A program/function node to check. + * @returns {void} + */ + function checkLastSegment(node) { + let loc, name; + + /* + * Skip if it expected no return value or unreachable. + * When unreachable, all paths are returned or thrown. + */ + if (!funcInfo.hasReturnValue || + funcInfo.codePath.currentSegments.every(isUnreachable) || + astUtils.isES5Constructor(node) || + isClassConstructor(node) + ) { + return; + } + + // Adjust a location and a message. + if (node.type === "Program") { + + // The head of program. + loc = { line: 1, column: 0 }; + name = "program"; + } else if (node.type === "ArrowFunctionExpression") { + + // `=>` token + loc = context.getSourceCode().getTokenBefore(node.body, astUtils.isArrowToken).loc.start; + } else if ( + node.parent.type === "MethodDefinition" || + (node.parent.type === "Property" && node.parent.method) + ) { + + // Method name. + loc = node.parent.key.loc.start; + } else { + + // Function name or `function` keyword. + loc = (node.id || node).loc.start; + } + + if (!name) { + name = astUtils.getFunctionNameWithKind(node); + } + + // Reports. + context.report({ + node, + loc, + messageId: "missingReturn", + data: { name } + }); + } + + return { + + // Initializes/Disposes state of each code path. + onCodePathStart(codePath, node) { + funcInfo = { + upper: funcInfo, + codePath, + hasReturn: false, + hasReturnValue: false, + messageId: "", + node + }; + }, + onCodePathEnd() { + funcInfo = funcInfo.upper; + }, + + // Reports a given return statement if it's inconsistent. + ReturnStatement(node) { + const argument = node.argument; + let hasReturnValue = Boolean(argument); + + if (treatUndefinedAsUnspecified && hasReturnValue) { + hasReturnValue = !isIdentifier(argument, "undefined") && argument.operator !== "void"; + } + + if (!funcInfo.hasReturn) { + funcInfo.hasReturn = true; + funcInfo.hasReturnValue = hasReturnValue; + funcInfo.messageId = hasReturnValue ? "missingReturnValue" : "unexpectedReturnValue"; + funcInfo.data = { + name: funcInfo.node.type === "Program" + ? "Program" + : lodash.upperFirst(astUtils.getFunctionNameWithKind(funcInfo.node)) + }; + } else if (funcInfo.hasReturnValue !== hasReturnValue) { + context.report({ + node, + messageId: funcInfo.messageId, + data: funcInfo.data + }); + } + }, + + // Reports a given program/function if the implicit returning is not consistent. + "Program:exit": checkLastSegment, + "FunctionDeclaration:exit": checkLastSegment, + "FunctionExpression:exit": checkLastSegment, + "ArrowFunctionExpression:exit": checkLastSegment + }; + } +}; diff --git a/eslint/lib/rules/consistent-this.js b/eslint/lib/rules/consistent-this.js new file mode 100644 index 0000000..e5bc967 --- /dev/null +++ b/eslint/lib/rules/consistent-this.js @@ -0,0 +1,151 @@ +/** + * @fileoverview Rule to enforce consistent naming of "this" context variables + * @author Raphael Pigulla + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "enforce consistent naming when capturing the current execution context", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/consistent-this" + }, + + schema: { + type: "array", + items: { + type: "string", + minLength: 1 + }, + uniqueItems: true + }, + + messages: { + aliasNotAssignedToThis: "Designated alias '{{name}}' is not assigned to 'this'.", + unexpectedAlias: "Unexpected alias '{{name}}' for 'this'." + } + }, + + create(context) { + let aliases = []; + + if (context.options.length === 0) { + aliases.push("that"); + } else { + aliases = context.options; + } + + /** + * Reports that a variable declarator or assignment expression is assigning + * a non-'this' value to the specified alias. + * @param {ASTNode} node The assigning node. + * @param {string} name the name of the alias that was incorrectly used. + * @returns {void} + */ + function reportBadAssignment(node, name) { + context.report({ node, messageId: "aliasNotAssignedToThis", data: { name } }); + } + + /** + * Checks that an assignment to an identifier only assigns 'this' to the + * appropriate alias, and the alias is only assigned to 'this'. + * @param {ASTNode} node The assigning node. + * @param {Identifier} name The name of the variable assigned to. + * @param {Expression} value The value of the assignment. + * @returns {void} + */ + function checkAssignment(node, name, value) { + const isThis = value.type === "ThisExpression"; + + if (aliases.indexOf(name) !== -1) { + if (!isThis || node.operator && node.operator !== "=") { + reportBadAssignment(node, name); + } + } else if (isThis) { + context.report({ node, messageId: "unexpectedAlias", data: { name } }); + } + } + + /** + * Ensures that a variable declaration of the alias in a program or function + * is assigned to the correct value. + * @param {string} alias alias the check the assignment of. + * @param {Object} scope scope of the current code we are checking. + * @private + * @returns {void} + */ + function checkWasAssigned(alias, scope) { + const variable = scope.set.get(alias); + + if (!variable) { + return; + } + + if (variable.defs.some(def => def.node.type === "VariableDeclarator" && + def.node.init !== null)) { + return; + } + + /* + * The alias has been declared and not assigned: check it was + * assigned later in the same scope. + */ + if (!variable.references.some(reference => { + const write = reference.writeExpr; + + return ( + reference.from === scope && + write && write.type === "ThisExpression" && + write.parent.operator === "=" + ); + })) { + variable.defs.map(def => def.node).forEach(node => { + reportBadAssignment(node, alias); + }); + } + } + + /** + * Check each alias to ensure that is was assigned to the correct value. + * @returns {void} + */ + function ensureWasAssigned() { + const scope = context.getScope(); + + aliases.forEach(alias => { + checkWasAssigned(alias, scope); + }); + } + + return { + "Program:exit": ensureWasAssigned, + "FunctionExpression:exit": ensureWasAssigned, + "FunctionDeclaration:exit": ensureWasAssigned, + + VariableDeclarator(node) { + const id = node.id; + const isDestructuring = + id.type === "ArrayPattern" || id.type === "ObjectPattern"; + + if (node.init !== null && !isDestructuring) { + checkAssignment(node, id.name, node.init); + } + }, + + AssignmentExpression(node) { + if (node.left.type === "Identifier") { + checkAssignment(node, node.left.name, node.right); + } + } + }; + + } +}; diff --git a/eslint/lib/rules/constructor-super.js b/eslint/lib/rules/constructor-super.js new file mode 100644 index 0000000..5a848f2 --- /dev/null +++ b/eslint/lib/rules/constructor-super.js @@ -0,0 +1,395 @@ +/** + * @fileoverview A rule to verify `super()` callings in constructor. + * @author Toru Nagashima + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Checks whether a given code path segment is reachable or not. + * @param {CodePathSegment} segment A code path segment to check. + * @returns {boolean} `true` if the segment is reachable. + */ +function isReachable(segment) { + return segment.reachable; +} + +/** + * Checks whether or not a given node is a constructor. + * @param {ASTNode} node A node to check. This node type is one of + * `Program`, `FunctionDeclaration`, `FunctionExpression`, and + * `ArrowFunctionExpression`. + * @returns {boolean} `true` if the node is a constructor. + */ +function isConstructorFunction(node) { + return ( + node.type === "FunctionExpression" && + node.parent.type === "MethodDefinition" && + node.parent.kind === "constructor" + ); +} + +/** + * Checks whether a given node can be a constructor or not. + * @param {ASTNode} node A node to check. + * @returns {boolean} `true` if the node can be a constructor. + */ +function isPossibleConstructor(node) { + if (!node) { + return false; + } + + switch (node.type) { + case "ClassExpression": + case "FunctionExpression": + case "ThisExpression": + case "MemberExpression": + case "CallExpression": + case "NewExpression": + case "YieldExpression": + case "TaggedTemplateExpression": + case "MetaProperty": + return true; + + case "Identifier": + return node.name !== "undefined"; + + case "AssignmentExpression": + return isPossibleConstructor(node.right); + + case "LogicalExpression": + return ( + isPossibleConstructor(node.left) || + isPossibleConstructor(node.right) + ); + + case "ConditionalExpression": + return ( + isPossibleConstructor(node.alternate) || + isPossibleConstructor(node.consequent) + ); + + case "SequenceExpression": { + const lastExpression = node.expressions[node.expressions.length - 1]; + + return isPossibleConstructor(lastExpression); + } + + default: + return false; + } +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "require `super()` calls in constructors", + category: "ECMAScript 6", + recommended: true, + url: "https://eslint.org/docs/rules/constructor-super" + }, + + schema: [], + + messages: { + missingSome: "Lacked a call of 'super()' in some code paths.", + missingAll: "Expected to call 'super()'.", + + duplicate: "Unexpected duplicate 'super()'.", + badSuper: "Unexpected 'super()' because 'super' is not a constructor.", + unexpected: "Unexpected 'super()'." + } + }, + + create(context) { + + /* + * {{hasExtends: boolean, scope: Scope, codePath: CodePath}[]} + * Information for each constructor. + * - upper: Information of the upper constructor. + * - hasExtends: A flag which shows whether own class has a valid `extends` + * part. + * - scope: The scope of own class. + * - codePath: The code path object of the constructor. + */ + let funcInfo = null; + + /* + * {Map} + * Information for each code path segment. + * - calledInSomePaths: A flag of be called `super()` in some code paths. + * - calledInEveryPaths: A flag of be called `super()` in all code paths. + * - validNodes: + */ + let segInfoMap = Object.create(null); + + /** + * Gets the flag which shows `super()` is called in some paths. + * @param {CodePathSegment} segment A code path segment to get. + * @returns {boolean} The flag which shows `super()` is called in some paths + */ + function isCalledInSomePath(segment) { + return segment.reachable && segInfoMap[segment.id].calledInSomePaths; + } + + /** + * Gets the flag which shows `super()` is called in all paths. + * @param {CodePathSegment} segment A code path segment to get. + * @returns {boolean} The flag which shows `super()` is called in all paths. + */ + function isCalledInEveryPath(segment) { + + /* + * If specific segment is the looped segment of the current segment, + * skip the segment. + * If not skipped, this never becomes true after a loop. + */ + if (segment.nextSegments.length === 1 && + segment.nextSegments[0].isLoopedPrevSegment(segment) + ) { + return true; + } + return segment.reachable && segInfoMap[segment.id].calledInEveryPaths; + } + + return { + + /** + * Stacks a constructor information. + * @param {CodePath} codePath A code path which was started. + * @param {ASTNode} node The current node. + * @returns {void} + */ + onCodePathStart(codePath, node) { + if (isConstructorFunction(node)) { + + // Class > ClassBody > MethodDefinition > FunctionExpression + const classNode = node.parent.parent.parent; + const superClass = classNode.superClass; + + funcInfo = { + upper: funcInfo, + isConstructor: true, + hasExtends: Boolean(superClass), + superIsConstructor: isPossibleConstructor(superClass), + codePath + }; + } else { + funcInfo = { + upper: funcInfo, + isConstructor: false, + hasExtends: false, + superIsConstructor: false, + codePath + }; + } + }, + + /** + * Pops a constructor information. + * And reports if `super()` lacked. + * @param {CodePath} codePath A code path which was ended. + * @param {ASTNode} node The current node. + * @returns {void} + */ + onCodePathEnd(codePath, node) { + const hasExtends = funcInfo.hasExtends; + + // Pop. + funcInfo = funcInfo.upper; + + if (!hasExtends) { + return; + } + + // Reports if `super()` lacked. + const segments = codePath.returnedSegments; + const calledInEveryPaths = segments.every(isCalledInEveryPath); + const calledInSomePaths = segments.some(isCalledInSomePath); + + if (!calledInEveryPaths) { + context.report({ + messageId: calledInSomePaths + ? "missingSome" + : "missingAll", + node: node.parent + }); + } + }, + + /** + * Initialize information of a given code path segment. + * @param {CodePathSegment} segment A code path segment to initialize. + * @returns {void} + */ + onCodePathSegmentStart(segment) { + if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) { + return; + } + + // Initialize info. + const info = segInfoMap[segment.id] = { + calledInSomePaths: false, + calledInEveryPaths: false, + validNodes: [] + }; + + // When there are previous segments, aggregates these. + const prevSegments = segment.prevSegments; + + if (prevSegments.length > 0) { + info.calledInSomePaths = prevSegments.some(isCalledInSomePath); + info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath); + } + }, + + /** + * Update information of the code path segment when a code path was + * looped. + * @param {CodePathSegment} fromSegment The code path segment of the + * end of a loop. + * @param {CodePathSegment} toSegment A code path segment of the head + * of a loop. + * @returns {void} + */ + onCodePathSegmentLoop(fromSegment, toSegment) { + if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) { + return; + } + + // Update information inside of the loop. + const isRealLoop = toSegment.prevSegments.length >= 2; + + funcInfo.codePath.traverseSegments( + { first: toSegment, last: fromSegment }, + segment => { + const info = segInfoMap[segment.id]; + const prevSegments = segment.prevSegments; + + // Updates flags. + info.calledInSomePaths = prevSegments.some(isCalledInSomePath); + info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath); + + // If flags become true anew, reports the valid nodes. + if (info.calledInSomePaths || isRealLoop) { + const nodes = info.validNodes; + + info.validNodes = []; + + for (let i = 0; i < nodes.length; ++i) { + const node = nodes[i]; + + context.report({ + messageId: "duplicate", + node + }); + } + } + } + ); + }, + + /** + * Checks for a call of `super()`. + * @param {ASTNode} node A CallExpression node to check. + * @returns {void} + */ + "CallExpression:exit"(node) { + if (!(funcInfo && funcInfo.isConstructor)) { + return; + } + + // Skips except `super()`. + if (node.callee.type !== "Super") { + return; + } + + // Reports if needed. + if (funcInfo.hasExtends) { + const segments = funcInfo.codePath.currentSegments; + let duplicate = false; + let info = null; + + for (let i = 0; i < segments.length; ++i) { + const segment = segments[i]; + + if (segment.reachable) { + info = segInfoMap[segment.id]; + + duplicate = duplicate || info.calledInSomePaths; + info.calledInSomePaths = info.calledInEveryPaths = true; + } + } + + if (info) { + if (duplicate) { + context.report({ + messageId: "duplicate", + node + }); + } else if (!funcInfo.superIsConstructor) { + context.report({ + messageId: "badSuper", + node + }); + } else { + info.validNodes.push(node); + } + } + } else if (funcInfo.codePath.currentSegments.some(isReachable)) { + context.report({ + messageId: "unexpected", + node + }); + } + }, + + /** + * Set the mark to the returned path as `super()` was called. + * @param {ASTNode} node A ReturnStatement node to check. + * @returns {void} + */ + ReturnStatement(node) { + if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) { + return; + } + + // Skips if no argument. + if (!node.argument) { + return; + } + + // Returning argument is a substitute of 'super()'. + const segments = funcInfo.codePath.currentSegments; + + for (let i = 0; i < segments.length; ++i) { + const segment = segments[i]; + + if (segment.reachable) { + const info = segInfoMap[segment.id]; + + info.calledInSomePaths = info.calledInEveryPaths = true; + } + } + }, + + /** + * Resets state. + * @returns {void} + */ + "Program:exit"() { + segInfoMap = Object.create(null); + } + }; + } +}; diff --git a/eslint/lib/rules/curly.js b/eslint/lib/rules/curly.js new file mode 100644 index 0000000..29f00c0 --- /dev/null +++ b/eslint/lib/rules/curly.js @@ -0,0 +1,488 @@ +/** + * @fileoverview Rule to flag statements without curly braces + * @author Nicholas C. Zakas + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "enforce consistent brace style for all control statements", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/curly" + }, + + schema: { + anyOf: [ + { + type: "array", + items: [ + { + enum: ["all"] + } + ], + minItems: 0, + maxItems: 1 + }, + { + type: "array", + items: [ + { + enum: ["multi", "multi-line", "multi-or-nest"] + }, + { + enum: ["consistent"] + } + ], + minItems: 0, + maxItems: 2 + } + ] + }, + + fixable: "code", + + messages: { + missingCurlyAfter: "Expected { after '{{name}}'.", + missingCurlyAfterCondition: "Expected { after '{{name}}' condition.", + unexpectedCurlyAfter: "Unnecessary { after '{{name}}'.", + unexpectedCurlyAfterCondition: "Unnecessary { after '{{name}}' condition." + } + }, + + create(context) { + + const multiOnly = (context.options[0] === "multi"); + const multiLine = (context.options[0] === "multi-line"); + const multiOrNest = (context.options[0] === "multi-or-nest"); + const consistent = (context.options[1] === "consistent"); + + const sourceCode = context.getSourceCode(); + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Determines if a given node is a one-liner that's on the same line as it's preceding code. + * @param {ASTNode} node The node to check. + * @returns {boolean} True if the node is a one-liner that's on the same line as it's preceding code. + * @private + */ + function isCollapsedOneLiner(node) { + const before = sourceCode.getTokenBefore(node); + const last = sourceCode.getLastToken(node); + const lastExcludingSemicolon = astUtils.isSemicolonToken(last) ? sourceCode.getTokenBefore(last) : last; + + return before.loc.start.line === lastExcludingSemicolon.loc.end.line; + } + + /** + * Determines if a given node is a one-liner. + * @param {ASTNode} node The node to check. + * @returns {boolean} True if the node is a one-liner. + * @private + */ + function isOneLiner(node) { + if (node.type === "EmptyStatement") { + return true; + } + + const first = sourceCode.getFirstToken(node); + const last = sourceCode.getLastToken(node); + const lastExcludingSemicolon = astUtils.isSemicolonToken(last) ? sourceCode.getTokenBefore(last) : last; + + return first.loc.start.line === lastExcludingSemicolon.loc.end.line; + } + + /** + * Determines if the given node is a lexical declaration (let, const, function, or class) + * @param {ASTNode} node The node to check + * @returns {boolean} True if the node is a lexical declaration + * @private + */ + function isLexicalDeclaration(node) { + if (node.type === "VariableDeclaration") { + return node.kind === "const" || node.kind === "let"; + } + + return node.type === "FunctionDeclaration" || node.type === "ClassDeclaration"; + } + + /** + * Checks if the given token is an `else` token or not. + * @param {Token} token The token to check. + * @returns {boolean} `true` if the token is an `else` token. + */ + function isElseKeywordToken(token) { + return token.value === "else" && token.type === "Keyword"; + } + + /** + * Gets the `else` keyword token of a given `IfStatement` node. + * @param {ASTNode} node A `IfStatement` node to get. + * @returns {Token} The `else` keyword token. + */ + function getElseKeyword(node) { + return node.alternate && sourceCode.getFirstTokenBetween(node.consequent, node.alternate, isElseKeywordToken); + } + + /** + * Determines whether the given node has an `else` keyword token as the first token after. + * @param {ASTNode} node The node to check. + * @returns {boolean} `true` if the node is followed by an `else` keyword token. + */ + function isFollowedByElseKeyword(node) { + const nextToken = sourceCode.getTokenAfter(node); + + return Boolean(nextToken) && isElseKeywordToken(nextToken); + } + + /** + * Determines if a semicolon needs to be inserted after removing a set of curly brackets, in order to avoid a SyntaxError. + * @param {Token} closingBracket The } token + * @returns {boolean} `true` if a semicolon needs to be inserted after the last statement in the block. + */ + function needsSemicolon(closingBracket) { + const tokenBefore = sourceCode.getTokenBefore(closingBracket); + const tokenAfter = sourceCode.getTokenAfter(closingBracket); + const lastBlockNode = sourceCode.getNodeByRangeIndex(tokenBefore.range[0]); + + if (astUtils.isSemicolonToken(tokenBefore)) { + + // If the last statement already has a semicolon, don't add another one. + return false; + } + + if (!tokenAfter) { + + // If there are no statements after this block, there is no need to add a semicolon. + return false; + } + + if (lastBlockNode.type === "BlockStatement" && lastBlockNode.parent.type !== "FunctionExpression" && lastBlockNode.parent.type !== "ArrowFunctionExpression") { + + /* + * If the last node surrounded by curly brackets is a BlockStatement (other than a FunctionExpression or an ArrowFunctionExpression), + * don't insert a semicolon. Otherwise, the semicolon would be parsed as a separate statement, which would cause + * a SyntaxError if it was followed by `else`. + */ + return false; + } + + if (tokenBefore.loc.end.line === tokenAfter.loc.start.line) { + + // If the next token is on the same line, insert a semicolon. + return true; + } + + if (/^[([/`+-]/u.test(tokenAfter.value)) { + + // If the next token starts with a character that would disrupt ASI, insert a semicolon. + return true; + } + + if (tokenBefore.type === "Punctuator" && (tokenBefore.value === "++" || tokenBefore.value === "--")) { + + // If the last token is ++ or --, insert a semicolon to avoid disrupting ASI. + return true; + } + + // Otherwise, do not insert a semicolon. + return false; + } + + /** + * Determines whether the code represented by the given node contains an `if` statement + * that would become associated with an `else` keyword directly appended to that code. + * + * Examples where it returns `true`: + * + * if (a) + * foo(); + * + * if (a) { + * foo(); + * } + * + * if (a) + * foo(); + * else if (b) + * bar(); + * + * while (a) + * if (b) + * if(c) + * foo(); + * else + * bar(); + * + * Examples where it returns `false`: + * + * if (a) + * foo(); + * else + * bar(); + * + * while (a) { + * if (b) + * if(c) + * foo(); + * else + * bar(); + * } + * + * while (a) + * if (b) { + * if(c) + * foo(); + * } + * else + * bar(); + * @param {ASTNode} node Node representing the code to check. + * @returns {boolean} `true` if an `if` statement within the code would become associated with an `else` appended to that code. + */ + function hasUnsafeIf(node) { + switch (node.type) { + case "IfStatement": + if (!node.alternate) { + return true; + } + return hasUnsafeIf(node.alternate); + case "ForStatement": + case "ForInStatement": + case "ForOfStatement": + case "LabeledStatement": + case "WithStatement": + case "WhileStatement": + return hasUnsafeIf(node.body); + default: + return false; + } + } + + /** + * Determines whether the existing curly braces around the single statement are necessary to preserve the semantics of the code. + * The braces, which make the given block body, are necessary in either of the following situations: + * + * 1. The statement is a lexical declaration. + * 2. Without the braces, an `if` within the statement would become associated with an `else` after the closing brace: + * + * if (a) { + * if (b) + * foo(); + * } + * else + * bar(); + * + * if (a) + * while (b) + * while (c) { + * while (d) + * if (e) + * while(f) + * foo(); + * } + * else + * bar(); + * @param {ASTNode} node `BlockStatement` body with exactly one statement directly inside. The statement can have its own nested statements. + * @returns {boolean} `true` if the braces are necessary - removing them (replacing the given `BlockStatement` body with its single statement content) + * would change the semantics of the code or produce a syntax error. + */ + function areBracesNecessary(node) { + const statement = node.body[0]; + + return isLexicalDeclaration(statement) || + hasUnsafeIf(statement) && isFollowedByElseKeyword(node); + } + + /** + * Prepares to check the body of a node to see if it's a block statement. + * @param {ASTNode} node The node to report if there's a problem. + * @param {ASTNode} body The body node to check for blocks. + * @param {string} name The name to report if there's a problem. + * @param {{ condition: boolean }} opts Options to pass to the report functions + * @returns {Object} a prepared check object, with "actual", "expected", "check" properties. + * "actual" will be `true` or `false` whether the body is already a block statement. + * "expected" will be `true` or `false` if the body should be a block statement or not, or + * `null` if it doesn't matter, depending on the rule options. It can be modified to change + * the final behavior of "check". + * "check" will be a function reporting appropriate problems depending on the other + * properties. + */ + function prepareCheck(node, body, name, opts) { + const hasBlock = (body.type === "BlockStatement"); + let expected = null; + + if (hasBlock && (body.body.length !== 1 || areBracesNecessary(body))) { + expected = true; + } else if (multiOnly) { + expected = false; + } else if (multiLine) { + if (!isCollapsedOneLiner(body)) { + expected = true; + } + + // otherwise, the body is allowed to have braces or not to have braces + + } else if (multiOrNest) { + if (hasBlock) { + const statement = body.body[0]; + const leadingCommentsInBlock = sourceCode.getCommentsBefore(statement); + + expected = !isOneLiner(statement) || leadingCommentsInBlock.length > 0; + } else { + expected = !isOneLiner(body); + } + } else { + + // default "all" + expected = true; + } + + return { + actual: hasBlock, + expected, + check() { + if (this.expected !== null && this.expected !== this.actual) { + if (this.expected) { + context.report({ + node, + loc: (name !== "else" ? node : getElseKeyword(node)).loc.start, + messageId: opts && opts.condition ? "missingCurlyAfterCondition" : "missingCurlyAfter", + data: { + name + }, + fix: fixer => fixer.replaceText(body, `{${sourceCode.getText(body)}}`) + }); + } else { + context.report({ + node, + loc: (name !== "else" ? node : getElseKeyword(node)).loc.start, + messageId: opts && opts.condition ? "unexpectedCurlyAfterCondition" : "unexpectedCurlyAfter", + data: { + name + }, + fix(fixer) { + + /* + * `do while` expressions sometimes need a space to be inserted after `do`. + * e.g. `do{foo()} while (bar)` should be corrected to `do foo() while (bar)` + */ + const needsPrecedingSpace = node.type === "DoWhileStatement" && + sourceCode.getTokenBefore(body).range[1] === body.range[0] && + !astUtils.canTokensBeAdjacent("do", sourceCode.getFirstToken(body, { skip: 1 })); + + const openingBracket = sourceCode.getFirstToken(body); + const closingBracket = sourceCode.getLastToken(body); + const lastTokenInBlock = sourceCode.getTokenBefore(closingBracket); + + if (needsSemicolon(closingBracket)) { + + /* + * If removing braces would cause a SyntaxError due to multiple statements on the same line (or + * change the semantics of the code due to ASI), don't perform a fix. + */ + return null; + } + + const resultingBodyText = sourceCode.getText().slice(openingBracket.range[1], lastTokenInBlock.range[0]) + + sourceCode.getText(lastTokenInBlock) + + sourceCode.getText().slice(lastTokenInBlock.range[1], closingBracket.range[0]); + + return fixer.replaceText(body, (needsPrecedingSpace ? " " : "") + resultingBodyText); + } + }); + } + } + } + }; + } + + /** + * Prepares to check the bodies of a "if", "else if" and "else" chain. + * @param {ASTNode} node The first IfStatement node of the chain. + * @returns {Object[]} prepared checks for each body of the chain. See `prepareCheck` for more + * information. + */ + function prepareIfChecks(node) { + const preparedChecks = []; + + for (let currentNode = node; currentNode; currentNode = currentNode.alternate) { + preparedChecks.push(prepareCheck(currentNode, currentNode.consequent, "if", { condition: true })); + if (currentNode.alternate && currentNode.alternate.type !== "IfStatement") { + preparedChecks.push(prepareCheck(currentNode, currentNode.alternate, "else")); + break; + } + } + + if (consistent) { + + /* + * If any node should have or already have braces, make sure they + * all have braces. + * If all nodes shouldn't have braces, make sure they don't. + */ + const expected = preparedChecks.some(preparedCheck => { + if (preparedCheck.expected !== null) { + return preparedCheck.expected; + } + return preparedCheck.actual; + }); + + preparedChecks.forEach(preparedCheck => { + preparedCheck.expected = expected; + }); + } + + return preparedChecks; + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + IfStatement(node) { + if (node.parent.type !== "IfStatement") { + prepareIfChecks(node).forEach(preparedCheck => { + preparedCheck.check(); + }); + } + }, + + WhileStatement(node) { + prepareCheck(node, node.body, "while", { condition: true }).check(); + }, + + DoWhileStatement(node) { + prepareCheck(node, node.body, "do").check(); + }, + + ForStatement(node) { + prepareCheck(node, node.body, "for", { condition: true }).check(); + }, + + ForInStatement(node) { + prepareCheck(node, node.body, "for-in").check(); + }, + + ForOfStatement(node) { + prepareCheck(node, node.body, "for-of").check(); + } + }; + } +}; diff --git a/eslint/lib/rules/default-case-last.js b/eslint/lib/rules/default-case-last.js new file mode 100644 index 0000000..80c5d6b --- /dev/null +++ b/eslint/lib/rules/default-case-last.js @@ -0,0 +1,44 @@ +/** + * @fileoverview Rule to enforce default clauses in switch statements to be last + * @author Milos Djermanovic + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "enforce default clauses in switch statements to be last", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/default-case-last" + }, + + schema: [], + + messages: { + notLast: "Default clause should be the last clause." + } + }, + + create(context) { + return { + SwitchStatement(node) { + const cases = node.cases, + indexOfDefault = cases.findIndex(c => c.test === null); + + if (indexOfDefault !== -1 && indexOfDefault !== cases.length - 1) { + const defaultClause = cases[indexOfDefault]; + + context.report({ node: defaultClause, messageId: "notLast" }); + } + } + }; + } +}; diff --git a/eslint/lib/rules/default-case.js b/eslint/lib/rules/default-case.js new file mode 100644 index 0000000..821e0d7 --- /dev/null +++ b/eslint/lib/rules/default-case.js @@ -0,0 +1,97 @@ +/** + * @fileoverview require default case in switch statements + * @author Aliaksei Shytkin + */ +"use strict"; + +const DEFAULT_COMMENT_PATTERN = /^no default$/iu; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "require `default` cases in `switch` statements", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/default-case" + }, + + schema: [{ + type: "object", + properties: { + commentPattern: { + type: "string" + } + }, + additionalProperties: false + }], + + messages: { + missingDefaultCase: "Expected a default case." + } + }, + + create(context) { + const options = context.options[0] || {}; + const commentPattern = options.commentPattern + ? new RegExp(options.commentPattern, "u") + : DEFAULT_COMMENT_PATTERN; + + const sourceCode = context.getSourceCode(); + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Shortcut to get last element of array + * @param {*[]} collection Array + * @returns {*} Last element + */ + function last(collection) { + return collection[collection.length - 1]; + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + + SwitchStatement(node) { + + if (!node.cases.length) { + + /* + * skip check of empty switch because there is no easy way + * to extract comments inside it now + */ + return; + } + + const hasDefault = node.cases.some(v => v.test === null); + + if (!hasDefault) { + + let comment; + + const lastCase = last(node.cases); + const comments = sourceCode.getCommentsAfter(lastCase); + + if (comments.length) { + comment = last(comments); + } + + if (!comment || !commentPattern.test(comment.value.trim())) { + context.report({ node, messageId: "missingDefaultCase" }); + } + } + } + }; + } +}; diff --git a/eslint/lib/rules/default-param-last.js b/eslint/lib/rules/default-param-last.js new file mode 100644 index 0000000..12e0b59 --- /dev/null +++ b/eslint/lib/rules/default-param-last.js @@ -0,0 +1,62 @@ +/** + * @fileoverview enforce default parameters to be last + * @author Chiawen Chen + */ + +"use strict"; + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "enforce default parameters to be last", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/default-param-last" + }, + + schema: [], + + messages: { + shouldBeLast: "Default parameters should be last." + } + }, + + create(context) { + + // eslint-disable-next-line jsdoc/require-description + /** + * @param {ASTNode} node function node + * @returns {void} + */ + function handleFunction(node) { + let hasSeenPlainParam = false; + + for (let i = node.params.length - 1; i >= 0; i -= 1) { + const param = node.params[i]; + + if ( + param.type !== "AssignmentPattern" && + param.type !== "RestElement" + ) { + hasSeenPlainParam = true; + continue; + } + + if (hasSeenPlainParam && param.type === "AssignmentPattern") { + context.report({ + node: param, + messageId: "shouldBeLast" + }); + } + } + } + + return { + FunctionDeclaration: handleFunction, + FunctionExpression: handleFunction, + ArrowFunctionExpression: handleFunction + }; + } +}; diff --git a/eslint/lib/rules/dot-location.js b/eslint/lib/rules/dot-location.js new file mode 100644 index 0000000..d483e21 --- /dev/null +++ b/eslint/lib/rules/dot-location.js @@ -0,0 +1,99 @@ +/** + * @fileoverview Validates newlines before and after dots + * @author Greg Cochard + */ + +"use strict"; + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "enforce consistent newlines before and after dots", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/dot-location" + }, + + schema: [ + { + enum: ["object", "property"] + } + ], + + fixable: "code", + + messages: { + expectedDotAfterObject: "Expected dot to be on same line as object.", + expectedDotBeforeProperty: "Expected dot to be on same line as property." + } + }, + + create(context) { + + const config = context.options[0]; + + // default to onObject if no preference is passed + const onObject = config === "object" || !config; + + const sourceCode = context.getSourceCode(); + + /** + * Reports if the dot between object and property is on the correct loccation. + * @param {ASTNode} node The `MemberExpression` node. + * @returns {void} + */ + function checkDotLocation(node) { + const property = node.property; + const dot = sourceCode.getTokenBefore(property); + + // `obj` expression can be parenthesized, but those paren tokens are not a part of the `obj` node. + const tokenBeforeDot = sourceCode.getTokenBefore(dot); + + const textBeforeDot = sourceCode.getText().slice(tokenBeforeDot.range[1], dot.range[0]); + const textAfterDot = sourceCode.getText().slice(dot.range[1], property.range[0]); + + if (onObject) { + if (!astUtils.isTokenOnSameLine(tokenBeforeDot, dot)) { + const neededTextAfterToken = astUtils.isDecimalIntegerNumericToken(tokenBeforeDot) ? " " : ""; + + context.report({ + node, + loc: dot.loc, + messageId: "expectedDotAfterObject", + fix: fixer => fixer.replaceTextRange([tokenBeforeDot.range[1], property.range[0]], `${neededTextAfterToken}.${textBeforeDot}${textAfterDot}`) + }); + } + } else if (!astUtils.isTokenOnSameLine(dot, property)) { + context.report({ + node, + loc: dot.loc, + messageId: "expectedDotBeforeProperty", + fix: fixer => fixer.replaceTextRange([tokenBeforeDot.range[1], property.range[0]], `${textBeforeDot}${textAfterDot}.`) + }); + } + } + + /** + * Checks the spacing of the dot within a member expression. + * @param {ASTNode} node The node to check. + * @returns {void} + */ + function checkNode(node) { + if (!node.computed) { + checkDotLocation(node); + } + } + + return { + MemberExpression: checkNode + }; + } +}; diff --git a/eslint/lib/rules/dot-notation.js b/eslint/lib/rules/dot-notation.js new file mode 100644 index 0000000..2e8fff8 --- /dev/null +++ b/eslint/lib/rules/dot-notation.js @@ -0,0 +1,173 @@ +/** + * @fileoverview Rule to warn about using dot notation instead of square bracket notation when possible. + * @author Josh Perez + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); +const keywords = require("./utils/keywords"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/u; + +// `null` literal must be handled separately. +const literalTypesToCheck = new Set(["string", "boolean"]); + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "enforce dot notation whenever possible", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/dot-notation" + }, + + schema: [ + { + type: "object", + properties: { + allowKeywords: { + type: "boolean", + default: true + }, + allowPattern: { + type: "string", + default: "" + } + }, + additionalProperties: false + } + ], + + fixable: "code", + + messages: { + useDot: "[{{key}}] is better written in dot notation.", + useBrackets: ".{{key}} is a syntax error." + } + }, + + create(context) { + const options = context.options[0] || {}; + const allowKeywords = options.allowKeywords === void 0 || options.allowKeywords; + const sourceCode = context.getSourceCode(); + + let allowPattern; + + if (options.allowPattern) { + allowPattern = new RegExp(options.allowPattern, "u"); + } + + /** + * Check if the property is valid dot notation + * @param {ASTNode} node The dot notation node + * @param {string} value Value which is to be checked + * @returns {void} + */ + function checkComputedProperty(node, value) { + if ( + validIdentifier.test(value) && + (allowKeywords || keywords.indexOf(String(value)) === -1) && + !(allowPattern && allowPattern.test(value)) + ) { + const formattedValue = node.property.type === "Literal" ? JSON.stringify(value) : `\`${value}\``; + + context.report({ + node: node.property, + messageId: "useDot", + data: { + key: formattedValue + }, + fix(fixer) { + const leftBracket = sourceCode.getTokenAfter(node.object, astUtils.isOpeningBracketToken); + const rightBracket = sourceCode.getLastToken(node); + + if (sourceCode.getFirstTokenBetween(leftBracket, rightBracket, { includeComments: true, filter: astUtils.isCommentToken })) { + + // Don't perform any fixes if there are comments inside the brackets. + return null; + } + + const tokenAfterProperty = sourceCode.getTokenAfter(rightBracket); + const needsSpaceAfterProperty = tokenAfterProperty && + rightBracket.range[1] === tokenAfterProperty.range[0] && + !astUtils.canTokensBeAdjacent(String(value), tokenAfterProperty); + + const textBeforeDot = astUtils.isDecimalInteger(node.object) ? " " : ""; + const textAfterProperty = needsSpaceAfterProperty ? " " : ""; + + return fixer.replaceTextRange( + [leftBracket.range[0], rightBracket.range[1]], + `${textBeforeDot}.${value}${textAfterProperty}` + ); + } + }); + } + } + + return { + MemberExpression(node) { + if ( + node.computed && + node.property.type === "Literal" && + (literalTypesToCheck.has(typeof node.property.value) || astUtils.isNullLiteral(node.property)) + ) { + checkComputedProperty(node, node.property.value); + } + if ( + node.computed && + node.property.type === "TemplateLiteral" && + node.property.expressions.length === 0 + ) { + checkComputedProperty(node, node.property.quasis[0].value.cooked); + } + if ( + !allowKeywords && + !node.computed && + keywords.indexOf(String(node.property.name)) !== -1 + ) { + context.report({ + node: node.property, + messageId: "useBrackets", + data: { + key: node.property.name + }, + fix(fixer) { + const dot = sourceCode.getTokenBefore(node.property); + const textAfterDot = sourceCode.text.slice(dot.range[1], node.property.range[0]); + + if (textAfterDot.trim()) { + + // Don't perform any fixes if there are comments between the dot and the property name. + return null; + } + + if (node.object.type === "Identifier" && node.object.name === "let") { + + /* + * A statement that starts with `let[` is parsed as a destructuring variable declaration, not + * a MemberExpression. + */ + return null; + } + + return fixer.replaceTextRange( + [dot.range[0], node.property.range[1]], + `[${textAfterDot}"${node.property.name}"]` + ); + } + }); + } + } + }; + } +}; diff --git a/eslint/lib/rules/eol-last.js b/eslint/lib/rules/eol-last.js new file mode 100644 index 0000000..fbba6c8 --- /dev/null +++ b/eslint/lib/rules/eol-last.js @@ -0,0 +1,112 @@ +/** + * @fileoverview Require or disallow newline at the end of files + * @author Nodeca Team + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const lodash = require("lodash"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "require or disallow newline at the end of files", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/eol-last" + }, + + fixable: "whitespace", + + schema: [ + { + enum: ["always", "never", "unix", "windows"] + } + ], + + messages: { + missing: "Newline required at end of file but not found.", + unexpected: "Newline not allowed at end of file." + } + }, + create(context) { + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + Program: function checkBadEOF(node) { + const sourceCode = context.getSourceCode(), + src = sourceCode.getText(), + location = { + column: lodash.last(sourceCode.lines).length, + line: sourceCode.lines.length + }, + LF = "\n", + CRLF = `\r${LF}`, + endsWithNewline = lodash.endsWith(src, LF); + + /* + * Empty source is always valid: No content in file so we don't + * need to lint for a newline on the last line of content. + */ + if (!src.length) { + return; + } + + let mode = context.options[0] || "always", + appendCRLF = false; + + if (mode === "unix") { + + // `"unix"` should behave exactly as `"always"` + mode = "always"; + } + if (mode === "windows") { + + // `"windows"` should behave exactly as `"always"`, but append CRLF in the fixer for backwards compatibility + mode = "always"; + appendCRLF = true; + } + if (mode === "always" && !endsWithNewline) { + + // File is not newline-terminated, but should be + context.report({ + node, + loc: location, + messageId: "missing", + fix(fixer) { + return fixer.insertTextAfterRange([0, src.length], appendCRLF ? CRLF : LF); + } + }); + } else if (mode === "never" && endsWithNewline) { + + // File is newline-terminated, but shouldn't be + context.report({ + node, + loc: location, + messageId: "unexpected", + fix(fixer) { + const finalEOLs = /(?:\r?\n)+$/u, + match = finalEOLs.exec(sourceCode.text), + start = match.index, + end = sourceCode.text.length; + + return fixer.replaceTextRange([start, end], ""); + } + }); + } + } + }; + } +}; diff --git a/eslint/lib/rules/eqeqeq.js b/eslint/lib/rules/eqeqeq.js new file mode 100644 index 0000000..57926db --- /dev/null +++ b/eslint/lib/rules/eqeqeq.js @@ -0,0 +1,174 @@ +/** + * @fileoverview Rule to flag statements that use != and == instead of !== and === + * @author Nicholas C. Zakas + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "require the use of `===` and `!==`", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/eqeqeq" + }, + + schema: { + anyOf: [ + { + type: "array", + items: [ + { + enum: ["always"] + }, + { + type: "object", + properties: { + null: { + enum: ["always", "never", "ignore"] + } + }, + additionalProperties: false + } + ], + additionalItems: false + }, + { + type: "array", + items: [ + { + enum: ["smart", "allow-null"] + } + ], + additionalItems: false + } + ] + }, + + fixable: "code", + + messages: { + unexpected: "Expected '{{expectedOperator}}' and instead saw '{{actualOperator}}'." + } + }, + + create(context) { + const config = context.options[0] || "always"; + const options = context.options[1] || {}; + const sourceCode = context.getSourceCode(); + + const nullOption = (config === "always") + ? options.null || "always" + : "ignore"; + const enforceRuleForNull = (nullOption === "always"); + const enforceInverseRuleForNull = (nullOption === "never"); + + /** + * Checks if an expression is a typeof expression + * @param {ASTNode} node The node to check + * @returns {boolean} if the node is a typeof expression + */ + function isTypeOf(node) { + return node.type === "UnaryExpression" && node.operator === "typeof"; + } + + /** + * Checks if either operand of a binary expression is a typeof operation + * @param {ASTNode} node The node to check + * @returns {boolean} if one of the operands is typeof + * @private + */ + function isTypeOfBinary(node) { + return isTypeOf(node.left) || isTypeOf(node.right); + } + + /** + * Checks if operands are literals of the same type (via typeof) + * @param {ASTNode} node The node to check + * @returns {boolean} if operands are of same type + * @private + */ + function areLiteralsAndSameType(node) { + return node.left.type === "Literal" && node.right.type === "Literal" && + typeof node.left.value === typeof node.right.value; + } + + /** + * Checks if one of the operands is a literal null + * @param {ASTNode} node The node to check + * @returns {boolean} if operands are null + * @private + */ + function isNullCheck(node) { + return astUtils.isNullLiteral(node.right) || astUtils.isNullLiteral(node.left); + } + + /** + * Reports a message for this rule. + * @param {ASTNode} node The binary expression node that was checked + * @param {string} expectedOperator The operator that was expected (either '==', '!=', '===', or '!==') + * @returns {void} + * @private + */ + function report(node, expectedOperator) { + const operatorToken = sourceCode.getFirstTokenBetween( + node.left, + node.right, + token => token.value === node.operator + ); + + context.report({ + node, + loc: operatorToken.loc, + messageId: "unexpected", + data: { expectedOperator, actualOperator: node.operator }, + fix(fixer) { + + // If the comparison is a `typeof` comparison or both sides are literals with the same type, then it's safe to fix. + if (isTypeOfBinary(node) || areLiteralsAndSameType(node)) { + return fixer.replaceText(operatorToken, expectedOperator); + } + return null; + } + }); + } + + return { + BinaryExpression(node) { + const isNull = isNullCheck(node); + + if (node.operator !== "==" && node.operator !== "!=") { + if (enforceInverseRuleForNull && isNull) { + report(node, node.operator.slice(0, -1)); + } + return; + } + + if (config === "smart" && (isTypeOfBinary(node) || + areLiteralsAndSameType(node) || isNull)) { + return; + } + + if (!enforceRuleForNull && isNull) { + return; + } + + report(node, `${node.operator}=`); + } + }; + + } +}; diff --git a/eslint/lib/rules/for-direction.js b/eslint/lib/rules/for-direction.js new file mode 100644 index 0000000..c15d10e --- /dev/null +++ b/eslint/lib/rules/for-direction.js @@ -0,0 +1,126 @@ +/** + * @fileoverview enforce "for" loop update clause moving the counter in the right direction.(for-direction) + * @author Aladdin-ADD + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "enforce \"for\" loop update clause moving the counter in the right direction.", + category: "Possible Errors", + recommended: true, + url: "https://eslint.org/docs/rules/for-direction" + }, + + fixable: null, + schema: [], + + messages: { + incorrectDirection: "The update clause in this loop moves the variable in the wrong direction." + } + }, + + create(context) { + + /** + * report an error. + * @param {ASTNode} node the node to report. + * @returns {void} + */ + function report(node) { + context.report({ + node, + messageId: "incorrectDirection" + }); + } + + /** + * check the right side of the assignment + * @param {ASTNode} update UpdateExpression to check + * @param {int} dir expected direction that could either be turned around or invalidated + * @returns {int} return dir, the negated dir or zero if it's not clear for identifiers + */ + function getRightDirection(update, dir) { + if (update.right.type === "UnaryExpression") { + if (update.right.operator === "-") { + return -dir; + } + } else if (update.right.type === "Identifier") { + return 0; + } + return dir; + } + + /** + * check UpdateExpression add/sub the counter + * @param {ASTNode} update UpdateExpression to check + * @param {string} counter variable name to check + * @returns {int} if add return 1, if sub return -1, if nochange, return 0 + */ + function getUpdateDirection(update, counter) { + if (update.argument.type === "Identifier" && update.argument.name === counter) { + if (update.operator === "++") { + return 1; + } + if (update.operator === "--") { + return -1; + } + } + return 0; + } + + /** + * check AssignmentExpression add/sub the counter + * @param {ASTNode} update AssignmentExpression to check + * @param {string} counter variable name to check + * @returns {int} if add return 1, if sub return -1, if nochange, return 0 + */ + function getAssignmentDirection(update, counter) { + if (update.left.name === counter) { + if (update.operator === "+=") { + return getRightDirection(update, 1); + } + if (update.operator === "-=") { + return getRightDirection(update, -1); + } + } + return 0; + } + return { + ForStatement(node) { + + if (node.test && node.test.type === "BinaryExpression" && node.test.left.type === "Identifier" && node.update) { + const counter = node.test.left.name; + const operator = node.test.operator; + const update = node.update; + + let wrongDirection; + + if (operator === "<" || operator === "<=") { + wrongDirection = -1; + } else if (operator === ">" || operator === ">=") { + wrongDirection = 1; + } else { + return; + } + + if (update.type === "UpdateExpression") { + if (getUpdateDirection(update, counter) === wrongDirection) { + report(node); + } + } else if (update.type === "AssignmentExpression" && getAssignmentDirection(update, counter) === wrongDirection) { + report(node); + } + } + } + }; + } +}; diff --git a/eslint/lib/rules/func-call-spacing.js b/eslint/lib/rules/func-call-spacing.js new file mode 100644 index 0000000..e2edd42 --- /dev/null +++ b/eslint/lib/rules/func-call-spacing.js @@ -0,0 +1,178 @@ +/** + * @fileoverview Rule to control spacing within function calls + * @author Matt DuVall + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "require or disallow spacing between function identifiers and their invocations", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/func-call-spacing" + }, + + fixable: "whitespace", + + schema: { + anyOf: [ + { + type: "array", + items: [ + { + enum: ["never"] + } + ], + minItems: 0, + maxItems: 1 + }, + { + type: "array", + items: [ + { + enum: ["always"] + }, + { + type: "object", + properties: { + allowNewlines: { + type: "boolean" + } + }, + additionalProperties: false + } + ], + minItems: 0, + maxItems: 2 + } + ] + }, + + messages: { + unexpected: "Unexpected newline between function name and paren.", + missing: "Missing space between function name and paren." + } + }, + + create(context) { + + const never = context.options[0] !== "always"; + const allowNewlines = !never && context.options[1] && context.options[1].allowNewlines; + const sourceCode = context.getSourceCode(); + const text = sourceCode.getText(); + + /** + * Check if open space is present in a function name + * @param {ASTNode} node node to evaluate + * @param {Token} leftToken The last token of the callee. This may be the closing parenthesis that encloses the callee. + * @param {Token} rightToken Tha first token of the arguments. this is the opening parenthesis that encloses the arguments. + * @returns {void} + * @private + */ + function checkSpacing(node, leftToken, rightToken) { + const textBetweenTokens = text.slice(leftToken.range[1], rightToken.range[0]).replace(/\/\*.*?\*\//gu, ""); + const hasWhitespace = /\s/u.test(textBetweenTokens); + const hasNewline = hasWhitespace && astUtils.LINEBREAK_MATCHER.test(textBetweenTokens); + + /* + * never allowNewlines hasWhitespace hasNewline message + * F F F F Missing space between function name and paren. + * F F F T (Invalid `!hasWhitespace && hasNewline`) + * F F T T Unexpected newline between function name and paren. + * F F T F (OK) + * F T T F (OK) + * F T T T (OK) + * F T F T (Invalid `!hasWhitespace && hasNewline`) + * F T F F Missing space between function name and paren. + * T T F F (Invalid `never && allowNewlines`) + * T T F T (Invalid `!hasWhitespace && hasNewline`) + * T T T T (Invalid `never && allowNewlines`) + * T T T F (Invalid `never && allowNewlines`) + * T F T F Unexpected space between function name and paren. + * T F T T Unexpected space between function name and paren. + * T F F T (Invalid `!hasWhitespace && hasNewline`) + * T F F F (OK) + * + * T T Unexpected space between function name and paren. + * F F Missing space between function name and paren. + * F F T Unexpected newline between function name and paren. + */ + + if (never && hasWhitespace) { + context.report({ + node, + loc: leftToken.loc.start, + messageId: "unexpected", + fix(fixer) { + + /* + * Only autofix if there is no newline + * https://github.com/eslint/eslint/issues/7787 + */ + if (!hasNewline) { + return fixer.removeRange([leftToken.range[1], rightToken.range[0]]); + } + + return null; + } + }); + } else if (!never && !hasWhitespace) { + context.report({ + node, + loc: leftToken.loc.start, + messageId: "missing", + fix(fixer) { + return fixer.insertTextBefore(rightToken, " "); + } + }); + } else if (!never && !allowNewlines && hasNewline) { + context.report({ + node, + loc: leftToken.loc.start, + messageId: "unexpected", + fix(fixer) { + return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], " "); + } + }); + } + } + + return { + "CallExpression, NewExpression"(node) { + const lastToken = sourceCode.getLastToken(node); + const lastCalleeToken = sourceCode.getLastToken(node.callee); + const parenToken = sourceCode.getFirstTokenBetween(lastCalleeToken, lastToken, astUtils.isOpeningParenToken); + const prevToken = parenToken && sourceCode.getTokenBefore(parenToken); + + // Parens in NewExpression are optional + if (!(parenToken && parenToken.range[1] < node.range[1])) { + return; + } + + checkSpacing(node, prevToken, parenToken); + }, + + ImportExpression(node) { + const leftToken = sourceCode.getFirstToken(node); + const rightToken = sourceCode.getTokenAfter(leftToken); + + checkSpacing(node, leftToken, rightToken); + } + }; + + } +}; diff --git a/eslint/lib/rules/func-name-matching.js b/eslint/lib/rules/func-name-matching.js new file mode 100644 index 0000000..83430ff --- /dev/null +++ b/eslint/lib/rules/func-name-matching.js @@ -0,0 +1,252 @@ +/** + * @fileoverview Rule to require function names to match the name of the variable or property to which they are assigned. + * @author Annie Zhang, Pavel Strashkin + */ + +"use strict"; + +//-------------------------------------------------------------------------- +// Requirements +//-------------------------------------------------------------------------- + +const astUtils = require("./utils/ast-utils"); +const esutils = require("esutils"); + +//-------------------------------------------------------------------------- +// Helpers +//-------------------------------------------------------------------------- + +/** + * Determines if a pattern is `module.exports` or `module["exports"]` + * @param {ASTNode} pattern The left side of the AssignmentExpression + * @returns {boolean} True if the pattern is `module.exports` or `module["exports"]` + */ +function isModuleExports(pattern) { + if (pattern.type === "MemberExpression" && pattern.object.type === "Identifier" && pattern.object.name === "module") { + + // module.exports + if (pattern.property.type === "Identifier" && pattern.property.name === "exports") { + return true; + } + + // module["exports"] + if (pattern.property.type === "Literal" && pattern.property.value === "exports") { + return true; + } + } + return false; +} + +/** + * Determines if a string name is a valid identifier + * @param {string} name The string to be checked + * @param {int} ecmaVersion The ECMAScript version if specified in the parserOptions config + * @returns {boolean} True if the string is a valid identifier + */ +function isIdentifier(name, ecmaVersion) { + if (ecmaVersion >= 6) { + return esutils.keyword.isIdentifierES6(name); + } + return esutils.keyword.isIdentifierES5(name); +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +const alwaysOrNever = { enum: ["always", "never"] }; +const optionsObject = { + type: "object", + properties: { + considerPropertyDescriptor: { + type: "boolean" + }, + includeCommonJSModuleExports: { + type: "boolean" + } + }, + additionalProperties: false +}; + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "require function names to match the name of the variable or property to which they are assigned", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/func-name-matching" + }, + + schema: { + anyOf: [{ + type: "array", + additionalItems: false, + items: [alwaysOrNever, optionsObject] + }, { + type: "array", + additionalItems: false, + items: [optionsObject] + }] + }, + + messages: { + matchProperty: "Function name `{{funcName}}` should match property name `{{name}}`.", + matchVariable: "Function name `{{funcName}}` should match variable name `{{name}}`.", + notMatchProperty: "Function name `{{funcName}}` should not match property name `{{name}}`.", + notMatchVariable: "Function name `{{funcName}}` should not match variable name `{{name}}`." + } + }, + + create(context) { + const options = (typeof context.options[0] === "object" ? context.options[0] : context.options[1]) || {}; + const nameMatches = typeof context.options[0] === "string" ? context.options[0] : "always"; + const considerPropertyDescriptor = options.considerPropertyDescriptor; + const includeModuleExports = options.includeCommonJSModuleExports; + const ecmaVersion = context.parserOptions && context.parserOptions.ecmaVersion ? context.parserOptions.ecmaVersion : 5; + + /** + * Check whether node is a certain CallExpression. + * @param {string} objName object name + * @param {string} funcName function name + * @param {ASTNode} node The node to check + * @returns {boolean} `true` if node matches CallExpression + */ + function isPropertyCall(objName, funcName, node) { + if (!node) { + return false; + } + return node.type === "CallExpression" && + node.callee.type === "MemberExpression" && + node.callee.object.name === objName && + node.callee.property.name === funcName; + } + + /** + * Compares identifiers based on the nameMatches option + * @param {string} x the first identifier + * @param {string} y the second identifier + * @returns {boolean} whether the two identifiers should warn. + */ + function shouldWarn(x, y) { + return (nameMatches === "always" && x !== y) || (nameMatches === "never" && x === y); + } + + /** + * Reports + * @param {ASTNode} node The node to report + * @param {string} name The variable or property name + * @param {string} funcName The function name + * @param {boolean} isProp True if the reported node is a property assignment + * @returns {void} + */ + function report(node, name, funcName, isProp) { + let messageId; + + if (nameMatches === "always" && isProp) { + messageId = "matchProperty"; + } else if (nameMatches === "always") { + messageId = "matchVariable"; + } else if (isProp) { + messageId = "notMatchProperty"; + } else { + messageId = "notMatchVariable"; + } + context.report({ + node, + messageId, + data: { + name, + funcName + } + }); + } + + /** + * Determines whether a given node is a string literal + * @param {ASTNode} node The node to check + * @returns {boolean} `true` if the node is a string literal + */ + function isStringLiteral(node) { + return node.type === "Literal" && typeof node.value === "string"; + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + VariableDeclarator(node) { + if (!node.init || node.init.type !== "FunctionExpression" || node.id.type !== "Identifier") { + return; + } + if (node.init.id && shouldWarn(node.id.name, node.init.id.name)) { + report(node, node.id.name, node.init.id.name, false); + } + }, + + AssignmentExpression(node) { + if ( + node.right.type !== "FunctionExpression" || + (node.left.computed && node.left.property.type !== "Literal") || + (!includeModuleExports && isModuleExports(node.left)) || + (node.left.type !== "Identifier" && node.left.type !== "MemberExpression") + ) { + return; + } + + const isProp = node.left.type === "MemberExpression"; + const name = isProp ? astUtils.getStaticPropertyName(node.left) : node.left.name; + + if (node.right.id && isIdentifier(name) && shouldWarn(name, node.right.id.name)) { + report(node, name, node.right.id.name, isProp); + } + }, + + Property(node) { + if (node.value.type !== "FunctionExpression" || !node.value.id || node.computed && !isStringLiteral(node.key)) { + return; + } + + if (node.key.type === "Identifier") { + const functionName = node.value.id.name; + let propertyName = node.key.name; + + if (considerPropertyDescriptor && propertyName === "value") { + if (isPropertyCall("Object", "defineProperty", node.parent.parent) || isPropertyCall("Reflect", "defineProperty", node.parent.parent)) { + const property = node.parent.parent.arguments[1]; + + if (isStringLiteral(property) && shouldWarn(property.value, functionName)) { + report(node, property.value, functionName, true); + } + } else if (isPropertyCall("Object", "defineProperties", node.parent.parent.parent.parent)) { + propertyName = node.parent.parent.key.name; + if (!node.parent.parent.computed && shouldWarn(propertyName, functionName)) { + report(node, propertyName, functionName, true); + } + } else if (isPropertyCall("Object", "create", node.parent.parent.parent.parent)) { + propertyName = node.parent.parent.key.name; + if (!node.parent.parent.computed && shouldWarn(propertyName, functionName)) { + report(node, propertyName, functionName, true); + } + } else if (shouldWarn(propertyName, functionName)) { + report(node, propertyName, functionName, true); + } + } else if (shouldWarn(propertyName, functionName)) { + report(node, propertyName, functionName, true); + } + return; + } + + if ( + isStringLiteral(node.key) && + isIdentifier(node.key.value, ecmaVersion) && + shouldWarn(node.key.value, node.value.id.name) + ) { + report(node, node.key.value, node.value.id.name, true); + } + } + }; + } +}; diff --git a/eslint/lib/rules/func-names.js b/eslint/lib/rules/func-names.js new file mode 100644 index 0000000..ecfedb9 --- /dev/null +++ b/eslint/lib/rules/func-names.js @@ -0,0 +1,190 @@ +/** + * @fileoverview Rule to warn when a function expression does not have a name. + * @author Kyle T. Nunery + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +/** + * Checks whether or not a given variable is a function name. + * @param {eslint-scope.Variable} variable A variable to check. + * @returns {boolean} `true` if the variable is a function name. + */ +function isFunctionName(variable) { + return variable && variable.defs[0].type === "FunctionName"; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "require or disallow named `function` expressions", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/func-names" + }, + + schema: { + definitions: { + value: { + enum: [ + "always", + "as-needed", + "never" + ] + } + }, + items: [ + { + $ref: "#/definitions/value" + }, + { + type: "object", + properties: { + generators: { + $ref: "#/definitions/value" + } + }, + additionalProperties: false + } + ] + }, + + messages: { + unnamed: "Unexpected unnamed {{name}}.", + named: "Unexpected named {{name}}." + } + }, + + create(context) { + + const sourceCode = context.getSourceCode(); + + /** + * Returns the config option for the given node. + * @param {ASTNode} node A node to get the config for. + * @returns {string} The config option. + */ + function getConfigForNode(node) { + if ( + node.generator && + context.options.length > 1 && + context.options[1].generators + ) { + return context.options[1].generators; + } + + return context.options[0] || "always"; + } + + /** + * Determines whether the current FunctionExpression node is a get, set, or + * shorthand method in an object literal or a class. + * @param {ASTNode} node A node to check. + * @returns {boolean} True if the node is a get, set, or shorthand method. + */ + function isObjectOrClassMethod(node) { + const parent = node.parent; + + return (parent.type === "MethodDefinition" || ( + parent.type === "Property" && ( + parent.method || + parent.kind === "get" || + parent.kind === "set" + ) + )); + } + + /** + * Determines whether the current FunctionExpression node has a name that would be + * inferred from context in a conforming ES6 environment. + * @param {ASTNode} node A node to check. + * @returns {boolean} True if the node would have a name assigned automatically. + */ + function hasInferredName(node) { + const parent = node.parent; + + return isObjectOrClassMethod(node) || + (parent.type === "VariableDeclarator" && parent.id.type === "Identifier" && parent.init === node) || + (parent.type === "Property" && parent.value === node) || + (parent.type === "AssignmentExpression" && parent.left.type === "Identifier" && parent.right === node) || + (parent.type === "AssignmentPattern" && parent.left.type === "Identifier" && parent.right === node); + } + + /** + * Reports that an unnamed function should be named + * @param {ASTNode} node The node to report in the event of an error. + * @returns {void} + */ + function reportUnexpectedUnnamedFunction(node) { + context.report({ + node, + messageId: "unnamed", + loc: astUtils.getFunctionHeadLoc(node, sourceCode), + data: { name: astUtils.getFunctionNameWithKind(node) } + }); + } + + /** + * Reports that a named function should be unnamed + * @param {ASTNode} node The node to report in the event of an error. + * @returns {void} + */ + function reportUnexpectedNamedFunction(node) { + context.report({ + node, + messageId: "named", + loc: astUtils.getFunctionHeadLoc(node, sourceCode), + data: { name: astUtils.getFunctionNameWithKind(node) } + }); + } + + /** + * The listener for function nodes. + * @param {ASTNode} node function node + * @returns {void} + */ + function handleFunction(node) { + + // Skip recursive functions. + const nameVar = context.getDeclaredVariables(node)[0]; + + if (isFunctionName(nameVar) && nameVar.references.length > 0) { + return; + } + + const hasName = Boolean(node.id && node.id.name); + const config = getConfigForNode(node); + + if (config === "never") { + if (hasName && node.type !== "FunctionDeclaration") { + reportUnexpectedNamedFunction(node); + } + } else if (config === "as-needed") { + if (!hasName && !hasInferredName(node)) { + reportUnexpectedUnnamedFunction(node); + } + } else { + if (!hasName && !isObjectOrClassMethod(node)) { + reportUnexpectedUnnamedFunction(node); + } + } + } + + return { + "FunctionExpression:exit": handleFunction, + "ExportDefaultDeclaration > FunctionDeclaration": handleFunction + }; + } +}; diff --git a/eslint/lib/rules/func-style.js b/eslint/lib/rules/func-style.js new file mode 100644 index 0000000..e150b1a --- /dev/null +++ b/eslint/lib/rules/func-style.js @@ -0,0 +1,98 @@ +/** + * @fileoverview Rule to enforce a particular function style + * @author Nicholas C. Zakas + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "enforce the consistent use of either `function` declarations or expressions", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/func-style" + }, + + schema: [ + { + enum: ["declaration", "expression"] + }, + { + type: "object", + properties: { + allowArrowFunctions: { + type: "boolean", + default: false + } + }, + additionalProperties: false + } + ], + + messages: { + expression: "Expected a function expression.", + declaration: "Expected a function declaration." + } + }, + + create(context) { + + const style = context.options[0], + allowArrowFunctions = context.options[1] && context.options[1].allowArrowFunctions, + enforceDeclarations = (style === "declaration"), + stack = []; + + const nodesToCheck = { + FunctionDeclaration(node) { + stack.push(false); + + if (!enforceDeclarations && node.parent.type !== "ExportDefaultDeclaration") { + context.report({ node, messageId: "expression" }); + } + }, + "FunctionDeclaration:exit"() { + stack.pop(); + }, + + FunctionExpression(node) { + stack.push(false); + + if (enforceDeclarations && node.parent.type === "VariableDeclarator") { + context.report({ node: node.parent, messageId: "declaration" }); + } + }, + "FunctionExpression:exit"() { + stack.pop(); + }, + + ThisExpression() { + if (stack.length > 0) { + stack[stack.length - 1] = true; + } + } + }; + + if (!allowArrowFunctions) { + nodesToCheck.ArrowFunctionExpression = function() { + stack.push(false); + }; + + nodesToCheck["ArrowFunctionExpression:exit"] = function(node) { + const hasThisExpr = stack.pop(); + + if (enforceDeclarations && !hasThisExpr && node.parent.type === "VariableDeclarator") { + context.report({ node: node.parent, messageId: "declaration" }); + } + }; + } + + return nodesToCheck; + + } +}; diff --git a/eslint/lib/rules/function-call-argument-newline.js b/eslint/lib/rules/function-call-argument-newline.js new file mode 100644 index 0000000..b6abbe9 --- /dev/null +++ b/eslint/lib/rules/function-call-argument-newline.js @@ -0,0 +1,122 @@ +/** + * @fileoverview Rule to enforce line breaks between arguments of a function call + * @author Alexey Gonchar + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "enforce line breaks between arguments of a function call", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/function-call-argument-newline" + }, + + fixable: "whitespace", + + schema: [ + { + enum: ["always", "never", "consistent"] + } + ], + + messages: { + unexpectedLineBreak: "There should be no line break here.", + missingLineBreak: "There should be a line break after this argument." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + const checkers = { + unexpected: { + messageId: "unexpectedLineBreak", + check: (prevToken, currentToken) => prevToken.loc.end.line !== currentToken.loc.start.line, + createFix: (token, tokenBefore) => fixer => + fixer.replaceTextRange([tokenBefore.range[1], token.range[0]], " ") + }, + missing: { + messageId: "missingLineBreak", + check: (prevToken, currentToken) => prevToken.loc.end.line === currentToken.loc.start.line, + createFix: (token, tokenBefore) => fixer => + fixer.replaceTextRange([tokenBefore.range[1], token.range[0]], "\n") + } + }; + + /** + * Check all arguments for line breaks in the CallExpression + * @param {CallExpression} node node to evaluate + * @param {{ messageId: string, check: Function }} checker selected checker + * @returns {void} + * @private + */ + function checkArguments(node, checker) { + for (let i = 1; i < node.arguments.length; i++) { + const prevArgToken = sourceCode.getLastToken(node.arguments[i - 1]); + const currentArgToken = sourceCode.getFirstToken(node.arguments[i]); + + if (checker.check(prevArgToken, currentArgToken)) { + const tokenBefore = sourceCode.getTokenBefore( + currentArgToken, + { includeComments: true } + ); + + const hasLineCommentBefore = tokenBefore.type === "Line"; + + context.report({ + node, + loc: { + start: tokenBefore.loc.end, + end: currentArgToken.loc.start + }, + messageId: checker.messageId, + fix: hasLineCommentBefore ? null : checker.createFix(currentArgToken, tokenBefore) + }); + } + } + } + + /** + * Check if open space is present in a function name + * @param {CallExpression} node node to evaluate + * @returns {void} + * @private + */ + function check(node) { + if (node.arguments.length < 2) { + return; + } + + const option = context.options[0] || "always"; + + if (option === "never") { + checkArguments(node, checkers.unexpected); + } else if (option === "always") { + checkArguments(node, checkers.missing); + } else if (option === "consistent") { + const firstArgToken = sourceCode.getLastToken(node.arguments[0]); + const secondArgToken = sourceCode.getFirstToken(node.arguments[1]); + + if (firstArgToken.loc.end.line === secondArgToken.loc.start.line) { + checkArguments(node, checkers.unexpected); + } else { + checkArguments(node, checkers.missing); + } + } + } + + return { + CallExpression: check, + NewExpression: check + }; + } +}; diff --git a/eslint/lib/rules/function-paren-newline.js b/eslint/lib/rules/function-paren-newline.js new file mode 100644 index 0000000..894c8e3 --- /dev/null +++ b/eslint/lib/rules/function-paren-newline.js @@ -0,0 +1,281 @@ +/** + * @fileoverview enforce consistent line breaks inside function parentheses + * @author Teddy Katz + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "enforce consistent line breaks inside function parentheses", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/function-paren-newline" + }, + + fixable: "whitespace", + + schema: [ + { + oneOf: [ + { + enum: ["always", "never", "consistent", "multiline", "multiline-arguments"] + }, + { + type: "object", + properties: { + minItems: { + type: "integer", + minimum: 0 + } + }, + additionalProperties: false + } + ] + } + ], + + messages: { + expectedBefore: "Expected newline before ')'.", + expectedAfter: "Expected newline after '('.", + expectedBetween: "Expected newline between arguments/params.", + unexpectedBefore: "Unexpected newline before ')'.", + unexpectedAfter: "Unexpected newline after '('." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + const rawOption = context.options[0] || "multiline"; + const multilineOption = rawOption === "multiline"; + const multilineArgumentsOption = rawOption === "multiline-arguments"; + const consistentOption = rawOption === "consistent"; + let minItems; + + if (typeof rawOption === "object") { + minItems = rawOption.minItems; + } else if (rawOption === "always") { + minItems = 0; + } else if (rawOption === "never") { + minItems = Infinity; + } else { + minItems = null; + } + + //---------------------------------------------------------------------- + // Helpers + //---------------------------------------------------------------------- + + /** + * Determines whether there should be newlines inside function parens + * @param {ASTNode[]} elements The arguments or parameters in the list + * @param {boolean} hasLeftNewline `true` if the left paren has a newline in the current code. + * @returns {boolean} `true` if there should be newlines inside the function parens + */ + function shouldHaveNewlines(elements, hasLeftNewline) { + if (multilineArgumentsOption && elements.length === 1) { + return hasLeftNewline; + } + if (multilineOption || multilineArgumentsOption) { + return elements.some((element, index) => index !== elements.length - 1 && element.loc.end.line !== elements[index + 1].loc.start.line); + } + if (consistentOption) { + return hasLeftNewline; + } + return elements.length >= minItems; + } + + /** + * Validates parens + * @param {Object} parens An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token + * @param {ASTNode[]} elements The arguments or parameters in the list + * @returns {void} + */ + function validateParens(parens, elements) { + const leftParen = parens.leftParen; + const rightParen = parens.rightParen; + const tokenAfterLeftParen = sourceCode.getTokenAfter(leftParen); + const tokenBeforeRightParen = sourceCode.getTokenBefore(rightParen); + const hasLeftNewline = !astUtils.isTokenOnSameLine(leftParen, tokenAfterLeftParen); + const hasRightNewline = !astUtils.isTokenOnSameLine(tokenBeforeRightParen, rightParen); + const needsNewlines = shouldHaveNewlines(elements, hasLeftNewline); + + if (hasLeftNewline && !needsNewlines) { + context.report({ + node: leftParen, + messageId: "unexpectedAfter", + fix(fixer) { + return sourceCode.getText().slice(leftParen.range[1], tokenAfterLeftParen.range[0]).trim() + + // If there is a comment between the ( and the first element, don't do a fix. + ? null + : fixer.removeRange([leftParen.range[1], tokenAfterLeftParen.range[0]]); + } + }); + } else if (!hasLeftNewline && needsNewlines) { + context.report({ + node: leftParen, + messageId: "expectedAfter", + fix: fixer => fixer.insertTextAfter(leftParen, "\n") + }); + } + + if (hasRightNewline && !needsNewlines) { + context.report({ + node: rightParen, + messageId: "unexpectedBefore", + fix(fixer) { + return sourceCode.getText().slice(tokenBeforeRightParen.range[1], rightParen.range[0]).trim() + + // If there is a comment between the last element and the ), don't do a fix. + ? null + : fixer.removeRange([tokenBeforeRightParen.range[1], rightParen.range[0]]); + } + }); + } else if (!hasRightNewline && needsNewlines) { + context.report({ + node: rightParen, + messageId: "expectedBefore", + fix: fixer => fixer.insertTextBefore(rightParen, "\n") + }); + } + } + + /** + * Validates a list of arguments or parameters + * @param {Object} parens An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token + * @param {ASTNode[]} elements The arguments or parameters in the list + * @returns {void} + */ + function validateArguments(parens, elements) { + const leftParen = parens.leftParen; + const tokenAfterLeftParen = sourceCode.getTokenAfter(leftParen); + const hasLeftNewline = !astUtils.isTokenOnSameLine(leftParen, tokenAfterLeftParen); + const needsNewlines = shouldHaveNewlines(elements, hasLeftNewline); + + for (let i = 0; i <= elements.length - 2; i++) { + const currentElement = elements[i]; + const nextElement = elements[i + 1]; + const hasNewLine = currentElement.loc.end.line !== nextElement.loc.start.line; + + if (!hasNewLine && needsNewlines) { + context.report({ + node: currentElement, + messageId: "expectedBetween", + fix: fixer => fixer.insertTextBefore(nextElement, "\n") + }); + } + } + } + + /** + * Gets the left paren and right paren tokens of a node. + * @param {ASTNode} node The node with parens + * @returns {Object} An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token. + * Can also return `null` if an expression has no parens (e.g. a NewExpression with no arguments, or an ArrowFunctionExpression + * with a single parameter) + */ + function getParenTokens(node) { + switch (node.type) { + case "NewExpression": + if (!node.arguments.length && !( + astUtils.isOpeningParenToken(sourceCode.getLastToken(node, { skip: 1 })) && + astUtils.isClosingParenToken(sourceCode.getLastToken(node)) + )) { + + // If the NewExpression does not have parens (e.g. `new Foo`), return null. + return null; + } + + // falls through + + case "CallExpression": + return { + leftParen: sourceCode.getTokenAfter(node.callee, astUtils.isOpeningParenToken), + rightParen: sourceCode.getLastToken(node) + }; + + case "FunctionDeclaration": + case "FunctionExpression": { + const leftParen = sourceCode.getFirstToken(node, astUtils.isOpeningParenToken); + const rightParen = node.params.length + ? sourceCode.getTokenAfter(node.params[node.params.length - 1], astUtils.isClosingParenToken) + : sourceCode.getTokenAfter(leftParen); + + return { leftParen, rightParen }; + } + + case "ArrowFunctionExpression": { + const firstToken = sourceCode.getFirstToken(node); + + if (!astUtils.isOpeningParenToken(firstToken)) { + + // If the ArrowFunctionExpression has a single param without parens, return null. + return null; + } + + return { + leftParen: firstToken, + rightParen: sourceCode.getTokenBefore(node.body, astUtils.isClosingParenToken) + }; + } + + case "ImportExpression": { + const leftParen = sourceCode.getFirstToken(node, 1); + const rightParen = sourceCode.getLastToken(node); + + return { leftParen, rightParen }; + } + + default: + throw new TypeError(`unexpected node with type ${node.type}`); + } + } + + //---------------------------------------------------------------------- + // Public + //---------------------------------------------------------------------- + + return { + [[ + "ArrowFunctionExpression", + "CallExpression", + "FunctionDeclaration", + "FunctionExpression", + "ImportExpression", + "NewExpression" + ]](node) { + const parens = getParenTokens(node); + let params; + + if (node.type === "ImportExpression") { + params = [node.source]; + } else if (astUtils.isFunction(node)) { + params = node.params; + } else { + params = node.arguments; + } + + if (parens) { + validateParens(parens, params); + + if (multilineArgumentsOption) { + validateArguments(parens, params); + } + } + } + }; + } +}; diff --git a/eslint/lib/rules/generator-star-spacing.js b/eslint/lib/rules/generator-star-spacing.js new file mode 100644 index 0000000..65534f7 --- /dev/null +++ b/eslint/lib/rules/generator-star-spacing.js @@ -0,0 +1,206 @@ +/** + * @fileoverview Rule to check the spacing around the * in generator functions. + * @author Jamund Ferguson + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +const OVERRIDE_SCHEMA = { + oneOf: [ + { + enum: ["before", "after", "both", "neither"] + }, + { + type: "object", + properties: { + before: { type: "boolean" }, + after: { type: "boolean" } + }, + additionalProperties: false + } + ] +}; + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "enforce consistent spacing around `*` operators in generator functions", + category: "ECMAScript 6", + recommended: false, + url: "https://eslint.org/docs/rules/generator-star-spacing" + }, + + fixable: "whitespace", + + schema: [ + { + oneOf: [ + { + enum: ["before", "after", "both", "neither"] + }, + { + type: "object", + properties: { + before: { type: "boolean" }, + after: { type: "boolean" }, + named: OVERRIDE_SCHEMA, + anonymous: OVERRIDE_SCHEMA, + method: OVERRIDE_SCHEMA + }, + additionalProperties: false + } + ] + } + ], + + messages: { + missingBefore: "Missing space before *.", + missingAfter: "Missing space after *.", + unexpectedBefore: "Unexpected space before *.", + unexpectedAfter: "Unexpected space after *." + } + }, + + create(context) { + + const optionDefinitions = { + before: { before: true, after: false }, + after: { before: false, after: true }, + both: { before: true, after: true }, + neither: { before: false, after: false } + }; + + /** + * Returns resolved option definitions based on an option and defaults + * @param {any} option The option object or string value + * @param {Object} defaults The defaults to use if options are not present + * @returns {Object} the resolved object definition + */ + function optionToDefinition(option, defaults) { + if (!option) { + return defaults; + } + + return typeof option === "string" + ? optionDefinitions[option] + : Object.assign({}, defaults, option); + } + + const modes = (function(option) { + const defaults = optionToDefinition(option, optionDefinitions.before); + + return { + named: optionToDefinition(option.named, defaults), + anonymous: optionToDefinition(option.anonymous, defaults), + method: optionToDefinition(option.method, defaults) + }; + }(context.options[0] || {})); + + const sourceCode = context.getSourceCode(); + + /** + * Checks if the given token is a star token or not. + * @param {Token} token The token to check. + * @returns {boolean} `true` if the token is a star token. + */ + function isStarToken(token) { + return token.value === "*" && token.type === "Punctuator"; + } + + /** + * Gets the generator star token of the given function node. + * @param {ASTNode} node The function node to get. + * @returns {Token} Found star token. + */ + function getStarToken(node) { + return sourceCode.getFirstToken( + (node.parent.method || node.parent.type === "MethodDefinition") ? node.parent : node, + isStarToken + ); + } + + /** + * capitalize a given string. + * @param {string} str the given string. + * @returns {string} the capitalized string. + */ + function capitalize(str) { + return str[0].toUpperCase() + str.slice(1); + } + + /** + * Checks the spacing between two tokens before or after the star token. + * @param {string} kind Either "named", "anonymous", or "method" + * @param {string} side Either "before" or "after". + * @param {Token} leftToken `function` keyword token if side is "before", or + * star token if side is "after". + * @param {Token} rightToken Star token if side is "before", or identifier + * token if side is "after". + * @returns {void} + */ + function checkSpacing(kind, side, leftToken, rightToken) { + if (!!(rightToken.range[0] - leftToken.range[1]) !== modes[kind][side]) { + const after = leftToken.value === "*"; + const spaceRequired = modes[kind][side]; + const node = after ? leftToken : rightToken; + const messageId = `${spaceRequired ? "missing" : "unexpected"}${capitalize(side)}`; + + context.report({ + node, + messageId, + fix(fixer) { + if (spaceRequired) { + if (after) { + return fixer.insertTextAfter(node, " "); + } + return fixer.insertTextBefore(node, " "); + } + return fixer.removeRange([leftToken.range[1], rightToken.range[0]]); + } + }); + } + } + + /** + * Enforces the spacing around the star if node is a generator function. + * @param {ASTNode} node A function expression or declaration node. + * @returns {void} + */ + function checkFunction(node) { + if (!node.generator) { + return; + } + + const starToken = getStarToken(node); + const prevToken = sourceCode.getTokenBefore(starToken); + const nextToken = sourceCode.getTokenAfter(starToken); + + let kind = "named"; + + if (node.parent.type === "MethodDefinition" || (node.parent.type === "Property" && node.parent.method)) { + kind = "method"; + } else if (!node.id) { + kind = "anonymous"; + } + + // Only check before when preceded by `function`|`static` keyword + if (!(kind === "method" && starToken === sourceCode.getFirstToken(node.parent))) { + checkSpacing(kind, "before", prevToken, starToken); + } + + checkSpacing(kind, "after", starToken, nextToken); + } + + return { + FunctionDeclaration: checkFunction, + FunctionExpression: checkFunction + }; + + } +}; diff --git a/eslint/lib/rules/getter-return.js b/eslint/lib/rules/getter-return.js new file mode 100644 index 0000000..e1468a5 --- /dev/null +++ b/eslint/lib/rules/getter-return.js @@ -0,0 +1,183 @@ +/** + * @fileoverview Enforces that a return statement is present in property getters. + * @author Aladdin-ADD(hh_2013@foxmail.com) + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ +const TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/u; + +/** + * Checks a given code path segment is reachable. + * @param {CodePathSegment} segment A segment to check. + * @returns {boolean} `true` if the segment is reachable. + */ +function isReachable(segment) { + return segment.reachable; +} + +/** + * Gets a readable location. + * + * - FunctionExpression -> the function name or `function` keyword. + * @param {ASTNode} node A function node to get. + * @returns {ASTNode|Token} The node or the token of a location. + */ +function getId(node) { + return node.id || node; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "enforce `return` statements in getters", + category: "Possible Errors", + recommended: true, + url: "https://eslint.org/docs/rules/getter-return" + }, + + fixable: null, + + schema: [ + { + type: "object", + properties: { + allowImplicit: { + type: "boolean", + default: false + } + }, + additionalProperties: false + } + ], + + messages: { + expected: "Expected to return a value in {{name}}.", + expectedAlways: "Expected {{name}} to always return a value." + } + }, + + create(context) { + + const options = context.options[0] || { allowImplicit: false }; + + let funcInfo = { + upper: null, + codePath: null, + hasReturn: false, + shouldCheck: false, + node: null + }; + + /** + * Checks whether or not the last code path segment is reachable. + * Then reports this function if the segment is reachable. + * + * If the last code path segment is reachable, there are paths which are not + * returned or thrown. + * @param {ASTNode} node A node to check. + * @returns {void} + */ + function checkLastSegment(node) { + if (funcInfo.shouldCheck && + funcInfo.codePath.currentSegments.some(isReachable) + ) { + context.report({ + node, + loc: getId(node).loc.start, + messageId: funcInfo.hasReturn ? "expectedAlways" : "expected", + data: { + name: astUtils.getFunctionNameWithKind(funcInfo.node) + } + }); + } + } + + /** + * Checks whether a node means a getter function. + * @param {ASTNode} node a node to check. + * @returns {boolean} if node means a getter, return true; else return false. + */ + function isGetter(node) { + const parent = node.parent; + + if (TARGET_NODE_TYPE.test(node.type) && node.body.type === "BlockStatement") { + if (parent.kind === "get") { + return true; + } + if (parent.type === "Property" && astUtils.getStaticPropertyName(parent) === "get" && parent.parent.type === "ObjectExpression") { + + // Object.defineProperty() + if (parent.parent.parent.type === "CallExpression" && + astUtils.getStaticPropertyName(parent.parent.parent.callee) === "defineProperty") { + return true; + } + + // Object.defineProperties() + if (parent.parent.parent.type === "Property" && + parent.parent.parent.parent.type === "ObjectExpression" && + parent.parent.parent.parent.parent.type === "CallExpression" && + astUtils.getStaticPropertyName(parent.parent.parent.parent.parent.callee) === "defineProperties") { + return true; + } + } + } + return false; + } + return { + + // Stacks this function's information. + onCodePathStart(codePath, node) { + funcInfo = { + upper: funcInfo, + codePath, + hasReturn: false, + shouldCheck: isGetter(node), + node + }; + }, + + // Pops this function's information. + onCodePathEnd() { + funcInfo = funcInfo.upper; + }, + + // Checks the return statement is valid. + ReturnStatement(node) { + if (funcInfo.shouldCheck) { + funcInfo.hasReturn = true; + + // if allowImplicit: false, should also check node.argument + if (!options.allowImplicit && !node.argument) { + context.report({ + node, + messageId: "expected", + data: { + name: astUtils.getFunctionNameWithKind(funcInfo.node) + } + }); + } + } + }, + + // Reports a given function if the last path is reachable. + "FunctionExpression:exit": checkLastSegment, + "ArrowFunctionExpression:exit": checkLastSegment + }; + } +}; diff --git a/eslint/lib/rules/global-require.js b/eslint/lib/rules/global-require.js new file mode 100644 index 0000000..4af3a6a --- /dev/null +++ b/eslint/lib/rules/global-require.js @@ -0,0 +1,81 @@ +/** + * @fileoverview Rule for disallowing require() outside of the top-level module context + * @author Jamund Ferguson + */ + +"use strict"; + +const ACCEPTABLE_PARENTS = [ + "AssignmentExpression", + "VariableDeclarator", + "MemberExpression", + "ExpressionStatement", + "CallExpression", + "ConditionalExpression", + "Program", + "VariableDeclaration" +]; + +/** + * Finds the eslint-scope reference in the given scope. + * @param {Object} scope The scope to search. + * @param {ASTNode} node The identifier node. + * @returns {Reference|null} Returns the found reference or null if none were found. + */ +function findReference(scope, node) { + const references = scope.references.filter(reference => reference.identifier.range[0] === node.range[0] && + reference.identifier.range[1] === node.range[1]); + + /* istanbul ignore else: correctly returns null */ + if (references.length === 1) { + return references[0]; + } + return null; + +} + +/** + * Checks if the given identifier node is shadowed in the given scope. + * @param {Object} scope The current scope. + * @param {ASTNode} node The identifier node to check. + * @returns {boolean} Whether or not the name is shadowed. + */ +function isShadowed(scope, node) { + const reference = findReference(scope, node); + + return reference && reference.resolved && reference.resolved.defs.length > 0; +} + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "require `require()` calls to be placed at top-level module scope", + category: "Node.js and CommonJS", + recommended: false, + url: "https://eslint.org/docs/rules/global-require" + }, + + schema: [], + messages: { + unexpected: "Unexpected require()." + } + }, + + create(context) { + return { + CallExpression(node) { + const currentScope = context.getScope(); + + if (node.callee.name === "require" && !isShadowed(currentScope, node.callee)) { + const isGoodRequire = context.getAncestors().every(parent => ACCEPTABLE_PARENTS.indexOf(parent.type) > -1); + + if (!isGoodRequire) { + context.report({ node, messageId: "unexpected" }); + } + } + } + }; + } +}; diff --git a/eslint/lib/rules/grouped-accessor-pairs.js b/eslint/lib/rules/grouped-accessor-pairs.js new file mode 100644 index 0000000..a790f83 --- /dev/null +++ b/eslint/lib/rules/grouped-accessor-pairs.js @@ -0,0 +1,224 @@ +/** + * @fileoverview Rule to require grouped accessor pairs in object literals and classes + * @author Milos Djermanovic + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Typedefs +//------------------------------------------------------------------------------ + +/** + * Property name if it can be computed statically, otherwise the list of the tokens of the key node. + * @typedef {string|Token[]} Key + */ + +/** + * Accessor nodes with the same key. + * @typedef {Object} AccessorData + * @property {Key} key Accessor's key + * @property {ASTNode[]} getters List of getter nodes. + * @property {ASTNode[]} setters List of setter nodes. + */ + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Checks whether or not the given lists represent the equal tokens in the same order. + * Tokens are compared by their properties, not by instance. + * @param {Token[]} left First list of tokens. + * @param {Token[]} right Second list of tokens. + * @returns {boolean} `true` if the lists have same tokens. + */ +function areEqualTokenLists(left, right) { + if (left.length !== right.length) { + return false; + } + + for (let i = 0; i < left.length; i++) { + const leftToken = left[i], + rightToken = right[i]; + + if (leftToken.type !== rightToken.type || leftToken.value !== rightToken.value) { + return false; + } + } + + return true; +} + +/** + * Checks whether or not the given keys are equal. + * @param {Key} left First key. + * @param {Key} right Second key. + * @returns {boolean} `true` if the keys are equal. + */ +function areEqualKeys(left, right) { + if (typeof left === "string" && typeof right === "string") { + + // Statically computed names. + return left === right; + } + if (Array.isArray(left) && Array.isArray(right)) { + + // Token lists. + return areEqualTokenLists(left, right); + } + + return false; +} + +/** + * Checks whether or not a given node is of an accessor kind ('get' or 'set'). + * @param {ASTNode} node A node to check. + * @returns {boolean} `true` if the node is of an accessor kind. + */ +function isAccessorKind(node) { + return node.kind === "get" || node.kind === "set"; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "require grouped accessor pairs in object literals and classes", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/grouped-accessor-pairs" + }, + + schema: [ + { + enum: ["anyOrder", "getBeforeSet", "setBeforeGet"] + } + ], + + messages: { + notGrouped: "Accessor pair {{ formerName }} and {{ latterName }} should be grouped.", + invalidOrder: "Expected {{ latterName }} to be before {{ formerName }}." + } + }, + + create(context) { + const order = context.options[0] || "anyOrder"; + const sourceCode = context.getSourceCode(); + + /** + * Reports the given accessor pair. + * @param {string} messageId messageId to report. + * @param {ASTNode} formerNode getter/setter node that is defined before `latterNode`. + * @param {ASTNode} latterNode getter/setter node that is defined after `formerNode`. + * @returns {void} + * @private + */ + function report(messageId, formerNode, latterNode) { + context.report({ + node: latterNode, + messageId, + loc: astUtils.getFunctionHeadLoc(latterNode.value, sourceCode), + data: { + formerName: astUtils.getFunctionNameWithKind(formerNode.value), + latterName: astUtils.getFunctionNameWithKind(latterNode.value) + } + }); + } + + /** + * Creates a new `AccessorData` object for the given getter or setter node. + * @param {ASTNode} node A getter or setter node. + * @returns {AccessorData} New `AccessorData` object that contains the given node. + * @private + */ + function createAccessorData(node) { + const name = astUtils.getStaticPropertyName(node); + const key = (name !== null) ? name : sourceCode.getTokens(node.key); + + return { + key, + getters: node.kind === "get" ? [node] : [], + setters: node.kind === "set" ? [node] : [] + }; + } + + /** + * Merges the given `AccessorData` object into the given accessors list. + * @param {AccessorData[]} accessors The list to merge into. + * @param {AccessorData} accessorData The object to merge. + * @returns {AccessorData[]} The same instance with the merged object. + * @private + */ + function mergeAccessorData(accessors, accessorData) { + const equalKeyElement = accessors.find(a => areEqualKeys(a.key, accessorData.key)); + + if (equalKeyElement) { + equalKeyElement.getters.push(...accessorData.getters); + equalKeyElement.setters.push(...accessorData.setters); + } else { + accessors.push(accessorData); + } + + return accessors; + } + + /** + * Checks accessor pairs in the given list of nodes. + * @param {ASTNode[]} nodes The list to check. + * @param {Function} shouldCheck – Predicate that returns `true` if the node should be checked. + * @returns {void} + * @private + */ + function checkList(nodes, shouldCheck) { + const accessors = nodes + .filter(shouldCheck) + .filter(isAccessorKind) + .map(createAccessorData) + .reduce(mergeAccessorData, []); + + for (const { getters, setters } of accessors) { + + // Don't report accessor properties that have duplicate getters or setters. + if (getters.length === 1 && setters.length === 1) { + const [getter] = getters, + [setter] = setters, + getterIndex = nodes.indexOf(getter), + setterIndex = nodes.indexOf(setter), + formerNode = getterIndex < setterIndex ? getter : setter, + latterNode = getterIndex < setterIndex ? setter : getter; + + if (Math.abs(getterIndex - setterIndex) > 1) { + report("notGrouped", formerNode, latterNode); + } else if ( + (order === "getBeforeSet" && getterIndex > setterIndex) || + (order === "setBeforeGet" && getterIndex < setterIndex) + ) { + report("invalidOrder", formerNode, latterNode); + } + } + } + } + + return { + ObjectExpression(node) { + checkList(node.properties, n => n.type === "Property"); + }, + ClassBody(node) { + checkList(node.body, n => n.type === "MethodDefinition" && !n.static); + checkList(node.body, n => n.type === "MethodDefinition" && n.static); + } + }; + } +}; diff --git a/eslint/lib/rules/guard-for-in.js b/eslint/lib/rules/guard-for-in.js new file mode 100644 index 0000000..2c0976d --- /dev/null +++ b/eslint/lib/rules/guard-for-in.js @@ -0,0 +1,76 @@ +/** + * @fileoverview Rule to flag for-in loops without if statements inside + * @author Nicholas C. Zakas + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "require `for-in` loops to include an `if` statement", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/guard-for-in" + }, + + schema: [], + messages: { + wrap: "The body of a for-in should be wrapped in an if statement to filter unwanted properties from the prototype." + } + }, + + create(context) { + + return { + + ForInStatement(node) { + const body = node.body; + + // empty statement + if (body.type === "EmptyStatement") { + return; + } + + // if statement + if (body.type === "IfStatement") { + return; + } + + // empty block + if (body.type === "BlockStatement" && body.body.length === 0) { + return; + } + + // block with just if statement + if (body.type === "BlockStatement" && body.body.length === 1 && body.body[0].type === "IfStatement") { + return; + } + + // block that starts with if statement + if (body.type === "BlockStatement" && body.body.length >= 1 && body.body[0].type === "IfStatement") { + const i = body.body[0]; + + // ... whose consequent is a continue + if (i.consequent.type === "ContinueStatement") { + return; + } + + // ... whose consequent is a block that contains only a continue + if (i.consequent.type === "BlockStatement" && i.consequent.body.length === 1 && i.consequent.body[0].type === "ContinueStatement") { + return; + } + } + + context.report({ node, messageId: "wrap" }); + } + }; + + } +}; diff --git a/eslint/lib/rules/handle-callback-err.js b/eslint/lib/rules/handle-callback-err.js new file mode 100644 index 0000000..6409466 --- /dev/null +++ b/eslint/lib/rules/handle-callback-err.js @@ -0,0 +1,95 @@ +/** + * @fileoverview Ensure handling of errors when we know they exist. + * @author Jamund Ferguson + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "require error handling in callbacks", + category: "Node.js and CommonJS", + recommended: false, + url: "https://eslint.org/docs/rules/handle-callback-err" + }, + + schema: [ + { + type: "string" + } + ], + messages: { + expected: "Expected error to be handled." + } + }, + + create(context) { + + const errorArgument = context.options[0] || "err"; + + /** + * Checks if the given argument should be interpreted as a regexp pattern. + * @param {string} stringToCheck The string which should be checked. + * @returns {boolean} Whether or not the string should be interpreted as a pattern. + */ + function isPattern(stringToCheck) { + const firstChar = stringToCheck[0]; + + return firstChar === "^"; + } + + /** + * Checks if the given name matches the configured error argument. + * @param {string} name The name which should be compared. + * @returns {boolean} Whether or not the given name matches the configured error variable name. + */ + function matchesConfiguredErrorName(name) { + if (isPattern(errorArgument)) { + const regexp = new RegExp(errorArgument, "u"); + + return regexp.test(name); + } + return name === errorArgument; + } + + /** + * Get the parameters of a given function scope. + * @param {Object} scope The function scope. + * @returns {Array} All parameters of the given scope. + */ + function getParameters(scope) { + return scope.variables.filter(variable => variable.defs[0] && variable.defs[0].type === "Parameter"); + } + + /** + * Check to see if we're handling the error object properly. + * @param {ASTNode} node The AST node to check. + * @returns {void} + */ + function checkForError(node) { + const scope = context.getScope(), + parameters = getParameters(scope), + firstParameter = parameters[0]; + + if (firstParameter && matchesConfiguredErrorName(firstParameter.name)) { + if (firstParameter.references.length === 0) { + context.report({ node, messageId: "expected" }); + } + } + } + + return { + FunctionDeclaration: checkForError, + FunctionExpression: checkForError, + ArrowFunctionExpression: checkForError + }; + + } +}; diff --git a/eslint/lib/rules/id-blacklist.js b/eslint/lib/rules/id-blacklist.js new file mode 100644 index 0000000..d77a35d --- /dev/null +++ b/eslint/lib/rules/id-blacklist.js @@ -0,0 +1,230 @@ +/** + * @fileoverview Rule that warns when identifier names that are + * blacklisted in the configuration are used. + * @author Keith Cirkel (http://keithcirkel.co.uk) + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Checks whether the given node represents assignment target in a normal assignment or destructuring. + * @param {ASTNode} node The node to check. + * @returns {boolean} `true` if the node is assignment target. + */ +function isAssignmentTarget(node) { + const parent = node.parent; + + return ( + + // normal assignment + ( + parent.type === "AssignmentExpression" && + parent.left === node + ) || + + // destructuring + parent.type === "ArrayPattern" || + parent.type === "RestElement" || + ( + parent.type === "Property" && + parent.value === node && + parent.parent.type === "ObjectPattern" + ) || + ( + parent.type === "AssignmentPattern" && + parent.left === node + ) + ); +} + +/** + * Checks whether the given node represents an imported name that is renamed in the same import/export specifier. + * + * Examples: + * import { a as b } from 'mod'; // node `a` is renamed import + * export { a as b } from 'mod'; // node `a` is renamed import + * @param {ASTNode} node `Identifier` node to check. + * @returns {boolean} `true` if the node is a renamed import. + */ +function isRenamedImport(node) { + const parent = node.parent; + + return ( + ( + parent.type === "ImportSpecifier" && + parent.imported !== parent.local && + parent.imported === node + ) || + ( + parent.type === "ExportSpecifier" && + parent.parent.source && // re-export + parent.local !== parent.exported && + parent.local === node + ) + ); +} + +/** + * Checks whether the given node is a renamed identifier node in an ObjectPattern destructuring. + * + * Examples: + * const { a : b } = foo; // node `a` is renamed node. + * @param {ASTNode} node `Identifier` node to check. + * @returns {boolean} `true` if the node is a renamed node in an ObjectPattern destructuring. + */ +function isRenamedInDestructuring(node) { + const parent = node.parent; + + return ( + ( + !parent.computed && + parent.type === "Property" && + parent.parent.type === "ObjectPattern" && + parent.value !== node && + parent.key === node + ) + ); +} + +/** + * Checks whether the given node represents shorthand definition of a property in an object literal. + * @param {ASTNode} node `Identifier` node to check. + * @returns {boolean} `true` if the node is a shorthand property definition. + */ +function isShorthandPropertyDefinition(node) { + const parent = node.parent; + + return ( + parent.type === "Property" && + parent.parent.type === "ObjectExpression" && + parent.shorthand + ); +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow specified identifiers", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/id-blacklist" + }, + + schema: { + type: "array", + items: { + type: "string" + }, + uniqueItems: true + }, + messages: { + blacklisted: "Identifier '{{name}}' is blacklisted." + } + }, + + create(context) { + + const blacklist = new Set(context.options); + const reportedNodes = new Set(); + + let globalScope; + + /** + * Checks whether the given name is blacklisted. + * @param {string} name The name to check. + * @returns {boolean} `true` if the name is blacklisted. + * @private + */ + function isBlacklisted(name) { + return blacklist.has(name); + } + + /** + * Checks whether the given node represents a reference to a global variable that is not declared in the source code. + * These identifiers will be allowed, as it is assumed that user has no control over the names of external global variables. + * @param {ASTNode} node `Identifier` node to check. + * @returns {boolean} `true` if the node is a reference to a global variable. + */ + function isReferenceToGlobalVariable(node) { + const variable = globalScope.set.get(node.name); + + return variable && variable.defs.length === 0 && + variable.references.some(ref => ref.identifier === node); + } + + /** + * Determines whether the given node should be checked. + * @param {ASTNode} node `Identifier` node. + * @returns {boolean} `true` if the node should be checked. + */ + function shouldCheck(node) { + const parent = node.parent; + + /* + * Member access has special rules for checking property names. + * Read access to a property with a blacklisted name is allowed, because it can be on an object that user has no control over. + * Write access isn't allowed, because it potentially creates a new property with a blacklisted name. + */ + if ( + parent.type === "MemberExpression" && + parent.property === node && + !parent.computed + ) { + return isAssignmentTarget(parent); + } + + return ( + parent.type !== "CallExpression" && + parent.type !== "NewExpression" && + !isRenamedImport(node) && + !isRenamedInDestructuring(node) && + !( + isReferenceToGlobalVariable(node) && + !isShorthandPropertyDefinition(node) + ) + ); + } + + /** + * Reports an AST node as a rule violation. + * @param {ASTNode} node The node to report. + * @returns {void} + * @private + */ + function report(node) { + if (!reportedNodes.has(node)) { + context.report({ + node, + messageId: "blacklisted", + data: { + name: node.name + } + }); + reportedNodes.add(node); + } + } + + return { + + Program() { + globalScope = context.getScope(); + }, + + Identifier(node) { + if (isBlacklisted(node.name) && shouldCheck(node)) { + report(node); + } + } + }; + } +}; diff --git a/eslint/lib/rules/id-length.js b/eslint/lib/rules/id-length.js new file mode 100644 index 0000000..a68873a --- /dev/null +++ b/eslint/lib/rules/id-length.js @@ -0,0 +1,132 @@ +/** + * @fileoverview Rule that warns when identifier names are shorter or longer + * than the values provided in configuration. + * @author Burak Yigit Kaya aka BYK + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "enforce minimum and maximum identifier lengths", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/id-length" + }, + + schema: [ + { + type: "object", + properties: { + min: { + type: "integer", + default: 2 + }, + max: { + type: "integer" + }, + exceptions: { + type: "array", + uniqueItems: true, + items: { + type: "string" + } + }, + properties: { + enum: ["always", "never"] + } + }, + additionalProperties: false + } + ], + messages: { + tooShort: "Identifier name '{{name}}' is too short (< {{min}}).", + tooLong: "Identifier name '{{name}}' is too long (> {{max}})." + } + }, + + create(context) { + const options = context.options[0] || {}; + const minLength = typeof options.min !== "undefined" ? options.min : 2; + const maxLength = typeof options.max !== "undefined" ? options.max : Infinity; + const properties = options.properties !== "never"; + const exceptions = (options.exceptions ? options.exceptions : []) + .reduce((obj, item) => { + obj[item] = true; + + return obj; + }, {}); + const reportedNode = new Set(); + + const SUPPORTED_EXPRESSIONS = { + MemberExpression: properties && function(parent) { + return !parent.computed && ( + + // regular property assignment + (parent.parent.left === parent && parent.parent.type === "AssignmentExpression" || + + // or the last identifier in an ObjectPattern destructuring + parent.parent.type === "Property" && parent.parent.value === parent && + parent.parent.parent.type === "ObjectPattern" && parent.parent.parent.parent.left === parent.parent.parent) + ); + }, + AssignmentPattern(parent, node) { + return parent.left === node; + }, + VariableDeclarator(parent, node) { + return parent.id === node; + }, + Property(parent, node) { + + if (parent.parent.type === "ObjectPattern") { + return ( + parent.value !== parent.key && parent.value === node || + parent.value === parent.key && parent.key === node && properties + ); + } + return properties && !parent.computed && parent.key === node; + }, + ImportDefaultSpecifier: true, + RestElement: true, + FunctionExpression: true, + ArrowFunctionExpression: true, + ClassDeclaration: true, + FunctionDeclaration: true, + MethodDefinition: true, + CatchClause: true, + ArrayPattern: true + }; + + return { + Identifier(node) { + const name = node.name; + const parent = node.parent; + + const isShort = name.length < minLength; + const isLong = name.length > maxLength; + + if (!(isShort || isLong) || exceptions[name]) { + return; // Nothing to report + } + + const isValidExpression = SUPPORTED_EXPRESSIONS[parent.type]; + + if (isValidExpression && !reportedNode.has(node) && (isValidExpression === true || isValidExpression(parent, node))) { + reportedNode.add(node); + context.report({ + node, + messageId: isShort ? "tooShort" : "tooLong", + data: { name, min: minLength, max: maxLength } + }); + } + } + }; + } +}; diff --git a/eslint/lib/rules/id-match.js b/eslint/lib/rules/id-match.js new file mode 100644 index 0000000..b97a497 --- /dev/null +++ b/eslint/lib/rules/id-match.js @@ -0,0 +1,225 @@ +/** + * @fileoverview Rule to flag non-matching identifiers + * @author Matthieu Larcher + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "require identifiers to match a specified regular expression", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/id-match" + }, + + schema: [ + { + type: "string" + }, + { + type: "object", + properties: { + properties: { + type: "boolean", + default: false + }, + onlyDeclarations: { + type: "boolean", + default: false + }, + ignoreDestructuring: { + type: "boolean", + default: false + } + } + } + ], + messages: { + notMatch: "Identifier '{{name}}' does not match the pattern '{{pattern}}'." + } + }, + + create(context) { + + //-------------------------------------------------------------------------- + // Options + //-------------------------------------------------------------------------- + const pattern = context.options[0] || "^.+$", + regexp = new RegExp(pattern, "u"); + + const options = context.options[1] || {}, + properties = !!options.properties, + onlyDeclarations = !!options.onlyDeclarations, + ignoreDestructuring = !!options.ignoreDestructuring; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + // contains reported nodes to avoid reporting twice on destructuring with shorthand notation + const reported = new Map(); + const ALLOWED_PARENT_TYPES = new Set(["CallExpression", "NewExpression"]); + const DECLARATION_TYPES = new Set(["FunctionDeclaration", "VariableDeclarator"]); + const IMPORT_TYPES = new Set(["ImportSpecifier", "ImportNamespaceSpecifier", "ImportDefaultSpecifier"]); + + /** + * Checks if a string matches the provided pattern + * @param {string} name The string to check. + * @returns {boolean} if the string is a match + * @private + */ + function isInvalid(name) { + return !regexp.test(name); + } + + /** + * Checks if a parent of a node is an ObjectPattern. + * @param {ASTNode} node The node to check. + * @returns {boolean} if the node is inside an ObjectPattern + * @private + */ + function isInsideObjectPattern(node) { + let { parent } = node; + + while (parent) { + if (parent.type === "ObjectPattern") { + return true; + } + + parent = parent.parent; + } + + return false; + } + + /** + * Verifies if we should report an error or not based on the effective + * parent node and the identifier name. + * @param {ASTNode} effectiveParent The effective parent node of the node to be reported + * @param {string} name The identifier name of the identifier node + * @returns {boolean} whether an error should be reported or not + */ + function shouldReport(effectiveParent, name) { + return (!onlyDeclarations || DECLARATION_TYPES.has(effectiveParent.type)) && + !ALLOWED_PARENT_TYPES.has(effectiveParent.type) && isInvalid(name); + } + + /** + * Reports an AST node as a rule violation. + * @param {ASTNode} node The node to report. + * @returns {void} + * @private + */ + function report(node) { + if (!reported.has(node)) { + context.report({ + node, + messageId: "notMatch", + data: { + name: node.name, + pattern + } + }); + reported.set(node, true); + } + } + + return { + + Identifier(node) { + const name = node.name, + parent = node.parent, + effectiveParent = (parent.type === "MemberExpression") ? parent.parent : parent; + + if (parent.type === "MemberExpression") { + + if (!properties) { + return; + } + + // Always check object names + if (parent.object.type === "Identifier" && + parent.object.name === name) { + if (isInvalid(name)) { + report(node); + } + + // Report AssignmentExpressions left side's assigned variable id + } else if (effectiveParent.type === "AssignmentExpression" && + effectiveParent.left.type === "MemberExpression" && + effectiveParent.left.property.name === node.name) { + if (isInvalid(name)) { + report(node); + } + + // Report AssignmentExpressions only if they are the left side of the assignment + } else if (effectiveParent.type === "AssignmentExpression" && effectiveParent.right.type !== "MemberExpression") { + if (isInvalid(name)) { + report(node); + } + } + + /* + * Properties have their own rules, and + * AssignmentPattern nodes can be treated like Properties: + * e.g.: const { no_camelcased = false } = bar; + */ + } else if (parent.type === "Property" || parent.type === "AssignmentPattern") { + + if (parent.parent && parent.parent.type === "ObjectPattern") { + if (parent.shorthand && parent.value.left && isInvalid(name)) { + + report(node); + } + + const assignmentKeyEqualsValue = parent.key.name === parent.value.name; + + // prevent checking righthand side of destructured object + if (!assignmentKeyEqualsValue && parent.key === node) { + return; + } + + const valueIsInvalid = parent.value.name && isInvalid(name); + + // ignore destructuring if the option is set, unless a new identifier is created + if (valueIsInvalid && !(assignmentKeyEqualsValue && ignoreDestructuring)) { + report(node); + } + } + + // never check properties or always ignore destructuring + if (!properties || (ignoreDestructuring && isInsideObjectPattern(node))) { + return; + } + + // don't check right hand side of AssignmentExpression to prevent duplicate warnings + if (parent.right !== node && shouldReport(effectiveParent, name)) { + report(node); + } + + // Check if it's an import specifier + } else if (IMPORT_TYPES.has(parent.type)) { + + // Report only if the local imported identifier is invalid + if (parent.local && parent.local.name === node.name && isInvalid(name)) { + report(node); + } + + // Report anything that is invalid that isn't a CallExpression + } else if (shouldReport(effectiveParent, name)) { + report(node); + } + } + + }; + + } +}; diff --git a/eslint/lib/rules/implicit-arrow-linebreak.js b/eslint/lib/rules/implicit-arrow-linebreak.js new file mode 100644 index 0000000..409145e --- /dev/null +++ b/eslint/lib/rules/implicit-arrow-linebreak.js @@ -0,0 +1,81 @@ +/** + * @fileoverview enforce the location of arrow function bodies + * @author Sharmila Jesupaul + */ +"use strict"; + +const { isCommentToken, isNotOpeningParenToken } = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +module.exports = { + meta: { + type: "layout", + + docs: { + description: "enforce the location of arrow function bodies", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/implicit-arrow-linebreak" + }, + + fixable: "whitespace", + + schema: [ + { + enum: ["beside", "below"] + } + ], + messages: { + expected: "Expected a linebreak before this expression.", + unexpected: "Expected no linebreak before this expression." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + const option = context.options[0] || "beside"; + + /** + * Validates the location of an arrow function body + * @param {ASTNode} node The arrow function body + * @returns {void} + */ + function validateExpression(node) { + if (node.body.type === "BlockStatement") { + return; + } + + const arrowToken = sourceCode.getTokenBefore(node.body, isNotOpeningParenToken); + const firstTokenOfBody = sourceCode.getTokenAfter(arrowToken); + + if (arrowToken.loc.end.line === firstTokenOfBody.loc.start.line && option === "below") { + context.report({ + node: firstTokenOfBody, + messageId: "expected", + fix: fixer => fixer.insertTextBefore(firstTokenOfBody, "\n") + }); + } else if (arrowToken.loc.end.line !== firstTokenOfBody.loc.start.line && option === "beside") { + context.report({ + node: firstTokenOfBody, + messageId: "unexpected", + fix(fixer) { + if (sourceCode.getFirstTokenBetween(arrowToken, firstTokenOfBody, { includeComments: true, filter: isCommentToken })) { + return null; + } + + return fixer.replaceTextRange([arrowToken.range[1], firstTokenOfBody.range[0]], " "); + } + }); + } + } + + //---------------------------------------------------------------------- + // Public + //---------------------------------------------------------------------- + return { + ArrowFunctionExpression: node => validateExpression(node) + }; + } +}; diff --git a/eslint/lib/rules/indent-legacy.js b/eslint/lib/rules/indent-legacy.js new file mode 100644 index 0000000..50010d3 --- /dev/null +++ b/eslint/lib/rules/indent-legacy.js @@ -0,0 +1,1125 @@ +/** + * @fileoverview This option sets a specific tab width for your code + * + * This rule has been ported and modified from nodeca. + * @author Vitaly Puzrin + * @author Gyandeep Singh + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +/* istanbul ignore next: this rule has known coverage issues, but it's deprecated and shouldn't be updated in the future anyway. */ +module.exports = { + meta: { + type: "layout", + + docs: { + description: "enforce consistent indentation", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/indent-legacy" + }, + + deprecated: true, + + replacedBy: ["indent"], + + fixable: "whitespace", + + schema: [ + { + oneOf: [ + { + enum: ["tab"] + }, + { + type: "integer", + minimum: 0 + } + ] + }, + { + type: "object", + properties: { + SwitchCase: { + type: "integer", + minimum: 0 + }, + VariableDeclarator: { + oneOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "object", + properties: { + var: { + type: "integer", + minimum: 0 + }, + let: { + type: "integer", + minimum: 0 + }, + const: { + type: "integer", + minimum: 0 + } + } + } + ] + }, + outerIIFEBody: { + type: "integer", + minimum: 0 + }, + MemberExpression: { + type: "integer", + minimum: 0 + }, + FunctionDeclaration: { + type: "object", + properties: { + parameters: { + oneOf: [ + { + type: "integer", + minimum: 0 + }, + { + enum: ["first"] + } + ] + }, + body: { + type: "integer", + minimum: 0 + } + } + }, + FunctionExpression: { + type: "object", + properties: { + parameters: { + oneOf: [ + { + type: "integer", + minimum: 0 + }, + { + enum: ["first"] + } + ] + }, + body: { + type: "integer", + minimum: 0 + } + } + }, + CallExpression: { + type: "object", + properties: { + parameters: { + oneOf: [ + { + type: "integer", + minimum: 0 + }, + { + enum: ["first"] + } + ] + } + } + }, + ArrayExpression: { + oneOf: [ + { + type: "integer", + minimum: 0 + }, + { + enum: ["first"] + } + ] + }, + ObjectExpression: { + oneOf: [ + { + type: "integer", + minimum: 0 + }, + { + enum: ["first"] + } + ] + } + }, + additionalProperties: false + } + ], + messages: { + expected: "Expected indentation of {{expected}} but found {{actual}}." + } + }, + + create(context) { + const DEFAULT_VARIABLE_INDENT = 1; + const DEFAULT_PARAMETER_INDENT = null; // For backwards compatibility, don't check parameter indentation unless specified in the config + const DEFAULT_FUNCTION_BODY_INDENT = 1; + + let indentType = "space"; + let indentSize = 4; + const options = { + SwitchCase: 0, + VariableDeclarator: { + var: DEFAULT_VARIABLE_INDENT, + let: DEFAULT_VARIABLE_INDENT, + const: DEFAULT_VARIABLE_INDENT + }, + outerIIFEBody: null, + FunctionDeclaration: { + parameters: DEFAULT_PARAMETER_INDENT, + body: DEFAULT_FUNCTION_BODY_INDENT + }, + FunctionExpression: { + parameters: DEFAULT_PARAMETER_INDENT, + body: DEFAULT_FUNCTION_BODY_INDENT + }, + CallExpression: { + arguments: DEFAULT_PARAMETER_INDENT + }, + ArrayExpression: 1, + ObjectExpression: 1 + }; + + const sourceCode = context.getSourceCode(); + + if (context.options.length) { + if (context.options[0] === "tab") { + indentSize = 1; + indentType = "tab"; + } else /* istanbul ignore else : this will be caught by options validation */ if (typeof context.options[0] === "number") { + indentSize = context.options[0]; + indentType = "space"; + } + + if (context.options[1]) { + const opts = context.options[1]; + + options.SwitchCase = opts.SwitchCase || 0; + const variableDeclaratorRules = opts.VariableDeclarator; + + if (typeof variableDeclaratorRules === "number") { + options.VariableDeclarator = { + var: variableDeclaratorRules, + let: variableDeclaratorRules, + const: variableDeclaratorRules + }; + } else if (typeof variableDeclaratorRules === "object") { + Object.assign(options.VariableDeclarator, variableDeclaratorRules); + } + + if (typeof opts.outerIIFEBody === "number") { + options.outerIIFEBody = opts.outerIIFEBody; + } + + if (typeof opts.MemberExpression === "number") { + options.MemberExpression = opts.MemberExpression; + } + + if (typeof opts.FunctionDeclaration === "object") { + Object.assign(options.FunctionDeclaration, opts.FunctionDeclaration); + } + + if (typeof opts.FunctionExpression === "object") { + Object.assign(options.FunctionExpression, opts.FunctionExpression); + } + + if (typeof opts.CallExpression === "object") { + Object.assign(options.CallExpression, opts.CallExpression); + } + + if (typeof opts.ArrayExpression === "number" || typeof opts.ArrayExpression === "string") { + options.ArrayExpression = opts.ArrayExpression; + } + + if (typeof opts.ObjectExpression === "number" || typeof opts.ObjectExpression === "string") { + options.ObjectExpression = opts.ObjectExpression; + } + } + } + + const caseIndentStore = {}; + + /** + * Creates an error message for a line, given the expected/actual indentation. + * @param {int} expectedAmount The expected amount of indentation characters for this line + * @param {int} actualSpaces The actual number of indentation spaces that were found on this line + * @param {int} actualTabs The actual number of indentation tabs that were found on this line + * @returns {string} An error message for this line + */ + function createErrorMessageData(expectedAmount, actualSpaces, actualTabs) { + const expectedStatement = `${expectedAmount} ${indentType}${expectedAmount === 1 ? "" : "s"}`; // e.g. "2 tabs" + const foundSpacesWord = `space${actualSpaces === 1 ? "" : "s"}`; // e.g. "space" + const foundTabsWord = `tab${actualTabs === 1 ? "" : "s"}`; // e.g. "tabs" + let foundStatement; + + if (actualSpaces > 0 && actualTabs > 0) { + foundStatement = `${actualSpaces} ${foundSpacesWord} and ${actualTabs} ${foundTabsWord}`; // e.g. "1 space and 2 tabs" + } else if (actualSpaces > 0) { + + /* + * Abbreviate the message if the expected indentation is also spaces. + * e.g. 'Expected 4 spaces but found 2' rather than 'Expected 4 spaces but found 2 spaces' + */ + foundStatement = indentType === "space" ? actualSpaces : `${actualSpaces} ${foundSpacesWord}`; + } else if (actualTabs > 0) { + foundStatement = indentType === "tab" ? actualTabs : `${actualTabs} ${foundTabsWord}`; + } else { + foundStatement = "0"; + } + return { + expected: expectedStatement, + actual: foundStatement + }; + } + + /** + * Reports a given indent violation + * @param {ASTNode} node Node violating the indent rule + * @param {int} needed Expected indentation character count + * @param {int} gottenSpaces Indentation space count in the actual node/code + * @param {int} gottenTabs Indentation tab count in the actual node/code + * @param {Object} [loc] Error line and column location + * @param {boolean} isLastNodeCheck Is the error for last node check + * @returns {void} + */ + function report(node, needed, gottenSpaces, gottenTabs, loc, isLastNodeCheck) { + if (gottenSpaces && gottenTabs) { + + // To avoid conflicts with `no-mixed-spaces-and-tabs`, don't report lines that have both spaces and tabs. + return; + } + + const desiredIndent = (indentType === "space" ? " " : "\t").repeat(needed); + + const textRange = isLastNodeCheck + ? [node.range[1] - node.loc.end.column, node.range[1] - node.loc.end.column + gottenSpaces + gottenTabs] + : [node.range[0] - node.loc.start.column, node.range[0] - node.loc.start.column + gottenSpaces + gottenTabs]; + + context.report({ + node, + loc, + messageId: "expected", + data: createErrorMessageData(needed, gottenSpaces, gottenTabs), + fix: fixer => fixer.replaceTextRange(textRange, desiredIndent) + }); + } + + /** + * Get the actual indent of node + * @param {ASTNode|Token} node Node to examine + * @param {boolean} [byLastLine=false] get indent of node's last line + * @returns {Object} The node's indent. Contains keys `space` and `tab`, representing the indent of each character. Also + * contains keys `goodChar` and `badChar`, where `goodChar` is the amount of the user's desired indentation character, and + * `badChar` is the amount of the other indentation character. + */ + function getNodeIndent(node, byLastLine) { + const token = byLastLine ? sourceCode.getLastToken(node) : sourceCode.getFirstToken(node); + const srcCharsBeforeNode = sourceCode.getText(token, token.loc.start.column).split(""); + const indentChars = srcCharsBeforeNode.slice(0, srcCharsBeforeNode.findIndex(char => char !== " " && char !== "\t")); + const spaces = indentChars.filter(char => char === " ").length; + const tabs = indentChars.filter(char => char === "\t").length; + + return { + space: spaces, + tab: tabs, + goodChar: indentType === "space" ? spaces : tabs, + badChar: indentType === "space" ? tabs : spaces + }; + } + + /** + * Checks node is the first in its own start line. By default it looks by start line. + * @param {ASTNode} node The node to check + * @param {boolean} [byEndLocation=false] Lookup based on start position or end + * @returns {boolean} true if its the first in the its start line + */ + function isNodeFirstInLine(node, byEndLocation) { + const firstToken = byEndLocation === true ? sourceCode.getLastToken(node, 1) : sourceCode.getTokenBefore(node), + startLine = byEndLocation === true ? node.loc.end.line : node.loc.start.line, + endLine = firstToken ? firstToken.loc.end.line : -1; + + return startLine !== endLine; + } + + /** + * Check indent for node + * @param {ASTNode} node Node to check + * @param {int} neededIndent needed indent + * @returns {void} + */ + function checkNodeIndent(node, neededIndent) { + const actualIndent = getNodeIndent(node, false); + + if ( + node.type !== "ArrayExpression" && + node.type !== "ObjectExpression" && + (actualIndent.goodChar !== neededIndent || actualIndent.badChar !== 0) && + isNodeFirstInLine(node) + ) { + report(node, neededIndent, actualIndent.space, actualIndent.tab); + } + + if (node.type === "IfStatement" && node.alternate) { + const elseToken = sourceCode.getTokenBefore(node.alternate); + + checkNodeIndent(elseToken, neededIndent); + + if (!isNodeFirstInLine(node.alternate)) { + checkNodeIndent(node.alternate, neededIndent); + } + } + + if (node.type === "TryStatement" && node.handler) { + const catchToken = sourceCode.getFirstToken(node.handler); + + checkNodeIndent(catchToken, neededIndent); + } + + if (node.type === "TryStatement" && node.finalizer) { + const finallyToken = sourceCode.getTokenBefore(node.finalizer); + + checkNodeIndent(finallyToken, neededIndent); + } + + if (node.type === "DoWhileStatement") { + const whileToken = sourceCode.getTokenAfter(node.body); + + checkNodeIndent(whileToken, neededIndent); + } + } + + /** + * Check indent for nodes list + * @param {ASTNode[]} nodes list of node objects + * @param {int} indent needed indent + * @returns {void} + */ + function checkNodesIndent(nodes, indent) { + nodes.forEach(node => checkNodeIndent(node, indent)); + } + + /** + * Check last node line indent this detects, that block closed correctly + * @param {ASTNode} node Node to examine + * @param {int} lastLineIndent needed indent + * @returns {void} + */ + function checkLastNodeLineIndent(node, lastLineIndent) { + const lastToken = sourceCode.getLastToken(node); + const endIndent = getNodeIndent(lastToken, true); + + if ((endIndent.goodChar !== lastLineIndent || endIndent.badChar !== 0) && isNodeFirstInLine(node, true)) { + report( + node, + lastLineIndent, + endIndent.space, + endIndent.tab, + { line: lastToken.loc.start.line, column: lastToken.loc.start.column }, + true + ); + } + } + + /** + * Check last node line indent this detects, that block closed correctly + * This function for more complicated return statement case, where closing parenthesis may be followed by ';' + * @param {ASTNode} node Node to examine + * @param {int} firstLineIndent first line needed indent + * @returns {void} + */ + function checkLastReturnStatementLineIndent(node, firstLineIndent) { + + /* + * in case if return statement ends with ');' we have traverse back to ')' + * otherwise we'll measure indent for ';' and replace ')' + */ + const lastToken = sourceCode.getLastToken(node, astUtils.isClosingParenToken); + const textBeforeClosingParenthesis = sourceCode.getText(lastToken, lastToken.loc.start.column).slice(0, -1); + + if (textBeforeClosingParenthesis.trim()) { + + // There are tokens before the closing paren, don't report this case + return; + } + + const endIndent = getNodeIndent(lastToken, true); + + if (endIndent.goodChar !== firstLineIndent) { + report( + node, + firstLineIndent, + endIndent.space, + endIndent.tab, + { line: lastToken.loc.start.line, column: lastToken.loc.start.column }, + true + ); + } + } + + /** + * Check first node line indent is correct + * @param {ASTNode} node Node to examine + * @param {int} firstLineIndent needed indent + * @returns {void} + */ + function checkFirstNodeLineIndent(node, firstLineIndent) { + const startIndent = getNodeIndent(node, false); + + if ((startIndent.goodChar !== firstLineIndent || startIndent.badChar !== 0) && isNodeFirstInLine(node)) { + report( + node, + firstLineIndent, + startIndent.space, + startIndent.tab, + { line: node.loc.start.line, column: node.loc.start.column } + ); + } + } + + /** + * Returns a parent node of given node based on a specified type + * if not present then return null + * @param {ASTNode} node node to examine + * @param {string} type type that is being looked for + * @param {string} stopAtList end points for the evaluating code + * @returns {ASTNode|void} if found then node otherwise null + */ + function getParentNodeByType(node, type, stopAtList) { + let parent = node.parent; + const stopAtSet = new Set(stopAtList || ["Program"]); + + while (parent.type !== type && !stopAtSet.has(parent.type) && parent.type !== "Program") { + parent = parent.parent; + } + + return parent.type === type ? parent : null; + } + + /** + * Returns the VariableDeclarator based on the current node + * if not present then return null + * @param {ASTNode} node node to examine + * @returns {ASTNode|void} if found then node otherwise null + */ + function getVariableDeclaratorNode(node) { + return getParentNodeByType(node, "VariableDeclarator"); + } + + /** + * Check to see if the node is part of the multi-line variable declaration. + * Also if its on the same line as the varNode + * @param {ASTNode} node node to check + * @param {ASTNode} varNode variable declaration node to check against + * @returns {boolean} True if all the above condition satisfy + */ + function isNodeInVarOnTop(node, varNode) { + return varNode && + varNode.parent.loc.start.line === node.loc.start.line && + varNode.parent.declarations.length > 1; + } + + /** + * Check to see if the argument before the callee node is multi-line and + * there should only be 1 argument before the callee node + * @param {ASTNode} node node to check + * @returns {boolean} True if arguments are multi-line + */ + function isArgBeforeCalleeNodeMultiline(node) { + const parent = node.parent; + + if (parent.arguments.length >= 2 && parent.arguments[1] === node) { + return parent.arguments[0].loc.end.line > parent.arguments[0].loc.start.line; + } + + return false; + } + + /** + * Check to see if the node is a file level IIFE + * @param {ASTNode} node The function node to check. + * @returns {boolean} True if the node is the outer IIFE + */ + function isOuterIIFE(node) { + const parent = node.parent; + let stmt = parent.parent; + + /* + * Verify that the node is an IIEF + */ + if ( + parent.type !== "CallExpression" || + parent.callee !== node) { + + return false; + } + + /* + * Navigate legal ancestors to determine whether this IIEF is outer + */ + while ( + stmt.type === "UnaryExpression" && ( + stmt.operator === "!" || + stmt.operator === "~" || + stmt.operator === "+" || + stmt.operator === "-") || + stmt.type === "AssignmentExpression" || + stmt.type === "LogicalExpression" || + stmt.type === "SequenceExpression" || + stmt.type === "VariableDeclarator") { + + stmt = stmt.parent; + } + + return (( + stmt.type === "ExpressionStatement" || + stmt.type === "VariableDeclaration") && + stmt.parent && stmt.parent.type === "Program" + ); + } + + /** + * Check indent for function block content + * @param {ASTNode} node A BlockStatement node that is inside of a function. + * @returns {void} + */ + function checkIndentInFunctionBlock(node) { + + /* + * Search first caller in chain. + * Ex.: + * + * Models <- Identifier + * .User + * .find() + * .exec(function() { + * // function body + * }); + * + * Looks for 'Models' + */ + const calleeNode = node.parent; // FunctionExpression + let indent; + + if (calleeNode.parent && + (calleeNode.parent.type === "Property" || + calleeNode.parent.type === "ArrayExpression")) { + + // If function is part of array or object, comma can be put at left + indent = getNodeIndent(calleeNode, false).goodChar; + } else { + + // If function is standalone, simple calculate indent + indent = getNodeIndent(calleeNode).goodChar; + } + + if (calleeNode.parent.type === "CallExpression") { + const calleeParent = calleeNode.parent; + + if (calleeNode.type !== "FunctionExpression" && calleeNode.type !== "ArrowFunctionExpression") { + if (calleeParent && calleeParent.loc.start.line < node.loc.start.line) { + indent = getNodeIndent(calleeParent).goodChar; + } + } else { + if (isArgBeforeCalleeNodeMultiline(calleeNode) && + calleeParent.callee.loc.start.line === calleeParent.callee.loc.end.line && + !isNodeFirstInLine(calleeNode)) { + indent = getNodeIndent(calleeParent).goodChar; + } + } + } + + /* + * function body indent should be indent + indent size, unless this + * is a FunctionDeclaration, FunctionExpression, or outer IIFE and the corresponding options are enabled. + */ + let functionOffset = indentSize; + + if (options.outerIIFEBody !== null && isOuterIIFE(calleeNode)) { + functionOffset = options.outerIIFEBody * indentSize; + } else if (calleeNode.type === "FunctionExpression") { + functionOffset = options.FunctionExpression.body * indentSize; + } else if (calleeNode.type === "FunctionDeclaration") { + functionOffset = options.FunctionDeclaration.body * indentSize; + } + indent += functionOffset; + + // check if the node is inside a variable + const parentVarNode = getVariableDeclaratorNode(node); + + if (parentVarNode && isNodeInVarOnTop(node, parentVarNode)) { + indent += indentSize * options.VariableDeclarator[parentVarNode.parent.kind]; + } + + if (node.body.length > 0) { + checkNodesIndent(node.body, indent); + } + + checkLastNodeLineIndent(node, indent - functionOffset); + } + + + /** + * Checks if the given node starts and ends on the same line + * @param {ASTNode} node The node to check + * @returns {boolean} Whether or not the block starts and ends on the same line. + */ + function isSingleLineNode(node) { + const lastToken = sourceCode.getLastToken(node), + startLine = node.loc.start.line, + endLine = lastToken.loc.end.line; + + return startLine === endLine; + } + + /** + * Check indent for array block content or object block content + * @param {ASTNode} node node to examine + * @returns {void} + */ + function checkIndentInArrayOrObjectBlock(node) { + + // Skip inline + if (isSingleLineNode(node)) { + return; + } + + let elements = (node.type === "ArrayExpression") ? node.elements : node.properties; + + // filter out empty elements example would be [ , 2] so remove first element as espree considers it as null + elements = elements.filter(elem => elem !== null); + + let nodeIndent; + let elementsIndent; + const parentVarNode = getVariableDeclaratorNode(node); + + // TODO - come up with a better strategy in future + if (isNodeFirstInLine(node)) { + const parent = node.parent; + + nodeIndent = getNodeIndent(parent).goodChar; + if (!parentVarNode || parentVarNode.loc.start.line !== node.loc.start.line) { + if (parent.type !== "VariableDeclarator" || parentVarNode === parentVarNode.parent.declarations[0]) { + if (parent.type === "VariableDeclarator" && parentVarNode.loc.start.line === parent.loc.start.line) { + nodeIndent += (indentSize * options.VariableDeclarator[parentVarNode.parent.kind]); + } else if (parent.type === "ObjectExpression" || parent.type === "ArrayExpression") { + const parentElements = node.parent.type === "ObjectExpression" ? node.parent.properties : node.parent.elements; + + if (parentElements[0] && + parentElements[0].loc.start.line === parent.loc.start.line && + parentElements[0].loc.end.line !== parent.loc.start.line) { + + /* + * If the first element of the array spans multiple lines, don't increase the expected indentation of the rest. + * e.g. [{ + * foo: 1 + * }, + * { + * bar: 1 + * }] + * the second object is not indented. + */ + } else if (typeof options[parent.type] === "number") { + nodeIndent += options[parent.type] * indentSize; + } else { + nodeIndent = parentElements[0].loc.start.column; + } + } else if (parent.type === "CallExpression" || parent.type === "NewExpression") { + if (typeof options.CallExpression.arguments === "number") { + nodeIndent += options.CallExpression.arguments * indentSize; + } else if (options.CallExpression.arguments === "first") { + if (parent.arguments.indexOf(node) !== -1) { + nodeIndent = parent.arguments[0].loc.start.column; + } + } else { + nodeIndent += indentSize; + } + } else if (parent.type === "LogicalExpression" || parent.type === "ArrowFunctionExpression") { + nodeIndent += indentSize; + } + } + } + + checkFirstNodeLineIndent(node, nodeIndent); + } else { + nodeIndent = getNodeIndent(node).goodChar; + } + + if (options[node.type] === "first") { + elementsIndent = elements.length ? elements[0].loc.start.column : 0; // If there are no elements, elementsIndent doesn't matter. + } else { + elementsIndent = nodeIndent + indentSize * options[node.type]; + } + + /* + * Check if the node is a multiple variable declaration; if so, then + * make sure indentation takes that into account. + */ + if (isNodeInVarOnTop(node, parentVarNode)) { + elementsIndent += indentSize * options.VariableDeclarator[parentVarNode.parent.kind]; + } + + checkNodesIndent(elements, elementsIndent); + + if (elements.length > 0) { + + // Skip last block line check if last item in same line + if (elements[elements.length - 1].loc.end.line === node.loc.end.line) { + return; + } + } + + checkLastNodeLineIndent(node, nodeIndent + + (isNodeInVarOnTop(node, parentVarNode) ? options.VariableDeclarator[parentVarNode.parent.kind] * indentSize : 0)); + } + + /** + * Check if the node or node body is a BlockStatement or not + * @param {ASTNode} node node to test + * @returns {boolean} True if it or its body is a block statement + */ + function isNodeBodyBlock(node) { + return node.type === "BlockStatement" || node.type === "ClassBody" || (node.body && node.body.type === "BlockStatement") || + (node.consequent && node.consequent.type === "BlockStatement"); + } + + /** + * Check indentation for blocks + * @param {ASTNode} node node to check + * @returns {void} + */ + function blockIndentationCheck(node) { + + // Skip inline blocks + if (isSingleLineNode(node)) { + return; + } + + if (node.parent && ( + node.parent.type === "FunctionExpression" || + node.parent.type === "FunctionDeclaration" || + node.parent.type === "ArrowFunctionExpression") + ) { + checkIndentInFunctionBlock(node); + return; + } + + let indent; + let nodesToCheck = []; + + /* + * For this statements we should check indent from statement beginning, + * not from the beginning of the block. + */ + const statementsWithProperties = [ + "IfStatement", "WhileStatement", "ForStatement", "ForInStatement", "ForOfStatement", "DoWhileStatement", "ClassDeclaration", "TryStatement" + ]; + + if (node.parent && statementsWithProperties.indexOf(node.parent.type) !== -1 && isNodeBodyBlock(node)) { + indent = getNodeIndent(node.parent).goodChar; + } else if (node.parent && node.parent.type === "CatchClause") { + indent = getNodeIndent(node.parent.parent).goodChar; + } else { + indent = getNodeIndent(node).goodChar; + } + + if (node.type === "IfStatement" && node.consequent.type !== "BlockStatement") { + nodesToCheck = [node.consequent]; + } else if (Array.isArray(node.body)) { + nodesToCheck = node.body; + } else { + nodesToCheck = [node.body]; + } + + if (nodesToCheck.length > 0) { + checkNodesIndent(nodesToCheck, indent + indentSize); + } + + if (node.type === "BlockStatement") { + checkLastNodeLineIndent(node, indent); + } + } + + /** + * Filter out the elements which are on the same line of each other or the node. + * basically have only 1 elements from each line except the variable declaration line. + * @param {ASTNode} node Variable declaration node + * @returns {ASTNode[]} Filtered elements + */ + function filterOutSameLineVars(node) { + return node.declarations.reduce((finalCollection, elem) => { + const lastElem = finalCollection[finalCollection.length - 1]; + + if ((elem.loc.start.line !== node.loc.start.line && !lastElem) || + (lastElem && lastElem.loc.start.line !== elem.loc.start.line)) { + finalCollection.push(elem); + } + + return finalCollection; + }, []); + } + + /** + * Check indentation for variable declarations + * @param {ASTNode} node node to examine + * @returns {void} + */ + function checkIndentInVariableDeclarations(node) { + const elements = filterOutSameLineVars(node); + const nodeIndent = getNodeIndent(node).goodChar; + const lastElement = elements[elements.length - 1]; + + const elementsIndent = nodeIndent + indentSize * options.VariableDeclarator[node.kind]; + + checkNodesIndent(elements, elementsIndent); + + // Only check the last line if there is any token after the last item + if (sourceCode.getLastToken(node).loc.end.line <= lastElement.loc.end.line) { + return; + } + + const tokenBeforeLastElement = sourceCode.getTokenBefore(lastElement); + + if (tokenBeforeLastElement.value === ",") { + + // Special case for comma-first syntax where the semicolon is indented + checkLastNodeLineIndent(node, getNodeIndent(tokenBeforeLastElement).goodChar); + } else { + checkLastNodeLineIndent(node, elementsIndent - indentSize); + } + } + + /** + * Check and decide whether to check for indentation for blockless nodes + * Scenarios are for or while statements without braces around them + * @param {ASTNode} node node to examine + * @returns {void} + */ + function blockLessNodes(node) { + if (node.body.type !== "BlockStatement") { + blockIndentationCheck(node); + } + } + + /** + * Returns the expected indentation for the case statement + * @param {ASTNode} node node to examine + * @param {int} [providedSwitchIndent] indent for switch statement + * @returns {int} indent size + */ + function expectedCaseIndent(node, providedSwitchIndent) { + const switchNode = (node.type === "SwitchStatement") ? node : node.parent; + const switchIndent = typeof providedSwitchIndent === "undefined" + ? getNodeIndent(switchNode).goodChar + : providedSwitchIndent; + let caseIndent; + + if (caseIndentStore[switchNode.loc.start.line]) { + return caseIndentStore[switchNode.loc.start.line]; + } + + if (switchNode.cases.length > 0 && options.SwitchCase === 0) { + caseIndent = switchIndent; + } else { + caseIndent = switchIndent + (indentSize * options.SwitchCase); + } + + caseIndentStore[switchNode.loc.start.line] = caseIndent; + return caseIndent; + + } + + /** + * Checks wether a return statement is wrapped in () + * @param {ASTNode} node node to examine + * @returns {boolean} the result + */ + function isWrappedInParenthesis(node) { + const regex = /^return\s*?\(\s*?\);*?/u; + + const statementWithoutArgument = sourceCode.getText(node).replace( + sourceCode.getText(node.argument), "" + ); + + return regex.test(statementWithoutArgument); + } + + return { + Program(node) { + if (node.body.length > 0) { + + // Root nodes should have no indent + checkNodesIndent(node.body, getNodeIndent(node).goodChar); + } + }, + + ClassBody: blockIndentationCheck, + + BlockStatement: blockIndentationCheck, + + WhileStatement: blockLessNodes, + + ForStatement: blockLessNodes, + + ForInStatement: blockLessNodes, + + ForOfStatement: blockLessNodes, + + DoWhileStatement: blockLessNodes, + + IfStatement(node) { + if (node.consequent.type !== "BlockStatement" && node.consequent.loc.start.line > node.loc.start.line) { + blockIndentationCheck(node); + } + }, + + VariableDeclaration(node) { + if (node.declarations[node.declarations.length - 1].loc.start.line > node.declarations[0].loc.start.line) { + checkIndentInVariableDeclarations(node); + } + }, + + ObjectExpression(node) { + checkIndentInArrayOrObjectBlock(node); + }, + + ArrayExpression(node) { + checkIndentInArrayOrObjectBlock(node); + }, + + MemberExpression(node) { + + if (typeof options.MemberExpression === "undefined") { + return; + } + + if (isSingleLineNode(node)) { + return; + } + + /* + * The typical layout of variable declarations and assignments + * alter the expectation of correct indentation. Skip them. + * TODO: Add appropriate configuration options for variable + * declarations and assignments. + */ + if (getParentNodeByType(node, "VariableDeclarator", ["FunctionExpression", "ArrowFunctionExpression"])) { + return; + } + + if (getParentNodeByType(node, "AssignmentExpression", ["FunctionExpression"])) { + return; + } + + const propertyIndent = getNodeIndent(node).goodChar + indentSize * options.MemberExpression; + + const checkNodes = [node.property]; + + const dot = sourceCode.getTokenBefore(node.property); + + if (dot.type === "Punctuator" && dot.value === ".") { + checkNodes.push(dot); + } + + checkNodesIndent(checkNodes, propertyIndent); + }, + + SwitchStatement(node) { + + // Switch is not a 'BlockStatement' + const switchIndent = getNodeIndent(node).goodChar; + const caseIndent = expectedCaseIndent(node, switchIndent); + + checkNodesIndent(node.cases, caseIndent); + + + checkLastNodeLineIndent(node, switchIndent); + }, + + SwitchCase(node) { + + // Skip inline cases + if (isSingleLineNode(node)) { + return; + } + const caseIndent = expectedCaseIndent(node); + + checkNodesIndent(node.consequent, caseIndent + indentSize); + }, + + FunctionDeclaration(node) { + if (isSingleLineNode(node)) { + return; + } + if (options.FunctionDeclaration.parameters === "first" && node.params.length) { + checkNodesIndent(node.params.slice(1), node.params[0].loc.start.column); + } else if (options.FunctionDeclaration.parameters !== null) { + checkNodesIndent(node.params, getNodeIndent(node).goodChar + indentSize * options.FunctionDeclaration.parameters); + } + }, + + FunctionExpression(node) { + if (isSingleLineNode(node)) { + return; + } + if (options.FunctionExpression.parameters === "first" && node.params.length) { + checkNodesIndent(node.params.slice(1), node.params[0].loc.start.column); + } else if (options.FunctionExpression.parameters !== null) { + checkNodesIndent(node.params, getNodeIndent(node).goodChar + indentSize * options.FunctionExpression.parameters); + } + }, + + ReturnStatement(node) { + if (isSingleLineNode(node)) { + return; + } + + const firstLineIndent = getNodeIndent(node).goodChar; + + // in case if return statement is wrapped in parenthesis + if (isWrappedInParenthesis(node)) { + checkLastReturnStatementLineIndent(node, firstLineIndent); + } else { + checkNodeIndent(node, firstLineIndent); + } + }, + + CallExpression(node) { + if (isSingleLineNode(node)) { + return; + } + if (options.CallExpression.arguments === "first" && node.arguments.length) { + checkNodesIndent(node.arguments.slice(1), node.arguments[0].loc.start.column); + } else if (options.CallExpression.arguments !== null) { + checkNodesIndent(node.arguments, getNodeIndent(node).goodChar + indentSize * options.CallExpression.arguments); + } + } + + }; + + } +}; diff --git a/eslint/lib/rules/indent.js b/eslint/lib/rules/indent.js new file mode 100644 index 0000000..d576fde --- /dev/null +++ b/eslint/lib/rules/indent.js @@ -0,0 +1,1686 @@ +/** + * @fileoverview This rule sets a specific indentation style and width for your code + * + * @author Teddy Katz + * @author Vitaly Puzrin + * @author Gyandeep Singh + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const lodash = require("lodash"); +const astUtils = require("./utils/ast-utils"); +const createTree = require("functional-red-black-tree"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +const KNOWN_NODES = new Set([ + "AssignmentExpression", + "AssignmentPattern", + "ArrayExpression", + "ArrayPattern", + "ArrowFunctionExpression", + "AwaitExpression", + "BlockStatement", + "BinaryExpression", + "BreakStatement", + "CallExpression", + "CatchClause", + "ClassBody", + "ClassDeclaration", + "ClassExpression", + "ConditionalExpression", + "ContinueStatement", + "DoWhileStatement", + "DebuggerStatement", + "EmptyStatement", + "ExperimentalRestProperty", + "ExperimentalSpreadProperty", + "ExpressionStatement", + "ForStatement", + "ForInStatement", + "ForOfStatement", + "FunctionDeclaration", + "FunctionExpression", + "Identifier", + "IfStatement", + "Literal", + "LabeledStatement", + "LogicalExpression", + "MemberExpression", + "MetaProperty", + "MethodDefinition", + "NewExpression", + "ObjectExpression", + "ObjectPattern", + "Program", + "Property", + "RestElement", + "ReturnStatement", + "SequenceExpression", + "SpreadElement", + "Super", + "SwitchCase", + "SwitchStatement", + "TaggedTemplateExpression", + "TemplateElement", + "TemplateLiteral", + "ThisExpression", + "ThrowStatement", + "TryStatement", + "UnaryExpression", + "UpdateExpression", + "VariableDeclaration", + "VariableDeclarator", + "WhileStatement", + "WithStatement", + "YieldExpression", + "JSXFragment", + "JSXOpeningFragment", + "JSXClosingFragment", + "JSXIdentifier", + "JSXNamespacedName", + "JSXMemberExpression", + "JSXEmptyExpression", + "JSXExpressionContainer", + "JSXElement", + "JSXClosingElement", + "JSXOpeningElement", + "JSXAttribute", + "JSXSpreadAttribute", + "JSXText", + "ExportDefaultDeclaration", + "ExportNamedDeclaration", + "ExportAllDeclaration", + "ExportSpecifier", + "ImportDeclaration", + "ImportSpecifier", + "ImportDefaultSpecifier", + "ImportNamespaceSpecifier", + "ImportExpression" +]); + +/* + * General rule strategy: + * 1. An OffsetStorage instance stores a map of desired offsets, where each token has a specified offset from another + * specified token or to the first column. + * 2. As the AST is traversed, modify the desired offsets of tokens accordingly. For example, when entering a + * BlockStatement, offset all of the tokens in the BlockStatement by 1 indent level from the opening curly + * brace of the BlockStatement. + * 3. After traversing the AST, calculate the expected indentation levels of every token according to the + * OffsetStorage container. + * 4. For each line, compare the expected indentation of the first token to the actual indentation in the file, + * and report the token if the two values are not equal. + */ + + +/** + * A mutable balanced binary search tree that stores (key, value) pairs. The keys are numeric, and must be unique. + * This is intended to be a generic wrapper around a balanced binary search tree library, so that the underlying implementation + * can easily be swapped out. + */ +class BinarySearchTree { + + /** + * Creates an empty tree + */ + constructor() { + this._rbTree = createTree(); + } + + /** + * Inserts an entry into the tree. + * @param {number} key The entry's key + * @param {*} value The entry's value + * @returns {void} + */ + insert(key, value) { + const iterator = this._rbTree.find(key); + + if (iterator.valid) { + this._rbTree = iterator.update(value); + } else { + this._rbTree = this._rbTree.insert(key, value); + } + } + + /** + * Finds the entry with the largest key less than or equal to the provided key + * @param {number} key The provided key + * @returns {{key: number, value: *}|null} The found entry, or null if no such entry exists. + */ + findLe(key) { + const iterator = this._rbTree.le(key); + + return iterator && { key: iterator.key, value: iterator.value }; + } + + /** + * Deletes all of the keys in the interval [start, end) + * @param {number} start The start of the range + * @param {number} end The end of the range + * @returns {void} + */ + deleteRange(start, end) { + + // Exit without traversing the tree if the range has zero size. + if (start === end) { + return; + } + const iterator = this._rbTree.ge(start); + + while (iterator.valid && iterator.key < end) { + this._rbTree = this._rbTree.remove(iterator.key); + iterator.next(); + } + } +} + +/** + * A helper class to get token-based info related to indentation + */ +class TokenInfo { + + // eslint-disable-next-line jsdoc/require-description + /** + * @param {SourceCode} sourceCode A SourceCode object + */ + constructor(sourceCode) { + this.sourceCode = sourceCode; + this.firstTokensByLineNumber = sourceCode.tokensAndComments.reduce((map, token) => { + if (!map.has(token.loc.start.line)) { + map.set(token.loc.start.line, token); + } + if (!map.has(token.loc.end.line) && sourceCode.text.slice(token.range[1] - token.loc.end.column, token.range[1]).trim()) { + map.set(token.loc.end.line, token); + } + return map; + }, new Map()); + } + + /** + * Gets the first token on a given token's line + * @param {Token|ASTNode} token a node or token + * @returns {Token} The first token on the given line + */ + getFirstTokenOfLine(token) { + return this.firstTokensByLineNumber.get(token.loc.start.line); + } + + /** + * Determines whether a token is the first token in its line + * @param {Token} token The token + * @returns {boolean} `true` if the token is the first on its line + */ + isFirstTokenOfLine(token) { + return this.getFirstTokenOfLine(token) === token; + } + + /** + * Get the actual indent of a token + * @param {Token} token Token to examine. This should be the first token on its line. + * @returns {string} The indentation characters that precede the token + */ + getTokenIndent(token) { + return this.sourceCode.text.slice(token.range[0] - token.loc.start.column, token.range[0]); + } +} + +/** + * A class to store information on desired offsets of tokens from each other + */ +class OffsetStorage { + + // eslint-disable-next-line jsdoc/require-description + /** + * @param {TokenInfo} tokenInfo a TokenInfo instance + * @param {number} indentSize The desired size of each indentation level + * @param {string} indentType The indentation character + */ + constructor(tokenInfo, indentSize, indentType) { + this._tokenInfo = tokenInfo; + this._indentSize = indentSize; + this._indentType = indentType; + + this._tree = new BinarySearchTree(); + this._tree.insert(0, { offset: 0, from: null, force: false }); + + this._lockedFirstTokens = new WeakMap(); + this._desiredIndentCache = new WeakMap(); + this._ignoredTokens = new WeakSet(); + } + + _getOffsetDescriptor(token) { + return this._tree.findLe(token.range[0]).value; + } + + /** + * Sets the offset column of token B to match the offset column of token A. + * **WARNING**: This matches a *column*, even if baseToken is not the first token on its line. In + * most cases, `setDesiredOffset` should be used instead. + * @param {Token} baseToken The first token + * @param {Token} offsetToken The second token, whose offset should be matched to the first token + * @returns {void} + */ + matchOffsetOf(baseToken, offsetToken) { + + /* + * lockedFirstTokens is a map from a token whose indentation is controlled by the "first" option to + * the token that it depends on. For example, with the `ArrayExpression: first` option, the first + * token of each element in the array after the first will be mapped to the first token of the first + * element. The desired indentation of each of these tokens is computed based on the desired indentation + * of the "first" element, rather than through the normal offset mechanism. + */ + this._lockedFirstTokens.set(offsetToken, baseToken); + } + + /** + * Sets the desired offset of a token. + * + * This uses a line-based offset collapsing behavior to handle tokens on the same line. + * For example, consider the following two cases: + * + * ( + * [ + * bar + * ] + * ) + * + * ([ + * bar + * ]) + * + * Based on the first case, it's clear that the `bar` token needs to have an offset of 1 indent level (4 spaces) from + * the `[` token, and the `[` token has to have an offset of 1 indent level from the `(` token. Since the `(` token is + * the first on its line (with an indent of 0 spaces), the `bar` token needs to be offset by 2 indent levels (8 spaces) + * from the start of its line. + * + * However, in the second case `bar` should only be indented by 4 spaces. This is because the offset of 1 indent level + * between the `(` and the `[` tokens gets "collapsed" because the two tokens are on the same line. As a result, the + * `(` token is mapped to the `[` token with an offset of 0, and the rule correctly decides that `bar` should be indented + * by 1 indent level from the start of the line. + * + * This is useful because rule listeners can usually just call `setDesiredOffset` for all the tokens in the node, + * without needing to check which lines those tokens are on. + * + * Note that since collapsing only occurs when two tokens are on the same line, there are a few cases where non-intuitive + * behavior can occur. For example, consider the following cases: + * + * foo( + * ). + * bar( + * baz + * ) + * + * foo( + * ).bar( + * baz + * ) + * + * Based on the first example, it would seem that `bar` should be offset by 1 indent level from `foo`, and `baz` + * should be offset by 1 indent level from `bar`. However, this is not correct, because it would result in `baz` + * being indented by 2 indent levels in the second case (since `foo`, `bar`, and `baz` are all on separate lines, no + * collapsing would occur). + * + * Instead, the correct way would be to offset `baz` by 1 level from `bar`, offset `bar` by 1 level from the `)`, and + * offset the `)` by 0 levels from `foo`. This ensures that the offset between `bar` and the `)` are correctly collapsed + * in the second case. + * @param {Token} token The token + * @param {Token} fromToken The token that `token` should be offset from + * @param {number} offset The desired indent level + * @returns {void} + */ + setDesiredOffset(token, fromToken, offset) { + return this.setDesiredOffsets(token.range, fromToken, offset); + } + + /** + * Sets the desired offset of all tokens in a range + * It's common for node listeners in this file to need to apply the same offset to a large, contiguous range of tokens. + * Moreover, the offset of any given token is usually updated multiple times (roughly once for each node that contains + * it). This means that the offset of each token is updated O(AST depth) times. + * It would not be performant to store and update the offsets for each token independently, because the rule would end + * up having a time complexity of O(number of tokens * AST depth), which is quite slow for large files. + * + * Instead, the offset tree is represented as a collection of contiguous offset ranges in a file. For example, the following + * list could represent the state of the offset tree at a given point: + * + * * Tokens starting in the interval [0, 15) are aligned with the beginning of the file + * * Tokens starting in the interval [15, 30) are offset by 1 indent level from the `bar` token + * * Tokens starting in the interval [30, 43) are offset by 1 indent level from the `foo` token + * * Tokens starting in the interval [43, 820) are offset by 2 indent levels from the `bar` token + * * Tokens starting in the interval [820, ∞) are offset by 1 indent level from the `baz` token + * + * The `setDesiredOffsets` methods inserts ranges like the ones above. The third line above would be inserted by using: + * `setDesiredOffsets([30, 43], fooToken, 1);` + * @param {[number, number]} range A [start, end] pair. All tokens with range[0] <= token.start < range[1] will have the offset applied. + * @param {Token} fromToken The token that this is offset from + * @param {number} offset The desired indent level + * @param {boolean} force `true` if this offset should not use the normal collapsing behavior. This should almost always be false. + * @returns {void} + */ + setDesiredOffsets(range, fromToken, offset, force) { + + /* + * Offset ranges are stored as a collection of nodes, where each node maps a numeric key to an offset + * descriptor. The tree for the example above would have the following nodes: + * + * * key: 0, value: { offset: 0, from: null } + * * key: 15, value: { offset: 1, from: barToken } + * * key: 30, value: { offset: 1, from: fooToken } + * * key: 43, value: { offset: 2, from: barToken } + * * key: 820, value: { offset: 1, from: bazToken } + * + * To find the offset descriptor for any given token, one needs to find the node with the largest key + * which is <= token.start. To make this operation fast, the nodes are stored in a balanced binary + * search tree indexed by key. + */ + + const descriptorToInsert = { offset, from: fromToken, force }; + + const descriptorAfterRange = this._tree.findLe(range[1]).value; + + const fromTokenIsInRange = fromToken && fromToken.range[0] >= range[0] && fromToken.range[1] <= range[1]; + const fromTokenDescriptor = fromTokenIsInRange && this._getOffsetDescriptor(fromToken); + + // First, remove any existing nodes in the range from the tree. + this._tree.deleteRange(range[0] + 1, range[1]); + + // Insert a new node into the tree for this range + this._tree.insert(range[0], descriptorToInsert); + + /* + * To avoid circular offset dependencies, keep the `fromToken` token mapped to whatever it was mapped to previously, + * even if it's in the current range. + */ + if (fromTokenIsInRange) { + this._tree.insert(fromToken.range[0], fromTokenDescriptor); + this._tree.insert(fromToken.range[1], descriptorToInsert); + } + + /* + * To avoid modifying the offset of tokens after the range, insert another node to keep the offset of the following + * tokens the same as it was before. + */ + this._tree.insert(range[1], descriptorAfterRange); + } + + /** + * Gets the desired indent of a token + * @param {Token} token The token + * @returns {string} The desired indent of the token + */ + getDesiredIndent(token) { + if (!this._desiredIndentCache.has(token)) { + + if (this._ignoredTokens.has(token)) { + + /* + * If the token is ignored, use the actual indent of the token as the desired indent. + * This ensures that no errors are reported for this token. + */ + this._desiredIndentCache.set( + token, + this._tokenInfo.getTokenIndent(token) + ); + } else if (this._lockedFirstTokens.has(token)) { + const firstToken = this._lockedFirstTokens.get(token); + + this._desiredIndentCache.set( + token, + + // (indentation for the first element's line) + this.getDesiredIndent(this._tokenInfo.getFirstTokenOfLine(firstToken)) + + + // (space between the start of the first element's line and the first element) + this._indentType.repeat(firstToken.loc.start.column - this._tokenInfo.getFirstTokenOfLine(firstToken).loc.start.column) + ); + } else { + const offsetInfo = this._getOffsetDescriptor(token); + const offset = ( + offsetInfo.from && + offsetInfo.from.loc.start.line === token.loc.start.line && + !/^\s*?\n/u.test(token.value) && + !offsetInfo.force + ) ? 0 : offsetInfo.offset * this._indentSize; + + this._desiredIndentCache.set( + token, + (offsetInfo.from ? this.getDesiredIndent(offsetInfo.from) : "") + this._indentType.repeat(offset) + ); + } + } + return this._desiredIndentCache.get(token); + } + + /** + * Ignores a token, preventing it from being reported. + * @param {Token} token The token + * @returns {void} + */ + ignoreToken(token) { + if (this._tokenInfo.isFirstTokenOfLine(token)) { + this._ignoredTokens.add(token); + } + } + + /** + * Gets the first token that the given token's indentation is dependent on + * @param {Token} token The token + * @returns {Token} The token that the given token depends on, or `null` if the given token is at the top level + */ + getFirstDependency(token) { + return this._getOffsetDescriptor(token).from; + } +} + +const ELEMENT_LIST_SCHEMA = { + oneOf: [ + { + type: "integer", + minimum: 0 + }, + { + enum: ["first", "off"] + } + ] +}; + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "enforce consistent indentation", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/indent" + }, + + fixable: "whitespace", + + schema: [ + { + oneOf: [ + { + enum: ["tab"] + }, + { + type: "integer", + minimum: 0 + } + ] + }, + { + type: "object", + properties: { + SwitchCase: { + type: "integer", + minimum: 0, + default: 0 + }, + VariableDeclarator: { + oneOf: [ + ELEMENT_LIST_SCHEMA, + { + type: "object", + properties: { + var: ELEMENT_LIST_SCHEMA, + let: ELEMENT_LIST_SCHEMA, + const: ELEMENT_LIST_SCHEMA + }, + additionalProperties: false + } + ] + }, + outerIIFEBody: { + oneOf: [ + { + type: "integer", + minimum: 0 + }, + { + enum: ["off"] + } + ] + }, + MemberExpression: { + oneOf: [ + { + type: "integer", + minimum: 0 + }, + { + enum: ["off"] + } + ] + }, + FunctionDeclaration: { + type: "object", + properties: { + parameters: ELEMENT_LIST_SCHEMA, + body: { + type: "integer", + minimum: 0 + } + }, + additionalProperties: false + }, + FunctionExpression: { + type: "object", + properties: { + parameters: ELEMENT_LIST_SCHEMA, + body: { + type: "integer", + minimum: 0 + } + }, + additionalProperties: false + }, + CallExpression: { + type: "object", + properties: { + arguments: ELEMENT_LIST_SCHEMA + }, + additionalProperties: false + }, + ArrayExpression: ELEMENT_LIST_SCHEMA, + ObjectExpression: ELEMENT_LIST_SCHEMA, + ImportDeclaration: ELEMENT_LIST_SCHEMA, + flatTernaryExpressions: { + type: "boolean", + default: false + }, + offsetTernaryExpressions: { + type: "boolean", + default: false + }, + ignoredNodes: { + type: "array", + items: { + type: "string", + not: { + pattern: ":exit$" + } + } + }, + ignoreComments: { + type: "boolean", + default: false + } + }, + additionalProperties: false + } + ], + messages: { + wrongIndentation: "Expected indentation of {{expected}} but found {{actual}}." + } + }, + + create(context) { + const DEFAULT_VARIABLE_INDENT = 1; + const DEFAULT_PARAMETER_INDENT = 1; + const DEFAULT_FUNCTION_BODY_INDENT = 1; + + let indentType = "space"; + let indentSize = 4; + const options = { + SwitchCase: 0, + VariableDeclarator: { + var: DEFAULT_VARIABLE_INDENT, + let: DEFAULT_VARIABLE_INDENT, + const: DEFAULT_VARIABLE_INDENT + }, + outerIIFEBody: 1, + FunctionDeclaration: { + parameters: DEFAULT_PARAMETER_INDENT, + body: DEFAULT_FUNCTION_BODY_INDENT + }, + FunctionExpression: { + parameters: DEFAULT_PARAMETER_INDENT, + body: DEFAULT_FUNCTION_BODY_INDENT + }, + CallExpression: { + arguments: DEFAULT_PARAMETER_INDENT + }, + MemberExpression: 1, + ArrayExpression: 1, + ObjectExpression: 1, + ImportDeclaration: 1, + flatTernaryExpressions: false, + ignoredNodes: [], + ignoreComments: false + }; + + if (context.options.length) { + if (context.options[0] === "tab") { + indentSize = 1; + indentType = "tab"; + } else { + indentSize = context.options[0]; + indentType = "space"; + } + + if (context.options[1]) { + Object.assign(options, context.options[1]); + + if (typeof options.VariableDeclarator === "number" || options.VariableDeclarator === "first") { + options.VariableDeclarator = { + var: options.VariableDeclarator, + let: options.VariableDeclarator, + const: options.VariableDeclarator + }; + } + } + } + + const sourceCode = context.getSourceCode(); + const tokenInfo = new TokenInfo(sourceCode); + const offsets = new OffsetStorage(tokenInfo, indentSize, indentType === "space" ? " " : "\t"); + const parameterParens = new WeakSet(); + + /** + * Creates an error message for a line, given the expected/actual indentation. + * @param {int} expectedAmount The expected amount of indentation characters for this line + * @param {int} actualSpaces The actual number of indentation spaces that were found on this line + * @param {int} actualTabs The actual number of indentation tabs that were found on this line + * @returns {string} An error message for this line + */ + function createErrorMessageData(expectedAmount, actualSpaces, actualTabs) { + const expectedStatement = `${expectedAmount} ${indentType}${expectedAmount === 1 ? "" : "s"}`; // e.g. "2 tabs" + const foundSpacesWord = `space${actualSpaces === 1 ? "" : "s"}`; // e.g. "space" + const foundTabsWord = `tab${actualTabs === 1 ? "" : "s"}`; // e.g. "tabs" + let foundStatement; + + if (actualSpaces > 0) { + + /* + * Abbreviate the message if the expected indentation is also spaces. + * e.g. 'Expected 4 spaces but found 2' rather than 'Expected 4 spaces but found 2 spaces' + */ + foundStatement = indentType === "space" ? actualSpaces : `${actualSpaces} ${foundSpacesWord}`; + } else if (actualTabs > 0) { + foundStatement = indentType === "tab" ? actualTabs : `${actualTabs} ${foundTabsWord}`; + } else { + foundStatement = "0"; + } + return { + expected: expectedStatement, + actual: foundStatement + }; + } + + /** + * Reports a given indent violation + * @param {Token} token Token violating the indent rule + * @param {string} neededIndent Expected indentation string + * @returns {void} + */ + function report(token, neededIndent) { + const actualIndent = Array.from(tokenInfo.getTokenIndent(token)); + const numSpaces = actualIndent.filter(char => char === " ").length; + const numTabs = actualIndent.filter(char => char === "\t").length; + + context.report({ + node: token, + messageId: "wrongIndentation", + data: createErrorMessageData(neededIndent.length, numSpaces, numTabs), + loc: { + start: { line: token.loc.start.line, column: 0 }, + end: { line: token.loc.start.line, column: token.loc.start.column } + }, + fix(fixer) { + const range = [token.range[0] - token.loc.start.column, token.range[0]]; + const newText = neededIndent; + + return fixer.replaceTextRange(range, newText); + } + }); + } + + /** + * Checks if a token's indentation is correct + * @param {Token} token Token to examine + * @param {string} desiredIndent Desired indentation of the string + * @returns {boolean} `true` if the token's indentation is correct + */ + function validateTokenIndent(token, desiredIndent) { + const indentation = tokenInfo.getTokenIndent(token); + + return indentation === desiredIndent || + + // To avoid conflicts with no-mixed-spaces-and-tabs, don't report mixed spaces and tabs. + indentation.includes(" ") && indentation.includes("\t"); + } + + /** + * Check to see if the node is a file level IIFE + * @param {ASTNode} node The function node to check. + * @returns {boolean} True if the node is the outer IIFE + */ + function isOuterIIFE(node) { + + /* + * Verify that the node is an IIFE + */ + if (!node.parent || node.parent.type !== "CallExpression" || node.parent.callee !== node) { + return false; + } + + /* + * Navigate legal ancestors to determine whether this IIFE is outer. + * A "legal ancestor" is an expression or statement that causes the function to get executed immediately. + * For example, `!(function(){})()` is an outer IIFE even though it is preceded by a ! operator. + */ + let statement = node.parent && node.parent.parent; + + while ( + statement.type === "UnaryExpression" && ["!", "~", "+", "-"].indexOf(statement.operator) > -1 || + statement.type === "AssignmentExpression" || + statement.type === "LogicalExpression" || + statement.type === "SequenceExpression" || + statement.type === "VariableDeclarator" + ) { + statement = statement.parent; + } + + return (statement.type === "ExpressionStatement" || statement.type === "VariableDeclaration") && statement.parent.type === "Program"; + } + + /** + * Counts the number of linebreaks that follow the last non-whitespace character in a string + * @param {string} string The string to check + * @returns {number} The number of JavaScript linebreaks that follow the last non-whitespace character, + * or the total number of linebreaks if the string is all whitespace. + */ + function countTrailingLinebreaks(string) { + const trailingWhitespace = string.match(/\s*$/u)[0]; + const linebreakMatches = trailingWhitespace.match(astUtils.createGlobalLinebreakMatcher()); + + return linebreakMatches === null ? 0 : linebreakMatches.length; + } + + /** + * Check indentation for lists of elements (arrays, objects, function params) + * @param {ASTNode[]} elements List of elements that should be offset + * @param {Token} startToken The start token of the list that element should be aligned against, e.g. '[' + * @param {Token} endToken The end token of the list, e.g. ']' + * @param {number|string} offset The amount that the elements should be offset + * @returns {void} + */ + function addElementListIndent(elements, startToken, endToken, offset) { + + /** + * Gets the first token of a given element, including surrounding parentheses. + * @param {ASTNode} element A node in the `elements` list + * @returns {Token} The first token of this element + */ + function getFirstToken(element) { + let token = sourceCode.getTokenBefore(element); + + while (astUtils.isOpeningParenToken(token) && token !== startToken) { + token = sourceCode.getTokenBefore(token); + } + return sourceCode.getTokenAfter(token); + } + + // Run through all the tokens in the list, and offset them by one indent level (mainly for comments, other things will end up overridden) + offsets.setDesiredOffsets( + [startToken.range[1], endToken.range[0]], + startToken, + typeof offset === "number" ? offset : 1 + ); + offsets.setDesiredOffset(endToken, startToken, 0); + + // If the preference is "first" but there is no first element (e.g. sparse arrays w/ empty first slot), fall back to 1 level. + if (offset === "first" && elements.length && !elements[0]) { + return; + } + elements.forEach((element, index) => { + if (!element) { + + // Skip holes in arrays + return; + } + if (offset === "off") { + + // Ignore the first token of every element if the "off" option is used + offsets.ignoreToken(getFirstToken(element)); + } + + // Offset the following elements correctly relative to the first element + if (index === 0) { + return; + } + if (offset === "first" && tokenInfo.isFirstTokenOfLine(getFirstToken(element))) { + offsets.matchOffsetOf(getFirstToken(elements[0]), getFirstToken(element)); + } else { + const previousElement = elements[index - 1]; + const firstTokenOfPreviousElement = previousElement && getFirstToken(previousElement); + const previousElementLastToken = previousElement && sourceCode.getLastToken(previousElement); + + if ( + previousElement && + previousElementLastToken.loc.end.line - countTrailingLinebreaks(previousElementLastToken.value) > startToken.loc.end.line + ) { + offsets.setDesiredOffsets( + [previousElement.range[1], element.range[1]], + firstTokenOfPreviousElement, + 0 + ); + } + } + }); + } + + /** + * Check and decide whether to check for indentation for blockless nodes + * Scenarios are for or while statements without braces around them + * @param {ASTNode} node node to examine + * @returns {void} + */ + function addBlocklessNodeIndent(node) { + if (node.type !== "BlockStatement") { + const lastParentToken = sourceCode.getTokenBefore(node, astUtils.isNotOpeningParenToken); + + let firstBodyToken = sourceCode.getFirstToken(node); + let lastBodyToken = sourceCode.getLastToken(node); + + while ( + astUtils.isOpeningParenToken(sourceCode.getTokenBefore(firstBodyToken)) && + astUtils.isClosingParenToken(sourceCode.getTokenAfter(lastBodyToken)) + ) { + firstBodyToken = sourceCode.getTokenBefore(firstBodyToken); + lastBodyToken = sourceCode.getTokenAfter(lastBodyToken); + } + + offsets.setDesiredOffsets([firstBodyToken.range[0], lastBodyToken.range[1]], lastParentToken, 1); + + /* + * For blockless nodes with semicolon-first style, don't indent the semicolon. + * e.g. + * if (foo) bar() + * ; [1, 2, 3].map(foo) + */ + const lastToken = sourceCode.getLastToken(node); + + if (node.type !== "EmptyStatement" && astUtils.isSemicolonToken(lastToken)) { + offsets.setDesiredOffset(lastToken, lastParentToken, 0); + } + } + } + + /** + * Checks the indentation for nodes that are like function calls (`CallExpression` and `NewExpression`) + * @param {ASTNode} node A CallExpression or NewExpression node + * @returns {void} + */ + function addFunctionCallIndent(node) { + let openingParen; + + if (node.arguments.length) { + openingParen = sourceCode.getFirstTokenBetween(node.callee, node.arguments[0], astUtils.isOpeningParenToken); + } else { + openingParen = sourceCode.getLastToken(node, 1); + } + const closingParen = sourceCode.getLastToken(node); + + parameterParens.add(openingParen); + parameterParens.add(closingParen); + + const offsetAfterToken = node.callee.type === "TaggedTemplateExpression" ? sourceCode.getFirstToken(node.callee.quasi) : openingParen; + const offsetToken = sourceCode.getTokenBefore(offsetAfterToken); + + offsets.setDesiredOffset(openingParen, offsetToken, 0); + + addElementListIndent(node.arguments, openingParen, closingParen, options.CallExpression.arguments); + } + + /** + * Checks the indentation of parenthesized values, given a list of tokens in a program + * @param {Token[]} tokens A list of tokens + * @returns {void} + */ + function addParensIndent(tokens) { + const parenStack = []; + const parenPairs = []; + + tokens.forEach(nextToken => { + + // Accumulate a list of parenthesis pairs + if (astUtils.isOpeningParenToken(nextToken)) { + parenStack.push(nextToken); + } else if (astUtils.isClosingParenToken(nextToken)) { + parenPairs.unshift({ left: parenStack.pop(), right: nextToken }); + } + }); + + parenPairs.forEach(pair => { + const leftParen = pair.left; + const rightParen = pair.right; + + // We only want to handle parens around expressions, so exclude parentheses that are in function parameters and function call arguments. + if (!parameterParens.has(leftParen) && !parameterParens.has(rightParen)) { + const parenthesizedTokens = new Set(sourceCode.getTokensBetween(leftParen, rightParen)); + + parenthesizedTokens.forEach(token => { + if (!parenthesizedTokens.has(offsets.getFirstDependency(token))) { + offsets.setDesiredOffset(token, leftParen, 1); + } + }); + } + + offsets.setDesiredOffset(rightParen, leftParen, 0); + }); + } + + /** + * Ignore all tokens within an unknown node whose offset do not depend + * on another token's offset within the unknown node + * @param {ASTNode} node Unknown Node + * @returns {void} + */ + function ignoreNode(node) { + const unknownNodeTokens = new Set(sourceCode.getTokens(node, { includeComments: true })); + + unknownNodeTokens.forEach(token => { + if (!unknownNodeTokens.has(offsets.getFirstDependency(token))) { + const firstTokenOfLine = tokenInfo.getFirstTokenOfLine(token); + + if (token === firstTokenOfLine) { + offsets.ignoreToken(token); + } else { + offsets.setDesiredOffset(token, firstTokenOfLine, 0); + } + } + }); + } + + /** + * Check whether the given token is on the first line of a statement. + * @param {Token} token The token to check. + * @param {ASTNode} leafNode The expression node that the token belongs directly. + * @returns {boolean} `true` if the token is on the first line of a statement. + */ + function isOnFirstLineOfStatement(token, leafNode) { + let node = leafNode; + + while (node.parent && !node.parent.type.endsWith("Statement") && !node.parent.type.endsWith("Declaration")) { + node = node.parent; + } + node = node.parent; + + return !node || node.loc.start.line === token.loc.start.line; + } + + /** + * Check whether there are any blank (whitespace-only) lines between + * two tokens on separate lines. + * @param {Token} firstToken The first token. + * @param {Token} secondToken The second token. + * @returns {boolean} `true` if the tokens are on separate lines and + * there exists a blank line between them, `false` otherwise. + */ + function hasBlankLinesBetween(firstToken, secondToken) { + const firstTokenLine = firstToken.loc.end.line; + const secondTokenLine = secondToken.loc.start.line; + + if (firstTokenLine === secondTokenLine || firstTokenLine === secondTokenLine - 1) { + return false; + } + + for (let line = firstTokenLine + 1; line < secondTokenLine; ++line) { + if (!tokenInfo.firstTokensByLineNumber.has(line)) { + return true; + } + } + + return false; + } + + const ignoredNodeFirstTokens = new Set(); + + const baseOffsetListeners = { + "ArrayExpression, ArrayPattern"(node) { + const openingBracket = sourceCode.getFirstToken(node); + const closingBracket = sourceCode.getTokenAfter(lodash.findLast(node.elements) || openingBracket, astUtils.isClosingBracketToken); + + addElementListIndent(node.elements, openingBracket, closingBracket, options.ArrayExpression); + }, + + "ObjectExpression, ObjectPattern"(node) { + const openingCurly = sourceCode.getFirstToken(node); + const closingCurly = sourceCode.getTokenAfter( + node.properties.length ? node.properties[node.properties.length - 1] : openingCurly, + astUtils.isClosingBraceToken + ); + + addElementListIndent(node.properties, openingCurly, closingCurly, options.ObjectExpression); + }, + + ArrowFunctionExpression(node) { + const firstToken = sourceCode.getFirstToken(node); + + if (astUtils.isOpeningParenToken(firstToken)) { + const openingParen = firstToken; + const closingParen = sourceCode.getTokenBefore(node.body, astUtils.isClosingParenToken); + + parameterParens.add(openingParen); + parameterParens.add(closingParen); + addElementListIndent(node.params, openingParen, closingParen, options.FunctionExpression.parameters); + } + addBlocklessNodeIndent(node.body); + }, + + AssignmentExpression(node) { + const operator = sourceCode.getFirstTokenBetween(node.left, node.right, token => token.value === node.operator); + + offsets.setDesiredOffsets([operator.range[0], node.range[1]], sourceCode.getLastToken(node.left), 1); + offsets.ignoreToken(operator); + offsets.ignoreToken(sourceCode.getTokenAfter(operator)); + }, + + "BinaryExpression, LogicalExpression"(node) { + const operator = sourceCode.getFirstTokenBetween(node.left, node.right, token => token.value === node.operator); + + /* + * For backwards compatibility, don't check BinaryExpression indents, e.g. + * var foo = bar && + * baz; + */ + + const tokenAfterOperator = sourceCode.getTokenAfter(operator); + + offsets.ignoreToken(operator); + offsets.ignoreToken(tokenAfterOperator); + offsets.setDesiredOffset(tokenAfterOperator, operator, 0); + }, + + "BlockStatement, ClassBody"(node) { + let blockIndentLevel; + + if (node.parent && isOuterIIFE(node.parent)) { + blockIndentLevel = options.outerIIFEBody; + } else if (node.parent && (node.parent.type === "FunctionExpression" || node.parent.type === "ArrowFunctionExpression")) { + blockIndentLevel = options.FunctionExpression.body; + } else if (node.parent && node.parent.type === "FunctionDeclaration") { + blockIndentLevel = options.FunctionDeclaration.body; + } else { + blockIndentLevel = 1; + } + + /* + * For blocks that aren't lone statements, ensure that the opening curly brace + * is aligned with the parent. + */ + if (!astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type)) { + offsets.setDesiredOffset(sourceCode.getFirstToken(node), sourceCode.getFirstToken(node.parent), 0); + } + + addElementListIndent(node.body, sourceCode.getFirstToken(node), sourceCode.getLastToken(node), blockIndentLevel); + }, + + CallExpression: addFunctionCallIndent, + + "ClassDeclaration[superClass], ClassExpression[superClass]"(node) { + const classToken = sourceCode.getFirstToken(node); + const extendsToken = sourceCode.getTokenBefore(node.superClass, astUtils.isNotOpeningParenToken); + + offsets.setDesiredOffsets([extendsToken.range[0], node.body.range[0]], classToken, 1); + }, + + ConditionalExpression(node) { + const firstToken = sourceCode.getFirstToken(node); + + // `flatTernaryExpressions` option is for the following style: + // var a = + // foo > 0 ? bar : + // foo < 0 ? baz : + // /*else*/ qiz ; + if (!options.flatTernaryExpressions || + !astUtils.isTokenOnSameLine(node.test, node.consequent) || + isOnFirstLineOfStatement(firstToken, node) + ) { + const questionMarkToken = sourceCode.getFirstTokenBetween(node.test, node.consequent, token => token.type === "Punctuator" && token.value === "?"); + const colonToken = sourceCode.getFirstTokenBetween(node.consequent, node.alternate, token => token.type === "Punctuator" && token.value === ":"); + + const firstConsequentToken = sourceCode.getTokenAfter(questionMarkToken); + const lastConsequentToken = sourceCode.getTokenBefore(colonToken); + const firstAlternateToken = sourceCode.getTokenAfter(colonToken); + + offsets.setDesiredOffset(questionMarkToken, firstToken, 1); + offsets.setDesiredOffset(colonToken, firstToken, 1); + + offsets.setDesiredOffset(firstConsequentToken, firstToken, + options.offsetTernaryExpressions ? 2 : 1); + + /* + * The alternate and the consequent should usually have the same indentation. + * If they share part of a line, align the alternate against the first token of the consequent. + * This allows the alternate to be indented correctly in cases like this: + * foo ? ( + * bar + * ) : ( // this '(' is aligned with the '(' above, so it's considered to be aligned with `foo` + * baz // as a result, `baz` is offset by 1 rather than 2 + * ) + */ + if (lastConsequentToken.loc.end.line === firstAlternateToken.loc.start.line) { + offsets.setDesiredOffset(firstAlternateToken, firstConsequentToken, 0); + } else { + + /** + * If the alternate and consequent do not share part of a line, offset the alternate from the first + * token of the conditional expression. For example: + * foo ? bar + * : baz + * + * If `baz` were aligned with `bar` rather than being offset by 1 from `foo`, `baz` would end up + * having no expected indentation. + */ + offsets.setDesiredOffset(firstAlternateToken, firstToken, + firstAlternateToken.type === "Punctuator" && + options.offsetTernaryExpressions ? 2 : 1); + } + } + }, + + "DoWhileStatement, WhileStatement, ForInStatement, ForOfStatement": node => addBlocklessNodeIndent(node.body), + + ExportNamedDeclaration(node) { + if (node.declaration === null) { + const closingCurly = sourceCode.getLastToken(node, astUtils.isClosingBraceToken); + + // Indent the specifiers in `export {foo, bar, baz}` + addElementListIndent(node.specifiers, sourceCode.getFirstToken(node, { skip: 1 }), closingCurly, 1); + + if (node.source) { + + // Indent everything after and including the `from` token in `export {foo, bar, baz} from 'qux'` + offsets.setDesiredOffsets([closingCurly.range[1], node.range[1]], sourceCode.getFirstToken(node), 1); + } + } + }, + + ForStatement(node) { + const forOpeningParen = sourceCode.getFirstToken(node, 1); + + if (node.init) { + offsets.setDesiredOffsets(node.init.range, forOpeningParen, 1); + } + if (node.test) { + offsets.setDesiredOffsets(node.test.range, forOpeningParen, 1); + } + if (node.update) { + offsets.setDesiredOffsets(node.update.range, forOpeningParen, 1); + } + addBlocklessNodeIndent(node.body); + }, + + "FunctionDeclaration, FunctionExpression"(node) { + const closingParen = sourceCode.getTokenBefore(node.body); + const openingParen = sourceCode.getTokenBefore(node.params.length ? node.params[0] : closingParen); + + parameterParens.add(openingParen); + parameterParens.add(closingParen); + addElementListIndent(node.params, openingParen, closingParen, options[node.type].parameters); + }, + + IfStatement(node) { + addBlocklessNodeIndent(node.consequent); + if (node.alternate && node.alternate.type !== "IfStatement") { + addBlocklessNodeIndent(node.alternate); + } + }, + + ImportDeclaration(node) { + if (node.specifiers.some(specifier => specifier.type === "ImportSpecifier")) { + const openingCurly = sourceCode.getFirstToken(node, astUtils.isOpeningBraceToken); + const closingCurly = sourceCode.getLastToken(node, astUtils.isClosingBraceToken); + + addElementListIndent(node.specifiers.filter(specifier => specifier.type === "ImportSpecifier"), openingCurly, closingCurly, options.ImportDeclaration); + } + + const fromToken = sourceCode.getLastToken(node, token => token.type === "Identifier" && token.value === "from"); + const sourceToken = sourceCode.getLastToken(node, token => token.type === "String"); + const semiToken = sourceCode.getLastToken(node, token => token.type === "Punctuator" && token.value === ";"); + + if (fromToken) { + const end = semiToken && semiToken.range[1] === sourceToken.range[1] ? node.range[1] : sourceToken.range[1]; + + offsets.setDesiredOffsets([fromToken.range[0], end], sourceCode.getFirstToken(node), 1); + } + }, + + ImportExpression(node) { + const openingParen = sourceCode.getFirstToken(node, 1); + const closingParen = sourceCode.getLastToken(node); + + parameterParens.add(openingParen); + parameterParens.add(closingParen); + offsets.setDesiredOffset(openingParen, sourceCode.getTokenBefore(openingParen), 0); + + addElementListIndent([node.source], openingParen, closingParen, options.CallExpression.arguments); + }, + + "MemberExpression, JSXMemberExpression, MetaProperty"(node) { + const object = node.type === "MetaProperty" ? node.meta : node.object; + const firstNonObjectToken = sourceCode.getFirstTokenBetween(object, node.property, astUtils.isNotClosingParenToken); + const secondNonObjectToken = sourceCode.getTokenAfter(firstNonObjectToken); + + const objectParenCount = sourceCode.getTokensBetween(object, node.property, { filter: astUtils.isClosingParenToken }).length; + const firstObjectToken = objectParenCount + ? sourceCode.getTokenBefore(object, { skip: objectParenCount - 1 }) + : sourceCode.getFirstToken(object); + const lastObjectToken = sourceCode.getTokenBefore(firstNonObjectToken); + const firstPropertyToken = node.computed ? firstNonObjectToken : secondNonObjectToken; + + if (node.computed) { + + // For computed MemberExpressions, match the closing bracket with the opening bracket. + offsets.setDesiredOffset(sourceCode.getLastToken(node), firstNonObjectToken, 0); + offsets.setDesiredOffsets(node.property.range, firstNonObjectToken, 1); + } + + /* + * If the object ends on the same line that the property starts, match against the last token + * of the object, to ensure that the MemberExpression is not indented. + * + * Otherwise, match against the first token of the object, e.g. + * foo + * .bar + * .baz // <-- offset by 1 from `foo` + */ + const offsetBase = lastObjectToken.loc.end.line === firstPropertyToken.loc.start.line + ? lastObjectToken + : firstObjectToken; + + if (typeof options.MemberExpression === "number") { + + // Match the dot (for non-computed properties) or the opening bracket (for computed properties) against the object. + offsets.setDesiredOffset(firstNonObjectToken, offsetBase, options.MemberExpression); + + /* + * For computed MemberExpressions, match the first token of the property against the opening bracket. + * Otherwise, match the first token of the property against the object. + */ + offsets.setDesiredOffset(secondNonObjectToken, node.computed ? firstNonObjectToken : offsetBase, options.MemberExpression); + } else { + + // If the MemberExpression option is off, ignore the dot and the first token of the property. + offsets.ignoreToken(firstNonObjectToken); + offsets.ignoreToken(secondNonObjectToken); + + // To ignore the property indentation, ensure that the property tokens depend on the ignored tokens. + offsets.setDesiredOffset(firstNonObjectToken, offsetBase, 0); + offsets.setDesiredOffset(secondNonObjectToken, firstNonObjectToken, 0); + } + }, + + NewExpression(node) { + + // Only indent the arguments if the NewExpression has parens (e.g. `new Foo(bar)` or `new Foo()`, but not `new Foo` + if (node.arguments.length > 0 || + astUtils.isClosingParenToken(sourceCode.getLastToken(node)) && + astUtils.isOpeningParenToken(sourceCode.getLastToken(node, 1))) { + addFunctionCallIndent(node); + } + }, + + Property(node) { + if (!node.shorthand && !node.method && node.kind === "init") { + const colon = sourceCode.getFirstTokenBetween(node.key, node.value, astUtils.isColonToken); + + offsets.ignoreToken(sourceCode.getTokenAfter(colon)); + } + }, + + SwitchStatement(node) { + const openingCurly = sourceCode.getTokenAfter(node.discriminant, astUtils.isOpeningBraceToken); + const closingCurly = sourceCode.getLastToken(node); + + offsets.setDesiredOffsets([openingCurly.range[1], closingCurly.range[0]], openingCurly, options.SwitchCase); + + if (node.cases.length) { + sourceCode.getTokensBetween( + node.cases[node.cases.length - 1], + closingCurly, + { includeComments: true, filter: astUtils.isCommentToken } + ).forEach(token => offsets.ignoreToken(token)); + } + }, + + SwitchCase(node) { + if (!(node.consequent.length === 1 && node.consequent[0].type === "BlockStatement")) { + const caseKeyword = sourceCode.getFirstToken(node); + const tokenAfterCurrentCase = sourceCode.getTokenAfter(node); + + offsets.setDesiredOffsets([caseKeyword.range[1], tokenAfterCurrentCase.range[0]], caseKeyword, 1); + } + }, + + TemplateLiteral(node) { + node.expressions.forEach((expression, index) => { + const previousQuasi = node.quasis[index]; + const nextQuasi = node.quasis[index + 1]; + const tokenToAlignFrom = previousQuasi.loc.start.line === previousQuasi.loc.end.line + ? sourceCode.getFirstToken(previousQuasi) + : null; + + offsets.setDesiredOffsets([previousQuasi.range[1], nextQuasi.range[0]], tokenToAlignFrom, 1); + offsets.setDesiredOffset(sourceCode.getFirstToken(nextQuasi), tokenToAlignFrom, 0); + }); + }, + + VariableDeclaration(node) { + let variableIndent = Object.prototype.hasOwnProperty.call(options.VariableDeclarator, node.kind) + ? options.VariableDeclarator[node.kind] + : DEFAULT_VARIABLE_INDENT; + + const firstToken = sourceCode.getFirstToken(node), + lastToken = sourceCode.getLastToken(node); + + if (options.VariableDeclarator[node.kind] === "first") { + if (node.declarations.length > 1) { + addElementListIndent( + node.declarations, + firstToken, + lastToken, + "first" + ); + return; + } + + variableIndent = DEFAULT_VARIABLE_INDENT; + } + + if (node.declarations[node.declarations.length - 1].loc.start.line > node.loc.start.line) { + + /* + * VariableDeclarator indentation is a bit different from other forms of indentation, in that the + * indentation of an opening bracket sometimes won't match that of a closing bracket. For example, + * the following indentations are correct: + * + * var foo = { + * ok: true + * }; + * + * var foo = { + * ok: true, + * }, + * bar = 1; + * + * Account for when exiting the AST (after indentations have already been set for the nodes in + * the declaration) by manually increasing the indentation level of the tokens in this declarator + * on the same line as the start of the declaration, provided that there are declarators that + * follow this one. + */ + offsets.setDesiredOffsets(node.range, firstToken, variableIndent, true); + } else { + offsets.setDesiredOffsets(node.range, firstToken, variableIndent); + } + + if (astUtils.isSemicolonToken(lastToken)) { + offsets.ignoreToken(lastToken); + } + }, + + VariableDeclarator(node) { + if (node.init) { + const equalOperator = sourceCode.getTokenBefore(node.init, astUtils.isNotOpeningParenToken); + const tokenAfterOperator = sourceCode.getTokenAfter(equalOperator); + + offsets.ignoreToken(equalOperator); + offsets.ignoreToken(tokenAfterOperator); + offsets.setDesiredOffsets([tokenAfterOperator.range[0], node.range[1]], equalOperator, 1); + offsets.setDesiredOffset(equalOperator, sourceCode.getLastToken(node.id), 0); + } + }, + + "JSXAttribute[value]"(node) { + const equalsToken = sourceCode.getFirstTokenBetween(node.name, node.value, token => token.type === "Punctuator" && token.value === "="); + + offsets.setDesiredOffsets([equalsToken.range[0], node.value.range[1]], sourceCode.getFirstToken(node.name), 1); + }, + + JSXElement(node) { + if (node.closingElement) { + addElementListIndent(node.children, sourceCode.getFirstToken(node.openingElement), sourceCode.getFirstToken(node.closingElement), 1); + } + }, + + JSXOpeningElement(node) { + const firstToken = sourceCode.getFirstToken(node); + let closingToken; + + if (node.selfClosing) { + closingToken = sourceCode.getLastToken(node, { skip: 1 }); + offsets.setDesiredOffset(sourceCode.getLastToken(node), closingToken, 0); + } else { + closingToken = sourceCode.getLastToken(node); + } + offsets.setDesiredOffsets(node.name.range, sourceCode.getFirstToken(node)); + addElementListIndent(node.attributes, firstToken, closingToken, 1); + }, + + JSXClosingElement(node) { + const firstToken = sourceCode.getFirstToken(node); + + offsets.setDesiredOffsets(node.name.range, firstToken, 1); + }, + + JSXFragment(node) { + const firstOpeningToken = sourceCode.getFirstToken(node.openingFragment); + const firstClosingToken = sourceCode.getFirstToken(node.closingFragment); + + addElementListIndent(node.children, firstOpeningToken, firstClosingToken, 1); + }, + + JSXOpeningFragment(node) { + const firstToken = sourceCode.getFirstToken(node); + const closingToken = sourceCode.getLastToken(node); + + offsets.setDesiredOffsets(node.range, firstToken, 1); + offsets.matchOffsetOf(firstToken, closingToken); + }, + + JSXClosingFragment(node) { + const firstToken = sourceCode.getFirstToken(node); + const slashToken = sourceCode.getLastToken(node, { skip: 1 }); + const closingToken = sourceCode.getLastToken(node); + const tokenToMatch = astUtils.isTokenOnSameLine(slashToken, closingToken) ? slashToken : closingToken; + + offsets.setDesiredOffsets(node.range, firstToken, 1); + offsets.matchOffsetOf(firstToken, tokenToMatch); + }, + + JSXExpressionContainer(node) { + const openingCurly = sourceCode.getFirstToken(node); + const closingCurly = sourceCode.getLastToken(node); + + offsets.setDesiredOffsets( + [openingCurly.range[1], closingCurly.range[0]], + openingCurly, + 1 + ); + }, + + JSXSpreadAttribute(node) { + const openingCurly = sourceCode.getFirstToken(node); + const closingCurly = sourceCode.getLastToken(node); + + offsets.setDesiredOffsets( + [openingCurly.range[1], closingCurly.range[0]], + openingCurly, + 1 + ); + }, + + "*"(node) { + const firstToken = sourceCode.getFirstToken(node); + + // Ensure that the children of every node are indented at least as much as the first token. + if (firstToken && !ignoredNodeFirstTokens.has(firstToken)) { + offsets.setDesiredOffsets(node.range, firstToken, 0); + } + } + }; + + const listenerCallQueue = []; + + /* + * To ignore the indentation of a node: + * 1. Don't call the node's listener when entering it (if it has a listener) + * 2. Don't set any offsets against the first token of the node. + * 3. Call `ignoreNode` on the node sometime after exiting it and before validating offsets. + */ + const offsetListeners = lodash.mapValues( + baseOffsetListeners, + + /* + * Offset listener calls are deferred until traversal is finished, and are called as + * part of the final `Program:exit` listener. This is necessary because a node might + * be matched by multiple selectors. + * + * Example: Suppose there is an offset listener for `Identifier`, and the user has + * specified in configuration that `MemberExpression > Identifier` should be ignored. + * Due to selector specificity rules, the `Identifier` listener will get called first. However, + * if a given Identifier node is supposed to be ignored, then the `Identifier` offset listener + * should not have been called at all. Without doing extra selector matching, we don't know + * whether the Identifier matches the `MemberExpression > Identifier` selector until the + * `MemberExpression > Identifier` listener is called. + * + * To avoid this, the `Identifier` listener isn't called until traversal finishes and all + * ignored nodes are known. + */ + listener => + node => + listenerCallQueue.push({ listener, node }) + ); + + // For each ignored node selector, set up a listener to collect it into the `ignoredNodes` set. + const ignoredNodes = new Set(); + + /** + * Ignores a node + * @param {ASTNode} node The node to ignore + * @returns {void} + */ + function addToIgnoredNodes(node) { + ignoredNodes.add(node); + ignoredNodeFirstTokens.add(sourceCode.getFirstToken(node)); + } + + const ignoredNodeListeners = options.ignoredNodes.reduce( + (listeners, ignoredSelector) => Object.assign(listeners, { [ignoredSelector]: addToIgnoredNodes }), + {} + ); + + /* + * Join the listeners, and add a listener to verify that all tokens actually have the correct indentation + * at the end. + * + * Using Object.assign will cause some offset listeners to be overwritten if the same selector also appears + * in `ignoredNodeListeners`. This isn't a problem because all of the matching nodes will be ignored, + * so those listeners wouldn't be called anyway. + */ + return Object.assign( + offsetListeners, + ignoredNodeListeners, + { + "*:exit"(node) { + + // If a node's type is nonstandard, we can't tell how its children should be offset, so ignore it. + if (!KNOWN_NODES.has(node.type)) { + addToIgnoredNodes(node); + } + }, + "Program:exit"() { + + // If ignoreComments option is enabled, ignore all comment tokens. + if (options.ignoreComments) { + sourceCode.getAllComments() + .forEach(comment => offsets.ignoreToken(comment)); + } + + // Invoke the queued offset listeners for the nodes that aren't ignored. + listenerCallQueue + .filter(nodeInfo => !ignoredNodes.has(nodeInfo.node)) + .forEach(nodeInfo => nodeInfo.listener(nodeInfo.node)); + + // Update the offsets for ignored nodes to prevent their child tokens from being reported. + ignoredNodes.forEach(ignoreNode); + + addParensIndent(sourceCode.ast.tokens); + + /* + * Create a Map from (tokenOrComment) => (precedingToken). + * This is necessary because sourceCode.getTokenBefore does not handle a comment as an argument correctly. + */ + const precedingTokens = sourceCode.ast.comments.reduce((commentMap, comment) => { + const tokenOrCommentBefore = sourceCode.getTokenBefore(comment, { includeComments: true }); + + return commentMap.set(comment, commentMap.has(tokenOrCommentBefore) ? commentMap.get(tokenOrCommentBefore) : tokenOrCommentBefore); + }, new WeakMap()); + + sourceCode.lines.forEach((line, lineIndex) => { + const lineNumber = lineIndex + 1; + + if (!tokenInfo.firstTokensByLineNumber.has(lineNumber)) { + + // Don't check indentation on blank lines + return; + } + + const firstTokenOfLine = tokenInfo.firstTokensByLineNumber.get(lineNumber); + + if (firstTokenOfLine.loc.start.line !== lineNumber) { + + // Don't check the indentation of multi-line tokens (e.g. template literals or block comments) twice. + return; + } + + if (astUtils.isCommentToken(firstTokenOfLine)) { + const tokenBefore = precedingTokens.get(firstTokenOfLine); + const tokenAfter = tokenBefore ? sourceCode.getTokenAfter(tokenBefore) : sourceCode.ast.tokens[0]; + const mayAlignWithBefore = tokenBefore && !hasBlankLinesBetween(tokenBefore, firstTokenOfLine); + const mayAlignWithAfter = tokenAfter && !hasBlankLinesBetween(firstTokenOfLine, tokenAfter); + + /* + * If a comment precedes a line that begins with a semicolon token, align to that token, i.e. + * + * let foo + * // comment + * ;(async () => {})() + */ + if (tokenAfter && astUtils.isSemicolonToken(tokenAfter) && !astUtils.isTokenOnSameLine(firstTokenOfLine, tokenAfter)) { + offsets.setDesiredOffset(firstTokenOfLine, tokenAfter, 0); + } + + // If a comment matches the expected indentation of the token immediately before or after, don't report it. + if ( + mayAlignWithBefore && validateTokenIndent(firstTokenOfLine, offsets.getDesiredIndent(tokenBefore)) || + mayAlignWithAfter && validateTokenIndent(firstTokenOfLine, offsets.getDesiredIndent(tokenAfter)) + ) { + return; + } + } + + // If the token matches the expected indentation, don't report it. + if (validateTokenIndent(firstTokenOfLine, offsets.getDesiredIndent(firstTokenOfLine))) { + return; + } + + // Otherwise, report the token/comment. + report(firstTokenOfLine, offsets.getDesiredIndent(firstTokenOfLine)); + }); + } + } + ); + } +}; diff --git a/eslint/lib/rules/index.js b/eslint/lib/rules/index.js new file mode 100644 index 0000000..7f563eb --- /dev/null +++ b/eslint/lib/rules/index.js @@ -0,0 +1,293 @@ +/** + * @fileoverview Collects the built-in rules into a map structure so that they can be imported all at once and without + * using the file-system directly. + * @author Peter (Somogyvari) Metz + */ + +"use strict"; + +/* eslint sort-keys: ["error", "asc"] */ + +const { LazyLoadingRuleMap } = require("./utils/lazy-loading-rule-map"); + +/** @type {Map} */ +module.exports = new LazyLoadingRuleMap(Object.entries({ + "accessor-pairs": () => require("./accessor-pairs"), + "array-bracket-newline": () => require("./array-bracket-newline"), + "array-bracket-spacing": () => require("./array-bracket-spacing"), + "array-callback-return": () => require("./array-callback-return"), + "array-element-newline": () => require("./array-element-newline"), + "arrow-body-style": () => require("./arrow-body-style"), + "arrow-parens": () => require("./arrow-parens"), + "arrow-spacing": () => require("./arrow-spacing"), + "block-scoped-var": () => require("./block-scoped-var"), + "block-spacing": () => require("./block-spacing"), + "brace-style": () => require("./brace-style"), + "callback-return": () => require("./callback-return"), + camelcase: () => require("./camelcase"), + "capitalized-comments": () => require("./capitalized-comments"), + "class-methods-use-this": () => require("./class-methods-use-this"), + "comma-dangle": () => require("./comma-dangle"), + "comma-spacing": () => require("./comma-spacing"), + "comma-style": () => require("./comma-style"), + complexity: () => require("./complexity"), + "computed-property-spacing": () => require("./computed-property-spacing"), + "consistent-return": () => require("./consistent-return"), + "consistent-this": () => require("./consistent-this"), + "constructor-super": () => require("./constructor-super"), + curly: () => require("./curly"), + "default-case": () => require("./default-case"), + "default-case-last": () => require("./default-case-last"), + "default-param-last": () => require("./default-param-last"), + "dot-location": () => require("./dot-location"), + "dot-notation": () => require("./dot-notation"), + "eol-last": () => require("./eol-last"), + eqeqeq: () => require("./eqeqeq"), + "for-direction": () => require("./for-direction"), + "func-call-spacing": () => require("./func-call-spacing"), + "func-name-matching": () => require("./func-name-matching"), + "func-names": () => require("./func-names"), + "func-style": () => require("./func-style"), + "function-call-argument-newline": () => require("./function-call-argument-newline"), + "function-paren-newline": () => require("./function-paren-newline"), + "generator-star-spacing": () => require("./generator-star-spacing"), + "getter-return": () => require("./getter-return"), + "global-require": () => require("./global-require"), + "grouped-accessor-pairs": () => require("./grouped-accessor-pairs"), + "guard-for-in": () => require("./guard-for-in"), + "handle-callback-err": () => require("./handle-callback-err"), + "id-blacklist": () => require("./id-blacklist"), + "id-length": () => require("./id-length"), + "id-match": () => require("./id-match"), + "implicit-arrow-linebreak": () => require("./implicit-arrow-linebreak"), + indent: () => require("./indent"), + "indent-legacy": () => require("./indent-legacy"), + "init-declarations": () => require("./init-declarations"), + "jsx-quotes": () => require("./jsx-quotes"), + "key-spacing": () => require("./key-spacing"), + "keyword-spacing": () => require("./keyword-spacing"), + "line-comment-position": () => require("./line-comment-position"), + "linebreak-style": () => require("./linebreak-style"), + "lines-around-comment": () => require("./lines-around-comment"), + "lines-around-directive": () => require("./lines-around-directive"), + "lines-between-class-members": () => require("./lines-between-class-members"), + "max-classes-per-file": () => require("./max-classes-per-file"), + "max-depth": () => require("./max-depth"), + "max-len": () => require("./max-len"), + "max-lines": () => require("./max-lines"), + "max-lines-per-function": () => require("./max-lines-per-function"), + "max-nested-callbacks": () => require("./max-nested-callbacks"), + "max-params": () => require("./max-params"), + "max-statements": () => require("./max-statements"), + "max-statements-per-line": () => require("./max-statements-per-line"), + "multiline-comment-style": () => require("./multiline-comment-style"), + "multiline-ternary": () => require("./multiline-ternary"), + "new-cap": () => require("./new-cap"), + "new-parens": () => require("./new-parens"), + "newline-after-var": () => require("./newline-after-var"), + "newline-before-return": () => require("./newline-before-return"), + "newline-per-chained-call": () => require("./newline-per-chained-call"), + "no-alert": () => require("./no-alert"), + "no-array-constructor": () => require("./no-array-constructor"), + "no-async-promise-executor": () => require("./no-async-promise-executor"), + "no-await-in-loop": () => require("./no-await-in-loop"), + "no-bitwise": () => require("./no-bitwise"), + "no-buffer-constructor": () => require("./no-buffer-constructor"), + "no-caller": () => require("./no-caller"), + "no-case-declarations": () => require("./no-case-declarations"), + "no-catch-shadow": () => require("./no-catch-shadow"), + "no-class-assign": () => require("./no-class-assign"), + "no-compare-neg-zero": () => require("./no-compare-neg-zero"), + "no-cond-assign": () => require("./no-cond-assign"), + "no-confusing-arrow": () => require("./no-confusing-arrow"), + "no-console": () => require("./no-console"), + "no-const-assign": () => require("./no-const-assign"), + "no-constant-condition": () => require("./no-constant-condition"), + "no-constructor-return": () => require("./no-constructor-return"), + "no-continue": () => require("./no-continue"), + "no-control-regex": () => require("./no-control-regex"), + "no-debugger": () => require("./no-debugger"), + "no-delete-var": () => require("./no-delete-var"), + "no-div-regex": () => require("./no-div-regex"), + "no-dupe-args": () => require("./no-dupe-args"), + "no-dupe-class-members": () => require("./no-dupe-class-members"), + "no-dupe-else-if": () => require("./no-dupe-else-if"), + "no-dupe-keys": () => require("./no-dupe-keys"), + "no-duplicate-case": () => require("./no-duplicate-case"), + "no-duplicate-imports": () => require("./no-duplicate-imports"), + "no-else-return": () => require("./no-else-return"), + "no-empty": () => require("./no-empty"), + "no-empty-character-class": () => require("./no-empty-character-class"), + "no-empty-function": () => require("./no-empty-function"), + "no-empty-pattern": () => require("./no-empty-pattern"), + "no-eq-null": () => require("./no-eq-null"), + "no-eval": () => require("./no-eval"), + "no-ex-assign": () => require("./no-ex-assign"), + "no-extend-native": () => require("./no-extend-native"), + "no-extra-bind": () => require("./no-extra-bind"), + "no-extra-boolean-cast": () => require("./no-extra-boolean-cast"), + "no-extra-label": () => require("./no-extra-label"), + "no-extra-parens": () => require("./no-extra-parens"), + "no-extra-semi": () => require("./no-extra-semi"), + "no-fallthrough": () => require("./no-fallthrough"), + "no-floating-decimal": () => require("./no-floating-decimal"), + "no-func-assign": () => require("./no-func-assign"), + "no-global-assign": () => require("./no-global-assign"), + "no-implicit-coercion": () => require("./no-implicit-coercion"), + "no-implicit-globals": () => require("./no-implicit-globals"), + "no-implied-eval": () => require("./no-implied-eval"), + "no-import-assign": () => require("./no-import-assign"), + "no-inline-comments": () => require("./no-inline-comments"), + "no-inner-declarations": () => require("./no-inner-declarations"), + "no-invalid-regexp": () => require("./no-invalid-regexp"), + "no-invalid-this": () => require("./no-invalid-this"), + "no-irregular-whitespace": () => require("./no-irregular-whitespace"), + "no-iterator": () => require("./no-iterator"), + "no-label-var": () => require("./no-label-var"), + "no-labels": () => require("./no-labels"), + "no-lone-blocks": () => require("./no-lone-blocks"), + "no-lonely-if": () => require("./no-lonely-if"), + "no-loop-func": () => require("./no-loop-func"), + "no-magic-numbers": () => require("./no-magic-numbers"), + "no-misleading-character-class": () => require("./no-misleading-character-class"), + "no-mixed-operators": () => require("./no-mixed-operators"), + "no-mixed-requires": () => require("./no-mixed-requires"), + "no-mixed-spaces-and-tabs": () => require("./no-mixed-spaces-and-tabs"), + "no-multi-assign": () => require("./no-multi-assign"), + "no-multi-spaces": () => require("./no-multi-spaces"), + "no-multi-str": () => require("./no-multi-str"), + "no-multiple-empty-lines": () => require("./no-multiple-empty-lines"), + "no-native-reassign": () => require("./no-native-reassign"), + "no-negated-condition": () => require("./no-negated-condition"), + "no-negated-in-lhs": () => require("./no-negated-in-lhs"), + "no-nested-ternary": () => require("./no-nested-ternary"), + "no-new": () => require("./no-new"), + "no-new-func": () => require("./no-new-func"), + "no-new-object": () => require("./no-new-object"), + "no-new-require": () => require("./no-new-require"), + "no-new-symbol": () => require("./no-new-symbol"), + "no-new-wrappers": () => require("./no-new-wrappers"), + "no-obj-calls": () => require("./no-obj-calls"), + "no-octal": () => require("./no-octal"), + "no-octal-escape": () => require("./no-octal-escape"), + "no-param-reassign": () => require("./no-param-reassign"), + "no-path-concat": () => require("./no-path-concat"), + "no-plusplus": () => require("./no-plusplus"), + "no-process-env": () => require("./no-process-env"), + "no-process-exit": () => require("./no-process-exit"), + "no-proto": () => require("./no-proto"), + "no-prototype-builtins": () => require("./no-prototype-builtins"), + "no-redeclare": () => require("./no-redeclare"), + "no-regex-spaces": () => require("./no-regex-spaces"), + "no-restricted-exports": () => require("./no-restricted-exports"), + "no-restricted-globals": () => require("./no-restricted-globals"), + "no-restricted-imports": () => require("./no-restricted-imports"), + "no-restricted-modules": () => require("./no-restricted-modules"), + "no-restricted-properties": () => require("./no-restricted-properties"), + "no-restricted-syntax": () => require("./no-restricted-syntax"), + "no-return-assign": () => require("./no-return-assign"), + "no-return-await": () => require("./no-return-await"), + "no-script-url": () => require("./no-script-url"), + "no-self-assign": () => require("./no-self-assign"), + "no-self-compare": () => require("./no-self-compare"), + "no-sequences": () => require("./no-sequences"), + "no-setter-return": () => require("./no-setter-return"), + "no-shadow": () => require("./no-shadow"), + "no-shadow-restricted-names": () => require("./no-shadow-restricted-names"), + "no-spaced-func": () => require("./no-spaced-func"), + "no-sparse-arrays": () => require("./no-sparse-arrays"), + "no-sync": () => require("./no-sync"), + "no-tabs": () => require("./no-tabs"), + "no-template-curly-in-string": () => require("./no-template-curly-in-string"), + "no-ternary": () => require("./no-ternary"), + "no-this-before-super": () => require("./no-this-before-super"), + "no-throw-literal": () => require("./no-throw-literal"), + "no-trailing-spaces": () => require("./no-trailing-spaces"), + "no-undef": () => require("./no-undef"), + "no-undef-init": () => require("./no-undef-init"), + "no-undefined": () => require("./no-undefined"), + "no-underscore-dangle": () => require("./no-underscore-dangle"), + "no-unexpected-multiline": () => require("./no-unexpected-multiline"), + "no-unmodified-loop-condition": () => require("./no-unmodified-loop-condition"), + "no-unneeded-ternary": () => require("./no-unneeded-ternary"), + "no-unreachable": () => require("./no-unreachable"), + "no-unsafe-finally": () => require("./no-unsafe-finally"), + "no-unsafe-negation": () => require("./no-unsafe-negation"), + "no-unused-expressions": () => require("./no-unused-expressions"), + "no-unused-labels": () => require("./no-unused-labels"), + "no-unused-vars": () => require("./no-unused-vars"), + "no-use-before-define": () => require("./no-use-before-define"), + "no-useless-backreference": () => require("./no-useless-backreference"), + "no-useless-call": () => require("./no-useless-call"), + "no-useless-catch": () => require("./no-useless-catch"), + "no-useless-computed-key": () => require("./no-useless-computed-key"), + "no-useless-concat": () => require("./no-useless-concat"), + "no-useless-constructor": () => require("./no-useless-constructor"), + "no-useless-escape": () => require("./no-useless-escape"), + "no-useless-rename": () => require("./no-useless-rename"), + "no-useless-return": () => require("./no-useless-return"), + "no-var": () => require("./no-var"), + "no-void": () => require("./no-void"), + "no-warning-comments": () => require("./no-warning-comments"), + "no-whitespace-before-property": () => require("./no-whitespace-before-property"), + "no-with": () => require("./no-with"), + "nonblock-statement-body-position": () => require("./nonblock-statement-body-position"), + "object-curly-newline": () => require("./object-curly-newline"), + "object-curly-spacing": () => require("./object-curly-spacing"), + "object-property-newline": () => require("./object-property-newline"), + "object-shorthand": () => require("./object-shorthand"), + "one-var": () => require("./one-var"), + "one-var-declaration-per-line": () => require("./one-var-declaration-per-line"), + "operator-assignment": () => require("./operator-assignment"), + "operator-linebreak": () => require("./operator-linebreak"), + "padded-blocks": () => require("./padded-blocks"), + "padding-line-between-statements": () => require("./padding-line-between-statements"), + "prefer-arrow-callback": () => require("./prefer-arrow-callback"), + "prefer-const": () => require("./prefer-const"), + "prefer-destructuring": () => require("./prefer-destructuring"), + "prefer-exponentiation-operator": () => require("./prefer-exponentiation-operator"), + "prefer-named-capture-group": () => require("./prefer-named-capture-group"), + "prefer-numeric-literals": () => require("./prefer-numeric-literals"), + "prefer-object-spread": () => require("./prefer-object-spread"), + "prefer-promise-reject-errors": () => require("./prefer-promise-reject-errors"), + "prefer-reflect": () => require("./prefer-reflect"), + "prefer-regex-literals": () => require("./prefer-regex-literals"), + "prefer-rest-params": () => require("./prefer-rest-params"), + "prefer-spread": () => require("./prefer-spread"), + "prefer-template": () => require("./prefer-template"), + "quote-props": () => require("./quote-props"), + quotes: () => require("./quotes"), + radix: () => require("./radix"), + "require-atomic-updates": () => require("./require-atomic-updates"), + "require-await": () => require("./require-await"), + "require-jsdoc": () => require("./require-jsdoc"), + "require-unicode-regexp": () => require("./require-unicode-regexp"), + "require-yield": () => require("./require-yield"), + "rest-spread-spacing": () => require("./rest-spread-spacing"), + semi: () => require("./semi"), + "semi-spacing": () => require("./semi-spacing"), + "semi-style": () => require("./semi-style"), + "sort-imports": () => require("./sort-imports"), + "sort-keys": () => require("./sort-keys"), + "sort-vars": () => require("./sort-vars"), + "space-before-blocks": () => require("./space-before-blocks"), + "space-before-function-paren": () => require("./space-before-function-paren"), + "space-in-parens": () => require("./space-in-parens"), + "space-infix-ops": () => require("./space-infix-ops"), + "space-unary-ops": () => require("./space-unary-ops"), + "spaced-comment": () => require("./spaced-comment"), + strict: () => require("./strict"), + "switch-colon-spacing": () => require("./switch-colon-spacing"), + "symbol-description": () => require("./symbol-description"), + "template-curly-spacing": () => require("./template-curly-spacing"), + "template-tag-spacing": () => require("./template-tag-spacing"), + "unicode-bom": () => require("./unicode-bom"), + "use-isnan": () => require("./use-isnan"), + "valid-jsdoc": () => require("./valid-jsdoc"), + "valid-typeof": () => require("./valid-typeof"), + "vars-on-top": () => require("./vars-on-top"), + "wrap-iife": () => require("./wrap-iife"), + "wrap-regex": () => require("./wrap-regex"), + "yield-star-spacing": () => require("./yield-star-spacing"), + yoda: () => require("./yoda") +})); diff --git a/eslint/lib/rules/init-declarations.js b/eslint/lib/rules/init-declarations.js new file mode 100644 index 0000000..6cfdf92 --- /dev/null +++ b/eslint/lib/rules/init-declarations.js @@ -0,0 +1,139 @@ +/** + * @fileoverview A rule to control the style of variable initializations. + * @author Colin Ihrig + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Checks whether or not a given node is a for loop. + * @param {ASTNode} block A node to check. + * @returns {boolean} `true` when the node is a for loop. + */ +function isForLoop(block) { + return block.type === "ForInStatement" || + block.type === "ForOfStatement" || + block.type === "ForStatement"; +} + +/** + * Checks whether or not a given declarator node has its initializer. + * @param {ASTNode} node A declarator node to check. + * @returns {boolean} `true` when the node has its initializer. + */ +function isInitialized(node) { + const declaration = node.parent; + const block = declaration.parent; + + if (isForLoop(block)) { + if (block.type === "ForStatement") { + return block.init === declaration; + } + return block.left === declaration; + } + return Boolean(node.init); +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "require or disallow initialization in variable declarations", + category: "Variables", + recommended: false, + url: "https://eslint.org/docs/rules/init-declarations" + }, + + schema: { + anyOf: [ + { + type: "array", + items: [ + { + enum: ["always"] + } + ], + minItems: 0, + maxItems: 1 + }, + { + type: "array", + items: [ + { + enum: ["never"] + }, + { + type: "object", + properties: { + ignoreForLoopInit: { + type: "boolean" + } + }, + additionalProperties: false + } + ], + minItems: 0, + maxItems: 2 + } + ] + }, + messages: { + initialized: "Variable '{{idName}}' should be initialized on declaration.", + notInitialized: "Variable '{{idName}}' should not be initialized on declaration." + } + }, + + create(context) { + + const MODE_ALWAYS = "always", + MODE_NEVER = "never"; + + const mode = context.options[0] || MODE_ALWAYS; + const params = context.options[1] || {}; + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + "VariableDeclaration:exit"(node) { + + const kind = node.kind, + declarations = node.declarations; + + for (let i = 0; i < declarations.length; ++i) { + const declaration = declarations[i], + id = declaration.id, + initialized = isInitialized(declaration), + isIgnoredForLoop = params.ignoreForLoopInit && isForLoop(node.parent); + let messageId = ""; + + if (mode === MODE_ALWAYS && !initialized) { + messageId = "initialized"; + } else if (mode === MODE_NEVER && kind !== "const" && initialized && !isIgnoredForLoop) { + messageId = "notInitialized"; + } + + if (id.type === "Identifier" && messageId) { + context.report({ + node: declaration, + messageId, + data: { + idName: id.name + } + }); + } + } + } + }; + } +}; diff --git a/eslint/lib/rules/jsx-quotes.js b/eslint/lib/rules/jsx-quotes.js new file mode 100644 index 0000000..3b282df --- /dev/null +++ b/eslint/lib/rules/jsx-quotes.js @@ -0,0 +1,95 @@ +/** + * @fileoverview A rule to ensure consistent quotes used in jsx syntax. + * @author Mathias Schreck + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Constants +//------------------------------------------------------------------------------ + +const QUOTE_SETTINGS = { + "prefer-double": { + quote: "\"", + description: "singlequote", + convert(str) { + return str.replace(/'/gu, "\""); + } + }, + "prefer-single": { + quote: "'", + description: "doublequote", + convert(str) { + return str.replace(/"/gu, "'"); + } + } +}; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "enforce the consistent use of either double or single quotes in JSX attributes", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/jsx-quotes" + }, + + fixable: "whitespace", + + schema: [ + { + enum: ["prefer-single", "prefer-double"] + } + ], + messages: { + unexpected: "Unexpected usage of {{description}}." + } + }, + + create(context) { + const quoteOption = context.options[0] || "prefer-double", + setting = QUOTE_SETTINGS[quoteOption]; + + /** + * Checks if the given string literal node uses the expected quotes + * @param {ASTNode} node A string literal node. + * @returns {boolean} Whether or not the string literal used the expected quotes. + * @public + */ + function usesExpectedQuotes(node) { + return node.value.indexOf(setting.quote) !== -1 || astUtils.isSurroundedBy(node.raw, setting.quote); + } + + return { + JSXAttribute(node) { + const attributeValue = node.value; + + if (attributeValue && astUtils.isStringLiteral(attributeValue) && !usesExpectedQuotes(attributeValue)) { + context.report({ + node: attributeValue, + messageId: "unexpected", + data: { + description: setting.description + }, + fix(fixer) { + return fixer.replaceText(attributeValue, setting.convert(attributeValue.raw)); + } + }); + } + } + }; + } +}; diff --git a/eslint/lib/rules/key-spacing.js b/eslint/lib/rules/key-spacing.js new file mode 100644 index 0000000..c405043 --- /dev/null +++ b/eslint/lib/rules/key-spacing.js @@ -0,0 +1,670 @@ +/** + * @fileoverview Rule to specify spacing of object literal keys and values + * @author Brandon Mills + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Checks whether a string contains a line terminator as defined in + * http://www.ecma-international.org/ecma-262/5.1/#sec-7.3 + * @param {string} str String to test. + * @returns {boolean} True if str contains a line terminator. + */ +function containsLineTerminator(str) { + return astUtils.LINEBREAK_MATCHER.test(str); +} + +/** + * Gets the last element of an array. + * @param {Array} arr An array. + * @returns {any} Last element of arr. + */ +function last(arr) { + return arr[arr.length - 1]; +} + +/** + * Checks whether a node is contained on a single line. + * @param {ASTNode} node AST Node being evaluated. + * @returns {boolean} True if the node is a single line. + */ +function isSingleLine(node) { + return (node.loc.end.line === node.loc.start.line); +} + +/** + * Checks whether the properties on a single line. + * @param {ASTNode[]} properties List of Property AST nodes. + * @returns {boolean} True if all properies is on a single line. + */ +function isSingleLineProperties(properties) { + const [firstProp] = properties, + lastProp = last(properties); + + return firstProp.loc.start.line === lastProp.loc.end.line; +} + +/** + * Initializes a single option property from the configuration with defaults for undefined values + * @param {Object} toOptions Object to be initialized + * @param {Object} fromOptions Object to be initialized from + * @returns {Object} The object with correctly initialized options and values + */ +function initOptionProperty(toOptions, fromOptions) { + toOptions.mode = fromOptions.mode || "strict"; + + // Set value of beforeColon + if (typeof fromOptions.beforeColon !== "undefined") { + toOptions.beforeColon = +fromOptions.beforeColon; + } else { + toOptions.beforeColon = 0; + } + + // Set value of afterColon + if (typeof fromOptions.afterColon !== "undefined") { + toOptions.afterColon = +fromOptions.afterColon; + } else { + toOptions.afterColon = 1; + } + + // Set align if exists + if (typeof fromOptions.align !== "undefined") { + if (typeof fromOptions.align === "object") { + toOptions.align = fromOptions.align; + } else { // "string" + toOptions.align = { + on: fromOptions.align, + mode: toOptions.mode, + beforeColon: toOptions.beforeColon, + afterColon: toOptions.afterColon + }; + } + } + + return toOptions; +} + +/** + * Initializes all the option values (singleLine, multiLine and align) from the configuration with defaults for undefined values + * @param {Object} toOptions Object to be initialized + * @param {Object} fromOptions Object to be initialized from + * @returns {Object} The object with correctly initialized options and values + */ +function initOptions(toOptions, fromOptions) { + if (typeof fromOptions.align === "object") { + + // Initialize the alignment configuration + toOptions.align = initOptionProperty({}, fromOptions.align); + toOptions.align.on = fromOptions.align.on || "colon"; + toOptions.align.mode = fromOptions.align.mode || "strict"; + + toOptions.multiLine = initOptionProperty({}, (fromOptions.multiLine || fromOptions)); + toOptions.singleLine = initOptionProperty({}, (fromOptions.singleLine || fromOptions)); + + } else { // string or undefined + toOptions.multiLine = initOptionProperty({}, (fromOptions.multiLine || fromOptions)); + toOptions.singleLine = initOptionProperty({}, (fromOptions.singleLine || fromOptions)); + + // If alignment options are defined in multiLine, pull them out into the general align configuration + if (toOptions.multiLine.align) { + toOptions.align = { + on: toOptions.multiLine.align.on, + mode: toOptions.multiLine.align.mode || toOptions.multiLine.mode, + beforeColon: toOptions.multiLine.align.beforeColon, + afterColon: toOptions.multiLine.align.afterColon + }; + } + } + + return toOptions; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "enforce consistent spacing between keys and values in object literal properties", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/key-spacing" + }, + + fixable: "whitespace", + + schema: [{ + anyOf: [ + { + type: "object", + properties: { + align: { + anyOf: [ + { + enum: ["colon", "value"] + }, + { + type: "object", + properties: { + mode: { + enum: ["strict", "minimum"] + }, + on: { + enum: ["colon", "value"] + }, + beforeColon: { + type: "boolean" + }, + afterColon: { + type: "boolean" + } + }, + additionalProperties: false + } + ] + }, + mode: { + enum: ["strict", "minimum"] + }, + beforeColon: { + type: "boolean" + }, + afterColon: { + type: "boolean" + } + }, + additionalProperties: false + }, + { + type: "object", + properties: { + singleLine: { + type: "object", + properties: { + mode: { + enum: ["strict", "minimum"] + }, + beforeColon: { + type: "boolean" + }, + afterColon: { + type: "boolean" + } + }, + additionalProperties: false + }, + multiLine: { + type: "object", + properties: { + align: { + anyOf: [ + { + enum: ["colon", "value"] + }, + { + type: "object", + properties: { + mode: { + enum: ["strict", "minimum"] + }, + on: { + enum: ["colon", "value"] + }, + beforeColon: { + type: "boolean" + }, + afterColon: { + type: "boolean" + } + }, + additionalProperties: false + } + ] + }, + mode: { + enum: ["strict", "minimum"] + }, + beforeColon: { + type: "boolean" + }, + afterColon: { + type: "boolean" + } + }, + additionalProperties: false + } + }, + additionalProperties: false + }, + { + type: "object", + properties: { + singleLine: { + type: "object", + properties: { + mode: { + enum: ["strict", "minimum"] + }, + beforeColon: { + type: "boolean" + }, + afterColon: { + type: "boolean" + } + }, + additionalProperties: false + }, + multiLine: { + type: "object", + properties: { + mode: { + enum: ["strict", "minimum"] + }, + beforeColon: { + type: "boolean" + }, + afterColon: { + type: "boolean" + } + }, + additionalProperties: false + }, + align: { + type: "object", + properties: { + mode: { + enum: ["strict", "minimum"] + }, + on: { + enum: ["colon", "value"] + }, + beforeColon: { + type: "boolean" + }, + afterColon: { + type: "boolean" + } + }, + additionalProperties: false + } + }, + additionalProperties: false + } + ] + }], + messages: { + extraKey: "Extra space after {{computed}}key '{{key}}'.", + extraValue: "Extra space before value for {{computed}}key '{{key}}'.", + missingKey: "Missing space after {{computed}}key '{{key}}'.", + missingValue: "Missing space before value for {{computed}}key '{{key}}'." + } + }, + + create(context) { + + /** + * OPTIONS + * "key-spacing": [2, { + * beforeColon: false, + * afterColon: true, + * align: "colon" // Optional, or "value" + * } + */ + const options = context.options[0] || {}, + ruleOptions = initOptions({}, options), + multiLineOptions = ruleOptions.multiLine, + singleLineOptions = ruleOptions.singleLine, + alignmentOptions = ruleOptions.align || null; + + const sourceCode = context.getSourceCode(); + + /** + * Checks whether a property is a member of the property group it follows. + * @param {ASTNode} lastMember The last Property known to be in the group. + * @param {ASTNode} candidate The next Property that might be in the group. + * @returns {boolean} True if the candidate property is part of the group. + */ + function continuesPropertyGroup(lastMember, candidate) { + const groupEndLine = lastMember.loc.start.line, + candidateStartLine = candidate.loc.start.line; + + if (candidateStartLine - groupEndLine <= 1) { + return true; + } + + /* + * Check that the first comment is adjacent to the end of the group, the + * last comment is adjacent to the candidate property, and that successive + * comments are adjacent to each other. + */ + const leadingComments = sourceCode.getCommentsBefore(candidate); + + if ( + leadingComments.length && + leadingComments[0].loc.start.line - groupEndLine <= 1 && + candidateStartLine - last(leadingComments).loc.end.line <= 1 + ) { + for (let i = 1; i < leadingComments.length; i++) { + if (leadingComments[i].loc.start.line - leadingComments[i - 1].loc.end.line > 1) { + return false; + } + } + return true; + } + + return false; + } + + /** + * Determines if the given property is key-value property. + * @param {ASTNode} property Property node to check. + * @returns {boolean} Whether the property is a key-value property. + */ + function isKeyValueProperty(property) { + return !( + (property.method || + property.shorthand || + property.kind !== "init" || property.type !== "Property") // Could be "ExperimentalSpreadProperty" or "SpreadElement" + ); + } + + /** + * Starting from the given a node (a property.key node here) looks forward + * until it finds the last token before a colon punctuator and returns it. + * @param {ASTNode} node The node to start looking from. + * @returns {ASTNode} The last token before a colon punctuator. + */ + function getLastTokenBeforeColon(node) { + const colonToken = sourceCode.getTokenAfter(node, astUtils.isColonToken); + + return sourceCode.getTokenBefore(colonToken); + } + + /** + * Starting from the given a node (a property.key node here) looks forward + * until it finds the colon punctuator and returns it. + * @param {ASTNode} node The node to start looking from. + * @returns {ASTNode} The colon punctuator. + */ + function getNextColon(node) { + return sourceCode.getTokenAfter(node, astUtils.isColonToken); + } + + /** + * Gets an object literal property's key as the identifier name or string value. + * @param {ASTNode} property Property node whose key to retrieve. + * @returns {string} The property's key. + */ + function getKey(property) { + const key = property.key; + + if (property.computed) { + return sourceCode.getText().slice(key.range[0], key.range[1]); + } + return astUtils.getStaticPropertyName(property); + } + + /** + * Reports an appropriately-formatted error if spacing is incorrect on one + * side of the colon. + * @param {ASTNode} property Key-value pair in an object literal. + * @param {string} side Side being verified - either "key" or "value". + * @param {string} whitespace Actual whitespace string. + * @param {int} expected Expected whitespace length. + * @param {string} mode Value of the mode as "strict" or "minimum" + * @returns {void} + */ + function report(property, side, whitespace, expected, mode) { + const diff = whitespace.length - expected, + nextColon = getNextColon(property.key), + tokenBeforeColon = sourceCode.getTokenBefore(nextColon, { includeComments: true }), + tokenAfterColon = sourceCode.getTokenAfter(nextColon, { includeComments: true }), + isKeySide = side === "key", + locStart = isKeySide ? tokenBeforeColon.loc.start : tokenAfterColon.loc.start, + isExtra = diff > 0, + diffAbs = Math.abs(diff), + spaces = Array(diffAbs + 1).join(" "); + + if (( + diff && mode === "strict" || + diff < 0 && mode === "minimum" || + diff > 0 && !expected && mode === "minimum") && + !(expected && containsLineTerminator(whitespace)) + ) { + let fix; + + if (isExtra) { + let range; + + // Remove whitespace + if (isKeySide) { + range = [tokenBeforeColon.range[1], tokenBeforeColon.range[1] + diffAbs]; + } else { + range = [tokenAfterColon.range[0] - diffAbs, tokenAfterColon.range[0]]; + } + fix = function(fixer) { + return fixer.removeRange(range); + }; + } else { + + // Add whitespace + if (isKeySide) { + fix = function(fixer) { + return fixer.insertTextAfter(tokenBeforeColon, spaces); + }; + } else { + fix = function(fixer) { + return fixer.insertTextBefore(tokenAfterColon, spaces); + }; + } + } + + let messageId = ""; + + if (isExtra) { + messageId = side === "key" ? "extraKey" : "extraValue"; + } else { + messageId = side === "key" ? "missingKey" : "missingValue"; + } + + context.report({ + node: property[side], + loc: locStart, + messageId, + data: { + computed: property.computed ? "computed " : "", + key: getKey(property) + }, + fix + }); + } + } + + /** + * Gets the number of characters in a key, including quotes around string + * keys and braces around computed property keys. + * @param {ASTNode} property Property of on object literal. + * @returns {int} Width of the key. + */ + function getKeyWidth(property) { + const startToken = sourceCode.getFirstToken(property); + const endToken = getLastTokenBeforeColon(property.key); + + return endToken.range[1] - startToken.range[0]; + } + + /** + * Gets the whitespace around the colon in an object literal property. + * @param {ASTNode} property Property node from an object literal. + * @returns {Object} Whitespace before and after the property's colon. + */ + function getPropertyWhitespace(property) { + const whitespace = /(\s*):(\s*)/u.exec(sourceCode.getText().slice( + property.key.range[1], property.value.range[0] + )); + + if (whitespace) { + return { + beforeColon: whitespace[1], + afterColon: whitespace[2] + }; + } + return null; + } + + /** + * Creates groups of properties. + * @param {ASTNode} node ObjectExpression node being evaluated. + * @returns {Array.} Groups of property AST node lists. + */ + function createGroups(node) { + if (node.properties.length === 1) { + return [node.properties]; + } + + return node.properties.reduce((groups, property) => { + const currentGroup = last(groups), + prev = last(currentGroup); + + if (!prev || continuesPropertyGroup(prev, property)) { + currentGroup.push(property); + } else { + groups.push([property]); + } + + return groups; + }, [ + [] + ]); + } + + /** + * Verifies correct vertical alignment of a group of properties. + * @param {ASTNode[]} properties List of Property AST nodes. + * @returns {void} + */ + function verifyGroupAlignment(properties) { + const length = properties.length, + widths = properties.map(getKeyWidth), // Width of keys, including quotes + align = alignmentOptions.on; // "value" or "colon" + let targetWidth = Math.max(...widths), + beforeColon, afterColon, mode; + + if (alignmentOptions && length > 1) { // When aligning values within a group, use the alignment configuration. + beforeColon = alignmentOptions.beforeColon; + afterColon = alignmentOptions.afterColon; + mode = alignmentOptions.mode; + } else { + beforeColon = multiLineOptions.beforeColon; + afterColon = multiLineOptions.afterColon; + mode = alignmentOptions.mode; + } + + // Conditionally include one space before or after colon + targetWidth += (align === "colon" ? beforeColon : afterColon); + + for (let i = 0; i < length; i++) { + const property = properties[i]; + const whitespace = getPropertyWhitespace(property); + + if (whitespace) { // Object literal getters/setters lack a colon + const width = widths[i]; + + if (align === "value") { + report(property, "key", whitespace.beforeColon, beforeColon, mode); + report(property, "value", whitespace.afterColon, targetWidth - width, mode); + } else { // align = "colon" + report(property, "key", whitespace.beforeColon, targetWidth - width, mode); + report(property, "value", whitespace.afterColon, afterColon, mode); + } + } + } + } + + /** + * Verifies spacing of property conforms to specified options. + * @param {ASTNode} node Property node being evaluated. + * @param {Object} lineOptions Configured singleLine or multiLine options + * @returns {void} + */ + function verifySpacing(node, lineOptions) { + const actual = getPropertyWhitespace(node); + + if (actual) { // Object literal getters/setters lack colons + report(node, "key", actual.beforeColon, lineOptions.beforeColon, lineOptions.mode); + report(node, "value", actual.afterColon, lineOptions.afterColon, lineOptions.mode); + } + } + + /** + * Verifies spacing of each property in a list. + * @param {ASTNode[]} properties List of Property AST nodes. + * @param {Object} lineOptions Configured singleLine or multiLine options + * @returns {void} + */ + function verifyListSpacing(properties, lineOptions) { + const length = properties.length; + + for (let i = 0; i < length; i++) { + verifySpacing(properties[i], lineOptions); + } + } + + /** + * Verifies vertical alignment, taking into account groups of properties. + * @param {ASTNode} node ObjectExpression node being evaluated. + * @returns {void} + */ + function verifyAlignment(node) { + createGroups(node).forEach(group => { + const properties = group.filter(isKeyValueProperty); + + if (properties.length > 0 && isSingleLineProperties(properties)) { + verifyListSpacing(properties, multiLineOptions); + } else { + verifyGroupAlignment(properties); + } + }); + } + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + if (alignmentOptions) { // Verify vertical alignment + + return { + ObjectExpression(node) { + if (isSingleLine(node)) { + verifyListSpacing(node.properties.filter(isKeyValueProperty), singleLineOptions); + } else { + verifyAlignment(node); + } + } + }; + + } + + // Obey beforeColon and afterColon in each property as configured + return { + Property(node) { + verifySpacing(node, isSingleLine(node.parent) ? singleLineOptions : multiLineOptions); + } + }; + + + } +}; diff --git a/eslint/lib/rules/keyword-spacing.js b/eslint/lib/rules/keyword-spacing.js new file mode 100644 index 0000000..2b3fef3 --- /dev/null +++ b/eslint/lib/rules/keyword-spacing.js @@ -0,0 +1,566 @@ +/** + * @fileoverview Rule to enforce spacing before and after keywords. + * @author Toru Nagashima + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"), + keywords = require("./utils/keywords"); + +//------------------------------------------------------------------------------ +// Constants +//------------------------------------------------------------------------------ + +const PREV_TOKEN = /^[)\]}>]$/u; +const NEXT_TOKEN = /^(?:[([{<~!]|\+\+?|--?)$/u; +const PREV_TOKEN_M = /^[)\]}>*]$/u; +const NEXT_TOKEN_M = /^[{*]$/u; +const TEMPLATE_OPEN_PAREN = /\$\{$/u; +const TEMPLATE_CLOSE_PAREN = /^\}/u; +const CHECK_TYPE = /^(?:JSXElement|RegularExpression|String|Template)$/u; +const KEYS = keywords.concat(["as", "async", "await", "from", "get", "let", "of", "set", "yield"]); + +// check duplications. +(function() { + KEYS.sort(); + for (let i = 1; i < KEYS.length; ++i) { + if (KEYS[i] === KEYS[i - 1]) { + throw new Error(`Duplication was found in the keyword list: ${KEYS[i]}`); + } + } +}()); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Checks whether or not a given token is a "Template" token ends with "${". + * @param {Token} token A token to check. + * @returns {boolean} `true` if the token is a "Template" token ends with "${". + */ +function isOpenParenOfTemplate(token) { + return token.type === "Template" && TEMPLATE_OPEN_PAREN.test(token.value); +} + +/** + * Checks whether or not a given token is a "Template" token starts with "}". + * @param {Token} token A token to check. + * @returns {boolean} `true` if the token is a "Template" token starts with "}". + */ +function isCloseParenOfTemplate(token) { + return token.type === "Template" && TEMPLATE_CLOSE_PAREN.test(token.value); +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "enforce consistent spacing before and after keywords", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/keyword-spacing" + }, + + fixable: "whitespace", + + schema: [ + { + type: "object", + properties: { + before: { type: "boolean", default: true }, + after: { type: "boolean", default: true }, + overrides: { + type: "object", + properties: KEYS.reduce((retv, key) => { + retv[key] = { + type: "object", + properties: { + before: { type: "boolean" }, + after: { type: "boolean" } + }, + additionalProperties: false + }; + return retv; + }, {}), + additionalProperties: false + } + }, + additionalProperties: false + } + ], + messages: { + expectedBefore: "Expected space(s) before \"{{value}}\".", + expectedAfter: "Expected space(s) after \"{{value}}\".", + unexpectedBefore: "Unexpected space(s) before \"{{value}}\".", + unexpectedAfter: "Unexpected space(s) after \"{{value}}\"." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + /** + * Reports a given token if there are not space(s) before the token. + * @param {Token} token A token to report. + * @param {RegExp} pattern A pattern of the previous token to check. + * @returns {void} + */ + function expectSpaceBefore(token, pattern) { + const prevToken = sourceCode.getTokenBefore(token); + + if (prevToken && + (CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) && + !isOpenParenOfTemplate(prevToken) && + astUtils.isTokenOnSameLine(prevToken, token) && + !sourceCode.isSpaceBetweenTokens(prevToken, token) + ) { + context.report({ + loc: token.loc.start, + messageId: "expectedBefore", + data: token, + fix(fixer) { + return fixer.insertTextBefore(token, " "); + } + }); + } + } + + /** + * Reports a given token if there are space(s) before the token. + * @param {Token} token A token to report. + * @param {RegExp} pattern A pattern of the previous token to check. + * @returns {void} + */ + function unexpectSpaceBefore(token, pattern) { + const prevToken = sourceCode.getTokenBefore(token); + + if (prevToken && + (CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) && + !isOpenParenOfTemplate(prevToken) && + astUtils.isTokenOnSameLine(prevToken, token) && + sourceCode.isSpaceBetweenTokens(prevToken, token) + ) { + context.report({ + loc: token.loc.start, + messageId: "unexpectedBefore", + data: token, + fix(fixer) { + return fixer.removeRange([prevToken.range[1], token.range[0]]); + } + }); + } + } + + /** + * Reports a given token if there are not space(s) after the token. + * @param {Token} token A token to report. + * @param {RegExp} pattern A pattern of the next token to check. + * @returns {void} + */ + function expectSpaceAfter(token, pattern) { + const nextToken = sourceCode.getTokenAfter(token); + + if (nextToken && + (CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) && + !isCloseParenOfTemplate(nextToken) && + astUtils.isTokenOnSameLine(token, nextToken) && + !sourceCode.isSpaceBetweenTokens(token, nextToken) + ) { + context.report({ + loc: token.loc.start, + messageId: "expectedAfter", + data: token, + fix(fixer) { + return fixer.insertTextAfter(token, " "); + } + }); + } + } + + /** + * Reports a given token if there are space(s) after the token. + * @param {Token} token A token to report. + * @param {RegExp} pattern A pattern of the next token to check. + * @returns {void} + */ + function unexpectSpaceAfter(token, pattern) { + const nextToken = sourceCode.getTokenAfter(token); + + if (nextToken && + (CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) && + !isCloseParenOfTemplate(nextToken) && + astUtils.isTokenOnSameLine(token, nextToken) && + sourceCode.isSpaceBetweenTokens(token, nextToken) + ) { + context.report({ + loc: token.loc.start, + messageId: "unexpectedAfter", + data: token, + fix(fixer) { + return fixer.removeRange([token.range[1], nextToken.range[0]]); + } + }); + } + } + + /** + * Parses the option object and determines check methods for each keyword. + * @param {Object|undefined} options The option object to parse. + * @returns {Object} - Normalized option object. + * Keys are keywords (there are for every keyword). + * Values are instances of `{"before": function, "after": function}`. + */ + function parseOptions(options = {}) { + const before = options.before !== false; + const after = options.after !== false; + const defaultValue = { + before: before ? expectSpaceBefore : unexpectSpaceBefore, + after: after ? expectSpaceAfter : unexpectSpaceAfter + }; + const overrides = (options && options.overrides) || {}; + const retv = Object.create(null); + + for (let i = 0; i < KEYS.length; ++i) { + const key = KEYS[i]; + const override = overrides[key]; + + if (override) { + const thisBefore = ("before" in override) ? override.before : before; + const thisAfter = ("after" in override) ? override.after : after; + + retv[key] = { + before: thisBefore ? expectSpaceBefore : unexpectSpaceBefore, + after: thisAfter ? expectSpaceAfter : unexpectSpaceAfter + }; + } else { + retv[key] = defaultValue; + } + } + + return retv; + } + + const checkMethodMap = parseOptions(context.options[0]); + + /** + * Reports a given token if usage of spacing followed by the token is + * invalid. + * @param {Token} token A token to report. + * @param {RegExp} [pattern] Optional. A pattern of the previous + * token to check. + * @returns {void} + */ + function checkSpacingBefore(token, pattern) { + checkMethodMap[token.value].before(token, pattern || PREV_TOKEN); + } + + /** + * Reports a given token if usage of spacing preceded by the token is + * invalid. + * @param {Token} token A token to report. + * @param {RegExp} [pattern] Optional. A pattern of the next + * token to check. + * @returns {void} + */ + function checkSpacingAfter(token, pattern) { + checkMethodMap[token.value].after(token, pattern || NEXT_TOKEN); + } + + /** + * Reports a given token if usage of spacing around the token is invalid. + * @param {Token} token A token to report. + * @returns {void} + */ + function checkSpacingAround(token) { + checkSpacingBefore(token); + checkSpacingAfter(token); + } + + /** + * Reports the first token of a given node if the first token is a keyword + * and usage of spacing around the token is invalid. + * @param {ASTNode|null} node A node to report. + * @returns {void} + */ + function checkSpacingAroundFirstToken(node) { + const firstToken = node && sourceCode.getFirstToken(node); + + if (firstToken && firstToken.type === "Keyword") { + checkSpacingAround(firstToken); + } + } + + /** + * Reports the first token of a given node if the first token is a keyword + * and usage of spacing followed by the token is invalid. + * + * This is used for unary operators (e.g. `typeof`), `function`, and `super`. + * Other rules are handling usage of spacing preceded by those keywords. + * @param {ASTNode|null} node A node to report. + * @returns {void} + */ + function checkSpacingBeforeFirstToken(node) { + const firstToken = node && sourceCode.getFirstToken(node); + + if (firstToken && firstToken.type === "Keyword") { + checkSpacingBefore(firstToken); + } + } + + /** + * Reports the previous token of a given node if the token is a keyword and + * usage of spacing around the token is invalid. + * @param {ASTNode|null} node A node to report. + * @returns {void} + */ + function checkSpacingAroundTokenBefore(node) { + if (node) { + const token = sourceCode.getTokenBefore(node, astUtils.isKeywordToken); + + checkSpacingAround(token); + } + } + + /** + * Reports `async` or `function` keywords of a given node if usage of + * spacing around those keywords is invalid. + * @param {ASTNode} node A node to report. + * @returns {void} + */ + function checkSpacingForFunction(node) { + const firstToken = node && sourceCode.getFirstToken(node); + + if (firstToken && + ((firstToken.type === "Keyword" && firstToken.value === "function") || + firstToken.value === "async") + ) { + checkSpacingBefore(firstToken); + } + } + + /** + * Reports `class` and `extends` keywords of a given node if usage of + * spacing around those keywords is invalid. + * @param {ASTNode} node A node to report. + * @returns {void} + */ + function checkSpacingForClass(node) { + checkSpacingAroundFirstToken(node); + checkSpacingAroundTokenBefore(node.superClass); + } + + /** + * Reports `if` and `else` keywords of a given node if usage of spacing + * around those keywords is invalid. + * @param {ASTNode} node A node to report. + * @returns {void} + */ + function checkSpacingForIfStatement(node) { + checkSpacingAroundFirstToken(node); + checkSpacingAroundTokenBefore(node.alternate); + } + + /** + * Reports `try`, `catch`, and `finally` keywords of a given node if usage + * of spacing around those keywords is invalid. + * @param {ASTNode} node A node to report. + * @returns {void} + */ + function checkSpacingForTryStatement(node) { + checkSpacingAroundFirstToken(node); + checkSpacingAroundFirstToken(node.handler); + checkSpacingAroundTokenBefore(node.finalizer); + } + + /** + * Reports `do` and `while` keywords of a given node if usage of spacing + * around those keywords is invalid. + * @param {ASTNode} node A node to report. + * @returns {void} + */ + function checkSpacingForDoWhileStatement(node) { + checkSpacingAroundFirstToken(node); + checkSpacingAroundTokenBefore(node.test); + } + + /** + * Reports `for` and `in` keywords of a given node if usage of spacing + * around those keywords is invalid. + * @param {ASTNode} node A node to report. + * @returns {void} + */ + function checkSpacingForForInStatement(node) { + checkSpacingAroundFirstToken(node); + checkSpacingAroundTokenBefore(node.right); + } + + /** + * Reports `for` and `of` keywords of a given node if usage of spacing + * around those keywords is invalid. + * @param {ASTNode} node A node to report. + * @returns {void} + */ + function checkSpacingForForOfStatement(node) { + if (node.await) { + checkSpacingBefore(sourceCode.getFirstToken(node, 0)); + checkSpacingAfter(sourceCode.getFirstToken(node, 1)); + } else { + checkSpacingAroundFirstToken(node); + } + checkSpacingAround(sourceCode.getTokenBefore(node.right, astUtils.isNotOpeningParenToken)); + } + + /** + * Reports `import`, `export`, `as`, and `from` keywords of a given node if + * usage of spacing around those keywords is invalid. + * + * This rule handles the `*` token in module declarations. + * + * import*as A from "./a"; /*error Expected space(s) after "import". + * error Expected space(s) before "as". + * @param {ASTNode} node A node to report. + * @returns {void} + */ + function checkSpacingForModuleDeclaration(node) { + const firstToken = sourceCode.getFirstToken(node); + + checkSpacingBefore(firstToken, PREV_TOKEN_M); + checkSpacingAfter(firstToken, NEXT_TOKEN_M); + + if (node.type === "ExportDefaultDeclaration") { + checkSpacingAround(sourceCode.getTokenAfter(firstToken)); + } + + if (node.source) { + const fromToken = sourceCode.getTokenBefore(node.source); + + checkSpacingBefore(fromToken, PREV_TOKEN_M); + checkSpacingAfter(fromToken, NEXT_TOKEN_M); + } + } + + /** + * Reports `as` keyword of a given node if usage of spacing around this + * keyword is invalid. + * @param {ASTNode} node A node to report. + * @returns {void} + */ + function checkSpacingForImportNamespaceSpecifier(node) { + const asToken = sourceCode.getFirstToken(node, 1); + + checkSpacingBefore(asToken, PREV_TOKEN_M); + } + + /** + * Reports `static`, `get`, and `set` keywords of a given node if usage of + * spacing around those keywords is invalid. + * @param {ASTNode} node A node to report. + * @returns {void} + */ + function checkSpacingForProperty(node) { + if (node.static) { + checkSpacingAroundFirstToken(node); + } + if (node.kind === "get" || + node.kind === "set" || + ( + (node.method || node.type === "MethodDefinition") && + node.value.async + ) + ) { + const token = sourceCode.getTokenBefore( + node.key, + tok => { + switch (tok.value) { + case "get": + case "set": + case "async": + return true; + default: + return false; + } + } + ); + + if (!token) { + throw new Error("Failed to find token get, set, or async beside method name"); + } + + + checkSpacingAround(token); + } + } + + /** + * Reports `await` keyword of a given node if usage of spacing before + * this keyword is invalid. + * @param {ASTNode} node A node to report. + * @returns {void} + */ + function checkSpacingForAwaitExpression(node) { + checkSpacingBefore(sourceCode.getFirstToken(node)); + } + + return { + + // Statements + DebuggerStatement: checkSpacingAroundFirstToken, + WithStatement: checkSpacingAroundFirstToken, + + // Statements - Control flow + BreakStatement: checkSpacingAroundFirstToken, + ContinueStatement: checkSpacingAroundFirstToken, + ReturnStatement: checkSpacingAroundFirstToken, + ThrowStatement: checkSpacingAroundFirstToken, + TryStatement: checkSpacingForTryStatement, + + // Statements - Choice + IfStatement: checkSpacingForIfStatement, + SwitchStatement: checkSpacingAroundFirstToken, + SwitchCase: checkSpacingAroundFirstToken, + + // Statements - Loops + DoWhileStatement: checkSpacingForDoWhileStatement, + ForInStatement: checkSpacingForForInStatement, + ForOfStatement: checkSpacingForForOfStatement, + ForStatement: checkSpacingAroundFirstToken, + WhileStatement: checkSpacingAroundFirstToken, + + // Statements - Declarations + ClassDeclaration: checkSpacingForClass, + ExportNamedDeclaration: checkSpacingForModuleDeclaration, + ExportDefaultDeclaration: checkSpacingForModuleDeclaration, + ExportAllDeclaration: checkSpacingForModuleDeclaration, + FunctionDeclaration: checkSpacingForFunction, + ImportDeclaration: checkSpacingForModuleDeclaration, + VariableDeclaration: checkSpacingAroundFirstToken, + + // Expressions + ArrowFunctionExpression: checkSpacingForFunction, + AwaitExpression: checkSpacingForAwaitExpression, + ClassExpression: checkSpacingForClass, + FunctionExpression: checkSpacingForFunction, + NewExpression: checkSpacingBeforeFirstToken, + Super: checkSpacingBeforeFirstToken, + ThisExpression: checkSpacingBeforeFirstToken, + UnaryExpression: checkSpacingBeforeFirstToken, + YieldExpression: checkSpacingBeforeFirstToken, + + // Others + ImportNamespaceSpecifier: checkSpacingForImportNamespaceSpecifier, + MethodDefinition: checkSpacingForProperty, + Property: checkSpacingForProperty + }; + } +}; diff --git a/eslint/lib/rules/line-comment-position.js b/eslint/lib/rules/line-comment-position.js new file mode 100644 index 0000000..77ee147 --- /dev/null +++ b/eslint/lib/rules/line-comment-position.js @@ -0,0 +1,122 @@ +/** + * @fileoverview Rule to enforce the position of line comments + * @author Alberto Rodríguez + */ +"use strict"; + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "enforce position of line comments", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/line-comment-position" + }, + + schema: [ + { + oneOf: [ + { + enum: ["above", "beside"] + }, + { + type: "object", + properties: { + position: { + enum: ["above", "beside"] + }, + ignorePattern: { + type: "string" + }, + applyDefaultPatterns: { + type: "boolean" + }, + applyDefaultIgnorePatterns: { + type: "boolean" + } + }, + additionalProperties: false + } + ] + } + ], + messages: { + above: "Expected comment to be above code.", + beside: "Expected comment to be beside code." + } + }, + + create(context) { + const options = context.options[0]; + + let above, + ignorePattern, + applyDefaultIgnorePatterns = true; + + if (!options || typeof options === "string") { + above = !options || options === "above"; + + } else { + above = !options.position || options.position === "above"; + ignorePattern = options.ignorePattern; + + if (Object.prototype.hasOwnProperty.call(options, "applyDefaultIgnorePatterns")) { + applyDefaultIgnorePatterns = options.applyDefaultIgnorePatterns; + } else { + applyDefaultIgnorePatterns = options.applyDefaultPatterns !== false; + } + } + + const defaultIgnoreRegExp = astUtils.COMMENTS_IGNORE_PATTERN; + const fallThroughRegExp = /^\s*falls?\s?through/u; + const customIgnoreRegExp = new RegExp(ignorePattern, "u"); + const sourceCode = context.getSourceCode(); + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + Program() { + const comments = sourceCode.getAllComments(); + + comments.filter(token => token.type === "Line").forEach(node => { + if (applyDefaultIgnorePatterns && (defaultIgnoreRegExp.test(node.value) || fallThroughRegExp.test(node.value))) { + return; + } + + if (ignorePattern && customIgnoreRegExp.test(node.value)) { + return; + } + + const previous = sourceCode.getTokenBefore(node, { includeComments: true }); + const isOnSameLine = previous && previous.loc.end.line === node.loc.start.line; + + if (above) { + if (isOnSameLine) { + context.report({ + node, + messageId: "above" + }); + } + } else { + if (!isOnSameLine) { + context.report({ + node, + messageId: "beside" + }); + } + } + }); + } + }; + } +}; diff --git a/eslint/lib/rules/linebreak-style.js b/eslint/lib/rules/linebreak-style.js new file mode 100644 index 0000000..078eaf2 --- /dev/null +++ b/eslint/lib/rules/linebreak-style.js @@ -0,0 +1,99 @@ +/** + * @fileoverview Rule to enforce a single linebreak style. + * @author Erik Mueller + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "enforce consistent linebreak style", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/linebreak-style" + }, + + fixable: "whitespace", + + schema: [ + { + enum: ["unix", "windows"] + } + ], + messages: { + expectedLF: "Expected linebreaks to be 'LF' but found 'CRLF'.", + expectedCRLF: "Expected linebreaks to be 'CRLF' but found 'LF'." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Builds a fix function that replaces text at the specified range in the source text. + * @param {int[]} range The range to replace + * @param {string} text The text to insert. + * @returns {Function} Fixer function + * @private + */ + function createFix(range, text) { + return function(fixer) { + return fixer.replaceTextRange(range, text); + }; + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + Program: function checkForLinebreakStyle(node) { + const linebreakStyle = context.options[0] || "unix", + expectedLF = linebreakStyle === "unix", + expectedLFChars = expectedLF ? "\n" : "\r\n", + source = sourceCode.getText(), + pattern = astUtils.createGlobalLinebreakMatcher(); + let match; + + let i = 0; + + while ((match = pattern.exec(source)) !== null) { + i++; + if (match[0] === expectedLFChars) { + continue; + } + + const index = match.index; + const range = [index, index + match[0].length]; + + context.report({ + node, + loc: { + line: i, + column: sourceCode.lines[i - 1].length + }, + messageId: expectedLF ? "expectedLF" : "expectedCRLF", + fix: createFix(range, expectedLFChars) + }); + } + } + }; + } +}; diff --git a/eslint/lib/rules/lines-around-comment.js b/eslint/lib/rules/lines-around-comment.js new file mode 100644 index 0000000..5e1b83c --- /dev/null +++ b/eslint/lib/rules/lines-around-comment.js @@ -0,0 +1,404 @@ +/** + * @fileoverview Enforces empty lines around comments. + * @author Jamund Ferguson + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const lodash = require("lodash"), + astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Return an array with with any line numbers that are empty. + * @param {Array} lines An array of each line of the file. + * @returns {Array} An array of line numbers. + */ +function getEmptyLineNums(lines) { + const emptyLines = lines.map((line, i) => ({ + code: line.trim(), + num: i + 1 + })).filter(line => !line.code).map(line => line.num); + + return emptyLines; +} + +/** + * Return an array with with any line numbers that contain comments. + * @param {Array} comments An array of comment tokens. + * @returns {Array} An array of line numbers. + */ +function getCommentLineNums(comments) { + const lines = []; + + comments.forEach(token => { + const start = token.loc.start.line; + const end = token.loc.end.line; + + lines.push(start, end); + }); + return lines; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "require empty lines around comments", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/lines-around-comment" + }, + + fixable: "whitespace", + + schema: [ + { + type: "object", + properties: { + beforeBlockComment: { + type: "boolean", + default: true + }, + afterBlockComment: { + type: "boolean", + default: false + }, + beforeLineComment: { + type: "boolean", + default: false + }, + afterLineComment: { + type: "boolean", + default: false + }, + allowBlockStart: { + type: "boolean", + default: false + }, + allowBlockEnd: { + type: "boolean", + default: false + }, + allowClassStart: { + type: "boolean" + }, + allowClassEnd: { + type: "boolean" + }, + allowObjectStart: { + type: "boolean" + }, + allowObjectEnd: { + type: "boolean" + }, + allowArrayStart: { + type: "boolean" + }, + allowArrayEnd: { + type: "boolean" + }, + ignorePattern: { + type: "string" + }, + applyDefaultIgnorePatterns: { + type: "boolean" + } + }, + additionalProperties: false + } + ], + messages: { + after: "Expected line after comment.", + before: "Expected line before comment." + } + }, + + create(context) { + + const options = Object.assign({}, context.options[0]); + const ignorePattern = options.ignorePattern; + const defaultIgnoreRegExp = astUtils.COMMENTS_IGNORE_PATTERN; + const customIgnoreRegExp = new RegExp(ignorePattern, "u"); + const applyDefaultIgnorePatterns = options.applyDefaultIgnorePatterns !== false; + + options.beforeBlockComment = typeof options.beforeBlockComment !== "undefined" ? options.beforeBlockComment : true; + + const sourceCode = context.getSourceCode(); + + const lines = sourceCode.lines, + numLines = lines.length + 1, + comments = sourceCode.getAllComments(), + commentLines = getCommentLineNums(comments), + emptyLines = getEmptyLineNums(lines), + commentAndEmptyLines = commentLines.concat(emptyLines); + + /** + * Returns whether or not comments are on lines starting with or ending with code + * @param {token} token The comment token to check. + * @returns {boolean} True if the comment is not alone. + */ + function codeAroundComment(token) { + let currentToken = token; + + do { + currentToken = sourceCode.getTokenBefore(currentToken, { includeComments: true }); + } while (currentToken && astUtils.isCommentToken(currentToken)); + + if (currentToken && astUtils.isTokenOnSameLine(currentToken, token)) { + return true; + } + + currentToken = token; + do { + currentToken = sourceCode.getTokenAfter(currentToken, { includeComments: true }); + } while (currentToken && astUtils.isCommentToken(currentToken)); + + if (currentToken && astUtils.isTokenOnSameLine(token, currentToken)) { + return true; + } + + return false; + } + + /** + * Returns whether or not comments are inside a node type or not. + * @param {ASTNode} parent The Comment parent node. + * @param {string} nodeType The parent type to check against. + * @returns {boolean} True if the comment is inside nodeType. + */ + function isParentNodeType(parent, nodeType) { + return parent.type === nodeType || + (parent.body && parent.body.type === nodeType) || + (parent.consequent && parent.consequent.type === nodeType); + } + + /** + * Returns the parent node that contains the given token. + * @param {token} token The token to check. + * @returns {ASTNode} The parent node that contains the given token. + */ + function getParentNodeOfToken(token) { + return sourceCode.getNodeByRangeIndex(token.range[0]); + } + + /** + * Returns whether or not comments are at the parent start or not. + * @param {token} token The Comment token. + * @param {string} nodeType The parent type to check against. + * @returns {boolean} True if the comment is at parent start. + */ + function isCommentAtParentStart(token, nodeType) { + const parent = getParentNodeOfToken(token); + + return parent && isParentNodeType(parent, nodeType) && + token.loc.start.line - parent.loc.start.line === 1; + } + + /** + * Returns whether or not comments are at the parent end or not. + * @param {token} token The Comment token. + * @param {string} nodeType The parent type to check against. + * @returns {boolean} True if the comment is at parent end. + */ + function isCommentAtParentEnd(token, nodeType) { + const parent = getParentNodeOfToken(token); + + return parent && isParentNodeType(parent, nodeType) && + parent.loc.end.line - token.loc.end.line === 1; + } + + /** + * Returns whether or not comments are at the block start or not. + * @param {token} token The Comment token. + * @returns {boolean} True if the comment is at block start. + */ + function isCommentAtBlockStart(token) { + return isCommentAtParentStart(token, "ClassBody") || isCommentAtParentStart(token, "BlockStatement") || isCommentAtParentStart(token, "SwitchCase"); + } + + /** + * Returns whether or not comments are at the block end or not. + * @param {token} token The Comment token. + * @returns {boolean} True if the comment is at block end. + */ + function isCommentAtBlockEnd(token) { + return isCommentAtParentEnd(token, "ClassBody") || isCommentAtParentEnd(token, "BlockStatement") || isCommentAtParentEnd(token, "SwitchCase") || isCommentAtParentEnd(token, "SwitchStatement"); + } + + /** + * Returns whether or not comments are at the class start or not. + * @param {token} token The Comment token. + * @returns {boolean} True if the comment is at class start. + */ + function isCommentAtClassStart(token) { + return isCommentAtParentStart(token, "ClassBody"); + } + + /** + * Returns whether or not comments are at the class end or not. + * @param {token} token The Comment token. + * @returns {boolean} True if the comment is at class end. + */ + function isCommentAtClassEnd(token) { + return isCommentAtParentEnd(token, "ClassBody"); + } + + /** + * Returns whether or not comments are at the object start or not. + * @param {token} token The Comment token. + * @returns {boolean} True if the comment is at object start. + */ + function isCommentAtObjectStart(token) { + return isCommentAtParentStart(token, "ObjectExpression") || isCommentAtParentStart(token, "ObjectPattern"); + } + + /** + * Returns whether or not comments are at the object end or not. + * @param {token} token The Comment token. + * @returns {boolean} True if the comment is at object end. + */ + function isCommentAtObjectEnd(token) { + return isCommentAtParentEnd(token, "ObjectExpression") || isCommentAtParentEnd(token, "ObjectPattern"); + } + + /** + * Returns whether or not comments are at the array start or not. + * @param {token} token The Comment token. + * @returns {boolean} True if the comment is at array start. + */ + function isCommentAtArrayStart(token) { + return isCommentAtParentStart(token, "ArrayExpression") || isCommentAtParentStart(token, "ArrayPattern"); + } + + /** + * Returns whether or not comments are at the array end or not. + * @param {token} token The Comment token. + * @returns {boolean} True if the comment is at array end. + */ + function isCommentAtArrayEnd(token) { + return isCommentAtParentEnd(token, "ArrayExpression") || isCommentAtParentEnd(token, "ArrayPattern"); + } + + /** + * Checks if a comment token has lines around it (ignores inline comments) + * @param {token} token The Comment token. + * @param {Object} opts Options to determine the newline. + * @param {boolean} opts.after Should have a newline after this line. + * @param {boolean} opts.before Should have a newline before this line. + * @returns {void} + */ + function checkForEmptyLine(token, opts) { + if (applyDefaultIgnorePatterns && defaultIgnoreRegExp.test(token.value)) { + return; + } + + if (ignorePattern && customIgnoreRegExp.test(token.value)) { + return; + } + + let after = opts.after, + before = opts.before; + + const prevLineNum = token.loc.start.line - 1, + nextLineNum = token.loc.end.line + 1, + commentIsNotAlone = codeAroundComment(token); + + const blockStartAllowed = options.allowBlockStart && + isCommentAtBlockStart(token) && + !(options.allowClassStart === false && + isCommentAtClassStart(token)), + blockEndAllowed = options.allowBlockEnd && isCommentAtBlockEnd(token) && !(options.allowClassEnd === false && isCommentAtClassEnd(token)), + classStartAllowed = options.allowClassStart && isCommentAtClassStart(token), + classEndAllowed = options.allowClassEnd && isCommentAtClassEnd(token), + objectStartAllowed = options.allowObjectStart && isCommentAtObjectStart(token), + objectEndAllowed = options.allowObjectEnd && isCommentAtObjectEnd(token), + arrayStartAllowed = options.allowArrayStart && isCommentAtArrayStart(token), + arrayEndAllowed = options.allowArrayEnd && isCommentAtArrayEnd(token); + + const exceptionStartAllowed = blockStartAllowed || classStartAllowed || objectStartAllowed || arrayStartAllowed; + const exceptionEndAllowed = blockEndAllowed || classEndAllowed || objectEndAllowed || arrayEndAllowed; + + // ignore top of the file and bottom of the file + if (prevLineNum < 1) { + before = false; + } + if (nextLineNum >= numLines) { + after = false; + } + + // we ignore all inline comments + if (commentIsNotAlone) { + return; + } + + const previousTokenOrComment = sourceCode.getTokenBefore(token, { includeComments: true }); + const nextTokenOrComment = sourceCode.getTokenAfter(token, { includeComments: true }); + + // check for newline before + if (!exceptionStartAllowed && before && !lodash.includes(commentAndEmptyLines, prevLineNum) && + !(astUtils.isCommentToken(previousTokenOrComment) && astUtils.isTokenOnSameLine(previousTokenOrComment, token))) { + const lineStart = token.range[0] - token.loc.start.column; + const range = [lineStart, lineStart]; + + context.report({ + node: token, + messageId: "before", + fix(fixer) { + return fixer.insertTextBeforeRange(range, "\n"); + } + }); + } + + // check for newline after + if (!exceptionEndAllowed && after && !lodash.includes(commentAndEmptyLines, nextLineNum) && + !(astUtils.isCommentToken(nextTokenOrComment) && astUtils.isTokenOnSameLine(token, nextTokenOrComment))) { + context.report({ + node: token, + messageId: "after", + fix(fixer) { + return fixer.insertTextAfter(token, "\n"); + } + }); + } + + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + Program() { + comments.forEach(token => { + if (token.type === "Line") { + if (options.beforeLineComment || options.afterLineComment) { + checkForEmptyLine(token, { + after: options.afterLineComment, + before: options.beforeLineComment + }); + } + } else if (token.type === "Block") { + if (options.beforeBlockComment || options.afterBlockComment) { + checkForEmptyLine(token, { + after: options.afterBlockComment, + before: options.beforeBlockComment + }); + } + } + }); + } + }; + } +}; diff --git a/eslint/lib/rules/lines-around-directive.js b/eslint/lib/rules/lines-around-directive.js new file mode 100644 index 0000000..fb439da --- /dev/null +++ b/eslint/lib/rules/lines-around-directive.js @@ -0,0 +1,201 @@ +/** + * @fileoverview Require or disallow newlines around directives. + * @author Kai Cataldo + * @deprecated + */ + +"use strict"; + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "require or disallow newlines around directives", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/lines-around-directive" + }, + + schema: [{ + oneOf: [ + { + enum: ["always", "never"] + }, + { + type: "object", + properties: { + before: { + enum: ["always", "never"] + }, + after: { + enum: ["always", "never"] + } + }, + additionalProperties: false, + minProperties: 2 + } + ] + }], + + fixable: "whitespace", + messages: { + expected: "Expected newline {{location}} \"{{value}}\" directive.", + unexpected: "Unexpected newline {{location}} \"{{value}}\" directive." + }, + deprecated: true, + replacedBy: ["padding-line-between-statements"] + }, + + create(context) { + const sourceCode = context.getSourceCode(); + const config = context.options[0] || "always"; + const expectLineBefore = typeof config === "string" ? config : config.before; + const expectLineAfter = typeof config === "string" ? config : config.after; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Check if node is preceded by a blank newline. + * @param {ASTNode} node Node to check. + * @returns {boolean} Whether or not the passed in node is preceded by a blank newline. + */ + function hasNewlineBefore(node) { + const tokenBefore = sourceCode.getTokenBefore(node, { includeComments: true }); + const tokenLineBefore = tokenBefore ? tokenBefore.loc.end.line : 0; + + return node.loc.start.line - tokenLineBefore >= 2; + } + + /** + * Gets the last token of a node that is on the same line as the rest of the node. + * This will usually be the last token of the node, but it will be the second-to-last token if the node has a trailing + * semicolon on a different line. + * @param {ASTNode} node A directive node + * @returns {Token} The last token of the node on the line + */ + function getLastTokenOnLine(node) { + const lastToken = sourceCode.getLastToken(node); + const secondToLastToken = sourceCode.getTokenBefore(lastToken); + + return astUtils.isSemicolonToken(lastToken) && lastToken.loc.start.line > secondToLastToken.loc.end.line + ? secondToLastToken + : lastToken; + } + + /** + * Check if node is followed by a blank newline. + * @param {ASTNode} node Node to check. + * @returns {boolean} Whether or not the passed in node is followed by a blank newline. + */ + function hasNewlineAfter(node) { + const lastToken = getLastTokenOnLine(node); + const tokenAfter = sourceCode.getTokenAfter(lastToken, { includeComments: true }); + + return tokenAfter.loc.start.line - lastToken.loc.end.line >= 2; + } + + /** + * Report errors for newlines around directives. + * @param {ASTNode} node Node to check. + * @param {string} location Whether the error was found before or after the directive. + * @param {boolean} expected Whether or not a newline was expected or unexpected. + * @returns {void} + */ + function reportError(node, location, expected) { + context.report({ + node, + messageId: expected ? "expected" : "unexpected", + data: { + value: node.expression.value, + location + }, + fix(fixer) { + const lastToken = getLastTokenOnLine(node); + + if (expected) { + return location === "before" ? fixer.insertTextBefore(node, "\n") : fixer.insertTextAfter(lastToken, "\n"); + } + return fixer.removeRange(location === "before" ? [node.range[0] - 1, node.range[0]] : [lastToken.range[1], lastToken.range[1] + 1]); + } + }); + } + + /** + * Check lines around directives in node + * @param {ASTNode} node node to check + * @returns {void} + */ + function checkDirectives(node) { + const directives = astUtils.getDirectivePrologue(node); + + if (!directives.length) { + return; + } + + const firstDirective = directives[0]; + const leadingComments = sourceCode.getCommentsBefore(firstDirective); + + /* + * Only check before the first directive if it is preceded by a comment or if it is at the top of + * the file and expectLineBefore is set to "never". This is to not force a newline at the top of + * the file if there are no comments as well as for compatibility with padded-blocks. + */ + if (leadingComments.length) { + if (expectLineBefore === "always" && !hasNewlineBefore(firstDirective)) { + reportError(firstDirective, "before", true); + } + + if (expectLineBefore === "never" && hasNewlineBefore(firstDirective)) { + reportError(firstDirective, "before", false); + } + } else if ( + node.type === "Program" && + expectLineBefore === "never" && + !leadingComments.length && + hasNewlineBefore(firstDirective) + ) { + reportError(firstDirective, "before", false); + } + + const lastDirective = directives[directives.length - 1]; + const statements = node.type === "Program" ? node.body : node.body.body; + + /* + * Do not check after the last directive if the body only + * contains a directive prologue and isn't followed by a comment to ensure + * this rule behaves well with padded-blocks. + */ + if (lastDirective === statements[statements.length - 1] && !lastDirective.trailingComments) { + return; + } + + if (expectLineAfter === "always" && !hasNewlineAfter(lastDirective)) { + reportError(lastDirective, "after", true); + } + + if (expectLineAfter === "never" && hasNewlineAfter(lastDirective)) { + reportError(lastDirective, "after", false); + } + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + Program: checkDirectives, + FunctionDeclaration: checkDirectives, + FunctionExpression: checkDirectives, + ArrowFunctionExpression: checkDirectives + }; + } +}; diff --git a/eslint/lib/rules/lines-between-class-members.js b/eslint/lib/rules/lines-between-class-members.js new file mode 100644 index 0000000..9723530 --- /dev/null +++ b/eslint/lib/rules/lines-between-class-members.js @@ -0,0 +1,133 @@ +/** + * @fileoverview Rule to check empty newline between class members + * @author 薛定谔的猫 + */ +"use strict"; + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "require or disallow an empty line between class members", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/lines-between-class-members" + }, + + fixable: "whitespace", + + schema: [ + { + enum: ["always", "never"] + }, + { + type: "object", + properties: { + exceptAfterSingleLine: { + type: "boolean", + default: false + } + }, + additionalProperties: false + } + ], + messages: { + never: "Unexpected blank line between class members.", + always: "Expected blank line between class members." + } + }, + + create(context) { + + const options = []; + + options[0] = context.options[0] || "always"; + options[1] = context.options[1] || { exceptAfterSingleLine: false }; + + const sourceCode = context.getSourceCode(); + + /** + * Return the last token among the consecutive tokens that have no exceed max line difference in between, before the first token in the next member. + * @param {Token} prevLastToken The last token in the previous member node. + * @param {Token} nextFirstToken The first token in the next member node. + * @param {number} maxLine The maximum number of allowed line difference between consecutive tokens. + * @returns {Token} The last token among the consecutive tokens. + */ + function findLastConsecutiveTokenAfter(prevLastToken, nextFirstToken, maxLine) { + const after = sourceCode.getTokenAfter(prevLastToken, { includeComments: true }); + + if (after !== nextFirstToken && after.loc.start.line - prevLastToken.loc.end.line <= maxLine) { + return findLastConsecutiveTokenAfter(after, nextFirstToken, maxLine); + } + return prevLastToken; + } + + /** + * Return the first token among the consecutive tokens that have no exceed max line difference in between, after the last token in the previous member. + * @param {Token} nextFirstToken The first token in the next member node. + * @param {Token} prevLastToken The last token in the previous member node. + * @param {number} maxLine The maximum number of allowed line difference between consecutive tokens. + * @returns {Token} The first token among the consecutive tokens. + */ + function findFirstConsecutiveTokenBefore(nextFirstToken, prevLastToken, maxLine) { + const before = sourceCode.getTokenBefore(nextFirstToken, { includeComments: true }); + + if (before !== prevLastToken && nextFirstToken.loc.start.line - before.loc.end.line <= maxLine) { + return findFirstConsecutiveTokenBefore(before, prevLastToken, maxLine); + } + return nextFirstToken; + } + + /** + * Checks if there is a token or comment between two tokens. + * @param {Token} before The token before. + * @param {Token} after The token after. + * @returns {boolean} True if there is a token or comment between two tokens. + */ + function hasTokenOrCommentBetween(before, after) { + return sourceCode.getTokensBetween(before, after, { includeComments: true }).length !== 0; + } + + return { + ClassBody(node) { + const body = node.body; + + for (let i = 0; i < body.length - 1; i++) { + const curFirst = sourceCode.getFirstToken(body[i]); + const curLast = sourceCode.getLastToken(body[i]); + const nextFirst = sourceCode.getFirstToken(body[i + 1]); + const isMulti = !astUtils.isTokenOnSameLine(curFirst, curLast); + const skip = !isMulti && options[1].exceptAfterSingleLine; + const beforePadding = findLastConsecutiveTokenAfter(curLast, nextFirst, 1); + const afterPadding = findFirstConsecutiveTokenBefore(nextFirst, curLast, 1); + const isPadded = afterPadding.loc.start.line - beforePadding.loc.end.line > 1; + const hasTokenInPadding = hasTokenOrCommentBetween(beforePadding, afterPadding); + const curLineLastToken = findLastConsecutiveTokenAfter(curLast, nextFirst, 0); + + if ((options[0] === "always" && !skip && !isPadded) || + (options[0] === "never" && isPadded)) { + context.report({ + node: body[i + 1], + messageId: isPadded ? "never" : "always", + fix(fixer) { + if (hasTokenInPadding) { + return null; + } + return isPadded + ? fixer.replaceTextRange([beforePadding.range[1], afterPadding.range[0]], "\n") + : fixer.insertTextAfter(curLineLastToken, "\n"); + } + }); + } + } + } + }; + } +}; diff --git a/eslint/lib/rules/max-classes-per-file.js b/eslint/lib/rules/max-classes-per-file.js new file mode 100644 index 0000000..bb48a54 --- /dev/null +++ b/eslint/lib/rules/max-classes-per-file.js @@ -0,0 +1,65 @@ +/** + * @fileoverview Enforce a maximum number of classes per file + * @author James Garbutt + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "enforce a maximum number of classes per file", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/max-classes-per-file" + }, + + schema: [ + { + type: "integer", + minimum: 1 + } + ], + + messages: { + maximumExceeded: "File has too many classes ({{ classCount }}). Maximum allowed is {{ max }}." + } + }, + create(context) { + + const maxClasses = context.options[0] || 1; + + let classCount = 0; + + return { + Program() { + classCount = 0; + }, + "Program:exit"(node) { + if (classCount > maxClasses) { + context.report({ + node, + messageId: "maximumExceeded", + data: { + classCount, + max: maxClasses + } + }); + } + }, + "ClassDeclaration, ClassExpression"() { + classCount++; + } + }; + } +}; diff --git a/eslint/lib/rules/max-depth.js b/eslint/lib/rules/max-depth.js new file mode 100644 index 0000000..5c5296b --- /dev/null +++ b/eslint/lib/rules/max-depth.js @@ -0,0 +1,154 @@ +/** + * @fileoverview A rule to set the maximum depth block can be nested in a function. + * @author Ian Christian Myers + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "enforce a maximum depth that blocks can be nested", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/max-depth" + }, + + schema: [ + { + oneOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "object", + properties: { + maximum: { + type: "integer", + minimum: 0 + }, + max: { + type: "integer", + minimum: 0 + } + }, + additionalProperties: false + } + ] + } + ], + messages: { + tooDeeply: "Blocks are nested too deeply ({{depth}}). Maximum allowed is {{maxDepth}}." + } + }, + + create(context) { + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + const functionStack = [], + option = context.options[0]; + let maxDepth = 4; + + if ( + typeof option === "object" && + (Object.prototype.hasOwnProperty.call(option, "maximum") || Object.prototype.hasOwnProperty.call(option, "max")) + ) { + maxDepth = option.maximum || option.max; + } + if (typeof option === "number") { + maxDepth = option; + } + + /** + * When parsing a new function, store it in our function stack + * @returns {void} + * @private + */ + function startFunction() { + functionStack.push(0); + } + + /** + * When parsing is done then pop out the reference + * @returns {void} + * @private + */ + function endFunction() { + functionStack.pop(); + } + + /** + * Save the block and Evaluate the node + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function pushBlock(node) { + const len = ++functionStack[functionStack.length - 1]; + + if (len > maxDepth) { + context.report({ node, messageId: "tooDeeply", data: { depth: len, maxDepth } }); + } + } + + /** + * Pop the saved block + * @returns {void} + * @private + */ + function popBlock() { + functionStack[functionStack.length - 1]--; + } + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + Program: startFunction, + FunctionDeclaration: startFunction, + FunctionExpression: startFunction, + ArrowFunctionExpression: startFunction, + + IfStatement(node) { + if (node.parent.type !== "IfStatement") { + pushBlock(node); + } + }, + SwitchStatement: pushBlock, + TryStatement: pushBlock, + DoWhileStatement: pushBlock, + WhileStatement: pushBlock, + WithStatement: pushBlock, + ForStatement: pushBlock, + ForInStatement: pushBlock, + ForOfStatement: pushBlock, + + "IfStatement:exit": popBlock, + "SwitchStatement:exit": popBlock, + "TryStatement:exit": popBlock, + "DoWhileStatement:exit": popBlock, + "WhileStatement:exit": popBlock, + "WithStatement:exit": popBlock, + "ForStatement:exit": popBlock, + "ForInStatement:exit": popBlock, + "ForOfStatement:exit": popBlock, + + "FunctionDeclaration:exit": endFunction, + "FunctionExpression:exit": endFunction, + "ArrowFunctionExpression:exit": endFunction, + "Program:exit": endFunction + }; + + } +}; diff --git a/eslint/lib/rules/max-len.js b/eslint/lib/rules/max-len.js new file mode 100644 index 0000000..995e0c5 --- /dev/null +++ b/eslint/lib/rules/max-len.js @@ -0,0 +1,422 @@ +/** + * @fileoverview Rule to check for max length on a line. + * @author Matt DuVall + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Constants +//------------------------------------------------------------------------------ + +const OPTIONS_SCHEMA = { + type: "object", + properties: { + code: { + type: "integer", + minimum: 0 + }, + comments: { + type: "integer", + minimum: 0 + }, + tabWidth: { + type: "integer", + minimum: 0 + }, + ignorePattern: { + type: "string" + }, + ignoreComments: { + type: "boolean" + }, + ignoreStrings: { + type: "boolean" + }, + ignoreUrls: { + type: "boolean" + }, + ignoreTemplateLiterals: { + type: "boolean" + }, + ignoreRegExpLiterals: { + type: "boolean" + }, + ignoreTrailingComments: { + type: "boolean" + } + }, + additionalProperties: false +}; + +const OPTIONS_OR_INTEGER_SCHEMA = { + anyOf: [ + OPTIONS_SCHEMA, + { + type: "integer", + minimum: 0 + } + ] +}; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "enforce a maximum line length", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/max-len" + }, + + schema: [ + OPTIONS_OR_INTEGER_SCHEMA, + OPTIONS_OR_INTEGER_SCHEMA, + OPTIONS_SCHEMA + ], + messages: { + max: "This line has a length of {{lineLength}}. Maximum allowed is {{maxLength}}.", + maxComment: "This line has a comment length of {{lineLength}}. Maximum allowed is {{maxCommentLength}}." + } + }, + + create(context) { + + /* + * Inspired by http://tools.ietf.org/html/rfc3986#appendix-B, however: + * - They're matching an entire string that we know is a URI + * - We're matching part of a string where we think there *might* be a URL + * - We're only concerned about URLs, as picking out any URI would cause + * too many false positives + * - We don't care about matching the entire URL, any small segment is fine + */ + const URL_REGEXP = /[^:/?#]:\/\/[^?#]/u; + + const sourceCode = context.getSourceCode(); + + /** + * Computes the length of a line that may contain tabs. The width of each + * tab will be the number of spaces to the next tab stop. + * @param {string} line The line. + * @param {int} tabWidth The width of each tab stop in spaces. + * @returns {int} The computed line length. + * @private + */ + function computeLineLength(line, tabWidth) { + let extraCharacterCount = 0; + + line.replace(/\t/gu, (match, offset) => { + const totalOffset = offset + extraCharacterCount, + previousTabStopOffset = tabWidth ? totalOffset % tabWidth : 0, + spaceCount = tabWidth - previousTabStopOffset; + + extraCharacterCount += spaceCount - 1; // -1 for the replaced tab + }); + return Array.from(line).length + extraCharacterCount; + } + + // The options object must be the last option specified… + const options = Object.assign({}, context.options[context.options.length - 1]); + + // …but max code length… + if (typeof context.options[0] === "number") { + options.code = context.options[0]; + } + + // …and tabWidth can be optionally specified directly as integers. + if (typeof context.options[1] === "number") { + options.tabWidth = context.options[1]; + } + + const maxLength = typeof options.code === "number" ? options.code : 80, + tabWidth = typeof options.tabWidth === "number" ? options.tabWidth : 4, + ignoreComments = !!options.ignoreComments, + ignoreStrings = !!options.ignoreStrings, + ignoreTemplateLiterals = !!options.ignoreTemplateLiterals, + ignoreRegExpLiterals = !!options.ignoreRegExpLiterals, + ignoreTrailingComments = !!options.ignoreTrailingComments || !!options.ignoreComments, + ignoreUrls = !!options.ignoreUrls, + maxCommentLength = options.comments; + let ignorePattern = options.ignorePattern || null; + + if (ignorePattern) { + ignorePattern = new RegExp(ignorePattern, "u"); + } + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Tells if a given comment is trailing: it starts on the current line and + * extends to or past the end of the current line. + * @param {string} line The source line we want to check for a trailing comment on + * @param {number} lineNumber The one-indexed line number for line + * @param {ASTNode} comment The comment to inspect + * @returns {boolean} If the comment is trailing on the given line + */ + function isTrailingComment(line, lineNumber, comment) { + return comment && + (comment.loc.start.line === lineNumber && lineNumber <= comment.loc.end.line) && + (comment.loc.end.line > lineNumber || comment.loc.end.column === line.length); + } + + /** + * Tells if a comment encompasses the entire line. + * @param {string} line The source line with a trailing comment + * @param {number} lineNumber The one-indexed line number this is on + * @param {ASTNode} comment The comment to remove + * @returns {boolean} If the comment covers the entire line + */ + function isFullLineComment(line, lineNumber, comment) { + const start = comment.loc.start, + end = comment.loc.end, + isFirstTokenOnLine = !line.slice(0, comment.loc.start.column).trim(); + + return comment && + (start.line < lineNumber || (start.line === lineNumber && isFirstTokenOnLine)) && + (end.line > lineNumber || (end.line === lineNumber && end.column === line.length)); + } + + /** + * Check if a node is a JSXEmptyExpression contained in a single line JSXExpressionContainer. + * @param {ASTNode} node A node to check. + * @returns {boolean} True if the node is a JSXEmptyExpression contained in a single line JSXExpressionContainer. + */ + function isJSXEmptyExpressionInSingleLineContainer(node) { + if (!node || !node.parent || node.type !== "JSXEmptyExpression" || node.parent.type !== "JSXExpressionContainer") { + return false; + } + + const parent = node.parent; + + return parent.loc.start.line === parent.loc.end.line; + } + + /** + * Gets the line after the comment and any remaining trailing whitespace is + * stripped. + * @param {string} line The source line with a trailing comment + * @param {ASTNode} comment The comment to remove + * @returns {string} Line without comment and trailing whitespace + */ + function stripTrailingComment(line, comment) { + + // loc.column is zero-indexed + return line.slice(0, comment.loc.start.column).replace(/\s+$/u, ""); + } + + /** + * Ensure that an array exists at [key] on `object`, and add `value` to it. + * @param {Object} object the object to mutate + * @param {string} key the object's key + * @param {*} value the value to add + * @returns {void} + * @private + */ + function ensureArrayAndPush(object, key, value) { + if (!Array.isArray(object[key])) { + object[key] = []; + } + object[key].push(value); + } + + /** + * Retrieves an array containing all strings (" or ') in the source code. + * @returns {ASTNode[]} An array of string nodes. + */ + function getAllStrings() { + return sourceCode.ast.tokens.filter(token => (token.type === "String" || + (token.type === "JSXText" && sourceCode.getNodeByRangeIndex(token.range[0] - 1).type === "JSXAttribute"))); + } + + /** + * Retrieves an array containing all template literals in the source code. + * @returns {ASTNode[]} An array of template literal nodes. + */ + function getAllTemplateLiterals() { + return sourceCode.ast.tokens.filter(token => token.type === "Template"); + } + + + /** + * Retrieves an array containing all RegExp literals in the source code. + * @returns {ASTNode[]} An array of RegExp literal nodes. + */ + function getAllRegExpLiterals() { + return sourceCode.ast.tokens.filter(token => token.type === "RegularExpression"); + } + + + /** + * A reducer to group an AST node by line number, both start and end. + * @param {Object} acc the accumulator + * @param {ASTNode} node the AST node in question + * @returns {Object} the modified accumulator + * @private + */ + function groupByLineNumber(acc, node) { + for (let i = node.loc.start.line; i <= node.loc.end.line; ++i) { + ensureArrayAndPush(acc, i, node); + } + return acc; + } + + /** + * Returns an array of all comments in the source code. + * If the element in the array is a JSXEmptyExpression contained with a single line JSXExpressionContainer, + * the element is changed with JSXExpressionContainer node. + * @returns {ASTNode[]} An array of comment nodes + */ + function getAllComments() { + const comments = []; + + sourceCode.getAllComments() + .forEach(commentNode => { + const containingNode = sourceCode.getNodeByRangeIndex(commentNode.range[0]); + + if (isJSXEmptyExpressionInSingleLineContainer(containingNode)) { + + // push a unique node only + if (comments[comments.length - 1] !== containingNode.parent) { + comments.push(containingNode.parent); + } + } else { + comments.push(commentNode); + } + }); + + return comments; + } + + /** + * Check the program for max length + * @param {ASTNode} node Node to examine + * @returns {void} + * @private + */ + function checkProgramForMaxLength(node) { + + // split (honors line-ending) + const lines = sourceCode.lines, + + // list of comments to ignore + comments = ignoreComments || maxCommentLength || ignoreTrailingComments ? getAllComments() : []; + + // we iterate over comments in parallel with the lines + let commentsIndex = 0; + + const strings = getAllStrings(); + const stringsByLine = strings.reduce(groupByLineNumber, {}); + + const templateLiterals = getAllTemplateLiterals(); + const templateLiteralsByLine = templateLiterals.reduce(groupByLineNumber, {}); + + const regExpLiterals = getAllRegExpLiterals(); + const regExpLiteralsByLine = regExpLiterals.reduce(groupByLineNumber, {}); + + lines.forEach((line, i) => { + + // i is zero-indexed, line numbers are one-indexed + const lineNumber = i + 1; + + /* + * if we're checking comment length; we need to know whether this + * line is a comment + */ + let lineIsComment = false; + let textToMeasure; + + /* + * We can short-circuit the comment checks if we're already out of + * comments to check. + */ + if (commentsIndex < comments.length) { + let comment = null; + + // iterate over comments until we find one past the current line + do { + comment = comments[++commentsIndex]; + } while (comment && comment.loc.start.line <= lineNumber); + + // and step back by one + comment = comments[--commentsIndex]; + + if (isFullLineComment(line, lineNumber, comment)) { + lineIsComment = true; + textToMeasure = line; + } else if (ignoreTrailingComments && isTrailingComment(line, lineNumber, comment)) { + textToMeasure = stripTrailingComment(line, comment); + + // ignore multiple trailing comments in the same line + let lastIndex = commentsIndex; + + while (isTrailingComment(textToMeasure, lineNumber, comments[--lastIndex])) { + textToMeasure = stripTrailingComment(textToMeasure, comments[lastIndex]); + } + } else { + textToMeasure = line; + } + } else { + textToMeasure = line; + } + if (ignorePattern && ignorePattern.test(textToMeasure) || + ignoreUrls && URL_REGEXP.test(textToMeasure) || + ignoreStrings && stringsByLine[lineNumber] || + ignoreTemplateLiterals && templateLiteralsByLine[lineNumber] || + ignoreRegExpLiterals && regExpLiteralsByLine[lineNumber] + ) { + + // ignore this line + return; + } + + const lineLength = computeLineLength(textToMeasure, tabWidth); + const commentLengthApplies = lineIsComment && maxCommentLength; + + if (lineIsComment && ignoreComments) { + return; + } + + if (commentLengthApplies) { + if (lineLength > maxCommentLength) { + context.report({ + node, + loc: { line: lineNumber, column: 0 }, + messageId: "maxComment", + data: { + lineLength, + maxCommentLength + } + }); + } + } else if (lineLength > maxLength) { + context.report({ + node, + loc: { line: lineNumber, column: 0 }, + messageId: "max", + data: { + lineLength, + maxLength + } + }); + } + }); + } + + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + Program: checkProgramForMaxLength + }; + + } +}; diff --git a/eslint/lib/rules/max-lines-per-function.js b/eslint/lib/rules/max-lines-per-function.js new file mode 100644 index 0000000..03539fa --- /dev/null +++ b/eslint/lib/rules/max-lines-per-function.js @@ -0,0 +1,214 @@ +/** + * @fileoverview A rule to set the maximum number of line of code in a function. + * @author Pete Ward + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +const lodash = require("lodash"); + +//------------------------------------------------------------------------------ +// Constants +//------------------------------------------------------------------------------ + +const OPTIONS_SCHEMA = { + type: "object", + properties: { + max: { + type: "integer", + minimum: 0 + }, + skipComments: { + type: "boolean" + }, + skipBlankLines: { + type: "boolean" + }, + IIFEs: { + type: "boolean" + } + }, + additionalProperties: false +}; + +const OPTIONS_OR_INTEGER_SCHEMA = { + oneOf: [ + OPTIONS_SCHEMA, + { + type: "integer", + minimum: 1 + } + ] +}; + +/** + * Given a list of comment nodes, return a map with numeric keys (source code line numbers) and comment token values. + * @param {Array} comments An array of comment nodes. + * @returns {Map.} A map with numeric keys (source code line numbers) and comment token values. + */ +function getCommentLineNumbers(comments) { + const map = new Map(); + + comments.forEach(comment => { + for (let i = comment.loc.start.line; i <= comment.loc.end.line; i++) { + map.set(i, comment); + } + }); + return map; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "enforce a maximum number of line of code in a function", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/max-lines-per-function" + }, + + schema: [ + OPTIONS_OR_INTEGER_SCHEMA + ], + messages: { + exceed: "{{name}} has too many lines ({{lineCount}}). Maximum allowed is {{maxLines}}." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + const lines = sourceCode.lines; + + const option = context.options[0]; + let maxLines = 50; + let skipComments = false; + let skipBlankLines = false; + let IIFEs = false; + + if (typeof option === "object") { + maxLines = typeof option.max === "number" ? option.max : 50; + skipComments = !!option.skipComments; + skipBlankLines = !!option.skipBlankLines; + IIFEs = !!option.IIFEs; + } else if (typeof option === "number") { + maxLines = option; + } + + const commentLineNumbers = getCommentLineNumbers(sourceCode.getAllComments()); + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Tells if a comment encompasses the entire line. + * @param {string} line The source line with a trailing comment + * @param {number} lineNumber The one-indexed line number this is on + * @param {ASTNode} comment The comment to remove + * @returns {boolean} If the comment covers the entire line + */ + function isFullLineComment(line, lineNumber, comment) { + const start = comment.loc.start, + end = comment.loc.end, + isFirstTokenOnLine = start.line === lineNumber && !line.slice(0, start.column).trim(), + isLastTokenOnLine = end.line === lineNumber && !line.slice(end.column).trim(); + + return comment && + (start.line < lineNumber || isFirstTokenOnLine) && + (end.line > lineNumber || isLastTokenOnLine); + } + + /** + * Identifies is a node is a FunctionExpression which is part of an IIFE + * @param {ASTNode} node Node to test + * @returns {boolean} True if it's an IIFE + */ + function isIIFE(node) { + return node.type === "FunctionExpression" && node.parent && node.parent.type === "CallExpression" && node.parent.callee === node; + } + + /** + * Identifies is a node is a FunctionExpression which is embedded within a MethodDefinition or Property + * @param {ASTNode} node Node to test + * @returns {boolean} True if it's a FunctionExpression embedded within a MethodDefinition or Property + */ + function isEmbedded(node) { + if (!node.parent) { + return false; + } + if (node !== node.parent.value) { + return false; + } + if (node.parent.type === "MethodDefinition") { + return true; + } + if (node.parent.type === "Property") { + return node.parent.method === true || node.parent.kind === "get" || node.parent.kind === "set"; + } + return false; + } + + /** + * Count the lines in the function + * @param {ASTNode} funcNode Function AST node + * @returns {void} + * @private + */ + function processFunction(funcNode) { + const node = isEmbedded(funcNode) ? funcNode.parent : funcNode; + + if (!IIFEs && isIIFE(node)) { + return; + } + let lineCount = 0; + + for (let i = node.loc.start.line - 1; i < node.loc.end.line; ++i) { + const line = lines[i]; + + if (skipComments) { + if (commentLineNumbers.has(i + 1) && isFullLineComment(line, i + 1, commentLineNumbers.get(i + 1))) { + continue; + } + } + + if (skipBlankLines) { + if (line.match(/^\s*$/u)) { + continue; + } + } + + lineCount++; + } + + if (lineCount > maxLines) { + const name = lodash.upperFirst(astUtils.getFunctionNameWithKind(funcNode)); + + context.report({ + node, + messageId: "exceed", + data: { name, lineCount, maxLines } + }); + } + } + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + FunctionDeclaration: processFunction, + FunctionExpression: processFunction, + ArrowFunctionExpression: processFunction + }; + } +}; diff --git a/eslint/lib/rules/max-lines.js b/eslint/lib/rules/max-lines.js new file mode 100644 index 0000000..299377b --- /dev/null +++ b/eslint/lib/rules/max-lines.js @@ -0,0 +1,148 @@ +/** + * @fileoverview enforce a maximum file length + * @author Alberto Rodríguez + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const lodash = require("lodash"); +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "enforce a maximum number of lines per file", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/max-lines" + }, + + schema: [ + { + oneOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "object", + properties: { + max: { + type: "integer", + minimum: 0 + }, + skipComments: { + type: "boolean" + }, + skipBlankLines: { + type: "boolean" + } + }, + additionalProperties: false + } + ] + } + ], + messages: { + exceed: "File has too many lines ({{actual}}). Maximum allowed is {{max}}." + } + }, + + create(context) { + const option = context.options[0]; + let max = 300; + + if (typeof option === "object" && Object.prototype.hasOwnProperty.call(option, "max")) { + max = option.max; + } else if (typeof option === "number") { + max = option; + } + + const skipComments = option && option.skipComments; + const skipBlankLines = option && option.skipBlankLines; + + const sourceCode = context.getSourceCode(); + + /** + * Returns whether or not a token is a comment node type + * @param {Token} token The token to check + * @returns {boolean} True if the token is a comment node + */ + function isCommentNodeType(token) { + return token && (token.type === "Block" || token.type === "Line"); + } + + /** + * Returns the line numbers of a comment that don't have any code on the same line + * @param {Node} comment The comment node to check + * @returns {number[]} The line numbers + */ + function getLinesWithoutCode(comment) { + let start = comment.loc.start.line; + let end = comment.loc.end.line; + + let token; + + token = comment; + do { + token = sourceCode.getTokenBefore(token, { includeComments: true }); + } while (isCommentNodeType(token)); + + if (token && astUtils.isTokenOnSameLine(token, comment)) { + start += 1; + } + + token = comment; + do { + token = sourceCode.getTokenAfter(token, { includeComments: true }); + } while (isCommentNodeType(token)); + + if (token && astUtils.isTokenOnSameLine(comment, token)) { + end -= 1; + } + + if (start <= end) { + return lodash.range(start, end + 1); + } + return []; + } + + return { + "Program:exit"() { + let lines = sourceCode.lines.map((text, i) => ({ lineNumber: i + 1, text })); + + if (skipBlankLines) { + lines = lines.filter(l => l.text.trim() !== ""); + } + + if (skipComments) { + const comments = sourceCode.getAllComments(); + + const commentLines = lodash.flatten(comments.map(comment => getLinesWithoutCode(comment))); + + lines = lines.filter(l => !lodash.includes(commentLines, l.lineNumber)); + } + + if (lines.length > max) { + context.report({ + loc: { line: 1, column: 0 }, + messageId: "exceed", + data: { + max, + actual: lines.length + } + }); + } + } + }; + } +}; diff --git a/eslint/lib/rules/max-nested-callbacks.js b/eslint/lib/rules/max-nested-callbacks.js new file mode 100644 index 0000000..df1bace --- /dev/null +++ b/eslint/lib/rules/max-nested-callbacks.js @@ -0,0 +1,117 @@ +/** + * @fileoverview Rule to enforce a maximum number of nested callbacks. + * @author Ian Christian Myers + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "enforce a maximum depth that callbacks can be nested", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/max-nested-callbacks" + }, + + schema: [ + { + oneOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "object", + properties: { + maximum: { + type: "integer", + minimum: 0 + }, + max: { + type: "integer", + minimum: 0 + } + }, + additionalProperties: false + } + ] + } + ], + messages: { + exceed: "Too many nested callbacks ({{num}}). Maximum allowed is {{max}}." + } + }, + + create(context) { + + //-------------------------------------------------------------------------- + // Constants + //-------------------------------------------------------------------------- + const option = context.options[0]; + let THRESHOLD = 10; + + if ( + typeof option === "object" && + (Object.prototype.hasOwnProperty.call(option, "maximum") || Object.prototype.hasOwnProperty.call(option, "max")) + ) { + THRESHOLD = option.maximum || option.max; + } else if (typeof option === "number") { + THRESHOLD = option; + } + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + const callbackStack = []; + + /** + * Checks a given function node for too many callbacks. + * @param {ASTNode} node The node to check. + * @returns {void} + * @private + */ + function checkFunction(node) { + const parent = node.parent; + + if (parent.type === "CallExpression") { + callbackStack.push(node); + } + + if (callbackStack.length > THRESHOLD) { + const opts = { num: callbackStack.length, max: THRESHOLD }; + + context.report({ node, messageId: "exceed", data: opts }); + } + } + + /** + * Pops the call stack. + * @returns {void} + * @private + */ + function popStack() { + callbackStack.pop(); + } + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + ArrowFunctionExpression: checkFunction, + "ArrowFunctionExpression:exit": popStack, + + FunctionExpression: checkFunction, + "FunctionExpression:exit": popStack + }; + + } +}; diff --git a/eslint/lib/rules/max-params.js b/eslint/lib/rules/max-params.js new file mode 100644 index 0000000..4eebe2d --- /dev/null +++ b/eslint/lib/rules/max-params.js @@ -0,0 +1,103 @@ +/** + * @fileoverview Rule to flag when a function has too many parameters + * @author Ilya Volodin + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const lodash = require("lodash"); + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "enforce a maximum number of parameters in function definitions", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/max-params" + }, + + schema: [ + { + oneOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "object", + properties: { + maximum: { + type: "integer", + minimum: 0 + }, + max: { + type: "integer", + minimum: 0 + } + }, + additionalProperties: false + } + ] + } + ], + messages: { + exceed: "{{name}} has too many parameters ({{count}}). Maximum allowed is {{max}}." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + const option = context.options[0]; + let numParams = 3; + + if ( + typeof option === "object" && + (Object.prototype.hasOwnProperty.call(option, "maximum") || Object.prototype.hasOwnProperty.call(option, "max")) + ) { + numParams = option.maximum || option.max; + } + if (typeof option === "number") { + numParams = option; + } + + /** + * Checks a function to see if it has too many parameters. + * @param {ASTNode} node The node to check. + * @returns {void} + * @private + */ + function checkFunction(node) { + if (node.params.length > numParams) { + context.report({ + loc: astUtils.getFunctionHeadLoc(node, sourceCode), + node, + messageId: "exceed", + data: { + name: lodash.upperFirst(astUtils.getFunctionNameWithKind(node)), + count: node.params.length, + max: numParams + } + }); + } + } + + return { + FunctionDeclaration: checkFunction, + ArrowFunctionExpression: checkFunction, + FunctionExpression: checkFunction + }; + + } +}; diff --git a/eslint/lib/rules/max-statements-per-line.js b/eslint/lib/rules/max-statements-per-line.js new file mode 100644 index 0000000..5407cff --- /dev/null +++ b/eslint/lib/rules/max-statements-per-line.js @@ -0,0 +1,196 @@ +/** + * @fileoverview Specify the maximum number of statements allowed per line. + * @author Kenneth Williams + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "enforce a maximum number of statements allowed per line", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/max-statements-per-line" + }, + + schema: [ + { + type: "object", + properties: { + max: { + type: "integer", + minimum: 1, + default: 1 + } + }, + additionalProperties: false + } + ], + messages: { + exceed: "This line has {{numberOfStatementsOnThisLine}} {{statements}}. Maximum allowed is {{maxStatementsPerLine}}." + } + }, + + create(context) { + + const sourceCode = context.getSourceCode(), + options = context.options[0] || {}, + maxStatementsPerLine = typeof options.max !== "undefined" ? options.max : 1; + + let lastStatementLine = 0, + numberOfStatementsOnThisLine = 0, + firstExtraStatement; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + const SINGLE_CHILD_ALLOWED = /^(?:(?:DoWhile|For|ForIn|ForOf|If|Labeled|While)Statement|Export(?:Default|Named)Declaration)$/u; + + /** + * Reports with the first extra statement, and clears it. + * @returns {void} + */ + function reportFirstExtraStatementAndClear() { + if (firstExtraStatement) { + context.report({ + node: firstExtraStatement, + messageId: "exceed", + data: { + numberOfStatementsOnThisLine, + maxStatementsPerLine, + statements: numberOfStatementsOnThisLine === 1 ? "statement" : "statements" + } + }); + } + firstExtraStatement = null; + } + + /** + * Gets the actual last token of a given node. + * @param {ASTNode} node A node to get. This is a node except EmptyStatement. + * @returns {Token} The actual last token. + */ + function getActualLastToken(node) { + return sourceCode.getLastToken(node, astUtils.isNotSemicolonToken); + } + + /** + * Addresses a given node. + * It updates the state of this rule, then reports the node if the node violated this rule. + * @param {ASTNode} node A node to check. + * @returns {void} + */ + function enterStatement(node) { + const line = node.loc.start.line; + + /* + * Skip to allow non-block statements if this is direct child of control statements. + * `if (a) foo();` is counted as 1. + * But `if (a) foo(); else foo();` should be counted as 2. + */ + if (SINGLE_CHILD_ALLOWED.test(node.parent.type) && + node.parent.alternate !== node + ) { + return; + } + + // Update state. + if (line === lastStatementLine) { + numberOfStatementsOnThisLine += 1; + } else { + reportFirstExtraStatementAndClear(); + numberOfStatementsOnThisLine = 1; + lastStatementLine = line; + } + + // Reports if the node violated this rule. + if (numberOfStatementsOnThisLine === maxStatementsPerLine + 1) { + firstExtraStatement = firstExtraStatement || node; + } + } + + /** + * Updates the state of this rule with the end line of leaving node to check with the next statement. + * @param {ASTNode} node A node to check. + * @returns {void} + */ + function leaveStatement(node) { + const line = getActualLastToken(node).loc.end.line; + + // Update state. + if (line !== lastStatementLine) { + reportFirstExtraStatementAndClear(); + numberOfStatementsOnThisLine = 1; + lastStatementLine = line; + } + } + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + BreakStatement: enterStatement, + ClassDeclaration: enterStatement, + ContinueStatement: enterStatement, + DebuggerStatement: enterStatement, + DoWhileStatement: enterStatement, + ExpressionStatement: enterStatement, + ForInStatement: enterStatement, + ForOfStatement: enterStatement, + ForStatement: enterStatement, + FunctionDeclaration: enterStatement, + IfStatement: enterStatement, + ImportDeclaration: enterStatement, + LabeledStatement: enterStatement, + ReturnStatement: enterStatement, + SwitchStatement: enterStatement, + ThrowStatement: enterStatement, + TryStatement: enterStatement, + VariableDeclaration: enterStatement, + WhileStatement: enterStatement, + WithStatement: enterStatement, + ExportNamedDeclaration: enterStatement, + ExportDefaultDeclaration: enterStatement, + ExportAllDeclaration: enterStatement, + + "BreakStatement:exit": leaveStatement, + "ClassDeclaration:exit": leaveStatement, + "ContinueStatement:exit": leaveStatement, + "DebuggerStatement:exit": leaveStatement, + "DoWhileStatement:exit": leaveStatement, + "ExpressionStatement:exit": leaveStatement, + "ForInStatement:exit": leaveStatement, + "ForOfStatement:exit": leaveStatement, + "ForStatement:exit": leaveStatement, + "FunctionDeclaration:exit": leaveStatement, + "IfStatement:exit": leaveStatement, + "ImportDeclaration:exit": leaveStatement, + "LabeledStatement:exit": leaveStatement, + "ReturnStatement:exit": leaveStatement, + "SwitchStatement:exit": leaveStatement, + "ThrowStatement:exit": leaveStatement, + "TryStatement:exit": leaveStatement, + "VariableDeclaration:exit": leaveStatement, + "WhileStatement:exit": leaveStatement, + "WithStatement:exit": leaveStatement, + "ExportNamedDeclaration:exit": leaveStatement, + "ExportDefaultDeclaration:exit": leaveStatement, + "ExportAllDeclaration:exit": leaveStatement, + "Program:exit": reportFirstExtraStatementAndClear + }; + } +}; diff --git a/eslint/lib/rules/max-statements.js b/eslint/lib/rules/max-statements.js new file mode 100644 index 0000000..437b393 --- /dev/null +++ b/eslint/lib/rules/max-statements.js @@ -0,0 +1,175 @@ +/** + * @fileoverview A rule to set the maximum number of statements in a function. + * @author Ian Christian Myers + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const lodash = require("lodash"); + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "enforce a maximum number of statements allowed in function blocks", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/max-statements" + }, + + schema: [ + { + oneOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "object", + properties: { + maximum: { + type: "integer", + minimum: 0 + }, + max: { + type: "integer", + minimum: 0 + } + }, + additionalProperties: false + } + ] + }, + { + type: "object", + properties: { + ignoreTopLevelFunctions: { + type: "boolean" + } + }, + additionalProperties: false + } + ], + messages: { + exceed: "{{name}} has too many statements ({{count}}). Maximum allowed is {{max}}." + } + }, + + create(context) { + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + const functionStack = [], + option = context.options[0], + ignoreTopLevelFunctions = context.options[1] && context.options[1].ignoreTopLevelFunctions || false, + topLevelFunctions = []; + let maxStatements = 10; + + if ( + typeof option === "object" && + (Object.prototype.hasOwnProperty.call(option, "maximum") || Object.prototype.hasOwnProperty.call(option, "max")) + ) { + maxStatements = option.maximum || option.max; + } else if (typeof option === "number") { + maxStatements = option; + } + + /** + * Reports a node if it has too many statements + * @param {ASTNode} node node to evaluate + * @param {int} count Number of statements in node + * @param {int} max Maximum number of statements allowed + * @returns {void} + * @private + */ + function reportIfTooManyStatements(node, count, max) { + if (count > max) { + const name = lodash.upperFirst(astUtils.getFunctionNameWithKind(node)); + + context.report({ + node, + messageId: "exceed", + data: { name, count, max } + }); + } + } + + /** + * When parsing a new function, store it in our function stack + * @returns {void} + * @private + */ + function startFunction() { + functionStack.push(0); + } + + /** + * Evaluate the node at the end of function + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function endFunction(node) { + const count = functionStack.pop(); + + if (ignoreTopLevelFunctions && functionStack.length === 0) { + topLevelFunctions.push({ node, count }); + } else { + reportIfTooManyStatements(node, count, maxStatements); + } + } + + /** + * Increment the count of the functions + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function countStatements(node) { + functionStack[functionStack.length - 1] += node.body.length; + } + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + FunctionDeclaration: startFunction, + FunctionExpression: startFunction, + ArrowFunctionExpression: startFunction, + + BlockStatement: countStatements, + + "FunctionDeclaration:exit": endFunction, + "FunctionExpression:exit": endFunction, + "ArrowFunctionExpression:exit": endFunction, + + "Program:exit"() { + if (topLevelFunctions.length === 1) { + return; + } + + topLevelFunctions.forEach(element => { + const count = element.count; + const node = element.node; + + reportIfTooManyStatements(node, count, maxStatements); + }); + } + }; + + } +}; diff --git a/eslint/lib/rules/multiline-comment-style.js b/eslint/lib/rules/multiline-comment-style.js new file mode 100644 index 0000000..9524818 --- /dev/null +++ b/eslint/lib/rules/multiline-comment-style.js @@ -0,0 +1,435 @@ +/** + * @fileoverview enforce a particular style for multiline comments + * @author Teddy Katz + */ +"use strict"; + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "enforce a particular style for multiline comments", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/multiline-comment-style" + }, + + fixable: "whitespace", + schema: [{ enum: ["starred-block", "separate-lines", "bare-block"] }], + messages: { + expectedBlock: "Expected a block comment instead of consecutive line comments.", + expectedBareBlock: "Expected a block comment without padding stars.", + startNewline: "Expected a linebreak after '/*'.", + endNewline: "Expected a linebreak before '*/'.", + missingStar: "Expected a '*' at the start of this line.", + alignment: "Expected this line to be aligned with the start of the comment.", + expectedLines: "Expected multiple line comments instead of a block comment." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + const option = context.options[0] || "starred-block"; + + //---------------------------------------------------------------------- + // Helpers + //---------------------------------------------------------------------- + + /** + * Checks if a comment line is starred. + * @param {string} line A string representing a comment line. + * @returns {boolean} Whether or not the comment line is starred. + */ + function isStarredCommentLine(line) { + return /^\s*\*/u.test(line); + } + + /** + * Checks if a comment group is in starred-block form. + * @param {Token[]} commentGroup A group of comments, containing either multiple line comments or a single block comment. + * @returns {boolean} Whether or not the comment group is in starred block form. + */ + function isStarredBlockComment([firstComment]) { + if (firstComment.type !== "Block") { + return false; + } + + const lines = firstComment.value.split(astUtils.LINEBREAK_MATCHER); + + // The first and last lines can only contain whitespace. + return lines.length > 0 && lines.every((line, i) => (i === 0 || i === lines.length - 1 ? /^\s*$/u : /^\s*\*/u).test(line)); + } + + /** + * Checks if a comment group is in JSDoc form. + * @param {Token[]} commentGroup A group of comments, containing either multiple line comments or a single block comment. + * @returns {boolean} Whether or not the comment group is in JSDoc form. + */ + function isJSDocComment([firstComment]) { + if (firstComment.type !== "Block") { + return false; + } + + const lines = firstComment.value.split(astUtils.LINEBREAK_MATCHER); + + return /^\*\s*$/u.test(lines[0]) && + lines.slice(1, -1).every(line => /^\s* /u.test(line)) && + /^\s*$/u.test(lines[lines.length - 1]); + } + + /** + * Processes a comment group that is currently in separate-line form, calculating the offset for each line. + * @param {Token[]} commentGroup A group of comments containing multiple line comments. + * @returns {string[]} An array of the processed lines. + */ + function processSeparateLineComments(commentGroup) { + const allLinesHaveLeadingSpace = commentGroup + .map(({ value }) => value) + .filter(line => line.trim().length) + .every(line => line.startsWith(" ")); + + return commentGroup.map(({ value }) => (allLinesHaveLeadingSpace ? value.replace(/^ /u, "") : value)); + } + + /** + * Processes a comment group that is currently in starred-block form, calculating the offset for each line. + * @param {Token} comment A single block comment token in starred-block form. + * @returns {string[]} An array of the processed lines. + */ + function processStarredBlockComment(comment) { + const lines = comment.value.split(astUtils.LINEBREAK_MATCHER) + .filter((line, i, linesArr) => !(i === 0 || i === linesArr.length - 1)) + .map(line => line.replace(/^\s*$/u, "")); + const allLinesHaveLeadingSpace = lines + .map(line => line.replace(/\s*\*/u, "")) + .filter(line => line.trim().length) + .every(line => line.startsWith(" ")); + + return lines.map(line => line.replace(allLinesHaveLeadingSpace ? /\s*\* ?/u : /\s*\*/u, "")); + } + + /** + * Processes a comment group that is currently in bare-block form, calculating the offset for each line. + * @param {Token} comment A single block comment token in bare-block form. + * @returns {string[]} An array of the processed lines. + */ + function processBareBlockComment(comment) { + const lines = comment.value.split(astUtils.LINEBREAK_MATCHER).map(line => line.replace(/^\s*$/u, "")); + const leadingWhitespace = `${sourceCode.text.slice(comment.range[0] - comment.loc.start.column, comment.range[0])} `; + let offset = ""; + + /* + * Calculate the offset of the least indented line and use that as the basis for offsetting all the lines. + * The first line should not be checked because it is inline with the opening block comment delimiter. + */ + for (const [i, line] of lines.entries()) { + if (!line.trim().length || i === 0) { + continue; + } + + const [, lineOffset] = line.match(/^(\s*\*?\s*)/u); + + if (lineOffset.length < leadingWhitespace.length) { + const newOffset = leadingWhitespace.slice(lineOffset.length - leadingWhitespace.length); + + if (newOffset.length > offset.length) { + offset = newOffset; + } + } + } + + return lines.map(line => { + const match = line.match(/^(\s*\*?\s*)(.*)/u); + const [, lineOffset, lineContents] = match; + + if (lineOffset.length > leadingWhitespace.length) { + return `${lineOffset.slice(leadingWhitespace.length - (offset.length + lineOffset.length))}${lineContents}`; + } + + if (lineOffset.length < leadingWhitespace.length) { + return `${lineOffset.slice(leadingWhitespace.length)}${lineContents}`; + } + + return lineContents; + }); + } + + /** + * Gets a list of comment lines in a group, formatting leading whitespace as necessary. + * @param {Token[]} commentGroup A group of comments containing either multiple line comments or a single block comment. + * @returns {string[]} A list of comment lines. + */ + function getCommentLines(commentGroup) { + const [firstComment] = commentGroup; + + if (firstComment.type === "Line") { + return processSeparateLineComments(commentGroup); + } + + if (isStarredBlockComment(commentGroup)) { + return processStarredBlockComment(firstComment); + } + + return processBareBlockComment(firstComment); + } + + /** + * Gets the initial offset (whitespace) from the beginning of a line to a given comment token. + * @param {Token} comment The token to check. + * @returns {string} The offset from the beginning of a line to the token. + */ + function getInitialOffset(comment) { + return sourceCode.text.slice(comment.range[0] - comment.loc.start.column, comment.range[0]); + } + + /** + * Converts a comment into starred-block form + * @param {Token} firstComment The first comment of the group being converted + * @param {string[]} commentLinesList A list of lines to appear in the new starred-block comment + * @returns {string} A representation of the comment value in starred-block form, excluding start and end markers + */ + function convertToStarredBlock(firstComment, commentLinesList) { + const initialOffset = getInitialOffset(firstComment); + + return `/*\n${commentLinesList.map(line => `${initialOffset} * ${line}`).join("\n")}\n${initialOffset} */`; + } + + /** + * Converts a comment into separate-line form + * @param {Token} firstComment The first comment of the group being converted + * @param {string[]} commentLinesList A list of lines to appear in the new starred-block comment + * @returns {string} A representation of the comment value in separate-line form + */ + function convertToSeparateLines(firstComment, commentLinesList) { + return commentLinesList.map(line => `// ${line}`).join(`\n${getInitialOffset(firstComment)}`); + } + + /** + * Converts a comment into bare-block form + * @param {Token} firstComment The first comment of the group being converted + * @param {string[]} commentLinesList A list of lines to appear in the new starred-block comment + * @returns {string} A representation of the comment value in bare-block form + */ + function convertToBlock(firstComment, commentLinesList) { + return `/* ${commentLinesList.join(`\n${getInitialOffset(firstComment)} `)} */`; + } + + /** + * Each method checks a group of comments to see if it's valid according to the given option. + * @param {Token[]} commentGroup A list of comments that appear together. This will either contain a single + * block comment or multiple line comments. + * @returns {void} + */ + const commentGroupCheckers = { + "starred-block"(commentGroup) { + const [firstComment] = commentGroup; + const commentLines = getCommentLines(commentGroup); + + if (commentLines.some(value => value.includes("*/"))) { + return; + } + + if (commentGroup.length > 1) { + context.report({ + loc: { + start: firstComment.loc.start, + end: commentGroup[commentGroup.length - 1].loc.end + }, + messageId: "expectedBlock", + fix(fixer) { + const range = [firstComment.range[0], commentGroup[commentGroup.length - 1].range[1]]; + + return commentLines.some(value => value.startsWith("/")) + ? null + : fixer.replaceTextRange(range, convertToStarredBlock(firstComment, commentLines)); + } + }); + } else { + const lines = firstComment.value.split(astUtils.LINEBREAK_MATCHER); + const expectedLeadingWhitespace = getInitialOffset(firstComment); + const expectedLinePrefix = `${expectedLeadingWhitespace} *`; + + if (!/^\*?\s*$/u.test(lines[0])) { + const start = firstComment.value.startsWith("*") ? firstComment.range[0] + 1 : firstComment.range[0]; + + context.report({ + loc: { + start: firstComment.loc.start, + end: { line: firstComment.loc.start.line, column: firstComment.loc.start.column + 2 } + }, + messageId: "startNewline", + fix: fixer => fixer.insertTextAfterRange([start, start + 2], `\n${expectedLinePrefix}`) + }); + } + + if (!/^\s*$/u.test(lines[lines.length - 1])) { + context.report({ + loc: { + start: { line: firstComment.loc.end.line, column: firstComment.loc.end.column - 2 }, + end: firstComment.loc.end + }, + messageId: "endNewline", + fix: fixer => fixer.replaceTextRange([firstComment.range[1] - 2, firstComment.range[1]], `\n${expectedLinePrefix}/`) + }); + } + + for (let lineNumber = firstComment.loc.start.line + 1; lineNumber <= firstComment.loc.end.line; lineNumber++) { + const lineText = sourceCode.lines[lineNumber - 1]; + const errorType = isStarredCommentLine(lineText) + ? "alignment" + : "missingStar"; + + if (!lineText.startsWith(expectedLinePrefix)) { + context.report({ + loc: { + start: { line: lineNumber, column: 0 }, + end: { line: lineNumber, column: lineText.length } + }, + messageId: errorType, + fix(fixer) { + const lineStartIndex = sourceCode.getIndexFromLoc({ line: lineNumber, column: 0 }); + + if (errorType === "alignment") { + const [, commentTextPrefix = ""] = lineText.match(/^(\s*\*)/u) || []; + const commentTextStartIndex = lineStartIndex + commentTextPrefix.length; + + return fixer.replaceTextRange([lineStartIndex, commentTextStartIndex], expectedLinePrefix); + } + + const [, commentTextPrefix = ""] = lineText.match(/^(\s*)/u) || []; + const commentTextStartIndex = lineStartIndex + commentTextPrefix.length; + let offset; + + for (const [idx, line] of lines.entries()) { + if (!/\S+/u.test(line)) { + continue; + } + + const lineTextToAlignWith = sourceCode.lines[firstComment.loc.start.line - 1 + idx]; + const [, prefix = "", initialOffset = ""] = lineTextToAlignWith.match(/^(\s*(?:\/?\*)?(\s*))/u) || []; + + offset = `${commentTextPrefix.slice(prefix.length)}${initialOffset}`; + + if (/^\s*\//u.test(lineText) && offset.length === 0) { + offset += " "; + } + break; + } + + return fixer.replaceTextRange([lineStartIndex, commentTextStartIndex], `${expectedLinePrefix}${offset}`); + } + }); + } + } + } + }, + "separate-lines"(commentGroup) { + const [firstComment] = commentGroup; + + if (firstComment.type !== "Block" || isJSDocComment(commentGroup)) { + return; + } + + const commentLines = getCommentLines(commentGroup); + const tokenAfter = sourceCode.getTokenAfter(firstComment, { includeComments: true }); + + if (tokenAfter && firstComment.loc.end.line === tokenAfter.loc.start.line) { + return; + } + + context.report({ + loc: { + start: firstComment.loc.start, + end: { line: firstComment.loc.start.line, column: firstComment.loc.start.column + 2 } + }, + messageId: "expectedLines", + fix(fixer) { + return fixer.replaceText(firstComment, convertToSeparateLines(firstComment, commentLines)); + } + }); + }, + "bare-block"(commentGroup) { + if (isJSDocComment(commentGroup)) { + return; + } + + const [firstComment] = commentGroup; + const commentLines = getCommentLines(commentGroup); + + // Disallows consecutive line comments in favor of using a block comment. + if (firstComment.type === "Line" && commentLines.length > 1 && + !commentLines.some(value => value.includes("*/"))) { + context.report({ + loc: { + start: firstComment.loc.start, + end: commentGroup[commentGroup.length - 1].loc.end + }, + messageId: "expectedBlock", + fix(fixer) { + return fixer.replaceTextRange( + [firstComment.range[0], commentGroup[commentGroup.length - 1].range[1]], + convertToBlock(firstComment, commentLines) + ); + } + }); + } + + // Prohibits block comments from having a * at the beginning of each line. + if (isStarredBlockComment(commentGroup)) { + context.report({ + loc: { + start: firstComment.loc.start, + end: { line: firstComment.loc.start.line, column: firstComment.loc.start.column + 2 } + }, + messageId: "expectedBareBlock", + fix(fixer) { + return fixer.replaceText(firstComment, convertToBlock(firstComment, commentLines)); + } + }); + } + } + }; + + //---------------------------------------------------------------------- + // Public + //---------------------------------------------------------------------- + + return { + Program() { + return sourceCode.getAllComments() + .filter(comment => comment.type !== "Shebang") + .filter(comment => !astUtils.COMMENTS_IGNORE_PATTERN.test(comment.value)) + .filter(comment => { + const tokenBefore = sourceCode.getTokenBefore(comment, { includeComments: true }); + + return !tokenBefore || tokenBefore.loc.end.line < comment.loc.start.line; + }) + .reduce((commentGroups, comment, index, commentList) => { + const tokenBefore = sourceCode.getTokenBefore(comment, { includeComments: true }); + + if ( + comment.type === "Line" && + index && commentList[index - 1].type === "Line" && + tokenBefore && tokenBefore.loc.end.line === comment.loc.start.line - 1 && + tokenBefore === commentList[index - 1] + ) { + commentGroups[commentGroups.length - 1].push(comment); + } else { + commentGroups.push([comment]); + } + + return commentGroups; + }, []) + .filter(commentGroup => !(commentGroup.length === 1 && commentGroup[0].loc.start.line === commentGroup[0].loc.end.line)) + .forEach(commentGroupCheckers[option]); + } + }; + } +}; diff --git a/eslint/lib/rules/multiline-ternary.js b/eslint/lib/rules/multiline-ternary.js new file mode 100644 index 0000000..1df90b6 --- /dev/null +++ b/eslint/lib/rules/multiline-ternary.js @@ -0,0 +1,95 @@ +/** + * @fileoverview Enforce newlines between operands of ternary expressions + * @author Kai Cataldo + */ + +"use strict"; + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "enforce newlines between operands of ternary expressions", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/multiline-ternary" + }, + + schema: [ + { + enum: ["always", "always-multiline", "never"] + } + ], + messages: { + expectedTestCons: "Expected newline between test and consequent of ternary expression.", + expectedConsAlt: "Expected newline between consequent and alternate of ternary expression.", + unexpectedTestCons: "Unexpected newline between test and consequent of ternary expression.", + unexpectedConsAlt: "Unexpected newline between consequent and alternate of ternary expression." + } + }, + + create(context) { + const option = context.options[0]; + const multiline = option !== "never"; + const allowSingleLine = option === "always-multiline"; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Tests whether node is preceded by supplied tokens + * @param {ASTNode} node node to check + * @param {ASTNode} parentNode parent of node to report + * @param {boolean} expected whether newline was expected or not + * @returns {void} + * @private + */ + function reportError(node, parentNode, expected) { + context.report({ + node, + messageId: `${expected ? "expected" : "unexpected"}${node === parentNode.test ? "TestCons" : "ConsAlt"}` + }); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + ConditionalExpression(node) { + const areTestAndConsequentOnSameLine = astUtils.isTokenOnSameLine(node.test, node.consequent); + const areConsequentAndAlternateOnSameLine = astUtils.isTokenOnSameLine(node.consequent, node.alternate); + + if (!multiline) { + if (!areTestAndConsequentOnSameLine) { + reportError(node.test, node, false); + } + + if (!areConsequentAndAlternateOnSameLine) { + reportError(node.consequent, node, false); + } + } else { + if (allowSingleLine && node.loc.start.line === node.loc.end.line) { + return; + } + + if (areTestAndConsequentOnSameLine) { + reportError(node.test, node, true); + } + + if (areConsequentAndAlternateOnSameLine) { + reportError(node.consequent, node, true); + } + } + } + }; + } +}; diff --git a/eslint/lib/rules/new-cap.js b/eslint/lib/rules/new-cap.js new file mode 100644 index 0000000..7cce968 --- /dev/null +++ b/eslint/lib/rules/new-cap.js @@ -0,0 +1,279 @@ +/** + * @fileoverview Rule to flag use of constructors without capital letters + * @author Nicholas C. Zakas + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const CAPS_ALLOWED = [ + "Array", + "Boolean", + "Date", + "Error", + "Function", + "Number", + "Object", + "RegExp", + "String", + "Symbol", + "BigInt" +]; + +/** + * Ensure that if the key is provided, it must be an array. + * @param {Object} obj Object to check with `key`. + * @param {string} key Object key to check on `obj`. + * @param {*} fallback If obj[key] is not present, this will be returned. + * @returns {string[]} Returns obj[key] if it's an Array, otherwise `fallback` + */ +function checkArray(obj, key, fallback) { + + /* istanbul ignore if */ + if (Object.prototype.hasOwnProperty.call(obj, key) && !Array.isArray(obj[key])) { + throw new TypeError(`${key}, if provided, must be an Array`); + } + return obj[key] || fallback; +} + +/** + * A reducer function to invert an array to an Object mapping the string form of the key, to `true`. + * @param {Object} map Accumulator object for the reduce. + * @param {string} key Object key to set to `true`. + * @returns {Object} Returns the updated Object for further reduction. + */ +function invert(map, key) { + map[key] = true; + return map; +} + +/** + * Creates an object with the cap is new exceptions as its keys and true as their values. + * @param {Object} config Rule configuration + * @returns {Object} Object with cap is new exceptions. + */ +function calculateCapIsNewExceptions(config) { + let capIsNewExceptions = checkArray(config, "capIsNewExceptions", CAPS_ALLOWED); + + if (capIsNewExceptions !== CAPS_ALLOWED) { + capIsNewExceptions = capIsNewExceptions.concat(CAPS_ALLOWED); + } + + return capIsNewExceptions.reduce(invert, {}); +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "require constructor names to begin with a capital letter", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/new-cap" + }, + + schema: [ + { + type: "object", + properties: { + newIsCap: { + type: "boolean", + default: true + }, + capIsNew: { + type: "boolean", + default: true + }, + newIsCapExceptions: { + type: "array", + items: { + type: "string" + } + }, + newIsCapExceptionPattern: { + type: "string" + }, + capIsNewExceptions: { + type: "array", + items: { + type: "string" + } + }, + capIsNewExceptionPattern: { + type: "string" + }, + properties: { + type: "boolean", + default: true + } + }, + additionalProperties: false + } + ], + messages: { + upper: "A function with a name starting with an uppercase letter should only be used as a constructor.", + lower: "A constructor name should not start with a lowercase letter." + } + }, + + create(context) { + + const config = Object.assign({}, context.options[0]); + + config.newIsCap = config.newIsCap !== false; + config.capIsNew = config.capIsNew !== false; + const skipProperties = config.properties === false; + + const newIsCapExceptions = checkArray(config, "newIsCapExceptions", []).reduce(invert, {}); + const newIsCapExceptionPattern = config.newIsCapExceptionPattern ? new RegExp(config.newIsCapExceptionPattern, "u") : null; + + const capIsNewExceptions = calculateCapIsNewExceptions(config); + const capIsNewExceptionPattern = config.capIsNewExceptionPattern ? new RegExp(config.capIsNewExceptionPattern, "u") : null; + + const listeners = {}; + + const sourceCode = context.getSourceCode(); + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Get exact callee name from expression + * @param {ASTNode} node CallExpression or NewExpression node + * @returns {string} name + */ + function extractNameFromExpression(node) { + + let name = ""; + + if (node.callee.type === "MemberExpression") { + name = astUtils.getStaticPropertyName(node.callee) || ""; + } else { + name = node.callee.name; + } + return name; + } + + /** + * Returns the capitalization state of the string - + * Whether the first character is uppercase, lowercase, or non-alphabetic + * @param {string} str String + * @returns {string} capitalization state: "non-alpha", "lower", or "upper" + */ + function getCap(str) { + const firstChar = str.charAt(0); + + const firstCharLower = firstChar.toLowerCase(); + const firstCharUpper = firstChar.toUpperCase(); + + if (firstCharLower === firstCharUpper) { + + // char has no uppercase variant, so it's non-alphabetic + return "non-alpha"; + } + if (firstChar === firstCharLower) { + return "lower"; + } + return "upper"; + + } + + /** + * Check if capitalization is allowed for a CallExpression + * @param {Object} allowedMap Object mapping calleeName to a Boolean + * @param {ASTNode} node CallExpression node + * @param {string} calleeName Capitalized callee name from a CallExpression + * @param {Object} pattern RegExp object from options pattern + * @returns {boolean} Returns true if the callee may be capitalized + */ + function isCapAllowed(allowedMap, node, calleeName, pattern) { + const sourceText = sourceCode.getText(node.callee); + + if (allowedMap[calleeName] || allowedMap[sourceText]) { + return true; + } + + if (pattern && pattern.test(sourceText)) { + return true; + } + + if (calleeName === "UTC" && node.callee.type === "MemberExpression") { + + // allow if callee is Date.UTC + return node.callee.object.type === "Identifier" && + node.callee.object.name === "Date"; + } + + return skipProperties && node.callee.type === "MemberExpression"; + } + + /** + * Reports the given messageId for the given node. The location will be the start of the property or the callee. + * @param {ASTNode} node CallExpression or NewExpression node. + * @param {string} messageId The messageId to report. + * @returns {void} + */ + function report(node, messageId) { + let callee = node.callee; + + if (callee.type === "MemberExpression") { + callee = callee.property; + } + + context.report({ node, loc: callee.loc.start, messageId }); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + if (config.newIsCap) { + listeners.NewExpression = function(node) { + + const constructorName = extractNameFromExpression(node); + + if (constructorName) { + const capitalization = getCap(constructorName); + const isAllowed = capitalization !== "lower" || isCapAllowed(newIsCapExceptions, node, constructorName, newIsCapExceptionPattern); + + if (!isAllowed) { + report(node, "lower"); + } + } + }; + } + + if (config.capIsNew) { + listeners.CallExpression = function(node) { + + const calleeName = extractNameFromExpression(node); + + if (calleeName) { + const capitalization = getCap(calleeName); + const isAllowed = capitalization !== "upper" || isCapAllowed(capIsNewExceptions, node, calleeName, capIsNewExceptionPattern); + + if (!isAllowed) { + report(node, "upper"); + } + } + }; + } + + return listeners; + } +}; diff --git a/eslint/lib/rules/new-parens.js b/eslint/lib/rules/new-parens.js new file mode 100644 index 0000000..405ec1b --- /dev/null +++ b/eslint/lib/rules/new-parens.js @@ -0,0 +1,99 @@ +/** + * @fileoverview Rule to flag when using constructor without parentheses + * @author Ilya Volodin + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "enforce or disallow parentheses when invoking a constructor with no arguments", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/new-parens" + }, + + fixable: "code", + schema: { + anyOf: [ + { + type: "array", + items: [ + { + enum: ["always", "never"] + } + ], + minItems: 0, + maxItems: 1 + } + ] + }, + messages: { + missing: "Missing '()' invoking a constructor.", + unnecessary: "Unnecessary '()' invoking a constructor with no arguments." + } + }, + + create(context) { + const options = context.options; + const always = options[0] !== "never"; // Default is always + + const sourceCode = context.getSourceCode(); + + return { + NewExpression(node) { + if (node.arguments.length !== 0) { + return; // if there are arguments, there have to be parens + } + + const lastToken = sourceCode.getLastToken(node); + const hasLastParen = lastToken && astUtils.isClosingParenToken(lastToken); + + // `hasParens` is true only if the new expression ends with its own parens, e.g., new new foo() does not end with its own parens + const hasParens = hasLastParen && + astUtils.isOpeningParenToken(sourceCode.getTokenBefore(lastToken)) && + node.callee.range[1] < node.range[1]; + + if (always) { + if (!hasParens) { + context.report({ + node, + messageId: "missing", + fix: fixer => fixer.insertTextAfter(node, "()") + }); + } + } else { + if (hasParens) { + context.report({ + node, + messageId: "unnecessary", + fix: fixer => [ + fixer.remove(sourceCode.getTokenBefore(lastToken)), + fixer.remove(lastToken), + fixer.insertTextBefore(node, "("), + fixer.insertTextAfter(node, ")") + ] + }); + } + } + } + }; + } +}; diff --git a/eslint/lib/rules/newline-after-var.js b/eslint/lib/rules/newline-after-var.js new file mode 100644 index 0000000..4809d9b --- /dev/null +++ b/eslint/lib/rules/newline-after-var.js @@ -0,0 +1,255 @@ +/** + * @fileoverview Rule to check empty newline after "var" statement + * @author Gopal Venkatesan + * @deprecated + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "require or disallow an empty line after variable declarations", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/newline-after-var" + }, + schema: [ + { + enum: ["never", "always"] + } + ], + fixable: "whitespace", + messages: { + expected: "Expected blank line after variable declarations.", + unexpected: "Unexpected blank line after variable declarations." + }, + + deprecated: true, + + replacedBy: ["padding-line-between-statements"] + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + // Default `mode` to "always". + const mode = context.options[0] === "never" ? "never" : "always"; + + // Cache starting and ending line numbers of comments for faster lookup + const commentEndLine = sourceCode.getAllComments().reduce((result, token) => { + result[token.loc.start.line] = token.loc.end.line; + return result; + }, {}); + + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Gets a token from the given node to compare line to the next statement. + * + * In general, the token is the last token of the node. However, the token is the second last token if the following conditions satisfy. + * + * - The last token is semicolon. + * - The semicolon is on a different line from the previous token of the semicolon. + * + * This behavior would address semicolon-less style code. e.g.: + * + * var foo = 1 + * + * ;(a || b).doSomething() + * @param {ASTNode} node The node to get. + * @returns {Token} The token to compare line to the next statement. + */ + function getLastToken(node) { + const lastToken = sourceCode.getLastToken(node); + + if (lastToken.type === "Punctuator" && lastToken.value === ";") { + const prevToken = sourceCode.getTokenBefore(lastToken); + + if (prevToken.loc.end.line !== lastToken.loc.start.line) { + return prevToken; + } + } + + return lastToken; + } + + /** + * Determine if provided keyword is a variable declaration + * @private + * @param {string} keyword keyword to test + * @returns {boolean} True if `keyword` is a type of var + */ + function isVar(keyword) { + return keyword === "var" || keyword === "let" || keyword === "const"; + } + + /** + * Determine if provided keyword is a variant of for specifiers + * @private + * @param {string} keyword keyword to test + * @returns {boolean} True if `keyword` is a variant of for specifier + */ + function isForTypeSpecifier(keyword) { + return keyword === "ForStatement" || keyword === "ForInStatement" || keyword === "ForOfStatement"; + } + + /** + * Determine if provided keyword is an export specifiers + * @private + * @param {string} nodeType nodeType to test + * @returns {boolean} True if `nodeType` is an export specifier + */ + function isExportSpecifier(nodeType) { + return nodeType === "ExportNamedDeclaration" || nodeType === "ExportSpecifier" || + nodeType === "ExportDefaultDeclaration" || nodeType === "ExportAllDeclaration"; + } + + /** + * Determine if provided node is the last of their parent block. + * @private + * @param {ASTNode} node node to test + * @returns {boolean} True if `node` is last of their parent block. + */ + function isLastNode(node) { + const token = sourceCode.getTokenAfter(node); + + return !token || (token.type === "Punctuator" && token.value === "}"); + } + + /** + * Gets the last line of a group of consecutive comments + * @param {number} commentStartLine The starting line of the group + * @returns {number} The number of the last comment line of the group + */ + function getLastCommentLineOfBlock(commentStartLine) { + const currentCommentEnd = commentEndLine[commentStartLine]; + + return commentEndLine[currentCommentEnd + 1] ? getLastCommentLineOfBlock(currentCommentEnd + 1) : currentCommentEnd; + } + + /** + * Determine if a token starts more than one line after a comment ends + * @param {token} token The token being checked + * @param {integer} commentStartLine The line number on which the comment starts + * @returns {boolean} True if `token` does not start immediately after a comment + */ + function hasBlankLineAfterComment(token, commentStartLine) { + return token.loc.start.line > getLastCommentLineOfBlock(commentStartLine) + 1; + } + + /** + * Checks that a blank line exists after a variable declaration when mode is + * set to "always", or checks that there is no blank line when mode is set + * to "never" + * @private + * @param {ASTNode} node `VariableDeclaration` node to test + * @returns {void} + */ + function checkForBlankLine(node) { + + /* + * lastToken is the last token on the node's line. It will usually also be the last token of the node, but it will + * sometimes be second-last if there is a semicolon on a different line. + */ + const lastToken = getLastToken(node), + + /* + * If lastToken is the last token of the node, nextToken should be the token after the node. Otherwise, nextToken + * is the last token of the node. + */ + nextToken = lastToken === sourceCode.getLastToken(node) ? sourceCode.getTokenAfter(node) : sourceCode.getLastToken(node), + nextLineNum = lastToken.loc.end.line + 1; + + // Ignore if there is no following statement + if (!nextToken) { + return; + } + + // Ignore if parent of node is a for variant + if (isForTypeSpecifier(node.parent.type)) { + return; + } + + // Ignore if parent of node is an export specifier + if (isExportSpecifier(node.parent.type)) { + return; + } + + /* + * Some coding styles use multiple `var` statements, so do nothing if + * the next token is a `var` statement. + */ + if (nextToken.type === "Keyword" && isVar(nextToken.value)) { + return; + } + + // Ignore if it is last statement in a block + if (isLastNode(node)) { + return; + } + + // Next statement is not a `var`... + const noNextLineToken = nextToken.loc.start.line > nextLineNum; + const hasNextLineComment = (typeof commentEndLine[nextLineNum] !== "undefined"); + + if (mode === "never" && noNextLineToken && !hasNextLineComment) { + context.report({ + node, + messageId: "unexpected", + data: { identifier: node.name }, + fix(fixer) { + const linesBetween = sourceCode.getText().slice(lastToken.range[1], nextToken.range[0]).split(astUtils.LINEBREAK_MATCHER); + + return fixer.replaceTextRange([lastToken.range[1], nextToken.range[0]], `${linesBetween.slice(0, -1).join("")}\n${linesBetween[linesBetween.length - 1]}`); + } + }); + } + + // Token on the next line, or comment without blank line + if ( + mode === "always" && ( + !noNextLineToken || + hasNextLineComment && !hasBlankLineAfterComment(nextToken, nextLineNum) + ) + ) { + context.report({ + node, + messageId: "expected", + data: { identifier: node.name }, + fix(fixer) { + if ((noNextLineToken ? getLastCommentLineOfBlock(nextLineNum) : lastToken.loc.end.line) === nextToken.loc.start.line) { + return fixer.insertTextBefore(nextToken, "\n\n"); + } + + return fixer.insertTextBeforeRange([nextToken.range[0] - nextToken.loc.start.column, nextToken.range[1]], "\n"); + } + }); + } + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + VariableDeclaration: checkForBlankLine + }; + + } +}; diff --git a/eslint/lib/rules/newline-before-return.js b/eslint/lib/rules/newline-before-return.js new file mode 100644 index 0000000..65ca323 --- /dev/null +++ b/eslint/lib/rules/newline-before-return.js @@ -0,0 +1,217 @@ +/** + * @fileoverview Rule to require newlines before `return` statement + * @author Kai Cataldo + * @deprecated + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "require an empty line before `return` statements", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/newline-before-return" + }, + + fixable: "whitespace", + schema: [], + messages: { + expected: "Expected newline before return statement." + }, + + deprecated: true, + replacedBy: ["padding-line-between-statements"] + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Tests whether node is preceded by supplied tokens + * @param {ASTNode} node node to check + * @param {Array} testTokens array of tokens to test against + * @returns {boolean} Whether or not the node is preceded by one of the supplied tokens + * @private + */ + function isPrecededByTokens(node, testTokens) { + const tokenBefore = sourceCode.getTokenBefore(node); + + return testTokens.some(token => tokenBefore.value === token); + } + + /** + * Checks whether node is the first node after statement or in block + * @param {ASTNode} node node to check + * @returns {boolean} Whether or not the node is the first node after statement or in block + * @private + */ + function isFirstNode(node) { + const parentType = node.parent.type; + + if (node.parent.body) { + return Array.isArray(node.parent.body) + ? node.parent.body[0] === node + : node.parent.body === node; + } + + if (parentType === "IfStatement") { + return isPrecededByTokens(node, ["else", ")"]); + } + if (parentType === "DoWhileStatement") { + return isPrecededByTokens(node, ["do"]); + } + if (parentType === "SwitchCase") { + return isPrecededByTokens(node, [":"]); + } + return isPrecededByTokens(node, [")"]); + + } + + /** + * Returns the number of lines of comments that precede the node + * @param {ASTNode} node node to check for overlapping comments + * @param {number} lineNumTokenBefore line number of previous token, to check for overlapping comments + * @returns {number} Number of lines of comments that precede the node + * @private + */ + function calcCommentLines(node, lineNumTokenBefore) { + const comments = sourceCode.getCommentsBefore(node); + let numLinesComments = 0; + + if (!comments.length) { + return numLinesComments; + } + + comments.forEach(comment => { + numLinesComments++; + + if (comment.type === "Block") { + numLinesComments += comment.loc.end.line - comment.loc.start.line; + } + + // avoid counting lines with inline comments twice + if (comment.loc.start.line === lineNumTokenBefore) { + numLinesComments--; + } + + if (comment.loc.end.line === node.loc.start.line) { + numLinesComments--; + } + }); + + return numLinesComments; + } + + /** + * Returns the line number of the token before the node that is passed in as an argument + * @param {ASTNode} node The node to use as the start of the calculation + * @returns {number} Line number of the token before `node` + * @private + */ + function getLineNumberOfTokenBefore(node) { + const tokenBefore = sourceCode.getTokenBefore(node); + let lineNumTokenBefore; + + /** + * Global return (at the beginning of a script) is a special case. + * If there is no token before `return`, then we expect no line + * break before the return. Comments are allowed to occupy lines + * before the global return, just no blank lines. + * Setting lineNumTokenBefore to zero in that case results in the + * desired behavior. + */ + if (tokenBefore) { + lineNumTokenBefore = tokenBefore.loc.end.line; + } else { + lineNumTokenBefore = 0; // global return at beginning of script + } + + return lineNumTokenBefore; + } + + /** + * Checks whether node is preceded by a newline + * @param {ASTNode} node node to check + * @returns {boolean} Whether or not the node is preceded by a newline + * @private + */ + function hasNewlineBefore(node) { + const lineNumNode = node.loc.start.line; + const lineNumTokenBefore = getLineNumberOfTokenBefore(node); + const commentLines = calcCommentLines(node, lineNumTokenBefore); + + return (lineNumNode - lineNumTokenBefore - commentLines) > 1; + } + + /** + * Checks whether it is safe to apply a fix to a given return statement. + * + * The fix is not considered safe if the given return statement has leading comments, + * as we cannot safely determine if the newline should be added before or after the comments. + * For more information, see: https://github.com/eslint/eslint/issues/5958#issuecomment-222767211 + * @param {ASTNode} node The return statement node to check. + * @returns {boolean} `true` if it can fix the node. + * @private + */ + function canFix(node) { + const leadingComments = sourceCode.getCommentsBefore(node); + const lastLeadingComment = leadingComments[leadingComments.length - 1]; + const tokenBefore = sourceCode.getTokenBefore(node); + + if (leadingComments.length === 0) { + return true; + } + + /* + * if the last leading comment ends in the same line as the previous token and + * does not share a line with the `return` node, we can consider it safe to fix. + * Example: + * function a() { + * var b; //comment + * return; + * } + */ + if (lastLeadingComment.loc.end.line === tokenBefore.loc.end.line && + lastLeadingComment.loc.end.line !== node.loc.start.line) { + return true; + } + + return false; + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + ReturnStatement(node) { + if (!isFirstNode(node) && !hasNewlineBefore(node)) { + context.report({ + node, + messageId: "expected", + fix(fixer) { + if (canFix(node)) { + const tokenBefore = sourceCode.getTokenBefore(node); + const newlines = node.loc.start.line === tokenBefore.loc.end.line ? "\n\n" : "\n"; + + return fixer.insertTextBefore(node, newlines); + } + return null; + } + }); + } + } + }; + } +}; diff --git a/eslint/lib/rules/newline-per-chained-call.js b/eslint/lib/rules/newline-per-chained-call.js new file mode 100644 index 0000000..8ad8838 --- /dev/null +++ b/eslint/lib/rules/newline-per-chained-call.js @@ -0,0 +1,110 @@ +/** + * @fileoverview Rule to ensure newline per method call when chaining calls + * @author Rajendra Patil + * @author Burak Yigit Kaya + */ + +"use strict"; + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "require a newline after each call in a method chain", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/newline-per-chained-call" + }, + + fixable: "whitespace", + + schema: [{ + type: "object", + properties: { + ignoreChainWithDepth: { + type: "integer", + minimum: 1, + maximum: 10, + default: 2 + } + }, + additionalProperties: false + }], + messages: { + expected: "Expected line break before `{{callee}}`." + } + }, + + create(context) { + + const options = context.options[0] || {}, + ignoreChainWithDepth = options.ignoreChainWithDepth || 2; + + const sourceCode = context.getSourceCode(); + + /** + * Get the prefix of a given MemberExpression node. + * If the MemberExpression node is a computed value it returns a + * left bracket. If not it returns a period. + * @param {ASTNode} node A MemberExpression node to get + * @returns {string} The prefix of the node. + */ + function getPrefix(node) { + return node.computed ? "[" : "."; + } + + /** + * Gets the property text of a given MemberExpression node. + * If the text is multiline, this returns only the first line. + * @param {ASTNode} node A MemberExpression node to get. + * @returns {string} The property text of the node. + */ + function getPropertyText(node) { + const prefix = getPrefix(node); + const lines = sourceCode.getText(node.property).split(astUtils.LINEBREAK_MATCHER); + const suffix = node.computed && lines.length === 1 ? "]" : ""; + + return prefix + lines[0] + suffix; + } + + return { + "CallExpression:exit"(node) { + if (!node.callee || node.callee.type !== "MemberExpression") { + return; + } + + const callee = node.callee; + let parent = callee.object; + let depth = 1; + + while (parent && parent.callee) { + depth += 1; + parent = parent.callee.object; + } + + if (depth > ignoreChainWithDepth && astUtils.isTokenOnSameLine(callee.object, callee.property)) { + context.report({ + node: callee.property, + loc: callee.property.loc.start, + messageId: "expected", + data: { + callee: getPropertyText(callee) + }, + fix(fixer) { + const firstTokenAfterObject = sourceCode.getTokenAfter(callee.object, astUtils.isNotClosingParenToken); + + return fixer.insertTextBefore(firstTokenAfterObject, "\n"); + } + }); + } + } + }; + } +}; diff --git a/eslint/lib/rules/no-alert.js b/eslint/lib/rules/no-alert.js new file mode 100644 index 0000000..22d0dd5 --- /dev/null +++ b/eslint/lib/rules/no-alert.js @@ -0,0 +1,129 @@ +/** + * @fileoverview Rule to flag use of alert, confirm, prompt + * @author Nicholas C. Zakas + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const { + getStaticPropertyName: getPropertyName, + getVariableByName +} = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Checks if the given name is a prohibited identifier. + * @param {string} name The name to check + * @returns {boolean} Whether or not the name is prohibited. + */ +function isProhibitedIdentifier(name) { + return /^(alert|confirm|prompt)$/u.test(name); +} + +/** + * Finds the eslint-scope reference in the given scope. + * @param {Object} scope The scope to search. + * @param {ASTNode} node The identifier node. + * @returns {Reference|null} Returns the found reference or null if none were found. + */ +function findReference(scope, node) { + const references = scope.references.filter(reference => reference.identifier.range[0] === node.range[0] && + reference.identifier.range[1] === node.range[1]); + + if (references.length === 1) { + return references[0]; + } + return null; +} + +/** + * Checks if the given identifier node is shadowed in the given scope. + * @param {Object} scope The current scope. + * @param {string} node The identifier node to check + * @returns {boolean} Whether or not the name is shadowed. + */ +function isShadowed(scope, node) { + const reference = findReference(scope, node); + + return reference && reference.resolved && reference.resolved.defs.length > 0; +} + +/** + * Checks if the given identifier node is a ThisExpression in the global scope or the global window property. + * @param {Object} scope The current scope. + * @param {string} node The identifier node to check + * @returns {boolean} Whether or not the node is a reference to the global object. + */ +function isGlobalThisReferenceOrGlobalWindow(scope, node) { + if (scope.type === "global" && node.type === "ThisExpression") { + return true; + } + if (node.name === "window" || (node.name === "globalThis" && getVariableByName(scope, "globalThis"))) { + return !isShadowed(scope, node); + } + + return false; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow the use of `alert`, `confirm`, and `prompt`", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-alert" + }, + + schema: [], + + messages: { + unexpected: "Unexpected {{name}}." + } + }, + + create(context) { + return { + CallExpression(node) { + const callee = node.callee, + currentScope = context.getScope(); + + // without window. + if (callee.type === "Identifier") { + const name = callee.name; + + if (!isShadowed(currentScope, callee) && isProhibitedIdentifier(callee.name)) { + context.report({ + node, + messageId: "unexpected", + data: { name } + }); + } + + } else if (callee.type === "MemberExpression" && isGlobalThisReferenceOrGlobalWindow(currentScope, callee.object)) { + const name = getPropertyName(callee); + + if (isProhibitedIdentifier(name)) { + context.report({ + node, + messageId: "unexpected", + data: { name } + }); + } + } + } + }; + + } +}; diff --git a/eslint/lib/rules/no-array-constructor.js b/eslint/lib/rules/no-array-constructor.js new file mode 100644 index 0000000..90c6d6b --- /dev/null +++ b/eslint/lib/rules/no-array-constructor.js @@ -0,0 +1,54 @@ +/** + * @fileoverview Disallow construction of dense arrays using the Array constructor + * @author Matt DuVall + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow `Array` constructors", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/no-array-constructor" + }, + + schema: [], + + messages: { + preferLiteral: "The array literal notation [] is preferable." + } + }, + + create(context) { + + /** + * Disallow construction of dense arrays using the Array constructor + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function check(node) { + if ( + node.arguments.length !== 1 && + node.callee.type === "Identifier" && + node.callee.name === "Array" + ) { + context.report({ node, messageId: "preferLiteral" }); + } + } + + return { + CallExpression: check, + NewExpression: check + }; + + } +}; diff --git a/eslint/lib/rules/no-async-promise-executor.js b/eslint/lib/rules/no-async-promise-executor.js new file mode 100644 index 0000000..553311e --- /dev/null +++ b/eslint/lib/rules/no-async-promise-executor.js @@ -0,0 +1,39 @@ +/** + * @fileoverview disallow using an async function as a Promise executor + * @author Teddy Katz + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow using an async function as a Promise executor", + category: "Possible Errors", + recommended: true, + url: "https://eslint.org/docs/rules/no-async-promise-executor" + }, + + fixable: null, + schema: [], + messages: { + async: "Promise executor functions should not be async." + } + }, + + create(context) { + return { + "NewExpression[callee.name='Promise'][arguments.0.async=true]"(node) { + context.report({ + node: context.getSourceCode().getFirstToken(node.arguments[0], token => token.value === "async"), + messageId: "async" + }); + } + }; + } +}; diff --git a/eslint/lib/rules/no-await-in-loop.js b/eslint/lib/rules/no-await-in-loop.js new file mode 100644 index 0000000..9ca8986 --- /dev/null +++ b/eslint/lib/rules/no-await-in-loop.js @@ -0,0 +1,106 @@ +/** + * @fileoverview Rule to disallow uses of await inside of loops. + * @author Nat Mote (nmote) + */ +"use strict"; + +/** + * Check whether it should stop traversing ancestors at the given node. + * @param {ASTNode} node A node to check. + * @returns {boolean} `true` if it should stop traversing. + */ +function isBoundary(node) { + const t = node.type; + + return ( + t === "FunctionDeclaration" || + t === "FunctionExpression" || + t === "ArrowFunctionExpression" || + + /* + * Don't report the await expressions on for-await-of loop since it's + * asynchronous iteration intentionally. + */ + (t === "ForOfStatement" && node.await === true) + ); +} + +/** + * Check whether the given node is in loop. + * @param {ASTNode} node A node to check. + * @param {ASTNode} parent A parent node to check. + * @returns {boolean} `true` if the node is in loop. + */ +function isLooped(node, parent) { + switch (parent.type) { + case "ForStatement": + return ( + node === parent.test || + node === parent.update || + node === parent.body + ); + + case "ForOfStatement": + case "ForInStatement": + return node === parent.body; + + case "WhileStatement": + case "DoWhileStatement": + return node === parent.test || node === parent.body; + + default: + return false; + } +} + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow `await` inside of loops", + category: "Possible Errors", + recommended: false, + url: "https://eslint.org/docs/rules/no-await-in-loop" + }, + + schema: [], + + messages: { + unexpectedAwait: "Unexpected `await` inside a loop." + } + }, + create(context) { + + /** + * Validate an await expression. + * @param {ASTNode} awaitNode An AwaitExpression or ForOfStatement node to validate. + * @returns {void} + */ + function validate(awaitNode) { + if (awaitNode.type === "ForOfStatement" && !awaitNode.await) { + return; + } + + let node = awaitNode; + let parent = node.parent; + + while (parent && !isBoundary(parent)) { + if (isLooped(node, parent)) { + context.report({ + node: awaitNode, + messageId: "unexpectedAwait" + }); + return; + } + node = parent; + parent = parent.parent; + } + } + + return { + AwaitExpression: validate, + ForOfStatement: validate + }; + } +}; diff --git a/eslint/lib/rules/no-bitwise.js b/eslint/lib/rules/no-bitwise.js new file mode 100644 index 0000000..a9c3360 --- /dev/null +++ b/eslint/lib/rules/no-bitwise.js @@ -0,0 +1,119 @@ +/** + * @fileoverview Rule to flag bitwise identifiers + * @author Nicholas C. Zakas + */ + +"use strict"; + +/* + * + * Set of bitwise operators. + * + */ +const BITWISE_OPERATORS = [ + "^", "|", "&", "<<", ">>", ">>>", + "^=", "|=", "&=", "<<=", ">>=", ">>>=", + "~" +]; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow bitwise operators", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/no-bitwise" + }, + + schema: [ + { + type: "object", + properties: { + allow: { + type: "array", + items: { + enum: BITWISE_OPERATORS + }, + uniqueItems: true + }, + int32Hint: { + type: "boolean", + default: false + } + }, + additionalProperties: false + } + ], + + messages: { + unexpected: "Unexpected use of '{{operator}}'." + } + }, + + create(context) { + const options = context.options[0] || {}; + const allowed = options.allow || []; + const int32Hint = options.int32Hint === true; + + /** + * Reports an unexpected use of a bitwise operator. + * @param {ASTNode} node Node which contains the bitwise operator. + * @returns {void} + */ + function report(node) { + context.report({ node, messageId: "unexpected", data: { operator: node.operator } }); + } + + /** + * Checks if the given node has a bitwise operator. + * @param {ASTNode} node The node to check. + * @returns {boolean} Whether or not the node has a bitwise operator. + */ + function hasBitwiseOperator(node) { + return BITWISE_OPERATORS.indexOf(node.operator) !== -1; + } + + /** + * Checks if exceptions were provided, e.g. `{ allow: ['~', '|'] }`. + * @param {ASTNode} node The node to check. + * @returns {boolean} Whether or not the node has a bitwise operator. + */ + function allowedOperator(node) { + return allowed.indexOf(node.operator) !== -1; + } + + /** + * Checks if the given bitwise operator is used for integer typecasting, i.e. "|0" + * @param {ASTNode} node The node to check. + * @returns {boolean} whether the node is used in integer typecasting. + */ + function isInt32Hint(node) { + return int32Hint && node.operator === "|" && node.right && + node.right.type === "Literal" && node.right.value === 0; + } + + /** + * Report if the given node contains a bitwise operator. + * @param {ASTNode} node The node to check. + * @returns {void} + */ + function checkNodeForBitwiseOperator(node) { + if (hasBitwiseOperator(node) && !allowedOperator(node) && !isInt32Hint(node)) { + report(node); + } + } + + return { + AssignmentExpression: checkNodeForBitwiseOperator, + BinaryExpression: checkNodeForBitwiseOperator, + UnaryExpression: checkNodeForBitwiseOperator + }; + + } +}; diff --git a/eslint/lib/rules/no-buffer-constructor.js b/eslint/lib/rules/no-buffer-constructor.js new file mode 100644 index 0000000..bf4c889 --- /dev/null +++ b/eslint/lib/rules/no-buffer-constructor.js @@ -0,0 +1,45 @@ +/** + * @fileoverview disallow use of the Buffer() constructor + * @author Teddy Katz + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow use of the `Buffer()` constructor", + category: "Node.js and CommonJS", + recommended: false, + url: "https://eslint.org/docs/rules/no-buffer-constructor" + }, + + schema: [], + + messages: { + deprecated: "{{expr}} is deprecated. Use Buffer.from(), Buffer.alloc(), or Buffer.allocUnsafe() instead." + } + }, + + create(context) { + + //---------------------------------------------------------------------- + // Public + //---------------------------------------------------------------------- + + return { + "CallExpression[callee.name='Buffer'], NewExpression[callee.name='Buffer']"(node) { + context.report({ + node, + messageId: "deprecated", + data: { expr: node.type === "CallExpression" ? "Buffer()" : "new Buffer()" } + }); + } + }; + } +}; diff --git a/eslint/lib/rules/no-caller.js b/eslint/lib/rules/no-caller.js new file mode 100644 index 0000000..5fe1bd4 --- /dev/null +++ b/eslint/lib/rules/no-caller.js @@ -0,0 +1,46 @@ +/** + * @fileoverview Rule to flag use of arguments.callee and arguments.caller. + * @author Nicholas C. Zakas + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow the use of `arguments.caller` or `arguments.callee`", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-caller" + }, + + schema: [], + + messages: { + unexpected: "Avoid arguments.{{prop}}." + } + }, + + create(context) { + + return { + + MemberExpression(node) { + const objectName = node.object.name, + propertyName = node.property.name; + + if (objectName === "arguments" && !node.computed && propertyName && propertyName.match(/^calle[er]$/u)) { + context.report({ node, messageId: "unexpected", data: { prop: propertyName } }); + } + + } + }; + + } +}; diff --git a/eslint/lib/rules/no-case-declarations.js b/eslint/lib/rules/no-case-declarations.js new file mode 100644 index 0000000..1d54e22 --- /dev/null +++ b/eslint/lib/rules/no-case-declarations.js @@ -0,0 +1,64 @@ +/** + * @fileoverview Rule to flag use of an lexical declarations inside a case clause + * @author Erik Arvidsson + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow lexical declarations in case clauses", + category: "Best Practices", + recommended: true, + url: "https://eslint.org/docs/rules/no-case-declarations" + }, + + schema: [], + + messages: { + unexpected: "Unexpected lexical declaration in case block." + } + }, + + create(context) { + + /** + * Checks whether or not a node is a lexical declaration. + * @param {ASTNode} node A direct child statement of a switch case. + * @returns {boolean} Whether or not the node is a lexical declaration. + */ + function isLexicalDeclaration(node) { + switch (node.type) { + case "FunctionDeclaration": + case "ClassDeclaration": + return true; + case "VariableDeclaration": + return node.kind !== "var"; + default: + return false; + } + } + + return { + SwitchCase(node) { + for (let i = 0; i < node.consequent.length; i++) { + const statement = node.consequent[i]; + + if (isLexicalDeclaration(statement)) { + context.report({ + node: statement, + messageId: "unexpected" + }); + } + } + } + }; + + } +}; diff --git a/eslint/lib/rules/no-catch-shadow.js b/eslint/lib/rules/no-catch-shadow.js new file mode 100644 index 0000000..4917af8 --- /dev/null +++ b/eslint/lib/rules/no-catch-shadow.js @@ -0,0 +1,80 @@ +/** + * @fileoverview Rule to flag variable leak in CatchClauses in IE 8 and earlier + * @author Ian Christian Myers + * @deprecated in ESLint v5.1.0 + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow `catch` clause parameters from shadowing variables in the outer scope", + category: "Variables", + recommended: false, + url: "https://eslint.org/docs/rules/no-catch-shadow" + }, + + replacedBy: ["no-shadow"], + + deprecated: true, + schema: [], + + messages: { + mutable: "Value of '{{name}}' may be overwritten in IE 8 and earlier." + } + }, + + create(context) { + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Check if the parameters are been shadowed + * @param {Object} scope current scope + * @param {string} name parameter name + * @returns {boolean} True is its been shadowed + */ + function paramIsShadowing(scope, name) { + return astUtils.getVariableByName(scope, name) !== null; + } + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + + "CatchClause[param!=null]"(node) { + let scope = context.getScope(); + + /* + * When ecmaVersion >= 6, CatchClause creates its own scope + * so start from one upper scope to exclude the current node + */ + if (scope.block === node) { + scope = scope.upper; + } + + if (paramIsShadowing(scope, node.param.name)) { + context.report({ node, messageId: "mutable", data: { name: node.param.name } }); + } + } + }; + + } +}; diff --git a/eslint/lib/rules/no-class-assign.js b/eslint/lib/rules/no-class-assign.js new file mode 100644 index 0000000..887058b --- /dev/null +++ b/eslint/lib/rules/no-class-assign.js @@ -0,0 +1,61 @@ +/** + * @fileoverview A rule to disallow modifying variables of class declarations + * @author Toru Nagashima + */ + +"use strict"; + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow reassigning class members", + category: "ECMAScript 6", + recommended: true, + url: "https://eslint.org/docs/rules/no-class-assign" + }, + + schema: [], + + messages: { + class: "'{{name}}' is a class." + } + }, + + create(context) { + + /** + * Finds and reports references that are non initializer and writable. + * @param {Variable} variable A variable to check. + * @returns {void} + */ + function checkVariable(variable) { + astUtils.getModifyingReferences(variable.references).forEach(reference => { + context.report({ node: reference.identifier, messageId: "class", data: { name: reference.identifier.name } }); + + }); + } + + /** + * Finds and reports references that are non initializer and writable. + * @param {ASTNode} node A ClassDeclaration/ClassExpression node to check. + * @returns {void} + */ + function checkForClass(node) { + context.getDeclaredVariables(node).forEach(checkVariable); + } + + return { + ClassDeclaration: checkForClass, + ClassExpression: checkForClass + }; + + } +}; diff --git a/eslint/lib/rules/no-compare-neg-zero.js b/eslint/lib/rules/no-compare-neg-zero.js new file mode 100644 index 0000000..0c6865a --- /dev/null +++ b/eslint/lib/rules/no-compare-neg-zero.js @@ -0,0 +1,60 @@ +/** + * @fileoverview The rule should warn against code that tries to compare against -0. + * @author Aladdin-ADD + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow comparing against -0", + category: "Possible Errors", + recommended: true, + url: "https://eslint.org/docs/rules/no-compare-neg-zero" + }, + + fixable: null, + schema: [], + + messages: { + unexpected: "Do not use the '{{operator}}' operator to compare against -0." + } + }, + + create(context) { + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Checks a given node is -0 + * @param {ASTNode} node A node to check. + * @returns {boolean} `true` if the node is -0. + */ + function isNegZero(node) { + return node.type === "UnaryExpression" && node.operator === "-" && node.argument.type === "Literal" && node.argument.value === 0; + } + const OPERATORS_TO_CHECK = new Set([">", ">=", "<", "<=", "==", "===", "!=", "!=="]); + + return { + BinaryExpression(node) { + if (OPERATORS_TO_CHECK.has(node.operator)) { + if (isNegZero(node.left) || isNegZero(node.right)) { + context.report({ + node, + messageId: "unexpected", + data: { operator: node.operator } + }); + } + } + } + }; + } +}; diff --git a/eslint/lib/rules/no-cond-assign.js b/eslint/lib/rules/no-cond-assign.js new file mode 100644 index 0000000..3843a7a --- /dev/null +++ b/eslint/lib/rules/no-cond-assign.js @@ -0,0 +1,159 @@ +/** + * @fileoverview Rule to flag assignment in a conditional statement's test expression + * @author Stephen Murray + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const TEST_CONDITION_PARENT_TYPES = new Set(["IfStatement", "WhileStatement", "DoWhileStatement", "ForStatement", "ConditionalExpression"]); + +const NODE_DESCRIPTIONS = { + DoWhileStatement: "a 'do...while' statement", + ForStatement: "a 'for' statement", + IfStatement: "an 'if' statement", + WhileStatement: "a 'while' statement" +}; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow assignment operators in conditional expressions", + category: "Possible Errors", + recommended: true, + url: "https://eslint.org/docs/rules/no-cond-assign" + }, + + schema: [ + { + enum: ["except-parens", "always"] + } + ], + + messages: { + unexpected: "Unexpected assignment within {{type}}.", + + // must match JSHint's error message + missing: "Expected a conditional expression and instead saw an assignment." + } + }, + + create(context) { + + const prohibitAssign = (context.options[0] || "except-parens"); + + const sourceCode = context.getSourceCode(); + + /** + * Check whether an AST node is the test expression for a conditional statement. + * @param {!Object} node The node to test. + * @returns {boolean} `true` if the node is the text expression for a conditional statement; otherwise, `false`. + */ + function isConditionalTestExpression(node) { + return node.parent && + TEST_CONDITION_PARENT_TYPES.has(node.parent.type) && + node === node.parent.test; + } + + /** + * Given an AST node, perform a bottom-up search for the first ancestor that represents a conditional statement. + * @param {!Object} node The node to use at the start of the search. + * @returns {?Object} The closest ancestor node that represents a conditional statement. + */ + function findConditionalAncestor(node) { + let currentAncestor = node; + + do { + if (isConditionalTestExpression(currentAncestor)) { + return currentAncestor.parent; + } + } while ((currentAncestor = currentAncestor.parent) && !astUtils.isFunction(currentAncestor)); + + return null; + } + + /** + * Check whether the code represented by an AST node is enclosed in two sets of parentheses. + * @param {!Object} node The node to test. + * @returns {boolean} `true` if the code is enclosed in two sets of parentheses; otherwise, `false`. + */ + function isParenthesisedTwice(node) { + const previousToken = sourceCode.getTokenBefore(node, 1), + nextToken = sourceCode.getTokenAfter(node, 1); + + return astUtils.isParenthesised(sourceCode, node) && + previousToken && astUtils.isOpeningParenToken(previousToken) && previousToken.range[1] <= node.range[0] && + astUtils.isClosingParenToken(nextToken) && nextToken.range[0] >= node.range[1]; + } + + /** + * Check a conditional statement's test expression for top-level assignments that are not enclosed in parentheses. + * @param {!Object} node The node for the conditional statement. + * @returns {void} + */ + function testForAssign(node) { + if (node.test && + (node.test.type === "AssignmentExpression") && + (node.type === "ForStatement" + ? !astUtils.isParenthesised(sourceCode, node.test) + : !isParenthesisedTwice(node.test) + ) + ) { + + context.report({ + node: node.test, + messageId: "missing" + }); + } + } + + /** + * Check whether an assignment expression is descended from a conditional statement's test expression. + * @param {!Object} node The node for the assignment expression. + * @returns {void} + */ + function testForConditionalAncestor(node) { + const ancestor = findConditionalAncestor(node); + + if (ancestor) { + context.report({ + node, + messageId: "unexpected", + data: { + type: NODE_DESCRIPTIONS[ancestor.type] || ancestor.type + } + }); + } + } + + if (prohibitAssign === "always") { + return { + AssignmentExpression: testForConditionalAncestor + }; + } + + return { + DoWhileStatement: testForAssign, + ForStatement: testForAssign, + IfStatement: testForAssign, + WhileStatement: testForAssign, + ConditionalExpression: testForAssign + }; + + } +}; diff --git a/eslint/lib/rules/no-confusing-arrow.js b/eslint/lib/rules/no-confusing-arrow.js new file mode 100644 index 0000000..9009b64 --- /dev/null +++ b/eslint/lib/rules/no-confusing-arrow.js @@ -0,0 +1,85 @@ +/** + * @fileoverview A rule to warn against using arrow functions when they could be + * confused with comparisons + * @author Jxck + */ + +"use strict"; + +const astUtils = require("./utils/ast-utils.js"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Checks whether or not a node is a conditional expression. + * @param {ASTNode} node node to test + * @returns {boolean} `true` if the node is a conditional expression. + */ +function isConditional(node) { + return node && node.type === "ConditionalExpression"; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow arrow functions where they could be confused with comparisons", + category: "ECMAScript 6", + recommended: false, + url: "https://eslint.org/docs/rules/no-confusing-arrow" + }, + + fixable: "code", + + schema: [{ + type: "object", + properties: { + allowParens: { type: "boolean", default: true } + }, + additionalProperties: false + }], + + messages: { + confusing: "Arrow function used ambiguously with a conditional expression." + } + }, + + create(context) { + const config = context.options[0] || {}; + const allowParens = config.allowParens || (config.allowParens === void 0); + const sourceCode = context.getSourceCode(); + + + /** + * Reports if an arrow function contains an ambiguous conditional. + * @param {ASTNode} node A node to check and report. + * @returns {void} + */ + function checkArrowFunc(node) { + const body = node.body; + + if (isConditional(body) && !(allowParens && astUtils.isParenthesised(sourceCode, body))) { + context.report({ + node, + messageId: "confusing", + fix(fixer) { + + // if `allowParens` is not set to true don't bother wrapping in parens + return allowParens && fixer.replaceText(node.body, `(${sourceCode.getText(node.body)})`); + } + }); + } + } + + return { + ArrowFunctionExpression: checkArrowFunc + }; + } +}; diff --git a/eslint/lib/rules/no-console.js b/eslint/lib/rules/no-console.js new file mode 100644 index 0000000..56dbbc3 --- /dev/null +++ b/eslint/lib/rules/no-console.js @@ -0,0 +1,134 @@ +/** + * @fileoverview Rule to flag use of console object + * @author Nicholas C. Zakas + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow the use of `console`", + category: "Possible Errors", + recommended: false, + url: "https://eslint.org/docs/rules/no-console" + }, + + schema: [ + { + type: "object", + properties: { + allow: { + type: "array", + items: { + type: "string" + }, + minItems: 1, + uniqueItems: true + } + }, + additionalProperties: false + } + ], + + messages: { + unexpected: "Unexpected console statement." + } + }, + + create(context) { + const options = context.options[0] || {}; + const allowed = options.allow || []; + + /** + * Checks whether the given reference is 'console' or not. + * @param {eslint-scope.Reference} reference The reference to check. + * @returns {boolean} `true` if the reference is 'console'. + */ + function isConsole(reference) { + const id = reference.identifier; + + return id && id.name === "console"; + } + + /** + * Checks whether the property name of the given MemberExpression node + * is allowed by options or not. + * @param {ASTNode} node The MemberExpression node to check. + * @returns {boolean} `true` if the property name of the node is allowed. + */ + function isAllowed(node) { + const propertyName = astUtils.getStaticPropertyName(node); + + return propertyName && allowed.indexOf(propertyName) !== -1; + } + + /** + * Checks whether the given reference is a member access which is not + * allowed by options or not. + * @param {eslint-scope.Reference} reference The reference to check. + * @returns {boolean} `true` if the reference is a member access which + * is not allowed by options. + */ + function isMemberAccessExceptAllowed(reference) { + const node = reference.identifier; + const parent = node.parent; + + return ( + parent.type === "MemberExpression" && + parent.object === node && + !isAllowed(parent) + ); + } + + /** + * Reports the given reference as a violation. + * @param {eslint-scope.Reference} reference The reference to report. + * @returns {void} + */ + function report(reference) { + const node = reference.identifier.parent; + + context.report({ + node, + loc: node.loc, + messageId: "unexpected" + }); + } + + return { + "Program:exit"() { + const scope = context.getScope(); + const consoleVar = astUtils.getVariableByName(scope, "console"); + const shadowed = consoleVar && consoleVar.defs.length > 0; + + /* + * 'scope.through' includes all references to undefined + * variables. If the variable 'console' is not defined, it uses + * 'scope.through'. + */ + const references = consoleVar + ? consoleVar.references + : scope.through.filter(isConsole); + + if (!shadowed) { + references + .filter(isMemberAccessExceptAllowed) + .forEach(report); + } + } + }; + } +}; diff --git a/eslint/lib/rules/no-const-assign.js b/eslint/lib/rules/no-const-assign.js new file mode 100644 index 0000000..e4ae891 --- /dev/null +++ b/eslint/lib/rules/no-const-assign.js @@ -0,0 +1,54 @@ +/** + * @fileoverview A rule to disallow modifying variables that are declared using `const` + * @author Toru Nagashima + */ + +"use strict"; + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow reassigning `const` variables", + category: "ECMAScript 6", + recommended: true, + url: "https://eslint.org/docs/rules/no-const-assign" + }, + + schema: [], + + messages: { + const: "'{{name}}' is constant." + } + }, + + create(context) { + + /** + * Finds and reports references that are non initializer and writable. + * @param {Variable} variable A variable to check. + * @returns {void} + */ + function checkVariable(variable) { + astUtils.getModifyingReferences(variable.references).forEach(reference => { + context.report({ node: reference.identifier, messageId: "const", data: { name: reference.identifier.name } }); + }); + } + + return { + VariableDeclaration(node) { + if (node.kind === "const") { + context.getDeclaredVariables(node).forEach(checkVariable); + } + } + }; + + } +}; diff --git a/eslint/lib/rules/no-constant-condition.js b/eslint/lib/rules/no-constant-condition.js new file mode 100644 index 0000000..5e58386 --- /dev/null +++ b/eslint/lib/rules/no-constant-condition.js @@ -0,0 +1,253 @@ +/** + * @fileoverview Rule to flag use constant conditions + * @author Christian Schulz + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const EQUALITY_OPERATORS = ["===", "!==", "==", "!="]; +const RELATIONAL_OPERATORS = [">", "<", ">=", "<=", "in", "instanceof"]; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow constant expressions in conditions", + category: "Possible Errors", + recommended: true, + url: "https://eslint.org/docs/rules/no-constant-condition" + }, + + schema: [ + { + type: "object", + properties: { + checkLoops: { + type: "boolean", + default: true + } + }, + additionalProperties: false + } + ], + + messages: { + unexpected: "Unexpected constant condition." + } + }, + + create(context) { + const options = context.options[0] || {}, + checkLoops = options.checkLoops !== false, + loopSetStack = []; + + let loopsInCurrentScope = new Set(); + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + + /** + * Checks if a branch node of LogicalExpression short circuits the whole condition + * @param {ASTNode} node The branch of main condition which needs to be checked + * @param {string} operator The operator of the main LogicalExpression. + * @returns {boolean} true when condition short circuits whole condition + */ + function isLogicalIdentity(node, operator) { + switch (node.type) { + case "Literal": + return (operator === "||" && node.value === true) || + (operator === "&&" && node.value === false); + + case "UnaryExpression": + return (operator === "&&" && node.operator === "void"); + + case "LogicalExpression": + return isLogicalIdentity(node.left, node.operator) || + isLogicalIdentity(node.right, node.operator); + + // no default + } + return false; + } + + /** + * Checks if a node has a constant truthiness value. + * @param {ASTNode} node The AST node to check. + * @param {boolean} inBooleanPosition `false` if checking branch of a condition. + * `true` in all other cases + * @returns {Bool} true when node's truthiness is constant + * @private + */ + function isConstant(node, inBooleanPosition) { + + // node.elements can return null values in the case of sparse arrays ex. [,] + if (!node) { + return true; + } + switch (node.type) { + case "Literal": + case "ArrowFunctionExpression": + case "FunctionExpression": + case "ObjectExpression": + return true; + case "TemplateLiteral": + return (inBooleanPosition && node.quasis.some(quasi => quasi.value.cooked.length)) || + node.expressions.every(exp => isConstant(exp, inBooleanPosition)); + + case "ArrayExpression": { + if (node.parent.type === "BinaryExpression" && node.parent.operator === "+") { + return node.elements.every(element => isConstant(element, false)); + } + return true; + } + + case "UnaryExpression": + if (node.operator === "void") { + return true; + } + + return (node.operator === "typeof" && inBooleanPosition) || + isConstant(node.argument, true); + + case "BinaryExpression": + return isConstant(node.left, false) && + isConstant(node.right, false) && + node.operator !== "in"; + + case "LogicalExpression": { + const isLeftConstant = isConstant(node.left, inBooleanPosition); + const isRightConstant = isConstant(node.right, inBooleanPosition); + const isLeftShortCircuit = (isLeftConstant && isLogicalIdentity(node.left, node.operator)); + const isRightShortCircuit = (isRightConstant && isLogicalIdentity(node.right, node.operator)); + + return (isLeftConstant && isRightConstant) || + ( + + // in the case of an "OR", we need to know if the right constant value is truthy + node.operator === "||" && + isRightConstant && + node.right.value && + ( + !node.parent || + node.parent.type !== "BinaryExpression" || + !(EQUALITY_OPERATORS.includes(node.parent.operator) || RELATIONAL_OPERATORS.includes(node.parent.operator)) + ) + ) || + isLeftShortCircuit || + isRightShortCircuit; + } + + case "AssignmentExpression": + return (node.operator === "=") && isConstant(node.right, inBooleanPosition); + + case "SequenceExpression": + return isConstant(node.expressions[node.expressions.length - 1], inBooleanPosition); + + // no default + } + return false; + } + + /** + * Tracks when the given node contains a constant condition. + * @param {ASTNode} node The AST node to check. + * @returns {void} + * @private + */ + function trackConstantConditionLoop(node) { + if (node.test && isConstant(node.test, true)) { + loopsInCurrentScope.add(node); + } + } + + /** + * Reports when the set contains the given constant condition node + * @param {ASTNode} node The AST node to check. + * @returns {void} + * @private + */ + function checkConstantConditionLoopInSet(node) { + if (loopsInCurrentScope.has(node)) { + loopsInCurrentScope.delete(node); + context.report({ node: node.test, messageId: "unexpected" }); + } + } + + /** + * Reports when the given node contains a constant condition. + * @param {ASTNode} node The AST node to check. + * @returns {void} + * @private + */ + function reportIfConstant(node) { + if (node.test && isConstant(node.test, true)) { + context.report({ node: node.test, messageId: "unexpected" }); + } + } + + /** + * Stores current set of constant loops in loopSetStack temporarily + * and uses a new set to track constant loops + * @returns {void} + * @private + */ + function enterFunction() { + loopSetStack.push(loopsInCurrentScope); + loopsInCurrentScope = new Set(); + } + + /** + * Reports when the set still contains stored constant conditions + * @returns {void} + * @private + */ + function exitFunction() { + loopsInCurrentScope = loopSetStack.pop(); + } + + /** + * Checks node when checkLoops option is enabled + * @param {ASTNode} node The AST node to check. + * @returns {void} + * @private + */ + function checkLoop(node) { + if (checkLoops) { + trackConstantConditionLoop(node); + } + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + ConditionalExpression: reportIfConstant, + IfStatement: reportIfConstant, + WhileStatement: checkLoop, + "WhileStatement:exit": checkConstantConditionLoopInSet, + DoWhileStatement: checkLoop, + "DoWhileStatement:exit": checkConstantConditionLoopInSet, + ForStatement: checkLoop, + "ForStatement > .test": node => checkLoop(node.parent), + "ForStatement:exit": checkConstantConditionLoopInSet, + FunctionDeclaration: enterFunction, + "FunctionDeclaration:exit": exitFunction, + FunctionExpression: enterFunction, + "FunctionExpression:exit": exitFunction, + YieldExpression: () => loopsInCurrentScope.clear() + }; + + } +}; diff --git a/eslint/lib/rules/no-constructor-return.js b/eslint/lib/rules/no-constructor-return.js new file mode 100644 index 0000000..4757770 --- /dev/null +++ b/eslint/lib/rules/no-constructor-return.js @@ -0,0 +1,62 @@ +/** + * @fileoverview Rule to disallow returning value from constructor. + * @author Pig Fang + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow returning value from constructor", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-constructor-return" + }, + + schema: {}, + + fixable: null, + + messages: { + unexpected: "Unexpected return statement in constructor." + } + }, + + create(context) { + const stack = []; + + return { + onCodePathStart(_, node) { + stack.push(node); + }, + onCodePathEnd() { + stack.pop(); + }, + ReturnStatement(node) { + const last = stack[stack.length - 1]; + + if (!last.parent) { + return; + } + + if ( + last.parent.type === "MethodDefinition" && + last.parent.kind === "constructor" && + (node.parent.parent === last || node.argument) + ) { + context.report({ + node, + messageId: "unexpected" + }); + } + } + }; + } +}; diff --git a/eslint/lib/rules/no-continue.js b/eslint/lib/rules/no-continue.js new file mode 100644 index 0000000..96718d1 --- /dev/null +++ b/eslint/lib/rules/no-continue.js @@ -0,0 +1,39 @@ +/** + * @fileoverview Rule to flag use of continue statement + * @author Borislav Zhivkov + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow `continue` statements", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/no-continue" + }, + + schema: [], + + messages: { + unexpected: "Unexpected use of continue statement." + } + }, + + create(context) { + + return { + ContinueStatement(node) { + context.report({ node, messageId: "unexpected" }); + } + }; + + } +}; diff --git a/eslint/lib/rules/no-control-regex.js b/eslint/lib/rules/no-control-regex.js new file mode 100644 index 0000000..b39f731 --- /dev/null +++ b/eslint/lib/rules/no-control-regex.js @@ -0,0 +1,113 @@ +/** + * @fileoverview Rule to forbid control characters from regular expressions. + * @author Nicholas C. Zakas + */ + +"use strict"; + +const RegExpValidator = require("regexpp").RegExpValidator; +const collector = new (class { + constructor() { + this.ecmaVersion = 2018; + this._source = ""; + this._controlChars = []; + this._validator = new RegExpValidator(this); + } + + onPatternEnter() { + this._controlChars = []; + } + + onCharacter(start, end, cp) { + if (cp >= 0x00 && + cp <= 0x1F && + ( + this._source.codePointAt(start) === cp || + this._source.slice(start, end).startsWith("\\x") || + this._source.slice(start, end).startsWith("\\u") + ) + ) { + this._controlChars.push(`\\x${`0${cp.toString(16)}`.slice(-2)}`); + } + } + + collectControlChars(regexpStr) { + try { + this._source = regexpStr; + this._validator.validatePattern(regexpStr); // Call onCharacter hook + } catch (err) { + + // Ignore syntax errors in RegExp. + } + return this._controlChars; + } +})(); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow control characters in regular expressions", + category: "Possible Errors", + recommended: true, + url: "https://eslint.org/docs/rules/no-control-regex" + }, + + schema: [], + + messages: { + unexpected: "Unexpected control character(s) in regular expression: {{controlChars}}." + } + }, + + create(context) { + + /** + * Get the regex expression + * @param {ASTNode} node node to evaluate + * @returns {RegExp|null} Regex if found else null + * @private + */ + function getRegExpPattern(node) { + if (node.regex) { + return node.regex.pattern; + } + if (typeof node.value === "string" && + (node.parent.type === "NewExpression" || node.parent.type === "CallExpression") && + node.parent.callee.type === "Identifier" && + node.parent.callee.name === "RegExp" && + node.parent.arguments[0] === node + ) { + return node.value; + } + + return null; + } + + return { + Literal(node) { + const pattern = getRegExpPattern(node); + + if (pattern) { + const controlCharacters = collector.collectControlChars(pattern); + + if (controlCharacters.length > 0) { + context.report({ + node, + messageId: "unexpected", + data: { + controlChars: controlCharacters.join(", ") + } + }); + } + } + } + }; + + } +}; diff --git a/eslint/lib/rules/no-debugger.js b/eslint/lib/rules/no-debugger.js new file mode 100644 index 0000000..95a28a8 --- /dev/null +++ b/eslint/lib/rules/no-debugger.js @@ -0,0 +1,43 @@ +/** + * @fileoverview Rule to flag use of a debugger statement + * @author Nicholas C. Zakas + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow the use of `debugger`", + category: "Possible Errors", + recommended: true, + url: "https://eslint.org/docs/rules/no-debugger" + }, + + fixable: null, + schema: [], + + messages: { + unexpected: "Unexpected 'debugger' statement." + } + }, + + create(context) { + + return { + DebuggerStatement(node) { + context.report({ + node, + messageId: "unexpected" + }); + } + }; + + } +}; diff --git a/eslint/lib/rules/no-delete-var.js b/eslint/lib/rules/no-delete-var.js new file mode 100644 index 0000000..aeab951 --- /dev/null +++ b/eslint/lib/rules/no-delete-var.js @@ -0,0 +1,42 @@ +/** + * @fileoverview Rule to flag when deleting variables + * @author Ilya Volodin + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow deleting variables", + category: "Variables", + recommended: true, + url: "https://eslint.org/docs/rules/no-delete-var" + }, + + schema: [], + + messages: { + unexpected: "Variables should not be deleted." + } + }, + + create(context) { + + return { + + UnaryExpression(node) { + if (node.operator === "delete" && node.argument.type === "Identifier") { + context.report({ node, messageId: "unexpected" }); + } + } + }; + + } +}; diff --git a/eslint/lib/rules/no-div-regex.js b/eslint/lib/rules/no-div-regex.js new file mode 100644 index 0000000..0ccabdc --- /dev/null +++ b/eslint/lib/rules/no-div-regex.js @@ -0,0 +1,53 @@ +/** + * @fileoverview Rule to check for ambiguous div operator in regexes + * @author Matt DuVall + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow division operators explicitly at the beginning of regular expressions", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-div-regex" + }, + + fixable: "code", + + schema: [], + + messages: { + unexpected: "A regular expression literal can be confused with '/='." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + return { + + Literal(node) { + const token = sourceCode.getFirstToken(node); + + if (token.type === "RegularExpression" && token.value[1] === "=") { + context.report({ + node, + messageId: "unexpected", + fix(fixer) { + return fixer.replaceTextRange([token.range[0] + 1, token.range[0] + 2], "[=]"); + } + }); + } + } + }; + + } +}; diff --git a/eslint/lib/rules/no-dupe-args.js b/eslint/lib/rules/no-dupe-args.js new file mode 100644 index 0000000..817277f --- /dev/null +++ b/eslint/lib/rules/no-dupe-args.js @@ -0,0 +1,80 @@ +/** + * @fileoverview Rule to flag duplicate arguments + * @author Jamund Ferguson + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow duplicate arguments in `function` definitions", + category: "Possible Errors", + recommended: true, + url: "https://eslint.org/docs/rules/no-dupe-args" + }, + + schema: [], + + messages: { + unexpected: "Duplicate param '{{name}}'." + } + }, + + create(context) { + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Checks whether or not a given definition is a parameter's. + * @param {eslint-scope.DefEntry} def A definition to check. + * @returns {boolean} `true` if the definition is a parameter's. + */ + function isParameter(def) { + return def.type === "Parameter"; + } + + /** + * Determines if a given node has duplicate parameters. + * @param {ASTNode} node The node to check. + * @returns {void} + * @private + */ + function checkParams(node) { + const variables = context.getDeclaredVariables(node); + + for (let i = 0; i < variables.length; ++i) { + const variable = variables[i]; + + // Checks and reports duplications. + const defs = variable.defs.filter(isParameter); + + if (defs.length >= 2) { + context.report({ + node, + messageId: "unexpected", + data: { name: variable.name } + }); + } + } + } + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + FunctionDeclaration: checkParams, + FunctionExpression: checkParams + }; + + } +}; diff --git a/eslint/lib/rules/no-dupe-class-members.js b/eslint/lib/rules/no-dupe-class-members.js new file mode 100644 index 0000000..b12939d --- /dev/null +++ b/eslint/lib/rules/no-dupe-class-members.js @@ -0,0 +1,103 @@ +/** + * @fileoverview A rule to disallow duplicate name in class members. + * @author Toru Nagashima + */ + +"use strict"; + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow duplicate class members", + category: "ECMAScript 6", + recommended: true, + url: "https://eslint.org/docs/rules/no-dupe-class-members" + }, + + schema: [], + + messages: { + unexpected: "Duplicate name '{{name}}'." + } + }, + + create(context) { + let stack = []; + + /** + * Gets state of a given member name. + * @param {string} name A name of a member. + * @param {boolean} isStatic A flag which specifies that is a static member. + * @returns {Object} A state of a given member name. + * - retv.init {boolean} A flag which shows the name is declared as normal member. + * - retv.get {boolean} A flag which shows the name is declared as getter. + * - retv.set {boolean} A flag which shows the name is declared as setter. + */ + function getState(name, isStatic) { + const stateMap = stack[stack.length - 1]; + const key = `$${name}`; // to avoid "__proto__". + + if (!stateMap[key]) { + stateMap[key] = { + nonStatic: { init: false, get: false, set: false }, + static: { init: false, get: false, set: false } + }; + } + + return stateMap[key][isStatic ? "static" : "nonStatic"]; + } + + return { + + // Initializes the stack of state of member declarations. + Program() { + stack = []; + }, + + // Initializes state of member declarations for the class. + ClassBody() { + stack.push(Object.create(null)); + }, + + // Disposes the state for the class. + "ClassBody:exit"() { + stack.pop(); + }, + + // Reports the node if its name has been declared already. + MethodDefinition(node) { + const name = astUtils.getStaticPropertyName(node); + + if (name === null || node.kind === "constructor") { + return; + } + + const state = getState(name, node.static); + let isDuplicate = false; + + if (node.kind === "get") { + isDuplicate = (state.init || state.get); + state.get = true; + } else if (node.kind === "set") { + isDuplicate = (state.init || state.set); + state.set = true; + } else { + isDuplicate = (state.init || state.get || state.set); + state.init = true; + } + + if (isDuplicate) { + context.report({ node, messageId: "unexpected", data: { name } }); + } + } + }; + } +}; diff --git a/eslint/lib/rules/no-dupe-else-if.js b/eslint/lib/rules/no-dupe-else-if.js new file mode 100644 index 0000000..cbeb437 --- /dev/null +++ b/eslint/lib/rules/no-dupe-else-if.js @@ -0,0 +1,122 @@ +/** + * @fileoverview Rule to disallow duplicate conditions in if-else-if chains + * @author Milos Djermanovic + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Determines whether the first given array is a subset of the second given array. + * @param {Function} comparator A function to compare two elements, should return `true` if they are equal. + * @param {Array} arrA The array to compare from. + * @param {Array} arrB The array to compare against. + * @returns {boolean} `true` if the array `arrA` is a subset of the array `arrB`. + */ +function isSubsetByComparator(comparator, arrA, arrB) { + return arrA.every(a => arrB.some(b => comparator(a, b))); +} + +/** + * Splits the given node by the given logical operator. + * @param {string} operator Logical operator `||` or `&&`. + * @param {ASTNode} node The node to split. + * @returns {ASTNode[]} Array of conditions that makes the node when joined by the operator. + */ +function splitByLogicalOperator(operator, node) { + if (node.type === "LogicalExpression" && node.operator === operator) { + return [...splitByLogicalOperator(operator, node.left), ...splitByLogicalOperator(operator, node.right)]; + } + return [node]; +} + +const splitByOr = splitByLogicalOperator.bind(null, "||"); +const splitByAnd = splitByLogicalOperator.bind(null, "&&"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow duplicate conditions in if-else-if chains", + category: "Possible Errors", + recommended: true, + url: "https://eslint.org/docs/rules/no-dupe-else-if" + }, + + schema: [], + + messages: { + unexpected: "This branch can never execute. Its condition is a duplicate or covered by previous conditions in the if-else-if chain." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + /** + * Determines whether the two given nodes are considered to be equal. In particular, given that the nodes + * represent expressions in a boolean context, `||` and `&&` can be considered as commutative operators. + * @param {ASTNode} a First node. + * @param {ASTNode} b Second node. + * @returns {boolean} `true` if the nodes are considered to be equal. + */ + function equal(a, b) { + if (a.type !== b.type) { + return false; + } + + if ( + a.type === "LogicalExpression" && + (a.operator === "||" || a.operator === "&&") && + a.operator === b.operator + ) { + return equal(a.left, b.left) && equal(a.right, b.right) || + equal(a.left, b.right) && equal(a.right, b.left); + } + + return astUtils.equalTokens(a, b, sourceCode); + } + + const isSubset = isSubsetByComparator.bind(null, equal); + + return { + IfStatement(node) { + const test = node.test, + conditionsToCheck = test.type === "LogicalExpression" && test.operator === "&&" + ? [test, ...splitByAnd(test)] + : [test]; + let current = node, + listToCheck = conditionsToCheck.map(c => splitByOr(c).map(splitByAnd)); + + while (current.parent && current.parent.type === "IfStatement" && current.parent.alternate === current) { + current = current.parent; + + const currentOrOperands = splitByOr(current.test).map(splitByAnd); + + listToCheck = listToCheck.map(orOperands => orOperands.filter( + orOperand => !currentOrOperands.some(currentOrOperand => isSubset(currentOrOperand, orOperand)) + )); + + if (listToCheck.some(orOperands => orOperands.length === 0)) { + context.report({ node: test, messageId: "unexpected" }); + break; + } + } + } + }; + } +}; diff --git a/eslint/lib/rules/no-dupe-keys.js b/eslint/lib/rules/no-dupe-keys.js new file mode 100644 index 0000000..89e1f2d --- /dev/null +++ b/eslint/lib/rules/no-dupe-keys.js @@ -0,0 +1,143 @@ +/** + * @fileoverview Rule to flag use of duplicate keys in an object. + * @author Ian Christian Myers + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const GET_KIND = /^(?:init|get)$/u; +const SET_KIND = /^(?:init|set)$/u; + +/** + * The class which stores properties' information of an object. + */ +class ObjectInfo { + + // eslint-disable-next-line jsdoc/require-description + /** + * @param {ObjectInfo|null} upper The information of the outer object. + * @param {ASTNode} node The ObjectExpression node of this information. + */ + constructor(upper, node) { + this.upper = upper; + this.node = node; + this.properties = new Map(); + } + + /** + * Gets the information of the given Property node. + * @param {ASTNode} node The Property node to get. + * @returns {{get: boolean, set: boolean}} The information of the property. + */ + getPropertyInfo(node) { + const name = astUtils.getStaticPropertyName(node); + + if (!this.properties.has(name)) { + this.properties.set(name, { get: false, set: false }); + } + return this.properties.get(name); + } + + /** + * Checks whether the given property has been defined already or not. + * @param {ASTNode} node The Property node to check. + * @returns {boolean} `true` if the property has been defined. + */ + isPropertyDefined(node) { + const entry = this.getPropertyInfo(node); + + return ( + (GET_KIND.test(node.kind) && entry.get) || + (SET_KIND.test(node.kind) && entry.set) + ); + } + + /** + * Defines the given property. + * @param {ASTNode} node The Property node to define. + * @returns {void} + */ + defineProperty(node) { + const entry = this.getPropertyInfo(node); + + if (GET_KIND.test(node.kind)) { + entry.get = true; + } + if (SET_KIND.test(node.kind)) { + entry.set = true; + } + } +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow duplicate keys in object literals", + category: "Possible Errors", + recommended: true, + url: "https://eslint.org/docs/rules/no-dupe-keys" + }, + + schema: [], + + messages: { + unexpected: "Duplicate key '{{name}}'." + } + }, + + create(context) { + let info = null; + + return { + ObjectExpression(node) { + info = new ObjectInfo(info, node); + }, + "ObjectExpression:exit"() { + info = info.upper; + }, + + Property(node) { + const name = astUtils.getStaticPropertyName(node); + + // Skip destructuring. + if (node.parent.type !== "ObjectExpression") { + return; + } + + // Skip if the name is not static. + if (name === null) { + return; + } + + // Reports if the name is defined already. + if (info.isPropertyDefined(node)) { + context.report({ + node: info.node, + loc: node.key.loc, + messageId: "unexpected", + data: { name } + }); + } + + // Update info. + info.defineProperty(node); + } + }; + } +}; diff --git a/eslint/lib/rules/no-duplicate-case.js b/eslint/lib/rules/no-duplicate-case.js new file mode 100644 index 0000000..c8a0fa9 --- /dev/null +++ b/eslint/lib/rules/no-duplicate-case.js @@ -0,0 +1,52 @@ +/** + * @fileoverview Rule to disallow a duplicate case label. + * @author Dieter Oberkofler + * @author Burak Yigit Kaya + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow duplicate case labels", + category: "Possible Errors", + recommended: true, + url: "https://eslint.org/docs/rules/no-duplicate-case" + }, + + schema: [], + + messages: { + unexpected: "Duplicate case label." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + return { + SwitchStatement(node) { + const previousKeys = new Set(); + + for (const switchCase of node.cases) { + if (switchCase.test) { + const key = sourceCode.getText(switchCase.test); + + if (previousKeys.has(key)) { + context.report({ node: switchCase, messageId: "unexpected" }); + } else { + previousKeys.add(key); + } + } + } + } + }; + } +}; diff --git a/eslint/lib/rules/no-duplicate-imports.js b/eslint/lib/rules/no-duplicate-imports.js new file mode 100644 index 0000000..7218dc6 --- /dev/null +++ b/eslint/lib/rules/no-duplicate-imports.js @@ -0,0 +1,142 @@ +/** + * @fileoverview Restrict usage of duplicate imports. + * @author Simen Bekkhus + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +/** + * Returns the name of the module imported or re-exported. + * @param {ASTNode} node A node to get. + * @returns {string} the name of the module, or empty string if no name. + */ +function getValue(node) { + if (node && node.source && node.source.value) { + return node.source.value.trim(); + } + + return ""; +} + +/** + * Checks if the name of the import or export exists in the given array, and reports if so. + * @param {RuleContext} context The ESLint rule context object. + * @param {ASTNode} node A node to get. + * @param {string} value The name of the imported or exported module. + * @param {string[]} array The array containing other imports or exports in the file. + * @param {string} messageId A messageId to be reported after the name of the module + * + * @returns {void} No return value + */ +function checkAndReport(context, node, value, array, messageId) { + if (array.indexOf(value) !== -1) { + context.report({ + node, + messageId, + data: { + module: value + } + }); + } +} + +/** + * @callback nodeCallback + * @param {ASTNode} node A node to handle. + */ + +/** + * Returns a function handling the imports of a given file + * @param {RuleContext} context The ESLint rule context object. + * @param {boolean} includeExports Whether or not to check for exports in addition to imports. + * @param {string[]} importsInFile The array containing other imports in the file. + * @param {string[]} exportsInFile The array containing other exports in the file. + * + * @returns {nodeCallback} A function passed to ESLint to handle the statement. + */ +function handleImports(context, includeExports, importsInFile, exportsInFile) { + return function(node) { + const value = getValue(node); + + if (value) { + checkAndReport(context, node, value, importsInFile, "import"); + + if (includeExports) { + checkAndReport(context, node, value, exportsInFile, "importAs"); + } + + importsInFile.push(value); + } + }; +} + +/** + * Returns a function handling the exports of a given file + * @param {RuleContext} context The ESLint rule context object. + * @param {string[]} importsInFile The array containing other imports in the file. + * @param {string[]} exportsInFile The array containing other exports in the file. + * + * @returns {nodeCallback} A function passed to ESLint to handle the statement. + */ +function handleExports(context, importsInFile, exportsInFile) { + return function(node) { + const value = getValue(node); + + if (value) { + checkAndReport(context, node, value, exportsInFile, "export"); + checkAndReport(context, node, value, importsInFile, "exportAs"); + + exportsInFile.push(value); + } + }; +} + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow duplicate module imports", + category: "ECMAScript 6", + recommended: false, + url: "https://eslint.org/docs/rules/no-duplicate-imports" + }, + + schema: [{ + type: "object", + properties: { + includeExports: { + type: "boolean", + default: false + } + }, + additionalProperties: false + }], + messages: { + import: "'{{module}}' import is duplicated.", + importAs: "'{{module}}' import is duplicated as export.", + export: "'{{module}}' export is duplicated.", + exportAs: "'{{module}}' export is duplicated as import." + } + }, + + create(context) { + const includeExports = (context.options[0] || {}).includeExports, + importsInFile = [], + exportsInFile = []; + + const handlers = { + ImportDeclaration: handleImports(context, includeExports, importsInFile, exportsInFile) + }; + + if (includeExports) { + handlers.ExportNamedDeclaration = handleExports(context, importsInFile, exportsInFile); + handlers.ExportAllDeclaration = handleExports(context, importsInFile, exportsInFile); + } + + return handlers; + } +}; diff --git a/eslint/lib/rules/no-else-return.js b/eslint/lib/rules/no-else-return.js new file mode 100644 index 0000000..84409fa --- /dev/null +++ b/eslint/lib/rules/no-else-return.js @@ -0,0 +1,404 @@ +/** + * @fileoverview Rule to flag `else` after a `return` in `if` + * @author Ian Christian Myers + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); +const FixTracker = require("./utils/fix-tracker"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow `else` blocks after `return` statements in `if` statements", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-else-return" + }, + + schema: [{ + type: "object", + properties: { + allowElseIf: { + type: "boolean", + default: true + } + }, + additionalProperties: false + }], + + fixable: "code", + + messages: { + unexpected: "Unnecessary 'else' after 'return'." + } + }, + + create(context) { + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Checks whether the given names can be safely used to declare block-scoped variables + * in the given scope. Name collisions can produce redeclaration syntax errors, + * or silently change references and modify behavior of the original code. + * + * This is not a generic function. In particular, it is assumed that the scope is a function scope or + * a function's inner scope, and that the names can be valid identifiers in the given scope. + * @param {string[]} names Array of variable names. + * @param {eslint-scope.Scope} scope Function scope or a function's inner scope. + * @returns {boolean} True if all names can be safely declared, false otherwise. + */ + function isSafeToDeclare(names, scope) { + + if (names.length === 0) { + return true; + } + + const functionScope = scope.variableScope; + + /* + * If this is a function scope, scope.variables will contain parameters, implicit variables such as "arguments", + * all function-scoped variables ('var'), and block-scoped variables defined in the scope. + * If this is an inner scope, scope.variables will contain block-scoped variables defined in the scope. + * + * Redeclaring any of these would cause a syntax error, except for the implicit variables. + */ + const declaredVariables = scope.variables.filter(({ defs }) => defs.length > 0); + + if (declaredVariables.some(({ name }) => names.includes(name))) { + return false; + } + + // Redeclaring a catch variable would also cause a syntax error. + if (scope !== functionScope && scope.upper.type === "catch") { + if (scope.upper.variables.some(({ name }) => names.includes(name))) { + return false; + } + } + + /* + * Redeclaring an implicit variable, such as "arguments", would not cause a syntax error. + * However, if the variable was used, declaring a new one with the same name would change references + * and modify behavior. + */ + const usedImplicitVariables = scope.variables.filter(({ defs, references }) => + defs.length === 0 && references.length > 0); + + if (usedImplicitVariables.some(({ name }) => names.includes(name))) { + return false; + } + + /* + * Declaring a variable with a name that was already used to reference a variable from an upper scope + * would change references and modify behavior. + */ + if (scope.through.some(t => names.includes(t.identifier.name))) { + return false; + } + + /* + * If the scope is an inner scope (not the function scope), an uninitialized `var` variable declared inside + * the scope node (directly or in one of its descendants) is neither declared nor 'through' in the scope. + * + * For example, this would be a syntax error "Identifier 'a' has already been declared": + * function foo() { if (bar) { let a; if (baz) { var a; } } } + */ + if (scope !== functionScope) { + const scopeNodeRange = scope.block.range; + const variablesToCheck = functionScope.variables.filter(({ name }) => names.includes(name)); + + if (variablesToCheck.some(v => v.defs.some(({ node: { range } }) => + scopeNodeRange[0] <= range[0] && range[1] <= scopeNodeRange[1]))) { + return false; + } + } + + return true; + } + + + /** + * Checks whether the removal of `else` and its braces is safe from variable name collisions. + * @param {Node} node The 'else' node. + * @param {eslint-scope.Scope} scope The scope in which the node and the whole 'if' statement is. + * @returns {boolean} True if it is safe, false otherwise. + */ + function isSafeFromNameCollisions(node, scope) { + + if (node.type === "FunctionDeclaration") { + + // Conditional function declaration. Scope and hoisting are unpredictable, different engines work differently. + return false; + } + + if (node.type !== "BlockStatement") { + return true; + } + + const elseBlockScope = scope.childScopes.find(({ block }) => block === node); + + if (!elseBlockScope) { + + // ecmaVersion < 6, `else` block statement cannot have its own scope, no possible collisions. + return true; + } + + /* + * elseBlockScope is supposed to merge into its upper scope. elseBlockScope.variables array contains + * only block-scoped variables (such as let and const variables or class and function declarations) + * defined directly in the elseBlockScope. These are exactly the only names that could cause collisions. + */ + const namesToCheck = elseBlockScope.variables.map(({ name }) => name); + + return isSafeToDeclare(namesToCheck, scope); + } + + /** + * Display the context report if rule is violated + * @param {Node} node The 'else' node + * @returns {void} + */ + function displayReport(node) { + const currentScope = context.getScope(); + + context.report({ + node, + messageId: "unexpected", + fix: fixer => { + + if (!isSafeFromNameCollisions(node, currentScope)) { + return null; + } + + const sourceCode = context.getSourceCode(); + const startToken = sourceCode.getFirstToken(node); + const elseToken = sourceCode.getTokenBefore(startToken); + const source = sourceCode.getText(node); + const lastIfToken = sourceCode.getTokenBefore(elseToken); + let fixedSource, firstTokenOfElseBlock; + + if (startToken.type === "Punctuator" && startToken.value === "{") { + firstTokenOfElseBlock = sourceCode.getTokenAfter(startToken); + } else { + firstTokenOfElseBlock = startToken; + } + + /* + * If the if block does not have curly braces and does not end in a semicolon + * and the else block starts with (, [, /, +, ` or -, then it is not + * safe to remove the else keyword, because ASI will not add a semicolon + * after the if block + */ + const ifBlockMaybeUnsafe = node.parent.consequent.type !== "BlockStatement" && lastIfToken.value !== ";"; + const elseBlockUnsafe = /^[([/+`-]/u.test(firstTokenOfElseBlock.value); + + if (ifBlockMaybeUnsafe && elseBlockUnsafe) { + return null; + } + + const endToken = sourceCode.getLastToken(node); + const lastTokenOfElseBlock = sourceCode.getTokenBefore(endToken); + + if (lastTokenOfElseBlock.value !== ";") { + const nextToken = sourceCode.getTokenAfter(endToken); + + const nextTokenUnsafe = nextToken && /^[([/+`-]/u.test(nextToken.value); + const nextTokenOnSameLine = nextToken && nextToken.loc.start.line === lastTokenOfElseBlock.loc.start.line; + + /* + * If the else block contents does not end in a semicolon, + * and the else block starts with (, [, /, +, ` or -, then it is not + * safe to remove the else block, because ASI will not add a semicolon + * after the remaining else block contents + */ + if (nextTokenUnsafe || (nextTokenOnSameLine && nextToken.value !== "}")) { + return null; + } + } + + if (startToken.type === "Punctuator" && startToken.value === "{") { + fixedSource = source.slice(1, -1); + } else { + fixedSource = source; + } + + /* + * Extend the replacement range to include the entire + * function to avoid conflicting with no-useless-return. + * https://github.com/eslint/eslint/issues/8026 + * + * Also, to avoid name collisions between two else blocks. + */ + return new FixTracker(fixer, sourceCode) + .retainEnclosingFunction(node) + .replaceTextRange([elseToken.range[0], node.range[1]], fixedSource); + } + }); + } + + /** + * Check to see if the node is a ReturnStatement + * @param {Node} node The node being evaluated + * @returns {boolean} True if node is a return + */ + function checkForReturn(node) { + return node.type === "ReturnStatement"; + } + + /** + * Naive return checking, does not iterate through the whole + * BlockStatement because we make the assumption that the ReturnStatement + * will be the last node in the body of the BlockStatement. + * @param {Node} node The consequent/alternate node + * @returns {boolean} True if it has a return + */ + function naiveHasReturn(node) { + if (node.type === "BlockStatement") { + const body = node.body, + lastChildNode = body[body.length - 1]; + + return lastChildNode && checkForReturn(lastChildNode); + } + return checkForReturn(node); + } + + /** + * Check to see if the node is valid for evaluation, + * meaning it has an else. + * @param {Node} node The node being evaluated + * @returns {boolean} True if the node is valid + */ + function hasElse(node) { + return node.alternate && node.consequent; + } + + /** + * If the consequent is an IfStatement, check to see if it has an else + * and both its consequent and alternate path return, meaning this is + * a nested case of rule violation. If-Else not considered currently. + * @param {Node} node The consequent node + * @returns {boolean} True if this is a nested rule violation + */ + function checkForIf(node) { + return node.type === "IfStatement" && hasElse(node) && + naiveHasReturn(node.alternate) && naiveHasReturn(node.consequent); + } + + /** + * Check the consequent/body node to make sure it is not + * a ReturnStatement or an IfStatement that returns on both + * code paths. + * @param {Node} node The consequent or body node + * @returns {boolean} `true` if it is a Return/If node that always returns. + */ + function checkForReturnOrIf(node) { + return checkForReturn(node) || checkForIf(node); + } + + + /** + * Check whether a node returns in every codepath. + * @param {Node} node The node to be checked + * @returns {boolean} `true` if it returns on every codepath. + */ + function alwaysReturns(node) { + if (node.type === "BlockStatement") { + + // If we have a BlockStatement, check each consequent body node. + return node.body.some(checkForReturnOrIf); + } + + /* + * If not a block statement, make sure the consequent isn't a + * ReturnStatement or an IfStatement with returns on both paths. + */ + return checkForReturnOrIf(node); + } + + + /** + * Check the if statement, but don't catch else-if blocks. + * @returns {void} + * @param {Node} node The node for the if statement to check + * @private + */ + function checkIfWithoutElse(node) { + const parent = node.parent; + + /* + * Fixing this would require splitting one statement into two, so no error should + * be reported if this node is in a position where only one statement is allowed. + */ + if (!astUtils.STATEMENT_LIST_PARENTS.has(parent.type)) { + return; + } + + const consequents = []; + let alternate; + + for (let currentNode = node; currentNode.type === "IfStatement"; currentNode = currentNode.alternate) { + if (!currentNode.alternate) { + return; + } + consequents.push(currentNode.consequent); + alternate = currentNode.alternate; + } + + if (consequents.every(alwaysReturns)) { + displayReport(alternate); + } + } + + /** + * Check the if statement + * @returns {void} + * @param {Node} node The node for the if statement to check + * @private + */ + function checkIfWithElse(node) { + const parent = node.parent; + + + /* + * Fixing this would require splitting one statement into two, so no error should + * be reported if this node is in a position where only one statement is allowed. + */ + if (!astUtils.STATEMENT_LIST_PARENTS.has(parent.type)) { + return; + } + + const alternate = node.alternate; + + if (alternate && alwaysReturns(node.consequent)) { + displayReport(alternate); + } + } + + const allowElseIf = !(context.options[0] && context.options[0].allowElseIf === false); + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + + "IfStatement:exit": allowElseIf ? checkIfWithoutElse : checkIfWithElse + + }; + + } +}; diff --git a/eslint/lib/rules/no-empty-character-class.js b/eslint/lib/rules/no-empty-character-class.js new file mode 100644 index 0000000..7dc219f --- /dev/null +++ b/eslint/lib/rules/no-empty-character-class.js @@ -0,0 +1,64 @@ +/** + * @fileoverview Rule to flag the use of empty character classes in regular expressions + * @author Ian Christian Myers + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/* + * plain-English description of the following regexp: + * 0. `^` fix the match at the beginning of the string + * 1. `\/`: the `/` that begins the regexp + * 2. `([^\\[]|\\.|\[([^\\\]]|\\.)+\])*`: regexp contents; 0 or more of the following + * 2.0. `[^\\[]`: any character that's not a `\` or a `[` (anything but escape sequences and character classes) + * 2.1. `\\.`: an escape sequence + * 2.2. `\[([^\\\]]|\\.)+\]`: a character class that isn't empty + * 3. `\/` the `/` that ends the regexp + * 4. `[gimuy]*`: optional regexp flags + * 5. `$`: fix the match at the end of the string + */ +const regex = /^\/([^\\[]|\\.|\[([^\\\]]|\\.)+\])*\/[gimuys]*$/u; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow empty character classes in regular expressions", + category: "Possible Errors", + recommended: true, + url: "https://eslint.org/docs/rules/no-empty-character-class" + }, + + schema: [], + + messages: { + unexpected: "Empty class." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + return { + + Literal(node) { + const token = sourceCode.getFirstToken(node); + + if (token.type === "RegularExpression" && !regex.test(token.value)) { + context.report({ node, messageId: "unexpected" }); + } + } + + }; + + } +}; diff --git a/eslint/lib/rules/no-empty-function.js b/eslint/lib/rules/no-empty-function.js new file mode 100644 index 0000000..c743211 --- /dev/null +++ b/eslint/lib/rules/no-empty-function.js @@ -0,0 +1,167 @@ +/** + * @fileoverview Rule to disallow empty functions. + * @author Toru Nagashima + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const ALLOW_OPTIONS = Object.freeze([ + "functions", + "arrowFunctions", + "generatorFunctions", + "methods", + "generatorMethods", + "getters", + "setters", + "constructors", + "asyncFunctions", + "asyncMethods" +]); + +/** + * Gets the kind of a given function node. + * @param {ASTNode} node A function node to get. This is one of + * an ArrowFunctionExpression, a FunctionDeclaration, or a + * FunctionExpression. + * @returns {string} The kind of the function. This is one of "functions", + * "arrowFunctions", "generatorFunctions", "asyncFunctions", "methods", + * "generatorMethods", "asyncMethods", "getters", "setters", and + * "constructors". + */ +function getKind(node) { + const parent = node.parent; + let kind = ""; + + if (node.type === "ArrowFunctionExpression") { + return "arrowFunctions"; + } + + // Detects main kind. + if (parent.type === "Property") { + if (parent.kind === "get") { + return "getters"; + } + if (parent.kind === "set") { + return "setters"; + } + kind = parent.method ? "methods" : "functions"; + + } else if (parent.type === "MethodDefinition") { + if (parent.kind === "get") { + return "getters"; + } + if (parent.kind === "set") { + return "setters"; + } + if (parent.kind === "constructor") { + return "constructors"; + } + kind = "methods"; + + } else { + kind = "functions"; + } + + // Detects prefix. + let prefix = ""; + + if (node.generator) { + prefix = "generator"; + } else if (node.async) { + prefix = "async"; + } else { + return kind; + } + return prefix + kind[0].toUpperCase() + kind.slice(1); +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow empty functions", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-empty-function" + }, + + schema: [ + { + type: "object", + properties: { + allow: { + type: "array", + items: { enum: ALLOW_OPTIONS }, + uniqueItems: true + } + }, + additionalProperties: false + } + ], + + messages: { + unexpected: "Unexpected empty {{name}}." + } + }, + + create(context) { + const options = context.options[0] || {}; + const allowed = options.allow || []; + + const sourceCode = context.getSourceCode(); + + /** + * Reports a given function node if the node matches the following patterns. + * + * - Not allowed by options. + * - The body is empty. + * - The body doesn't have any comments. + * @param {ASTNode} node A function node to report. This is one of + * an ArrowFunctionExpression, a FunctionDeclaration, or a + * FunctionExpression. + * @returns {void} + */ + function reportIfEmpty(node) { + const kind = getKind(node); + const name = astUtils.getFunctionNameWithKind(node); + const innerComments = sourceCode.getTokens(node.body, { + includeComments: true, + filter: astUtils.isCommentToken + }); + + if (allowed.indexOf(kind) === -1 && + node.body.type === "BlockStatement" && + node.body.body.length === 0 && + innerComments.length === 0 + ) { + context.report({ + node, + loc: node.body.loc.start, + messageId: "unexpected", + data: { name } + }); + } + } + + return { + ArrowFunctionExpression: reportIfEmpty, + FunctionDeclaration: reportIfEmpty, + FunctionExpression: reportIfEmpty + }; + } +}; diff --git a/eslint/lib/rules/no-empty-pattern.js b/eslint/lib/rules/no-empty-pattern.js new file mode 100644 index 0000000..9f34bfd --- /dev/null +++ b/eslint/lib/rules/no-empty-pattern.js @@ -0,0 +1,43 @@ +/** + * @fileoverview Rule to disallow an empty pattern + * @author Alberto Rodríguez + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow empty destructuring patterns", + category: "Best Practices", + recommended: true, + url: "https://eslint.org/docs/rules/no-empty-pattern" + }, + + schema: [], + + messages: { + unexpected: "Unexpected empty {{type}} pattern." + } + }, + + create(context) { + return { + ObjectPattern(node) { + if (node.properties.length === 0) { + context.report({ node, messageId: "unexpected", data: { type: "object" } }); + } + }, + ArrayPattern(node) { + if (node.elements.length === 0) { + context.report({ node, messageId: "unexpected", data: { type: "array" } }); + } + } + }; + } +}; diff --git a/eslint/lib/rules/no-empty.js b/eslint/lib/rules/no-empty.js new file mode 100644 index 0000000..45bf03c --- /dev/null +++ b/eslint/lib/rules/no-empty.js @@ -0,0 +1,86 @@ +/** + * @fileoverview Rule to flag use of an empty block statement + * @author Nicholas C. Zakas + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow empty block statements", + category: "Possible Errors", + recommended: true, + url: "https://eslint.org/docs/rules/no-empty" + }, + + schema: [ + { + type: "object", + properties: { + allowEmptyCatch: { + type: "boolean", + default: false + } + }, + additionalProperties: false + } + ], + + messages: { + unexpected: "Empty {{type}} statement." + } + }, + + create(context) { + const options = context.options[0] || {}, + allowEmptyCatch = options.allowEmptyCatch || false; + + const sourceCode = context.getSourceCode(); + + return { + BlockStatement(node) { + + // if the body is not empty, we can just return immediately + if (node.body.length !== 0) { + return; + } + + // a function is generally allowed to be empty + if (astUtils.isFunction(node.parent)) { + return; + } + + if (allowEmptyCatch && node.parent.type === "CatchClause") { + return; + } + + // any other block is only allowed to be empty, if it contains a comment + if (sourceCode.getCommentsInside(node).length > 0) { + return; + } + + context.report({ node, messageId: "unexpected", data: { type: "block" } }); + }, + + SwitchStatement(node) { + + if (typeof node.cases === "undefined" || node.cases.length === 0) { + context.report({ node, messageId: "unexpected", data: { type: "switch" } }); + } + } + }; + + } +}; diff --git a/eslint/lib/rules/no-eq-null.js b/eslint/lib/rules/no-eq-null.js new file mode 100644 index 0000000..b8dead9 --- /dev/null +++ b/eslint/lib/rules/no-eq-null.js @@ -0,0 +1,46 @@ +/** + * @fileoverview Rule to flag comparisons to null without a type-checking + * operator. + * @author Ian Christian Myers + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow `null` comparisons without type-checking operators", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-eq-null" + }, + + schema: [], + + messages: { + unexpected: "Use '===' to compare with null." + } + }, + + create(context) { + + return { + + BinaryExpression(node) { + const badOperator = node.operator === "==" || node.operator === "!="; + + if (node.right.type === "Literal" && node.right.raw === "null" && badOperator || + node.left.type === "Literal" && node.left.raw === "null" && badOperator) { + context.report({ node, messageId: "unexpected" }); + } + } + }; + + } +}; diff --git a/eslint/lib/rules/no-eval.js b/eslint/lib/rules/no-eval.js new file mode 100644 index 0000000..811ad4e --- /dev/null +++ b/eslint/lib/rules/no-eval.js @@ -0,0 +1,307 @@ +/** + * @fileoverview Rule to flag use of eval() statement + * @author Nicholas C. Zakas + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const candidatesOfGlobalObject = Object.freeze([ + "global", + "window", + "globalThis" +]); + +/** + * Checks a given node is a Identifier node of the specified name. + * @param {ASTNode} node A node to check. + * @param {string} name A name to check. + * @returns {boolean} `true` if the node is a Identifier node of the name. + */ +function isIdentifier(node, name) { + return node.type === "Identifier" && node.name === name; +} + +/** + * Checks a given node is a Literal node of the specified string value. + * @param {ASTNode} node A node to check. + * @param {string} name A name to check. + * @returns {boolean} `true` if the node is a Literal node of the name. + */ +function isConstant(node, name) { + switch (node.type) { + case "Literal": + return node.value === name; + + case "TemplateLiteral": + return ( + node.expressions.length === 0 && + node.quasis[0].value.cooked === name + ); + + default: + return false; + } +} + +/** + * Checks a given node is a MemberExpression node which has the specified name's + * property. + * @param {ASTNode} node A node to check. + * @param {string} name A name to check. + * @returns {boolean} `true` if the node is a MemberExpression node which has + * the specified name's property + */ +function isMember(node, name) { + return ( + node.type === "MemberExpression" && + (node.computed ? isConstant : isIdentifier)(node.property, name) + ); +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow the use of `eval()`", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-eval" + }, + + schema: [ + { + type: "object", + properties: { + allowIndirect: { type: "boolean", default: false } + }, + additionalProperties: false + } + ], + + messages: { + unexpected: "eval can be harmful." + } + }, + + create(context) { + const allowIndirect = Boolean( + context.options[0] && + context.options[0].allowIndirect + ); + const sourceCode = context.getSourceCode(); + let funcInfo = null; + + /** + * Pushs a variable scope (Program or Function) information to the stack. + * + * This is used in order to check whether or not `this` binding is a + * reference to the global object. + * @param {ASTNode} node A node of the scope. This is one of Program, + * FunctionDeclaration, FunctionExpression, and ArrowFunctionExpression. + * @returns {void} + */ + function enterVarScope(node) { + const strict = context.getScope().isStrict; + + funcInfo = { + upper: funcInfo, + node, + strict, + defaultThis: false, + initialized: strict + }; + } + + /** + * Pops a variable scope from the stack. + * @returns {void} + */ + function exitVarScope() { + funcInfo = funcInfo.upper; + } + + /** + * Reports a given node. + * + * `node` is `Identifier` or `MemberExpression`. + * The parent of `node` might be `CallExpression`. + * + * The location of the report is always `eval` `Identifier` (or possibly + * `Literal`). The type of the report is `CallExpression` if the parent is + * `CallExpression`. Otherwise, it's the given node type. + * @param {ASTNode} node A node to report. + * @returns {void} + */ + function report(node) { + const parent = node.parent; + const locationNode = node.type === "MemberExpression" + ? node.property + : node; + + const reportNode = parent.type === "CallExpression" && parent.callee === node + ? parent + : node; + + context.report({ + node: reportNode, + loc: locationNode.loc, + messageId: "unexpected" + }); + } + + /** + * Reports accesses of `eval` via the global object. + * @param {eslint-scope.Scope} globalScope The global scope. + * @returns {void} + */ + function reportAccessingEvalViaGlobalObject(globalScope) { + for (let i = 0; i < candidatesOfGlobalObject.length; ++i) { + const name = candidatesOfGlobalObject[i]; + const variable = astUtils.getVariableByName(globalScope, name); + + if (!variable) { + continue; + } + + const references = variable.references; + + for (let j = 0; j < references.length; ++j) { + const identifier = references[j].identifier; + let node = identifier.parent; + + // To detect code like `window.window.eval`. + while (isMember(node, name)) { + node = node.parent; + } + + // Reports. + if (isMember(node, "eval")) { + report(node); + } + } + } + } + + /** + * Reports all accesses of `eval` (excludes direct calls to eval). + * @param {eslint-scope.Scope} globalScope The global scope. + * @returns {void} + */ + function reportAccessingEval(globalScope) { + const variable = astUtils.getVariableByName(globalScope, "eval"); + + if (!variable) { + return; + } + + const references = variable.references; + + for (let i = 0; i < references.length; ++i) { + const reference = references[i]; + const id = reference.identifier; + + if (id.name === "eval" && !astUtils.isCallee(id)) { + + // Is accessing to eval (excludes direct calls to eval) + report(id); + } + } + } + + if (allowIndirect) { + + // Checks only direct calls to eval. It's simple! + return { + "CallExpression:exit"(node) { + const callee = node.callee; + + if (isIdentifier(callee, "eval")) { + report(callee); + } + } + }; + } + + return { + "CallExpression:exit"(node) { + const callee = node.callee; + + if (isIdentifier(callee, "eval")) { + report(callee); + } + }, + + Program(node) { + const scope = context.getScope(), + features = context.parserOptions.ecmaFeatures || {}, + strict = + scope.isStrict || + node.sourceType === "module" || + (features.globalReturn && scope.childScopes[0].isStrict); + + funcInfo = { + upper: null, + node, + strict, + defaultThis: true, + initialized: true + }; + }, + + "Program:exit"() { + const globalScope = context.getScope(); + + exitVarScope(); + reportAccessingEval(globalScope); + reportAccessingEvalViaGlobalObject(globalScope); + }, + + FunctionDeclaration: enterVarScope, + "FunctionDeclaration:exit": exitVarScope, + FunctionExpression: enterVarScope, + "FunctionExpression:exit": exitVarScope, + ArrowFunctionExpression: enterVarScope, + "ArrowFunctionExpression:exit": exitVarScope, + + ThisExpression(node) { + if (!isMember(node.parent, "eval")) { + return; + } + + /* + * `this.eval` is found. + * Checks whether or not the value of `this` is the global object. + */ + if (!funcInfo.initialized) { + funcInfo.initialized = true; + funcInfo.defaultThis = astUtils.isDefaultThisBinding( + funcInfo.node, + sourceCode + ); + } + + if (!funcInfo.strict && funcInfo.defaultThis) { + + // `this.eval` is possible built-in `eval`. + report(node.parent); + } + } + }; + + } +}; diff --git a/eslint/lib/rules/no-ex-assign.js b/eslint/lib/rules/no-ex-assign.js new file mode 100644 index 0000000..1163920 --- /dev/null +++ b/eslint/lib/rules/no-ex-assign.js @@ -0,0 +1,52 @@ +/** + * @fileoverview Rule to flag assignment of the exception parameter + * @author Stephen Murray + */ + +"use strict"; + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow reassigning exceptions in `catch` clauses", + category: "Possible Errors", + recommended: true, + url: "https://eslint.org/docs/rules/no-ex-assign" + }, + + schema: [], + + messages: { + unexpected: "Do not assign to the exception parameter." + } + }, + + create(context) { + + /** + * Finds and reports references that are non initializer and writable. + * @param {Variable} variable A variable to check. + * @returns {void} + */ + function checkVariable(variable) { + astUtils.getModifyingReferences(variable.references).forEach(reference => { + context.report({ node: reference.identifier, messageId: "unexpected" }); + }); + } + + return { + CatchClause(node) { + context.getDeclaredVariables(node).forEach(checkVariable); + } + }; + + } +}; diff --git a/eslint/lib/rules/no-extend-native.js b/eslint/lib/rules/no-extend-native.js new file mode 100644 index 0000000..7ab25ab --- /dev/null +++ b/eslint/lib/rules/no-extend-native.js @@ -0,0 +1,181 @@ +/** + * @fileoverview Rule to flag adding properties to native object's prototypes. + * @author David Nelson + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); +const globals = require("globals"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const propertyDefinitionMethods = new Set(["defineProperty", "defineProperties"]); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow extending native types", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-extend-native" + }, + + schema: [ + { + type: "object", + properties: { + exceptions: { + type: "array", + items: { + type: "string" + }, + uniqueItems: true + } + }, + additionalProperties: false + } + ], + + messages: { + unexpected: "{{builtin}} prototype is read only, properties should not be added." + } + }, + + create(context) { + + const config = context.options[0] || {}; + const exceptions = new Set(config.exceptions || []); + const modifiedBuiltins = new Set( + Object.keys(globals.builtin) + .filter(builtin => builtin[0].toUpperCase() === builtin[0]) + .filter(builtin => !exceptions.has(builtin)) + ); + + /** + * Reports a lint error for the given node. + * @param {ASTNode} node The node to report. + * @param {string} builtin The name of the native builtin being extended. + * @returns {void} + */ + function reportNode(node, builtin) { + context.report({ + node, + messageId: "unexpected", + data: { + builtin + } + }); + } + + /** + * Check to see if the `prototype` property of the given object + * identifier node is being accessed. + * @param {ASTNode} identifierNode The Identifier representing the object + * to check. + * @returns {boolean} True if the identifier is the object of a + * MemberExpression and its `prototype` property is being accessed, + * false otherwise. + */ + function isPrototypePropertyAccessed(identifierNode) { + return Boolean( + identifierNode && + identifierNode.parent && + identifierNode.parent.type === "MemberExpression" && + identifierNode.parent.object === identifierNode && + astUtils.getStaticPropertyName(identifierNode.parent) === "prototype" + ); + } + + /** + * Checks that an identifier is an object of a prototype whose member + * is being assigned in an AssignmentExpression. + * Example: Object.prototype.foo = "bar" + * @param {ASTNode} identifierNode The identifier to check. + * @returns {boolean} True if the identifier's prototype is modified. + */ + function isInPrototypePropertyAssignment(identifierNode) { + return Boolean( + isPrototypePropertyAccessed(identifierNode) && + identifierNode.parent.parent.type === "MemberExpression" && + identifierNode.parent.parent.parent.type === "AssignmentExpression" && + identifierNode.parent.parent.parent.left === identifierNode.parent.parent + ); + } + + /** + * Checks that an identifier is an object of a prototype whose member + * is being extended via the Object.defineProperty() or + * Object.defineProperties() methods. + * Example: Object.defineProperty(Array.prototype, "foo", ...) + * Example: Object.defineProperties(Array.prototype, ...) + * @param {ASTNode} identifierNode The identifier to check. + * @returns {boolean} True if the identifier's prototype is modified. + */ + function isInDefinePropertyCall(identifierNode) { + return Boolean( + isPrototypePropertyAccessed(identifierNode) && + identifierNode.parent.parent.type === "CallExpression" && + identifierNode.parent.parent.arguments[0] === identifierNode.parent && + identifierNode.parent.parent.callee.type === "MemberExpression" && + identifierNode.parent.parent.callee.object.type === "Identifier" && + identifierNode.parent.parent.callee.object.name === "Object" && + identifierNode.parent.parent.callee.property.type === "Identifier" && + propertyDefinitionMethods.has(identifierNode.parent.parent.callee.property.name) + ); + } + + /** + * Check to see if object prototype access is part of a prototype + * extension. There are three ways a prototype can be extended: + * 1. Assignment to prototype property (Object.prototype.foo = 1) + * 2. Object.defineProperty()/Object.defineProperties() on a prototype + * If prototype extension is detected, report the AssignmentExpression + * or CallExpression node. + * @param {ASTNode} identifierNode The Identifier representing the object + * which prototype is being accessed and possibly extended. + * @returns {void} + */ + function checkAndReportPrototypeExtension(identifierNode) { + if (isInPrototypePropertyAssignment(identifierNode)) { + + // Identifier --> MemberExpression --> MemberExpression --> AssignmentExpression + reportNode(identifierNode.parent.parent.parent, identifierNode.name); + } else if (isInDefinePropertyCall(identifierNode)) { + + // Identifier --> MemberExpression --> CallExpression + reportNode(identifierNode.parent.parent, identifierNode.name); + } + } + + return { + + "Program:exit"() { + const globalScope = context.getScope(); + + modifiedBuiltins.forEach(builtin => { + const builtinVar = globalScope.set.get(builtin); + + if (builtinVar && builtinVar.references) { + builtinVar.references + .map(ref => ref.identifier) + .forEach(checkAndReportPrototypeExtension); + } + }); + } + }; + + } +}; diff --git a/eslint/lib/rules/no-extra-bind.js b/eslint/lib/rules/no-extra-bind.js new file mode 100644 index 0000000..df69592 --- /dev/null +++ b/eslint/lib/rules/no-extra-bind.js @@ -0,0 +1,173 @@ +/** + * @fileoverview Rule to flag unnecessary bind calls + * @author Bence Dányi + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const SIDE_EFFECT_FREE_NODE_TYPES = new Set(["Literal", "Identifier", "ThisExpression", "FunctionExpression"]); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow unnecessary calls to `.bind()`", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-extra-bind" + }, + + schema: [], + fixable: "code", + + messages: { + unexpected: "The function binding is unnecessary." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + let scopeInfo = null; + + /** + * Checks if a node is free of side effects. + * + * This check is stricter than it needs to be, in order to keep the implementation simple. + * @param {ASTNode} node A node to check. + * @returns {boolean} True if the node is known to be side-effect free, false otherwise. + */ + function isSideEffectFree(node) { + return SIDE_EFFECT_FREE_NODE_TYPES.has(node.type); + } + + /** + * Reports a given function node. + * @param {ASTNode} node A node to report. This is a FunctionExpression or + * an ArrowFunctionExpression. + * @returns {void} + */ + function report(node) { + context.report({ + node: node.parent.parent, + messageId: "unexpected", + loc: node.parent.property.loc, + fix(fixer) { + if (node.parent.parent.arguments.length && !isSideEffectFree(node.parent.parent.arguments[0])) { + return null; + } + + const firstTokenToRemove = sourceCode + .getFirstTokenBetween(node.parent.object, node.parent.property, astUtils.isNotClosingParenToken); + const lastTokenToRemove = sourceCode.getLastToken(node.parent.parent); + + if (sourceCode.commentsExistBetween(firstTokenToRemove, lastTokenToRemove)) { + return null; + } + + return fixer.removeRange([firstTokenToRemove.range[0], node.parent.parent.range[1]]); + } + }); + } + + /** + * Checks whether or not a given function node is the callee of `.bind()` + * method. + * + * e.g. `(function() {}.bind(foo))` + * @param {ASTNode} node A node to report. This is a FunctionExpression or + * an ArrowFunctionExpression. + * @returns {boolean} `true` if the node is the callee of `.bind()` method. + */ + function isCalleeOfBindMethod(node) { + const parent = node.parent; + const grandparent = parent.parent; + + return ( + grandparent && + grandparent.type === "CallExpression" && + grandparent.callee === parent && + grandparent.arguments.length === 1 && + grandparent.arguments[0].type !== "SpreadElement" && + parent.type === "MemberExpression" && + parent.object === node && + astUtils.getStaticPropertyName(parent) === "bind" + ); + } + + /** + * Adds a scope information object to the stack. + * @param {ASTNode} node A node to add. This node is a FunctionExpression + * or a FunctionDeclaration node. + * @returns {void} + */ + function enterFunction(node) { + scopeInfo = { + isBound: isCalleeOfBindMethod(node), + thisFound: false, + upper: scopeInfo + }; + } + + /** + * Removes the scope information object from the top of the stack. + * At the same time, this reports the function node if the function has + * `.bind()` and the `this` keywords found. + * @param {ASTNode} node A node to remove. This node is a + * FunctionExpression or a FunctionDeclaration node. + * @returns {void} + */ + function exitFunction(node) { + if (scopeInfo.isBound && !scopeInfo.thisFound) { + report(node); + } + + scopeInfo = scopeInfo.upper; + } + + /** + * Reports a given arrow function if the function is callee of `.bind()` + * method. + * @param {ASTNode} node A node to report. This node is an + * ArrowFunctionExpression. + * @returns {void} + */ + function exitArrowFunction(node) { + if (isCalleeOfBindMethod(node)) { + report(node); + } + } + + /** + * Set the mark as the `this` keyword was found in this scope. + * @returns {void} + */ + function markAsThisFound() { + if (scopeInfo) { + scopeInfo.thisFound = true; + } + } + + return { + "ArrowFunctionExpression:exit": exitArrowFunction, + FunctionDeclaration: enterFunction, + "FunctionDeclaration:exit": exitFunction, + FunctionExpression: enterFunction, + "FunctionExpression:exit": exitFunction, + ThisExpression: markAsThisFound + }; + } +}; diff --git a/eslint/lib/rules/no-extra-boolean-cast.js b/eslint/lib/rules/no-extra-boolean-cast.js new file mode 100644 index 0000000..aba8e63 --- /dev/null +++ b/eslint/lib/rules/no-extra-boolean-cast.js @@ -0,0 +1,306 @@ +/** + * @fileoverview Rule to flag unnecessary double negation in Boolean contexts + * @author Brandon Mills + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); +const eslintUtils = require("eslint-utils"); + +const precedence = astUtils.getPrecedence; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow unnecessary boolean casts", + category: "Possible Errors", + recommended: true, + url: "https://eslint.org/docs/rules/no-extra-boolean-cast" + }, + + schema: [{ + type: "object", + properties: { + enforceForLogicalOperands: { + type: "boolean", + default: false + } + }, + additionalProperties: false + }], + fixable: "code", + + messages: { + unexpectedCall: "Redundant Boolean call.", + unexpectedNegation: "Redundant double negation." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + // Node types which have a test which will coerce values to booleans. + const BOOLEAN_NODE_TYPES = [ + "IfStatement", + "DoWhileStatement", + "WhileStatement", + "ConditionalExpression", + "ForStatement" + ]; + + /** + * Check if a node is a Boolean function or constructor. + * @param {ASTNode} node the node + * @returns {boolean} If the node is Boolean function or constructor + */ + function isBooleanFunctionOrConstructorCall(node) { + + // Boolean() and new Boolean() + return (node.type === "CallExpression" || node.type === "NewExpression") && + node.callee.type === "Identifier" && + node.callee.name === "Boolean"; + } + + /** + * Checks whether the node is a logical expression and that the option is enabled + * @param {ASTNode} node the node + * @returns {boolean} if the node is a logical expression and option is enabled + */ + function isLogicalContext(node) { + return node.type === "LogicalExpression" && + (node.operator === "||" || node.operator === "&&") && + (context.options.length && context.options[0].enforceForLogicalOperands === true); + + } + + + /** + * Check if a node is in a context where its value would be coerced to a boolean at runtime. + * @param {ASTNode} node The node + * @returns {boolean} If it is in a boolean context + */ + function isInBooleanContext(node) { + return ( + (isBooleanFunctionOrConstructorCall(node.parent) && + node === node.parent.arguments[0]) || + + (BOOLEAN_NODE_TYPES.indexOf(node.parent.type) !== -1 && + node === node.parent.test) || + + // ! + (node.parent.type === "UnaryExpression" && + node.parent.operator === "!") + ); + } + + /** + * Checks whether the node is a context that should report an error + * Acts recursively if it is in a logical context + * @param {ASTNode} node the node + * @returns {boolean} If the node is in one of the flagged contexts + */ + function isInFlaggedContext(node) { + return isInBooleanContext(node) || + (isLogicalContext(node.parent) && + + // For nested logical statements + isInFlaggedContext(node.parent) + ); + } + + + /** + * Check if a node has comments inside. + * @param {ASTNode} node The node to check. + * @returns {boolean} `true` if it has comments inside. + */ + function hasCommentsInside(node) { + return Boolean(sourceCode.getCommentsInside(node).length); + } + + /** + * Checks if the given node is wrapped in grouping parentheses. Parentheses for constructs such as if() don't count. + * @param {ASTNode} node The node to check. + * @returns {boolean} `true` if the node is parenthesized. + * @private + */ + function isParenthesized(node) { + return eslintUtils.isParenthesized(1, node, sourceCode); + } + + /** + * Determines whether the given node needs to be parenthesized when replacing the previous node. + * It assumes that `previousNode` is the node to be reported by this rule, so it has a limited list + * of possible parent node types. By the same assumption, the node's role in a particular parent is already known. + * For example, if the parent is `ConditionalExpression`, `previousNode` must be its `test` child. + * @param {ASTNode} previousNode Previous node. + * @param {ASTNode} node The node to check. + * @returns {boolean} `true` if the node needs to be parenthesized. + */ + function needsParens(previousNode, node) { + if (isParenthesized(previousNode)) { + + // parentheses around the previous node will stay, so there is no need for an additional pair + return false; + } + + // parent of the previous node will become parent of the replacement node + const parent = previousNode.parent; + + switch (parent.type) { + case "CallExpression": + case "NewExpression": + return node.type === "SequenceExpression"; + case "IfStatement": + case "DoWhileStatement": + case "WhileStatement": + case "ForStatement": + return false; + case "ConditionalExpression": + return precedence(node) <= precedence(parent); + case "UnaryExpression": + return precedence(node) < precedence(parent); + case "LogicalExpression": + if (previousNode === parent.left) { + return precedence(node) < precedence(parent); + } + return precedence(node) <= precedence(parent); + + /* istanbul ignore next */ + default: + throw new Error(`Unexpected parent type: ${parent.type}`); + } + } + + return { + UnaryExpression(node) { + const parent = node.parent; + + + // Exit early if it's guaranteed not to match + if (node.operator !== "!" || + parent.type !== "UnaryExpression" || + parent.operator !== "!") { + return; + } + + + if (isInFlaggedContext(parent)) { + context.report({ + node: parent, + messageId: "unexpectedNegation", + fix(fixer) { + if (hasCommentsInside(parent)) { + return null; + } + + if (needsParens(parent, node.argument)) { + return fixer.replaceText(parent, `(${sourceCode.getText(node.argument)})`); + } + + let prefix = ""; + const tokenBefore = sourceCode.getTokenBefore(parent); + const firstReplacementToken = sourceCode.getFirstToken(node.argument); + + if ( + tokenBefore && + tokenBefore.range[1] === parent.range[0] && + !astUtils.canTokensBeAdjacent(tokenBefore, firstReplacementToken) + ) { + prefix = " "; + } + + return fixer.replaceText(parent, prefix + sourceCode.getText(node.argument)); + } + }); + } + }, + + CallExpression(node) { + if (node.callee.type !== "Identifier" || node.callee.name !== "Boolean") { + return; + } + + if (isInFlaggedContext(node)) { + context.report({ + node, + messageId: "unexpectedCall", + fix(fixer) { + const parent = node.parent; + + if (node.arguments.length === 0) { + if (parent.type === "UnaryExpression" && parent.operator === "!") { + + /* + * !Boolean() -> true + */ + + if (hasCommentsInside(parent)) { + return null; + } + + const replacement = "true"; + let prefix = ""; + const tokenBefore = sourceCode.getTokenBefore(parent); + + if ( + tokenBefore && + tokenBefore.range[1] === parent.range[0] && + !astUtils.canTokensBeAdjacent(tokenBefore, replacement) + ) { + prefix = " "; + } + + return fixer.replaceText(parent, prefix + replacement); + } + + /* + * Boolean() -> false + */ + + if (hasCommentsInside(node)) { + return null; + } + + return fixer.replaceText(node, "false"); + } + + if (node.arguments.length === 1) { + const argument = node.arguments[0]; + + if (argument.type === "SpreadElement" || hasCommentsInside(node)) { + return null; + } + + /* + * Boolean(expression) -> expression + */ + + if (needsParens(node, argument)) { + return fixer.replaceText(node, `(${sourceCode.getText(argument)})`); + } + + return fixer.replaceText(node, sourceCode.getText(argument)); + } + + // two or more arguments + return null; + } + }); + } + } + }; + + } +}; diff --git a/eslint/lib/rules/no-extra-label.js b/eslint/lib/rules/no-extra-label.js new file mode 100644 index 0000000..81406e7 --- /dev/null +++ b/eslint/lib/rules/no-extra-label.js @@ -0,0 +1,149 @@ +/** + * @fileoverview Rule to disallow unnecessary labels + * @author Toru Nagashima + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow unnecessary labels", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-extra-label" + }, + + schema: [], + fixable: "code", + + messages: { + unexpected: "This label '{{name}}' is unnecessary." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + let scopeInfo = null; + + /** + * Creates a new scope with a breakable statement. + * @param {ASTNode} node A node to create. This is a BreakableStatement. + * @returns {void} + */ + function enterBreakableStatement(node) { + scopeInfo = { + label: node.parent.type === "LabeledStatement" ? node.parent.label : null, + breakable: true, + upper: scopeInfo + }; + } + + /** + * Removes the top scope of the stack. + * @returns {void} + */ + function exitBreakableStatement() { + scopeInfo = scopeInfo.upper; + } + + /** + * Creates a new scope with a labeled statement. + * + * This ignores it if the body is a breakable statement. + * In this case it's handled in the `enterBreakableStatement` function. + * @param {ASTNode} node A node to create. This is a LabeledStatement. + * @returns {void} + */ + function enterLabeledStatement(node) { + if (!astUtils.isBreakableStatement(node.body)) { + scopeInfo = { + label: node.label, + breakable: false, + upper: scopeInfo + }; + } + } + + /** + * Removes the top scope of the stack. + * + * This ignores it if the body is a breakable statement. + * In this case it's handled in the `exitBreakableStatement` function. + * @param {ASTNode} node A node. This is a LabeledStatement. + * @returns {void} + */ + function exitLabeledStatement(node) { + if (!astUtils.isBreakableStatement(node.body)) { + scopeInfo = scopeInfo.upper; + } + } + + /** + * Reports a given control node if it's unnecessary. + * @param {ASTNode} node A node. This is a BreakStatement or a + * ContinueStatement. + * @returns {void} + */ + function reportIfUnnecessary(node) { + if (!node.label) { + return; + } + + const labelNode = node.label; + + for (let info = scopeInfo; info !== null; info = info.upper) { + if (info.breakable || info.label && info.label.name === labelNode.name) { + if (info.breakable && info.label && info.label.name === labelNode.name) { + context.report({ + node: labelNode, + messageId: "unexpected", + data: labelNode, + fix(fixer) { + const breakOrContinueToken = sourceCode.getFirstToken(node); + + if (sourceCode.commentsExistBetween(breakOrContinueToken, labelNode)) { + return null; + } + + return fixer.removeRange([breakOrContinueToken.range[1], labelNode.range[1]]); + } + }); + } + return; + } + } + } + + return { + WhileStatement: enterBreakableStatement, + "WhileStatement:exit": exitBreakableStatement, + DoWhileStatement: enterBreakableStatement, + "DoWhileStatement:exit": exitBreakableStatement, + ForStatement: enterBreakableStatement, + "ForStatement:exit": exitBreakableStatement, + ForInStatement: enterBreakableStatement, + "ForInStatement:exit": exitBreakableStatement, + ForOfStatement: enterBreakableStatement, + "ForOfStatement:exit": exitBreakableStatement, + SwitchStatement: enterBreakableStatement, + "SwitchStatement:exit": exitBreakableStatement, + LabeledStatement: enterLabeledStatement, + "LabeledStatement:exit": exitLabeledStatement, + BreakStatement: reportIfUnnecessary, + ContinueStatement: reportIfUnnecessary + }; + } +}; diff --git a/eslint/lib/rules/no-extra-parens.js b/eslint/lib/rules/no-extra-parens.js new file mode 100644 index 0000000..a3dd5ba --- /dev/null +++ b/eslint/lib/rules/no-extra-parens.js @@ -0,0 +1,1107 @@ +/** + * @fileoverview Disallow parenthesising higher precedence subexpressions. + * @author Michael Ficarra + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +const { isParenthesized: isParenthesizedRaw } = require("eslint-utils"); +const astUtils = require("./utils/ast-utils.js"); + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "disallow unnecessary parentheses", + category: "Possible Errors", + recommended: false, + url: "https://eslint.org/docs/rules/no-extra-parens" + }, + + fixable: "code", + + schema: { + anyOf: [ + { + type: "array", + items: [ + { + enum: ["functions"] + } + ], + minItems: 0, + maxItems: 1 + }, + { + type: "array", + items: [ + { + enum: ["all"] + }, + { + type: "object", + properties: { + conditionalAssign: { type: "boolean" }, + nestedBinaryExpressions: { type: "boolean" }, + returnAssign: { type: "boolean" }, + ignoreJSX: { enum: ["none", "all", "single-line", "multi-line"] }, + enforceForArrowConditionals: { type: "boolean" }, + enforceForSequenceExpressions: { type: "boolean" }, + enforceForNewInMemberExpressions: { type: "boolean" } + }, + additionalProperties: false + } + ], + minItems: 0, + maxItems: 2 + } + ] + }, + + messages: { + unexpected: "Unnecessary parentheses around expression." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + const tokensToIgnore = new WeakSet(); + const precedence = astUtils.getPrecedence; + const ALL_NODES = context.options[0] !== "functions"; + const EXCEPT_COND_ASSIGN = ALL_NODES && context.options[1] && context.options[1].conditionalAssign === false; + const NESTED_BINARY = ALL_NODES && context.options[1] && context.options[1].nestedBinaryExpressions === false; + const EXCEPT_RETURN_ASSIGN = ALL_NODES && context.options[1] && context.options[1].returnAssign === false; + const IGNORE_JSX = ALL_NODES && context.options[1] && context.options[1].ignoreJSX; + const IGNORE_ARROW_CONDITIONALS = ALL_NODES && context.options[1] && + context.options[1].enforceForArrowConditionals === false; + const IGNORE_SEQUENCE_EXPRESSIONS = ALL_NODES && context.options[1] && + context.options[1].enforceForSequenceExpressions === false; + const IGNORE_NEW_IN_MEMBER_EXPR = ALL_NODES && context.options[1] && + context.options[1].enforceForNewInMemberExpressions === false; + + const PRECEDENCE_OF_ASSIGNMENT_EXPR = precedence({ type: "AssignmentExpression" }); + const PRECEDENCE_OF_UPDATE_EXPR = precedence({ type: "UpdateExpression" }); + + let reportsBuffer; + + /** + * Determines if this rule should be enforced for a node given the current configuration. + * @param {ASTNode} node The node to be checked. + * @returns {boolean} True if the rule should be enforced for this node. + * @private + */ + function ruleApplies(node) { + if (node.type === "JSXElement" || node.type === "JSXFragment") { + const isSingleLine = node.loc.start.line === node.loc.end.line; + + switch (IGNORE_JSX) { + + // Exclude this JSX element from linting + case "all": + return false; + + // Exclude this JSX element if it is multi-line element + case "multi-line": + return isSingleLine; + + // Exclude this JSX element if it is single-line element + case "single-line": + return !isSingleLine; + + // Nothing special to be done for JSX elements + case "none": + break; + + // no default + } + } + + if (node.type === "SequenceExpression" && IGNORE_SEQUENCE_EXPRESSIONS) { + return false; + } + + return ALL_NODES || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression"; + } + + /** + * Determines if a node is surrounded by parentheses. + * @param {ASTNode} node The node to be checked. + * @returns {boolean} True if the node is parenthesised. + * @private + */ + function isParenthesised(node) { + return isParenthesizedRaw(1, node, sourceCode); + } + + /** + * Determines if a node is surrounded by parentheses twice. + * @param {ASTNode} node The node to be checked. + * @returns {boolean} True if the node is doubly parenthesised. + * @private + */ + function isParenthesisedTwice(node) { + return isParenthesizedRaw(2, node, sourceCode); + } + + /** + * Determines if a node is surrounded by (potentially) invalid parentheses. + * @param {ASTNode} node The node to be checked. + * @returns {boolean} True if the node is incorrectly parenthesised. + * @private + */ + function hasExcessParens(node) { + return ruleApplies(node) && isParenthesised(node); + } + + /** + * Determines if a node that is expected to be parenthesised is surrounded by + * (potentially) invalid extra parentheses. + * @param {ASTNode} node The node to be checked. + * @returns {boolean} True if the node is has an unexpected extra pair of parentheses. + * @private + */ + function hasDoubleExcessParens(node) { + return ruleApplies(node) && isParenthesisedTwice(node); + } + + /** + * Determines if a node that is expected to be parenthesised is surrounded by + * (potentially) invalid extra parentheses with considering precedence level of the node. + * If the preference level of the node is not higher or equal to precedence lower limit, it also checks + * whether the node is surrounded by parentheses twice or not. + * @param {ASTNode} node The node to be checked. + * @param {number} precedenceLowerLimit The lower limit of precedence. + * @returns {boolean} True if the node is has an unexpected extra pair of parentheses. + * @private + */ + function hasExcessParensWithPrecedence(node, precedenceLowerLimit) { + if (ruleApplies(node) && isParenthesised(node)) { + if ( + precedence(node) >= precedenceLowerLimit || + isParenthesisedTwice(node) + ) { + return true; + } + } + return false; + } + + /** + * Determines if a node test expression is allowed to have a parenthesised assignment + * @param {ASTNode} node The node to be checked. + * @returns {boolean} True if the assignment can be parenthesised. + * @private + */ + function isCondAssignException(node) { + return EXCEPT_COND_ASSIGN && node.test.type === "AssignmentExpression"; + } + + /** + * Determines if a node is in a return statement + * @param {ASTNode} node The node to be checked. + * @returns {boolean} True if the node is in a return statement. + * @private + */ + function isInReturnStatement(node) { + for (let currentNode = node; currentNode; currentNode = currentNode.parent) { + if ( + currentNode.type === "ReturnStatement" || + (currentNode.type === "ArrowFunctionExpression" && currentNode.body.type !== "BlockStatement") + ) { + return true; + } + } + + return false; + } + + /** + * Determines if a constructor function is newed-up with parens + * @param {ASTNode} newExpression The NewExpression node to be checked. + * @returns {boolean} True if the constructor is called with parens. + * @private + */ + function isNewExpressionWithParens(newExpression) { + const lastToken = sourceCode.getLastToken(newExpression); + const penultimateToken = sourceCode.getTokenBefore(lastToken); + + return newExpression.arguments.length > 0 || + ( + + // The expression should end with its own parens, e.g., new new foo() is not a new expression with parens + astUtils.isOpeningParenToken(penultimateToken) && + astUtils.isClosingParenToken(lastToken) && + newExpression.callee.range[1] < newExpression.range[1] + ); + } + + /** + * Determines if a node is or contains an assignment expression + * @param {ASTNode} node The node to be checked. + * @returns {boolean} True if the node is or contains an assignment expression. + * @private + */ + function containsAssignment(node) { + if (node.type === "AssignmentExpression") { + return true; + } + if (node.type === "ConditionalExpression" && + (node.consequent.type === "AssignmentExpression" || node.alternate.type === "AssignmentExpression")) { + return true; + } + if ((node.left && node.left.type === "AssignmentExpression") || + (node.right && node.right.type === "AssignmentExpression")) { + return true; + } + + return false; + } + + /** + * Determines if a node is contained by or is itself a return statement and is allowed to have a parenthesised assignment + * @param {ASTNode} node The node to be checked. + * @returns {boolean} True if the assignment can be parenthesised. + * @private + */ + function isReturnAssignException(node) { + if (!EXCEPT_RETURN_ASSIGN || !isInReturnStatement(node)) { + return false; + } + + if (node.type === "ReturnStatement") { + return node.argument && containsAssignment(node.argument); + } + if (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") { + return containsAssignment(node.body); + } + return containsAssignment(node); + + } + + /** + * Determines if a node following a [no LineTerminator here] restriction is + * surrounded by (potentially) invalid extra parentheses. + * @param {Token} token The token preceding the [no LineTerminator here] restriction. + * @param {ASTNode} node The node to be checked. + * @returns {boolean} True if the node is incorrectly parenthesised. + * @private + */ + function hasExcessParensNoLineTerminator(token, node) { + if (token.loc.end.line === node.loc.start.line) { + return hasExcessParens(node); + } + + return hasDoubleExcessParens(node); + } + + /** + * Determines whether a node should be preceded by an additional space when removing parens + * @param {ASTNode} node node to evaluate; must be surrounded by parentheses + * @returns {boolean} `true` if a space should be inserted before the node + * @private + */ + function requiresLeadingSpace(node) { + const leftParenToken = sourceCode.getTokenBefore(node); + const tokenBeforeLeftParen = sourceCode.getTokenBefore(leftParenToken, { includeComments: true }); + const tokenAfterLeftParen = sourceCode.getTokenAfter(leftParenToken, { includeComments: true }); + + return tokenBeforeLeftParen && + tokenBeforeLeftParen.range[1] === leftParenToken.range[0] && + leftParenToken.range[1] === tokenAfterLeftParen.range[0] && + !astUtils.canTokensBeAdjacent(tokenBeforeLeftParen, tokenAfterLeftParen); + } + + /** + * Determines whether a node should be followed by an additional space when removing parens + * @param {ASTNode} node node to evaluate; must be surrounded by parentheses + * @returns {boolean} `true` if a space should be inserted after the node + * @private + */ + function requiresTrailingSpace(node) { + const nextTwoTokens = sourceCode.getTokensAfter(node, { count: 2 }); + const rightParenToken = nextTwoTokens[0]; + const tokenAfterRightParen = nextTwoTokens[1]; + const tokenBeforeRightParen = sourceCode.getLastToken(node); + + return rightParenToken && tokenAfterRightParen && + !sourceCode.isSpaceBetweenTokens(rightParenToken, tokenAfterRightParen) && + !astUtils.canTokensBeAdjacent(tokenBeforeRightParen, tokenAfterRightParen); + } + + /** + * Determines if a given expression node is an IIFE + * @param {ASTNode} node The node to check + * @returns {boolean} `true` if the given node is an IIFE + */ + function isIIFE(node) { + return node.type === "CallExpression" && node.callee.type === "FunctionExpression"; + } + + /** + * Determines if the given node can be the assignment target in destructuring or the LHS of an assignment. + * This is to avoid an autofix that could change behavior because parsers mistakenly allow invalid syntax, + * such as `(a = b) = c` and `[(a = b) = c] = []`. Ideally, this function shouldn't be necessary. + * @param {ASTNode} [node] The node to check + * @returns {boolean} `true` if the given node can be a valid assignment target + */ + function canBeAssignmentTarget(node) { + return node && (node.type === "Identifier" || node.type === "MemberExpression"); + } + + /** + * Report the node + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function report(node) { + const leftParenToken = sourceCode.getTokenBefore(node); + const rightParenToken = sourceCode.getTokenAfter(node); + + if (!isParenthesisedTwice(node)) { + if (tokensToIgnore.has(sourceCode.getFirstToken(node))) { + return; + } + + if (isIIFE(node) && !isParenthesised(node.callee)) { + return; + } + } + + /** + * Finishes reporting + * @returns {void} + * @private + */ + function finishReport() { + context.report({ + node, + loc: leftParenToken.loc, + messageId: "unexpected", + fix(fixer) { + const parenthesizedSource = sourceCode.text.slice(leftParenToken.range[1], rightParenToken.range[0]); + + return fixer.replaceTextRange([ + leftParenToken.range[0], + rightParenToken.range[1] + ], (requiresLeadingSpace(node) ? " " : "") + parenthesizedSource + (requiresTrailingSpace(node) ? " " : "")); + } + }); + } + + if (reportsBuffer) { + reportsBuffer.reports.push({ node, finishReport }); + return; + } + + finishReport(); + } + + /** + * Evaluate a argument of the node. + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function checkArgumentWithPrecedence(node) { + if (hasExcessParensWithPrecedence(node.argument, precedence(node))) { + report(node.argument); + } + } + + /** + * Check if a member expression contains a call expression + * @param {ASTNode} node MemberExpression node to evaluate + * @returns {boolean} true if found, false if not + */ + function doesMemberExpressionContainCallExpression(node) { + let currentNode = node.object; + let currentNodeType = node.object.type; + + while (currentNodeType === "MemberExpression") { + currentNode = currentNode.object; + currentNodeType = currentNode.type; + } + + return currentNodeType === "CallExpression"; + } + + /** + * Evaluate a new call + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function checkCallNew(node) { + const callee = node.callee; + + if (hasExcessParensWithPrecedence(callee, precedence(node))) { + const hasNewParensException = callee.type === "NewExpression" && !isNewExpressionWithParens(callee); + + if ( + hasDoubleExcessParens(callee) || + !isIIFE(node) && !hasNewParensException && !( + + // Allow extra parens around a new expression if they are intervening parentheses. + node.type === "NewExpression" && + callee.type === "MemberExpression" && + doesMemberExpressionContainCallExpression(callee) + ) + ) { + report(node.callee); + } + } + node.arguments + .filter(arg => hasExcessParensWithPrecedence(arg, PRECEDENCE_OF_ASSIGNMENT_EXPR)) + .forEach(report); + } + + /** + * Evaluate binary logicals + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function checkBinaryLogical(node) { + const prec = precedence(node); + const leftPrecedence = precedence(node.left); + const rightPrecedence = precedence(node.right); + const isExponentiation = node.operator === "**"; + const shouldSkipLeft = NESTED_BINARY && (node.left.type === "BinaryExpression" || node.left.type === "LogicalExpression"); + const shouldSkipRight = NESTED_BINARY && (node.right.type === "BinaryExpression" || node.right.type === "LogicalExpression"); + + if (!shouldSkipLeft && hasExcessParens(node.left)) { + if ( + !(node.left.type === "UnaryExpression" && isExponentiation) && + (leftPrecedence > prec || (leftPrecedence === prec && !isExponentiation)) || + isParenthesisedTwice(node.left) + ) { + report(node.left); + } + } + + if (!shouldSkipRight && hasExcessParens(node.right)) { + if ( + (rightPrecedence > prec || (rightPrecedence === prec && isExponentiation)) || + isParenthesisedTwice(node.right) + ) { + report(node.right); + } + } + } + + /** + * Check the parentheses around the super class of the given class definition. + * @param {ASTNode} node The node of class declarations to check. + * @returns {void} + */ + function checkClass(node) { + if (!node.superClass) { + return; + } + + /* + * If `node.superClass` is a LeftHandSideExpression, parentheses are extra. + * Otherwise, parentheses are needed. + */ + const hasExtraParens = precedence(node.superClass) > PRECEDENCE_OF_UPDATE_EXPR + ? hasExcessParens(node.superClass) + : hasDoubleExcessParens(node.superClass); + + if (hasExtraParens) { + report(node.superClass); + } + } + + /** + * Check the parentheses around the argument of the given spread operator. + * @param {ASTNode} node The node of spread elements/properties to check. + * @returns {void} + */ + function checkSpreadOperator(node) { + if (hasExcessParensWithPrecedence(node.argument, PRECEDENCE_OF_ASSIGNMENT_EXPR)) { + report(node.argument); + } + } + + /** + * Checks the parentheses for an ExpressionStatement or ExportDefaultDeclaration + * @param {ASTNode} node The ExpressionStatement.expression or ExportDefaultDeclaration.declaration node + * @returns {void} + */ + function checkExpressionOrExportStatement(node) { + const firstToken = isParenthesised(node) ? sourceCode.getTokenBefore(node) : sourceCode.getFirstToken(node); + const secondToken = sourceCode.getTokenAfter(firstToken, astUtils.isNotOpeningParenToken); + const thirdToken = secondToken ? sourceCode.getTokenAfter(secondToken) : null; + const tokenAfterClosingParens = secondToken ? sourceCode.getTokenAfter(secondToken, astUtils.isNotClosingParenToken) : null; + + if ( + astUtils.isOpeningParenToken(firstToken) && + ( + astUtils.isOpeningBraceToken(secondToken) || + secondToken.type === "Keyword" && ( + secondToken.value === "function" || + secondToken.value === "class" || + secondToken.value === "let" && + tokenAfterClosingParens && + ( + astUtils.isOpeningBracketToken(tokenAfterClosingParens) || + tokenAfterClosingParens.type === "Identifier" + ) + ) || + secondToken && secondToken.type === "Identifier" && secondToken.value === "async" && thirdToken && thirdToken.type === "Keyword" && thirdToken.value === "function" + ) + ) { + tokensToIgnore.add(secondToken); + } + + if (hasExcessParens(node)) { + report(node); + } + } + + /** + * Finds the path from the given node to the specified ancestor. + * @param {ASTNode} node First node in the path. + * @param {ASTNode} ancestor Last node in the path. + * @returns {ASTNode[]} Path, including both nodes. + * @throws {Error} If the given node does not have the specified ancestor. + */ + function pathToAncestor(node, ancestor) { + const path = [node]; + let currentNode = node; + + while (currentNode !== ancestor) { + + currentNode = currentNode.parent; + + /* istanbul ignore if */ + if (currentNode === null) { + throw new Error("Nodes are not in the ancestor-descendant relationship."); + } + + path.push(currentNode); + } + + return path; + } + + /** + * Finds the path from the given node to the specified descendant. + * @param {ASTNode} node First node in the path. + * @param {ASTNode} descendant Last node in the path. + * @returns {ASTNode[]} Path, including both nodes. + * @throws {Error} If the given node does not have the specified descendant. + */ + function pathToDescendant(node, descendant) { + return pathToAncestor(descendant, node).reverse(); + } + + /** + * Checks whether the syntax of the given ancestor of an 'in' expression inside a for-loop initializer + * is preventing the 'in' keyword from being interpreted as a part of an ill-formed for-in loop. + * @param {ASTNode} node Ancestor of an 'in' expression. + * @param {ASTNode} child Child of the node, ancestor of the same 'in' expression or the 'in' expression itself. + * @returns {boolean} True if the keyword 'in' would be interpreted as the 'in' operator, without any parenthesis. + */ + function isSafelyEnclosingInExpression(node, child) { + switch (node.type) { + case "ArrayExpression": + case "ArrayPattern": + case "BlockStatement": + case "ObjectExpression": + case "ObjectPattern": + case "TemplateLiteral": + return true; + case "ArrowFunctionExpression": + case "FunctionExpression": + return node.params.includes(child); + case "CallExpression": + case "NewExpression": + return node.arguments.includes(child); + case "MemberExpression": + return node.computed && node.property === child; + case "ConditionalExpression": + return node.consequent === child; + default: + return false; + } + } + + /** + * Starts a new reports buffering. Warnings will be stored in a buffer instead of being reported immediately. + * An additional logic that requires multiple nodes (e.g. a whole subtree) may dismiss some of the stored warnings. + * @returns {void} + */ + function startNewReportsBuffering() { + reportsBuffer = { + upper: reportsBuffer, + inExpressionNodes: [], + reports: [] + }; + } + + /** + * Ends the current reports buffering. + * @returns {void} + */ + function endCurrentReportsBuffering() { + const { upper, inExpressionNodes, reports } = reportsBuffer; + + if (upper) { + upper.inExpressionNodes.push(...inExpressionNodes); + upper.reports.push(...reports); + } else { + + // flush remaining reports + reports.forEach(({ finishReport }) => finishReport()); + } + + reportsBuffer = upper; + } + + /** + * Checks whether the given node is in the current reports buffer. + * @param {ASTNode} node Node to check. + * @returns {boolean} True if the node is in the current buffer, false otherwise. + */ + function isInCurrentReportsBuffer(node) { + return reportsBuffer.reports.some(r => r.node === node); + } + + /** + * Removes the given node from the current reports buffer. + * @param {ASTNode} node Node to remove. + * @returns {void} + */ + function removeFromCurrentReportsBuffer(node) { + reportsBuffer.reports = reportsBuffer.reports.filter(r => r.node !== node); + } + + return { + ArrayExpression(node) { + node.elements + .filter(e => e && hasExcessParensWithPrecedence(e, PRECEDENCE_OF_ASSIGNMENT_EXPR)) + .forEach(report); + }, + + ArrayPattern(node) { + node.elements + .filter(e => canBeAssignmentTarget(e) && hasExcessParens(e)) + .forEach(report); + }, + + ArrowFunctionExpression(node) { + if (isReturnAssignException(node)) { + return; + } + + if (node.body.type === "ConditionalExpression" && + IGNORE_ARROW_CONDITIONALS + ) { + return; + } + + if (node.body.type !== "BlockStatement") { + const firstBodyToken = sourceCode.getFirstToken(node.body, astUtils.isNotOpeningParenToken); + const tokenBeforeFirst = sourceCode.getTokenBefore(firstBodyToken); + + if (astUtils.isOpeningParenToken(tokenBeforeFirst) && astUtils.isOpeningBraceToken(firstBodyToken)) { + tokensToIgnore.add(firstBodyToken); + } + if (hasExcessParensWithPrecedence(node.body, PRECEDENCE_OF_ASSIGNMENT_EXPR)) { + report(node.body); + } + } + }, + + AssignmentExpression(node) { + if (canBeAssignmentTarget(node.left) && hasExcessParens(node.left)) { + report(node.left); + } + + if (!isReturnAssignException(node) && hasExcessParensWithPrecedence(node.right, precedence(node))) { + report(node.right); + } + }, + + BinaryExpression(node) { + if (reportsBuffer && node.operator === "in") { + reportsBuffer.inExpressionNodes.push(node); + } + + checkBinaryLogical(node); + }, + + CallExpression: checkCallNew, + + ClassBody(node) { + node.body + .filter(member => member.type === "MethodDefinition" && member.computed && member.key) + .filter(member => hasExcessParensWithPrecedence(member.key, PRECEDENCE_OF_ASSIGNMENT_EXPR)) + .forEach(member => report(member.key)); + }, + + ConditionalExpression(node) { + if (isReturnAssignException(node)) { + return; + } + if ( + !isCondAssignException(node) && + hasExcessParensWithPrecedence(node.test, precedence({ type: "LogicalExpression", operator: "||" })) + ) { + report(node.test); + } + + if (hasExcessParensWithPrecedence(node.consequent, PRECEDENCE_OF_ASSIGNMENT_EXPR)) { + report(node.consequent); + } + + if (hasExcessParensWithPrecedence(node.alternate, PRECEDENCE_OF_ASSIGNMENT_EXPR)) { + report(node.alternate); + } + }, + + DoWhileStatement(node) { + if (hasExcessParens(node.test) && !isCondAssignException(node)) { + report(node.test); + } + }, + + ExportDefaultDeclaration: node => checkExpressionOrExportStatement(node.declaration), + ExpressionStatement: node => checkExpressionOrExportStatement(node.expression), + + "ForInStatement, ForOfStatement"(node) { + if (node.left.type !== "VariableDeclarator") { + const firstLeftToken = sourceCode.getFirstToken(node.left, astUtils.isNotOpeningParenToken); + + if ( + firstLeftToken.value === "let" && ( + + /* + * If `let` is the only thing on the left side of the loop, it's the loop variable: `for ((let) of foo);` + * Removing it will cause a syntax error, because it will be parsed as the start of a VariableDeclarator. + */ + (firstLeftToken.range[1] === node.left.range[1] || /* + * If `let` is followed by a `[` token, it's a property access on the `let` value: `for ((let[foo]) of bar);` + * Removing it will cause the property access to be parsed as a destructuring declaration of `foo` instead. + */ + astUtils.isOpeningBracketToken( + sourceCode.getTokenAfter(firstLeftToken, astUtils.isNotClosingParenToken) + )) + ) + ) { + tokensToIgnore.add(firstLeftToken); + } + } + + if (node.type === "ForOfStatement") { + const hasExtraParens = node.right.type === "SequenceExpression" + ? hasDoubleExcessParens(node.right) + : hasExcessParens(node.right); + + if (hasExtraParens) { + report(node.right); + } + } else if (hasExcessParens(node.right)) { + report(node.right); + } + + if (hasExcessParens(node.left)) { + report(node.left); + } + }, + + ForStatement(node) { + if (node.test && hasExcessParens(node.test) && !isCondAssignException(node)) { + report(node.test); + } + + if (node.update && hasExcessParens(node.update)) { + report(node.update); + } + + if (node.init) { + startNewReportsBuffering(); + + if (hasExcessParens(node.init)) { + report(node.init); + } + } + }, + + "ForStatement > *.init:exit"(node) { + + /* + * Removing parentheses around `in` expressions might change semantics and cause errors. + * + * For example, this valid for loop: + * for (let a = (b in c); ;); + * after removing parentheses would be treated as an invalid for-in loop: + * for (let a = b in c; ;); + */ + + if (reportsBuffer.reports.length) { + reportsBuffer.inExpressionNodes.forEach(inExpressionNode => { + const path = pathToDescendant(node, inExpressionNode); + let nodeToExclude; + + for (let i = 0; i < path.length; i++) { + const pathNode = path[i]; + + if (i < path.length - 1) { + const nextPathNode = path[i + 1]; + + if (isSafelyEnclosingInExpression(pathNode, nextPathNode)) { + + // The 'in' expression in safely enclosed by the syntax of its ancestor nodes (e.g. by '{}' or '[]'). + return; + } + } + + if (isParenthesised(pathNode)) { + if (isInCurrentReportsBuffer(pathNode)) { + + // This node was supposed to be reported, but parentheses might be necessary. + + if (isParenthesisedTwice(pathNode)) { + + /* + * This node is parenthesised twice, it certainly has at least one pair of `extra` parentheses. + * If the --fix option is on, the current fixing iteration will remove only one pair of parentheses. + * The remaining pair is safely enclosing the 'in' expression. + */ + return; + } + + // Exclude the outermost node only. + if (!nodeToExclude) { + nodeToExclude = pathNode; + } + + // Don't break the loop here, there might be some safe nodes or parentheses that will stay inside. + + } else { + + // This node will stay parenthesised, the 'in' expression in safely enclosed by '()'. + return; + } + } + } + + // Exclude the node from the list (i.e. treat parentheses as necessary) + removeFromCurrentReportsBuffer(nodeToExclude); + }); + } + + endCurrentReportsBuffering(); + }, + + IfStatement(node) { + if (hasExcessParens(node.test) && !isCondAssignException(node)) { + report(node.test); + } + }, + + ImportExpression(node) { + const { source } = node; + + if (source.type === "SequenceExpression") { + if (hasDoubleExcessParens(source)) { + report(source); + } + } else if (hasExcessParens(source)) { + report(source); + } + }, + + LogicalExpression: checkBinaryLogical, + + MemberExpression(node) { + const nodeObjHasExcessParens = hasExcessParens(node.object); + + if ( + nodeObjHasExcessParens && + precedence(node.object) >= precedence(node) && + ( + node.computed || + !( + astUtils.isDecimalInteger(node.object) || + + // RegExp literal is allowed to have parens (#1589) + (node.object.type === "Literal" && node.object.regex) + ) + ) + ) { + report(node.object); + } + + if (nodeObjHasExcessParens && + node.object.type === "CallExpression" && + node.parent.type !== "NewExpression") { + report(node.object); + } + + if (nodeObjHasExcessParens && + !IGNORE_NEW_IN_MEMBER_EXPR && + node.object.type === "NewExpression" && + isNewExpressionWithParens(node.object)) { + report(node.object); + } + + if (node.computed && hasExcessParens(node.property)) { + report(node.property); + } + }, + + NewExpression: checkCallNew, + + ObjectExpression(node) { + node.properties + .filter(property => property.value && hasExcessParensWithPrecedence(property.value, PRECEDENCE_OF_ASSIGNMENT_EXPR)) + .forEach(property => report(property.value)); + }, + + ObjectPattern(node) { + node.properties + .filter(property => { + const value = property.value; + + return canBeAssignmentTarget(value) && hasExcessParens(value); + }).forEach(property => report(property.value)); + }, + + Property(node) { + if (node.computed) { + const { key } = node; + + if (key && hasExcessParensWithPrecedence(key, PRECEDENCE_OF_ASSIGNMENT_EXPR)) { + report(key); + } + } + }, + + RestElement(node) { + const argument = node.argument; + + if (canBeAssignmentTarget(argument) && hasExcessParens(argument)) { + report(argument); + } + }, + + ReturnStatement(node) { + const returnToken = sourceCode.getFirstToken(node); + + if (isReturnAssignException(node)) { + return; + } + + if (node.argument && + hasExcessParensNoLineTerminator(returnToken, node.argument) && + + // RegExp literal is allowed to have parens (#1589) + !(node.argument.type === "Literal" && node.argument.regex)) { + report(node.argument); + } + }, + + SequenceExpression(node) { + const precedenceOfNode = precedence(node); + + node.expressions + .filter(e => hasExcessParensWithPrecedence(e, precedenceOfNode)) + .forEach(report); + }, + + SwitchCase(node) { + if (node.test && hasExcessParens(node.test)) { + report(node.test); + } + }, + + SwitchStatement(node) { + if (hasExcessParens(node.discriminant)) { + report(node.discriminant); + } + }, + + ThrowStatement(node) { + const throwToken = sourceCode.getFirstToken(node); + + if (hasExcessParensNoLineTerminator(throwToken, node.argument)) { + report(node.argument); + } + }, + + UnaryExpression: checkArgumentWithPrecedence, + UpdateExpression: checkArgumentWithPrecedence, + AwaitExpression: checkArgumentWithPrecedence, + + VariableDeclarator(node) { + if ( + node.init && hasExcessParensWithPrecedence(node.init, PRECEDENCE_OF_ASSIGNMENT_EXPR) && + + // RegExp literal is allowed to have parens (#1589) + !(node.init.type === "Literal" && node.init.regex) + ) { + report(node.init); + } + }, + + WhileStatement(node) { + if (hasExcessParens(node.test) && !isCondAssignException(node)) { + report(node.test); + } + }, + + WithStatement(node) { + if (hasExcessParens(node.object)) { + report(node.object); + } + }, + + YieldExpression(node) { + if (node.argument) { + const yieldToken = sourceCode.getFirstToken(node); + + if ((precedence(node.argument) >= precedence(node) && + hasExcessParensNoLineTerminator(yieldToken, node.argument)) || + hasDoubleExcessParens(node.argument)) { + report(node.argument); + } + } + }, + + ClassDeclaration: checkClass, + ClassExpression: checkClass, + + SpreadElement: checkSpreadOperator, + SpreadProperty: checkSpreadOperator, + ExperimentalSpreadProperty: checkSpreadOperator, + + TemplateLiteral(node) { + node.expressions + .filter(e => e && hasExcessParens(e)) + .forEach(report); + }, + + AssignmentPattern(node) { + const { left, right } = node; + + if (canBeAssignmentTarget(left) && hasExcessParens(left)) { + report(left); + } + + if (right && hasExcessParensWithPrecedence(right, PRECEDENCE_OF_ASSIGNMENT_EXPR)) { + report(right); + } + } + }; + + } +}; diff --git a/eslint/lib/rules/no-extra-semi.js b/eslint/lib/rules/no-extra-semi.js new file mode 100644 index 0000000..e0a8df0 --- /dev/null +++ b/eslint/lib/rules/no-extra-semi.js @@ -0,0 +1,126 @@ +/** + * @fileoverview Rule to flag use of unnecessary semicolons + * @author Nicholas C. Zakas + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const FixTracker = require("./utils/fix-tracker"); +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow unnecessary semicolons", + category: "Possible Errors", + recommended: true, + url: "https://eslint.org/docs/rules/no-extra-semi" + }, + + fixable: "code", + schema: [], + + messages: { + unexpected: "Unnecessary semicolon." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + /** + * Reports an unnecessary semicolon error. + * @param {Node|Token} nodeOrToken A node or a token to be reported. + * @returns {void} + */ + function report(nodeOrToken) { + context.report({ + node: nodeOrToken, + messageId: "unexpected", + fix(fixer) { + + /* + * Expand the replacement range to include the surrounding + * tokens to avoid conflicting with semi. + * https://github.com/eslint/eslint/issues/7928 + */ + return new FixTracker(fixer, context.getSourceCode()) + .retainSurroundingTokens(nodeOrToken) + .remove(nodeOrToken); + } + }); + } + + /** + * Checks for a part of a class body. + * This checks tokens from a specified token to a next MethodDefinition or the end of class body. + * @param {Token} firstToken The first token to check. + * @returns {void} + */ + function checkForPartOfClassBody(firstToken) { + for (let token = firstToken; + token.type === "Punctuator" && !astUtils.isClosingBraceToken(token); + token = sourceCode.getTokenAfter(token) + ) { + if (astUtils.isSemicolonToken(token)) { + report(token); + } + } + } + + return { + + /** + * Reports this empty statement, except if the parent node is a loop. + * @param {Node} node A EmptyStatement node to be reported. + * @returns {void} + */ + EmptyStatement(node) { + const parent = node.parent, + allowedParentTypes = [ + "ForStatement", + "ForInStatement", + "ForOfStatement", + "WhileStatement", + "DoWhileStatement", + "IfStatement", + "LabeledStatement", + "WithStatement" + ]; + + if (allowedParentTypes.indexOf(parent.type) === -1) { + report(node); + } + }, + + /** + * Checks tokens from the head of this class body to the first MethodDefinition or the end of this class body. + * @param {Node} node A ClassBody node to check. + * @returns {void} + */ + ClassBody(node) { + checkForPartOfClassBody(sourceCode.getFirstToken(node, 1)); // 0 is `{`. + }, + + /** + * Checks tokens from this MethodDefinition to the next MethodDefinition or the end of this class body. + * @param {Node} node A MethodDefinition node of the start point. + * @returns {void} + */ + MethodDefinition(node) { + checkForPartOfClassBody(sourceCode.getTokenAfter(node)); + } + }; + + } +}; diff --git a/eslint/lib/rules/no-fallthrough.js b/eslint/lib/rules/no-fallthrough.js new file mode 100644 index 0000000..dd1f3ed --- /dev/null +++ b/eslint/lib/rules/no-fallthrough.js @@ -0,0 +1,142 @@ +/** + * @fileoverview Rule to flag fall-through cases in switch statements. + * @author Matt DuVall + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const lodash = require("lodash"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const DEFAULT_FALLTHROUGH_COMMENT = /falls?\s?through/iu; + +/** + * Checks whether or not a given node has a fallthrough comment. + * @param {ASTNode} node A SwitchCase node to get comments. + * @param {RuleContext} context A rule context which stores comments. + * @param {RegExp} fallthroughCommentPattern A pattern to match comment to. + * @returns {boolean} `true` if the node has a valid fallthrough comment. + */ +function hasFallthroughComment(node, context, fallthroughCommentPattern) { + const sourceCode = context.getSourceCode(); + const comment = lodash.last(sourceCode.getCommentsBefore(node)); + + return Boolean(comment && fallthroughCommentPattern.test(comment.value)); +} + +/** + * Checks whether or not a given code path segment is reachable. + * @param {CodePathSegment} segment A CodePathSegment to check. + * @returns {boolean} `true` if the segment is reachable. + */ +function isReachable(segment) { + return segment.reachable; +} + +/** + * Checks whether a node and a token are separated by blank lines + * @param {ASTNode} node The node to check + * @param {Token} token The token to compare against + * @returns {boolean} `true` if there are blank lines between node and token + */ +function hasBlankLinesBetween(node, token) { + return token.loc.start.line > node.loc.end.line + 1; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow fallthrough of `case` statements", + category: "Best Practices", + recommended: true, + url: "https://eslint.org/docs/rules/no-fallthrough" + }, + + schema: [ + { + type: "object", + properties: { + commentPattern: { + type: "string", + default: "" + } + }, + additionalProperties: false + } + ], + messages: { + case: "Expected a 'break' statement before 'case'.", + default: "Expected a 'break' statement before 'default'." + } + }, + + create(context) { + const options = context.options[0] || {}; + let currentCodePath = null; + const sourceCode = context.getSourceCode(); + + /* + * We need to use leading comments of the next SwitchCase node because + * trailing comments is wrong if semicolons are omitted. + */ + let fallthroughCase = null; + let fallthroughCommentPattern = null; + + if (options.commentPattern) { + fallthroughCommentPattern = new RegExp(options.commentPattern, "u"); + } else { + fallthroughCommentPattern = DEFAULT_FALLTHROUGH_COMMENT; + } + + return { + onCodePathStart(codePath) { + currentCodePath = codePath; + }, + onCodePathEnd() { + currentCodePath = currentCodePath.upper; + }, + + SwitchCase(node) { + + /* + * Checks whether or not there is a fallthrough comment. + * And reports the previous fallthrough node if that does not exist. + */ + if (fallthroughCase && !hasFallthroughComment(node, context, fallthroughCommentPattern)) { + context.report({ + messageId: node.test ? "case" : "default", + node + }); + } + fallthroughCase = null; + }, + + "SwitchCase:exit"(node) { + const nextToken = sourceCode.getTokenAfter(node); + + /* + * `reachable` meant fall through because statements preceded by + * `break`, `return`, or `throw` are unreachable. + * And allows empty cases and the last case. + */ + if (currentCodePath.currentSegments.some(isReachable) && + (node.consequent.length > 0 || hasBlankLinesBetween(node, nextToken)) && + lodash.last(node.parent.cases) !== node) { + fallthroughCase = node; + } + } + }; + } +}; diff --git a/eslint/lib/rules/no-floating-decimal.js b/eslint/lib/rules/no-floating-decimal.js new file mode 100644 index 0000000..b1d8832 --- /dev/null +++ b/eslint/lib/rules/no-floating-decimal.js @@ -0,0 +1,70 @@ +/** + * @fileoverview Rule to flag use of a leading/trailing decimal point in a numeric literal + * @author James Allardice + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow leading or trailing decimal points in numeric literals", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-floating-decimal" + }, + + schema: [], + fixable: "code", + messages: { + leading: "A leading decimal point can be confused with a dot.", + trailing: "A trailing decimal point can be confused with a dot." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + return { + Literal(node) { + + if (typeof node.value === "number") { + if (node.raw.startsWith(".")) { + context.report({ + node, + messageId: "leading", + fix(fixer) { + const tokenBefore = sourceCode.getTokenBefore(node); + const needsSpaceBefore = tokenBefore && + tokenBefore.range[1] === node.range[0] && + !astUtils.canTokensBeAdjacent(tokenBefore, `0${node.raw}`); + + return fixer.insertTextBefore(node, needsSpaceBefore ? " 0" : "0"); + } + }); + } + if (node.raw.indexOf(".") === node.raw.length - 1) { + context.report({ + node, + messageId: "trailing", + fix: fixer => fixer.insertTextAfter(node, "0") + }); + } + } + } + }; + + } +}; diff --git a/eslint/lib/rules/no-func-assign.js b/eslint/lib/rules/no-func-assign.js new file mode 100644 index 0000000..33d0ad9 --- /dev/null +++ b/eslint/lib/rules/no-func-assign.js @@ -0,0 +1,76 @@ +/** + * @fileoverview Rule to flag use of function declaration identifiers as variables. + * @author Ian Christian Myers + */ + +"use strict"; + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow reassigning `function` declarations", + category: "Possible Errors", + recommended: true, + url: "https://eslint.org/docs/rules/no-func-assign" + }, + + schema: [], + + messages: { + isAFunction: "'{{name}}' is a function." + } + }, + + create(context) { + + /** + * Reports a reference if is non initializer and writable. + * @param {References} references Collection of reference to check. + * @returns {void} + */ + function checkReference(references) { + astUtils.getModifyingReferences(references).forEach(reference => { + context.report({ + node: reference.identifier, + messageId: "isAFunction", + data: { + name: reference.identifier.name + } + }); + }); + } + + /** + * Finds and reports references that are non initializer and writable. + * @param {Variable} variable A variable to check. + * @returns {void} + */ + function checkVariable(variable) { + if (variable.defs[0].type === "FunctionName") { + checkReference(variable.references); + } + } + + /** + * Checks parameters of a given function node. + * @param {ASTNode} node A function node to check. + * @returns {void} + */ + function checkForFunction(node) { + context.getDeclaredVariables(node).forEach(checkVariable); + } + + return { + FunctionDeclaration: checkForFunction, + FunctionExpression: checkForFunction + }; + } +}; diff --git a/eslint/lib/rules/no-global-assign.js b/eslint/lib/rules/no-global-assign.js new file mode 100644 index 0000000..ea854c4 --- /dev/null +++ b/eslint/lib/rules/no-global-assign.js @@ -0,0 +1,94 @@ +/** + * @fileoverview Rule to disallow assignments to native objects or read-only global variables + * @author Ilya Volodin + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow assignments to native objects or read-only global variables", + category: "Best Practices", + recommended: true, + url: "https://eslint.org/docs/rules/no-global-assign" + }, + + schema: [ + { + type: "object", + properties: { + exceptions: { + type: "array", + items: { type: "string" }, + uniqueItems: true + } + }, + additionalProperties: false + } + ], + + messages: { + globalShouldNotBeModified: "Read-only global '{{name}}' should not be modified." + } + }, + + create(context) { + const config = context.options[0]; + const exceptions = (config && config.exceptions) || []; + + /** + * Reports write references. + * @param {Reference} reference A reference to check. + * @param {int} index The index of the reference in the references. + * @param {Reference[]} references The array that the reference belongs to. + * @returns {void} + */ + function checkReference(reference, index, references) { + const identifier = reference.identifier; + + if (reference.init === false && + reference.isWrite() && + + /* + * Destructuring assignments can have multiple default value, + * so possibly there are multiple writeable references for the same identifier. + */ + (index === 0 || references[index - 1].identifier !== identifier) + ) { + context.report({ + node: identifier, + messageId: "globalShouldNotBeModified", + data: { + name: identifier.name + } + }); + } + } + + /** + * Reports write references if a given variable is read-only builtin. + * @param {Variable} variable A variable to check. + * @returns {void} + */ + function checkVariable(variable) { + if (variable.writeable === false && exceptions.indexOf(variable.name) === -1) { + variable.references.forEach(checkReference); + } + } + + return { + Program() { + const globalScope = context.getScope(); + + globalScope.variables.forEach(checkVariable); + } + }; + } +}; diff --git a/eslint/lib/rules/no-implicit-coercion.js b/eslint/lib/rules/no-implicit-coercion.js new file mode 100644 index 0000000..6d5ee61 --- /dev/null +++ b/eslint/lib/rules/no-implicit-coercion.js @@ -0,0 +1,300 @@ +/** + * @fileoverview A rule to disallow the type conversions with shorter notations. + * @author Toru Nagashima + */ + +"use strict"; + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const INDEX_OF_PATTERN = /^(?:i|lastI)ndexOf$/u; +const ALLOWABLE_OPERATORS = ["~", "!!", "+", "*"]; + +/** + * Parses and normalizes an option object. + * @param {Object} options An option object to parse. + * @returns {Object} The parsed and normalized option object. + */ +function parseOptions(options) { + return { + boolean: "boolean" in options ? options.boolean : true, + number: "number" in options ? options.number : true, + string: "string" in options ? options.string : true, + allow: options.allow || [] + }; +} + +/** + * Checks whether or not a node is a double logical nigating. + * @param {ASTNode} node An UnaryExpression node to check. + * @returns {boolean} Whether or not the node is a double logical nigating. + */ +function isDoubleLogicalNegating(node) { + return ( + node.operator === "!" && + node.argument.type === "UnaryExpression" && + node.argument.operator === "!" + ); +} + +/** + * Checks whether or not a node is a binary negating of `.indexOf()` method calling. + * @param {ASTNode} node An UnaryExpression node to check. + * @returns {boolean} Whether or not the node is a binary negating of `.indexOf()` method calling. + */ +function isBinaryNegatingOfIndexOf(node) { + return ( + node.operator === "~" && + node.argument.type === "CallExpression" && + node.argument.callee.type === "MemberExpression" && + node.argument.callee.property.type === "Identifier" && + INDEX_OF_PATTERN.test(node.argument.callee.property.name) + ); +} + +/** + * Checks whether or not a node is a multiplying by one. + * @param {BinaryExpression} node A BinaryExpression node to check. + * @returns {boolean} Whether or not the node is a multiplying by one. + */ +function isMultiplyByOne(node) { + return node.operator === "*" && ( + node.left.type === "Literal" && node.left.value === 1 || + node.right.type === "Literal" && node.right.value === 1 + ); +} + +/** + * Checks whether the result of a node is numeric or not + * @param {ASTNode} node The node to test + * @returns {boolean} true if the node is a number literal or a `Number()`, `parseInt` or `parseFloat` call + */ +function isNumeric(node) { + return ( + node.type === "Literal" && typeof node.value === "number" || + node.type === "CallExpression" && ( + node.callee.name === "Number" || + node.callee.name === "parseInt" || + node.callee.name === "parseFloat" + ) + ); +} + +/** + * Returns the first non-numeric operand in a BinaryExpression. Designed to be + * used from bottom to up since it walks up the BinaryExpression trees using + * node.parent to find the result. + * @param {BinaryExpression} node The BinaryExpression node to be walked up on + * @returns {ASTNode|null} The first non-numeric item in the BinaryExpression tree or null + */ +function getNonNumericOperand(node) { + const left = node.left, + right = node.right; + + if (right.type !== "BinaryExpression" && !isNumeric(right)) { + return right; + } + + if (left.type !== "BinaryExpression" && !isNumeric(left)) { + return left; + } + + return null; +} + +/** + * Checks whether a node is an empty string literal or not. + * @param {ASTNode} node The node to check. + * @returns {boolean} Whether or not the passed in node is an + * empty string literal or not. + */ +function isEmptyString(node) { + return astUtils.isStringLiteral(node) && (node.value === "" || (node.type === "TemplateLiteral" && node.quasis.length === 1 && node.quasis[0].value.cooked === "")); +} + +/** + * Checks whether or not a node is a concatenating with an empty string. + * @param {ASTNode} node A BinaryExpression node to check. + * @returns {boolean} Whether or not the node is a concatenating with an empty string. + */ +function isConcatWithEmptyString(node) { + return node.operator === "+" && ( + (isEmptyString(node.left) && !astUtils.isStringLiteral(node.right)) || + (isEmptyString(node.right) && !astUtils.isStringLiteral(node.left)) + ); +} + +/** + * Checks whether or not a node is appended with an empty string. + * @param {ASTNode} node An AssignmentExpression node to check. + * @returns {boolean} Whether or not the node is appended with an empty string. + */ +function isAppendEmptyString(node) { + return node.operator === "+=" && isEmptyString(node.right); +} + +/** + * Returns the operand that is not an empty string from a flagged BinaryExpression. + * @param {ASTNode} node The flagged BinaryExpression node to check. + * @returns {ASTNode} The operand that is not an empty string from a flagged BinaryExpression. + */ +function getNonEmptyOperand(node) { + return isEmptyString(node.left) ? node.right : node.left; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow shorthand type conversions", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-implicit-coercion" + }, + + fixable: "code", + + schema: [{ + type: "object", + properties: { + boolean: { + type: "boolean", + default: true + }, + number: { + type: "boolean", + default: true + }, + string: { + type: "boolean", + default: true + }, + allow: { + type: "array", + items: { + enum: ALLOWABLE_OPERATORS + }, + uniqueItems: true + } + }, + additionalProperties: false + }], + + messages: { + useRecommendation: "use `{{recommendation}}` instead." + } + }, + + create(context) { + const options = parseOptions(context.options[0] || {}); + const sourceCode = context.getSourceCode(); + + /** + * Reports an error and autofixes the node + * @param {ASTNode} node An ast node to report the error on. + * @param {string} recommendation The recommended code for the issue + * @param {bool} shouldFix Whether this report should fix the node + * @returns {void} + */ + function report(node, recommendation, shouldFix) { + context.report({ + node, + messageId: "useRecommendation", + data: { + recommendation + }, + fix(fixer) { + if (!shouldFix) { + return null; + } + + const tokenBefore = sourceCode.getTokenBefore(node); + + if ( + tokenBefore && + tokenBefore.range[1] === node.range[0] && + !astUtils.canTokensBeAdjacent(tokenBefore, recommendation) + ) { + return fixer.replaceText(node, ` ${recommendation}`); + } + return fixer.replaceText(node, recommendation); + } + }); + } + + return { + UnaryExpression(node) { + let operatorAllowed; + + // !!foo + operatorAllowed = options.allow.indexOf("!!") >= 0; + if (!operatorAllowed && options.boolean && isDoubleLogicalNegating(node)) { + const recommendation = `Boolean(${sourceCode.getText(node.argument.argument)})`; + + report(node, recommendation, true); + } + + // ~foo.indexOf(bar) + operatorAllowed = options.allow.indexOf("~") >= 0; + if (!operatorAllowed && options.boolean && isBinaryNegatingOfIndexOf(node)) { + const recommendation = `${sourceCode.getText(node.argument)} !== -1`; + + report(node, recommendation, false); + } + + // +foo + operatorAllowed = options.allow.indexOf("+") >= 0; + if (!operatorAllowed && options.number && node.operator === "+" && !isNumeric(node.argument)) { + const recommendation = `Number(${sourceCode.getText(node.argument)})`; + + report(node, recommendation, true); + } + }, + + // Use `:exit` to prevent double reporting + "BinaryExpression:exit"(node) { + let operatorAllowed; + + // 1 * foo + operatorAllowed = options.allow.indexOf("*") >= 0; + const nonNumericOperand = !operatorAllowed && options.number && isMultiplyByOne(node) && getNonNumericOperand(node); + + if (nonNumericOperand) { + const recommendation = `Number(${sourceCode.getText(nonNumericOperand)})`; + + report(node, recommendation, true); + } + + // "" + foo + operatorAllowed = options.allow.indexOf("+") >= 0; + if (!operatorAllowed && options.string && isConcatWithEmptyString(node)) { + const recommendation = `String(${sourceCode.getText(getNonEmptyOperand(node))})`; + + report(node, recommendation, true); + } + }, + + AssignmentExpression(node) { + + // foo += "" + const operatorAllowed = options.allow.indexOf("+") >= 0; + + if (!operatorAllowed && options.string && isAppendEmptyString(node)) { + const code = sourceCode.getText(getNonEmptyOperand(node)); + const recommendation = `${code} = String(${code})`; + + report(node, recommendation, true); + } + } + }; + } +}; diff --git a/eslint/lib/rules/no-implicit-globals.js b/eslint/lib/rules/no-implicit-globals.js new file mode 100644 index 0000000..d4bfa3a --- /dev/null +++ b/eslint/lib/rules/no-implicit-globals.js @@ -0,0 +1,140 @@ +/** + * @fileoverview Rule to check for implicit global variables, functions and classes. + * @author Joshua Peek + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow declarations in the global scope", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-implicit-globals" + }, + + schema: [{ + type: "object", + properties: { + lexicalBindings: { + type: "boolean", + default: false + } + }, + additionalProperties: false + }], + + messages: { + globalNonLexicalBinding: "Unexpected {{kind}} declaration in the global scope, wrap in an IIFE for a local variable, assign as global property for a global variable.", + globalLexicalBinding: "Unexpected {{kind}} declaration in the global scope, wrap in a block or in an IIFE.", + globalVariableLeak: "Global variable leak, declare the variable if it is intended to be local.", + assignmentToReadonlyGlobal: "Unexpected assignment to read-only global variable.", + redeclarationOfReadonlyGlobal: "Unexpected redeclaration of read-only global variable." + } + }, + + create(context) { + + const checkLexicalBindings = context.options[0] && context.options[0].lexicalBindings === true; + + /** + * Reports the node. + * @param {ASTNode} node Node to report. + * @param {string} messageId Id of the message to report. + * @param {string|undefined} kind Declaration kind, can be 'var', 'const', 'let', function or class. + * @returns {void} + */ + function report(node, messageId, kind) { + context.report({ + node, + messageId, + data: { + kind + } + }); + } + + return { + Program() { + const scope = context.getScope(); + + scope.variables.forEach(variable => { + + // Only ESLint global variables have the `writable` key. + const isReadonlyEslintGlobalVariable = variable.writeable === false; + const isWritableEslintGlobalVariable = variable.writeable === true; + + if (isWritableEslintGlobalVariable) { + + // Everything is allowed with writable ESLint global variables. + return; + } + + variable.defs.forEach(def => { + const defNode = def.node; + + if (def.type === "FunctionName" || (def.type === "Variable" && def.parent.kind === "var")) { + if (isReadonlyEslintGlobalVariable) { + report(defNode, "redeclarationOfReadonlyGlobal"); + } else { + report( + defNode, + "globalNonLexicalBinding", + def.type === "FunctionName" ? "function" : `'${def.parent.kind}'` + ); + } + } + + if (checkLexicalBindings) { + if (def.type === "ClassName" || + (def.type === "Variable" && (def.parent.kind === "let" || def.parent.kind === "const"))) { + if (isReadonlyEslintGlobalVariable) { + report(defNode, "redeclarationOfReadonlyGlobal"); + } else { + report( + defNode, + "globalLexicalBinding", + def.type === "ClassName" ? "class" : `'${def.parent.kind}'` + ); + } + } + } + }); + }); + + // Undeclared assigned variables. + scope.implicit.variables.forEach(variable => { + const scopeVariable = scope.set.get(variable.name); + let messageId; + + if (scopeVariable) { + + // ESLint global variable + if (scopeVariable.writeable) { + return; + } + messageId = "assignmentToReadonlyGlobal"; + + } else { + + // Reference to an unknown variable, possible global leak. + messageId = "globalVariableLeak"; + } + + // def.node is an AssignmentExpression, ForInStatement or ForOfStatement. + variable.defs.forEach(def => { + report(def.node, messageId); + }); + }); + } + }; + + } +}; diff --git a/eslint/lib/rules/no-implied-eval.js b/eslint/lib/rules/no-implied-eval.js new file mode 100644 index 0000000..1668a04 --- /dev/null +++ b/eslint/lib/rules/no-implied-eval.js @@ -0,0 +1,152 @@ +/** + * @fileoverview Rule to flag use of implied eval via setTimeout and setInterval + * @author James Allardice + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); +const { getStaticValue } = require("eslint-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow the use of `eval()`-like methods", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-implied-eval" + }, + + schema: [], + + messages: { + impliedEval: "Implied eval. Consider passing a function instead of a string." + } + }, + + create(context) { + const EVAL_LIKE_FUNCS = Object.freeze(["setTimeout", "execScript", "setInterval"]); + const GLOBAL_CANDIDATES = Object.freeze(["global", "window", "globalThis"]); + + /** + * Checks whether a node is evaluated as a string or not. + * @param {ASTNode} node A node to check. + * @returns {boolean} True if the node is evaluated as a string. + */ + function isEvaluatedString(node) { + if ( + (node.type === "Literal" && typeof node.value === "string") || + node.type === "TemplateLiteral" + ) { + return true; + } + if (node.type === "BinaryExpression" && node.operator === "+") { + return isEvaluatedString(node.left) || isEvaluatedString(node.right); + } + return false; + } + + /** + * Checks whether a node is an Identifier node named one of the specified names. + * @param {ASTNode} node A node to check. + * @param {string[]} specifiers Array of specified name. + * @returns {boolean} True if the node is a Identifier node which has specified name. + */ + function isSpecifiedIdentifier(node, specifiers) { + return node.type === "Identifier" && specifiers.includes(node.name); + } + + /** + * Checks a given node is a MemberExpression node which has the specified name's + * property. + * @param {ASTNode} node A node to check. + * @param {string[]} specifiers Array of specified name. + * @returns {boolean} `true` if the node is a MemberExpression node which has + * the specified name's property + */ + function isSpecifiedMember(node, specifiers) { + return node.type === "MemberExpression" && specifiers.includes(astUtils.getStaticPropertyName(node)); + } + + /** + * Reports if the `CallExpression` node has evaluated argument. + * @param {ASTNode} node A CallExpression to check. + * @returns {void} + */ + function reportImpliedEvalCallExpression(node) { + const [firstArgument] = node.arguments; + + if (firstArgument) { + + const staticValue = getStaticValue(firstArgument, context.getScope()); + const isStaticString = staticValue && typeof staticValue.value === "string"; + const isString = isStaticString || isEvaluatedString(firstArgument); + + if (isString) { + context.report({ + node, + messageId: "impliedEval" + }); + } + } + + } + + /** + * Reports calls of `implied eval` via the global references. + * @param {Variable} globalVar A global variable to check. + * @returns {void} + */ + function reportImpliedEvalViaGlobal(globalVar) { + const { references, name } = globalVar; + + references.forEach(ref => { + const identifier = ref.identifier; + let node = identifier.parent; + + while (isSpecifiedMember(node, [name])) { + node = node.parent; + } + + if (isSpecifiedMember(node, EVAL_LIKE_FUNCS)) { + const parent = node.parent; + + if (parent.type === "CallExpression" && parent.callee === node) { + reportImpliedEvalCallExpression(parent); + } + } + }); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + CallExpression(node) { + if (isSpecifiedIdentifier(node.callee, EVAL_LIKE_FUNCS)) { + reportImpliedEvalCallExpression(node); + } + }, + "Program:exit"() { + const globalScope = context.getScope(); + + GLOBAL_CANDIDATES + .map(candidate => astUtils.getVariableByName(globalScope, candidate)) + .filter(globalVar => !!globalVar && globalVar.defs.length === 0) + .forEach(reportImpliedEvalViaGlobal); + } + }; + + } +}; diff --git a/eslint/lib/rules/no-import-assign.js b/eslint/lib/rules/no-import-assign.js new file mode 100644 index 0000000..32e445f --- /dev/null +++ b/eslint/lib/rules/no-import-assign.js @@ -0,0 +1,238 @@ +/** + * @fileoverview Rule to flag updates of imported bindings. + * @author Toru Nagashima + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const { findVariable, getPropertyName } = require("eslint-utils"); + +const MutationMethods = { + Object: new Set([ + "assign", "defineProperties", "defineProperty", "freeze", + "setPrototypeOf" + ]), + Reflect: new Set([ + "defineProperty", "deleteProperty", "set", "setPrototypeOf" + ]) +}; + +/** + * Check if a given node is LHS of an assignment node. + * @param {ASTNode} node The node to check. + * @returns {boolean} `true` if the node is LHS. + */ +function isAssignmentLeft(node) { + const { parent } = node; + + return ( + ( + parent.type === "AssignmentExpression" && + parent.left === node + ) || + + // Destructuring assignments + parent.type === "ArrayPattern" || + ( + parent.type === "Property" && + parent.value === node && + parent.parent.type === "ObjectPattern" + ) || + parent.type === "RestElement" || + ( + parent.type === "AssignmentPattern" && + parent.left === node + ) + ); +} + +/** + * Check if a given node is the operand of mutation unary operator. + * @param {ASTNode} node The node to check. + * @returns {boolean} `true` if the node is the operand of mutation unary operator. + */ +function isOperandOfMutationUnaryOperator(node) { + const { parent } = node; + + return ( + ( + parent.type === "UpdateExpression" && + parent.argument === node + ) || + ( + parent.type === "UnaryExpression" && + parent.operator === "delete" && + parent.argument === node + ) + ); +} + +/** + * Check if a given node is the iteration variable of `for-in`/`for-of` syntax. + * @param {ASTNode} node The node to check. + * @returns {boolean} `true` if the node is the iteration variable. + */ +function isIterationVariable(node) { + const { parent } = node; + + return ( + ( + parent.type === "ForInStatement" && + parent.left === node + ) || + ( + parent.type === "ForOfStatement" && + parent.left === node + ) + ); +} + +/** + * Check if a given node is the iteration variable of `for-in`/`for-of` syntax. + * @param {ASTNode} node The node to check. + * @param {Scope} scope A `escope.Scope` object to find variable (whichever). + * @returns {boolean} `true` if the node is the iteration variable. + */ +function isArgumentOfWellKnownMutationFunction(node, scope) { + const { parent } = node; + + if ( + parent.type === "CallExpression" && + parent.arguments[0] === node && + parent.callee.type === "MemberExpression" && + parent.callee.object.type === "Identifier" + ) { + const { callee } = parent; + const { object } = callee; + + if (Object.keys(MutationMethods).includes(object.name)) { + const variable = findVariable(scope, object); + + return ( + variable !== null && + variable.scope.type === "global" && + MutationMethods[object.name].has(getPropertyName(callee, scope)) + ); + } + } + + return false; +} + +/** + * Check if the identifier node is placed at to update members. + * @param {ASTNode} id The Identifier node to check. + * @param {Scope} scope A `escope.Scope` object to find variable (whichever). + * @returns {boolean} `true` if the member of `id` was updated. + */ +function isMemberWrite(id, scope) { + const { parent } = id; + + return ( + ( + parent.type === "MemberExpression" && + parent.object === id && + ( + isAssignmentLeft(parent) || + isOperandOfMutationUnaryOperator(parent) || + isIterationVariable(parent) + ) + ) || + isArgumentOfWellKnownMutationFunction(id, scope) + ); +} + +/** + * Get the mutation node. + * @param {ASTNode} id The Identifier node to get. + * @returns {ASTNode} The mutation node. + */ +function getWriteNode(id) { + let node = id.parent; + + while ( + node && + node.type !== "AssignmentExpression" && + node.type !== "UpdateExpression" && + node.type !== "UnaryExpression" && + node.type !== "CallExpression" && + node.type !== "ForInStatement" && + node.type !== "ForOfStatement" + ) { + node = node.parent; + } + + return node || id; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow assigning to imported bindings", + category: "Possible Errors", + recommended: true, + url: "https://eslint.org/docs/rules/no-import-assign" + }, + + schema: [], + + messages: { + readonly: "'{{name}}' is read-only.", + readonlyMember: "The members of '{{name}}' are read-only." + } + }, + + create(context) { + return { + ImportDeclaration(node) { + const scope = context.getScope(); + + for (const variable of context.getDeclaredVariables(node)) { + const shouldCheckMembers = variable.defs.some( + d => d.node.type === "ImportNamespaceSpecifier" + ); + let prevIdNode = null; + + for (const reference of variable.references) { + const idNode = reference.identifier; + + /* + * AssignmentPattern (e.g. `[a = 0] = b`) makes two write + * references for the same identifier. This should skip + * the one of the two in order to prevent redundant reports. + */ + if (idNode === prevIdNode) { + continue; + } + prevIdNode = idNode; + + if (reference.isWrite()) { + context.report({ + node: getWriteNode(idNode), + messageId: "readonly", + data: { name: idNode.name } + }); + } else if (shouldCheckMembers && isMemberWrite(idNode, scope)) { + context.report({ + node: getWriteNode(idNode), + messageId: "readonlyMember", + data: { name: idNode.name } + }); + } + } + } + } + }; + + } +}; diff --git a/eslint/lib/rules/no-inline-comments.js b/eslint/lib/rules/no-inline-comments.js new file mode 100644 index 0000000..41b0f1e --- /dev/null +++ b/eslint/lib/rules/no-inline-comments.js @@ -0,0 +1,89 @@ +/** + * @fileoverview Enforces or disallows inline comments. + * @author Greg Cochard + */ +"use strict"; + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow inline comments after code", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/no-inline-comments" + }, + + schema: [], + + messages: { + unexpectedInlineComment: "Unexpected comment inline with code." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + /** + * Will check that comments are not on lines starting with or ending with code + * @param {ASTNode} node The comment node to check + * @private + * @returns {void} + */ + function testCodeAroundComment(node) { + + const startLine = String(sourceCode.lines[node.loc.start.line - 1]), + endLine = String(sourceCode.lines[node.loc.end.line - 1]), + preamble = startLine.slice(0, node.loc.start.column).trim(), + postamble = endLine.slice(node.loc.end.column).trim(), + isPreambleEmpty = !preamble, + isPostambleEmpty = !postamble; + + // Nothing on both sides + if (isPreambleEmpty && isPostambleEmpty) { + return; + } + + // JSX Exception + if ( + (isPreambleEmpty || preamble === "{") && + (isPostambleEmpty || postamble === "}") + ) { + const enclosingNode = sourceCode.getNodeByRangeIndex(node.range[0]); + + if (enclosingNode && enclosingNode.type === "JSXEmptyExpression") { + return; + } + } + + // Don't report ESLint directive comments + if (astUtils.isDirectiveComment(node)) { + return; + } + + context.report({ + node, + messageId: "unexpectedInlineComment" + }); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + Program() { + const comments = sourceCode.getAllComments(); + + comments.filter(token => token.type !== "Shebang").forEach(testCodeAroundComment); + } + }; + } +}; diff --git a/eslint/lib/rules/no-inner-declarations.js b/eslint/lib/rules/no-inner-declarations.js new file mode 100644 index 0000000..e1c29e0 --- /dev/null +++ b/eslint/lib/rules/no-inner-declarations.js @@ -0,0 +1,96 @@ +/** + * @fileoverview Rule to enforce declarations in program or function body root. + * @author Brandon Mills + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow variable or `function` declarations in nested blocks", + category: "Possible Errors", + recommended: true, + url: "https://eslint.org/docs/rules/no-inner-declarations" + }, + + schema: [ + { + enum: ["functions", "both"] + } + ], + + messages: { + moveDeclToRoot: "Move {{type}} declaration to {{body}} root." + } + }, + + create(context) { + + /** + * Find the nearest Program or Function ancestor node. + * @returns {Object} Ancestor's type and distance from node. + */ + function nearestBody() { + const ancestors = context.getAncestors(); + let ancestor = ancestors.pop(), + generation = 1; + + while (ancestor && ["Program", "FunctionDeclaration", + "FunctionExpression", "ArrowFunctionExpression" + ].indexOf(ancestor.type) < 0) { + generation += 1; + ancestor = ancestors.pop(); + } + + return { + + // Type of containing ancestor + type: ancestor.type, + + // Separation between ancestor and node + distance: generation + }; + } + + /** + * Ensure that a given node is at a program or function body's root. + * @param {ASTNode} node Declaration node to check. + * @returns {void} + */ + function check(node) { + const body = nearestBody(), + valid = ((body.type === "Program" && body.distance === 1) || + body.distance === 2); + + if (!valid) { + context.report({ + node, + messageId: "moveDeclToRoot", + data: { + type: (node.type === "FunctionDeclaration" ? "function" : "variable"), + body: (body.type === "Program" ? "program" : "function body") + } + }); + } + } + + return { + + FunctionDeclaration: check, + VariableDeclaration(node) { + if (context.options[0] === "both" && node.kind === "var") { + check(node); + } + } + + }; + + } +}; diff --git a/eslint/lib/rules/no-invalid-regexp.js b/eslint/lib/rules/no-invalid-regexp.js new file mode 100644 index 0000000..c09e36f --- /dev/null +++ b/eslint/lib/rules/no-invalid-regexp.js @@ -0,0 +1,130 @@ +/** + * @fileoverview Validate strings passed to the RegExp constructor + * @author Michael Ficarra + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const RegExpValidator = require("regexpp").RegExpValidator; +const validator = new RegExpValidator({ ecmaVersion: 2018 }); +const validFlags = /[gimuys]/gu; +const undefined1 = void 0; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow invalid regular expression strings in `RegExp` constructors", + category: "Possible Errors", + recommended: true, + url: "https://eslint.org/docs/rules/no-invalid-regexp" + }, + + schema: [{ + type: "object", + properties: { + allowConstructorFlags: { + type: "array", + items: { + type: "string" + } + } + }, + additionalProperties: false + }], + + messages: { + regexMessage: "{{message}}." + } + }, + + create(context) { + + const options = context.options[0]; + let allowedFlags = null; + + if (options && options.allowConstructorFlags) { + const temp = options.allowConstructorFlags.join("").replace(validFlags, ""); + + if (temp) { + allowedFlags = new RegExp(`[${temp}]`, "giu"); + } + } + + /** + * Check if node is a string + * @param {ASTNode} node node to evaluate + * @returns {boolean} True if its a string + * @private + */ + function isString(node) { + return node && node.type === "Literal" && typeof node.value === "string"; + } + + /** + * Check syntax error in a given pattern. + * @param {string} pattern The RegExp pattern to validate. + * @param {boolean} uFlag The Unicode flag. + * @returns {string|null} The syntax error. + */ + function validateRegExpPattern(pattern, uFlag) { + try { + validator.validatePattern(pattern, undefined1, undefined1, uFlag); + return null; + } catch (err) { + return err.message; + } + } + + /** + * Check syntax error in a given flags. + * @param {string} flags The RegExp flags to validate. + * @returns {string|null} The syntax error. + */ + function validateRegExpFlags(flags) { + try { + validator.validateFlags(flags); + return null; + } catch (err) { + return `Invalid flags supplied to RegExp constructor '${flags}'`; + } + } + + return { + "CallExpression, NewExpression"(node) { + if (node.callee.type !== "Identifier" || node.callee.name !== "RegExp" || !isString(node.arguments[0])) { + return; + } + const pattern = node.arguments[0].value; + let flags = isString(node.arguments[1]) ? node.arguments[1].value : ""; + + if (allowedFlags) { + flags = flags.replace(allowedFlags, ""); + } + + // If flags are unknown, check both are errored or not. + const message = validateRegExpFlags(flags) || ( + flags + ? validateRegExpPattern(pattern, flags.indexOf("u") !== -1) + : validateRegExpPattern(pattern, true) && validateRegExpPattern(pattern, false) + ); + + if (message) { + context.report({ + node, + messageId: "regexMessage", + data: { message } + }); + } + } + }; + } +}; diff --git a/eslint/lib/rules/no-invalid-this.js b/eslint/lib/rules/no-invalid-this.js new file mode 100644 index 0000000..a79c586 --- /dev/null +++ b/eslint/lib/rules/no-invalid-this.js @@ -0,0 +1,145 @@ +/** + * @fileoverview A rule to disallow `this` keywords outside of classes or class-like objects. + * @author Toru Nagashima + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow `this` keywords outside of classes or class-like objects", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-invalid-this" + }, + + schema: [ + { + type: "object", + properties: { + capIsConstructor: { + type: "boolean", + default: true + } + }, + additionalProperties: false + } + ], + + messages: { + unexpectedThis: "Unexpected 'this'." + } + }, + + create(context) { + const options = context.options[0] || {}; + const capIsConstructor = options.capIsConstructor !== false; + const stack = [], + sourceCode = context.getSourceCode(); + + /** + * Gets the current checking context. + * + * The return value has a flag that whether or not `this` keyword is valid. + * The flag is initialized when got at the first time. + * @returns {{valid: boolean}} + * an object which has a flag that whether or not `this` keyword is valid. + */ + stack.getCurrent = function() { + const current = this[this.length - 1]; + + if (!current.init) { + current.init = true; + current.valid = !astUtils.isDefaultThisBinding( + current.node, + sourceCode, + { capIsConstructor } + ); + } + return current; + }; + + /** + * Pushs new checking context into the stack. + * + * The checking context is not initialized yet. + * Because most functions don't have `this` keyword. + * When `this` keyword was found, the checking context is initialized. + * @param {ASTNode} node A function node that was entered. + * @returns {void} + */ + function enterFunction(node) { + + // `this` can be invalid only under strict mode. + stack.push({ + init: !context.getScope().isStrict, + node, + valid: true + }); + } + + /** + * Pops the current checking context from the stack. + * @returns {void} + */ + function exitFunction() { + stack.pop(); + } + + return { + + /* + * `this` is invalid only under strict mode. + * Modules is always strict mode. + */ + Program(node) { + const scope = context.getScope(), + features = context.parserOptions.ecmaFeatures || {}; + + stack.push({ + init: true, + node, + valid: !( + scope.isStrict || + node.sourceType === "module" || + (features.globalReturn && scope.childScopes[0].isStrict) + ) + }); + }, + + "Program:exit"() { + stack.pop(); + }, + + FunctionDeclaration: enterFunction, + "FunctionDeclaration:exit": exitFunction, + FunctionExpression: enterFunction, + "FunctionExpression:exit": exitFunction, + + // Reports if `this` of the current context is invalid. + ThisExpression(node) { + const current = stack.getCurrent(); + + if (current && !current.valid) { + context.report({ + node, + messageId: "unexpectedThis" + }); + } + } + }; + } +}; diff --git a/eslint/lib/rules/no-irregular-whitespace.js b/eslint/lib/rules/no-irregular-whitespace.js new file mode 100644 index 0000000..2184233 --- /dev/null +++ b/eslint/lib/rules/no-irregular-whitespace.js @@ -0,0 +1,251 @@ +/** + * @fileoverview Rule to disallow whitespace that is not a tab or space, whitespace inside strings and comments are allowed + * @author Jonathan Kingston + * @author Christophe Porteneuve + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Constants +//------------------------------------------------------------------------------ + +const ALL_IRREGULARS = /[\f\v\u0085\ufeff\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000\u2028\u2029]/u; +const IRREGULAR_WHITESPACE = /[\f\v\u0085\ufeff\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000]+/mgu; +const IRREGULAR_LINE_TERMINATORS = /[\u2028\u2029]/mgu; +const LINE_BREAK = astUtils.createGlobalLinebreakMatcher(); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow irregular whitespace", + category: "Possible Errors", + recommended: true, + url: "https://eslint.org/docs/rules/no-irregular-whitespace" + }, + + schema: [ + { + type: "object", + properties: { + skipComments: { + type: "boolean", + default: false + }, + skipStrings: { + type: "boolean", + default: true + }, + skipTemplates: { + type: "boolean", + default: false + }, + skipRegExps: { + type: "boolean", + default: false + } + }, + additionalProperties: false + } + ], + + messages: { + noIrregularWhitespace: "Irregular whitespace not allowed." + } + }, + + create(context) { + + // Module store of errors that we have found + let errors = []; + + // Lookup the `skipComments` option, which defaults to `false`. + const options = context.options[0] || {}; + const skipComments = !!options.skipComments; + const skipStrings = options.skipStrings !== false; + const skipRegExps = !!options.skipRegExps; + const skipTemplates = !!options.skipTemplates; + + const sourceCode = context.getSourceCode(); + const commentNodes = sourceCode.getAllComments(); + + /** + * Removes errors that occur inside a string node + * @param {ASTNode} node to check for matching errors. + * @returns {void} + * @private + */ + function removeWhitespaceError(node) { + const locStart = node.loc.start; + const locEnd = node.loc.end; + + errors = errors.filter(({ loc: errorLoc }) => { + if (errorLoc.line >= locStart.line && errorLoc.line <= locEnd.line) { + if (errorLoc.column >= locStart.column && (errorLoc.column <= locEnd.column || errorLoc.line < locEnd.line)) { + return false; + } + } + return true; + }); + } + + /** + * Checks identifier or literal nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors + * @param {ASTNode} node to check for matching errors. + * @returns {void} + * @private + */ + function removeInvalidNodeErrorsInIdentifierOrLiteral(node) { + const shouldCheckStrings = skipStrings && (typeof node.value === "string"); + const shouldCheckRegExps = skipRegExps && Boolean(node.regex); + + if (shouldCheckStrings || shouldCheckRegExps) { + + // If we have irregular characters remove them from the errors list + if (ALL_IRREGULARS.test(node.raw)) { + removeWhitespaceError(node); + } + } + } + + /** + * Checks template string literal nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors + * @param {ASTNode} node to check for matching errors. + * @returns {void} + * @private + */ + function removeInvalidNodeErrorsInTemplateLiteral(node) { + if (typeof node.value.raw === "string") { + if (ALL_IRREGULARS.test(node.value.raw)) { + removeWhitespaceError(node); + } + } + } + + /** + * Checks comment nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors + * @param {ASTNode} node to check for matching errors. + * @returns {void} + * @private + */ + function removeInvalidNodeErrorsInComment(node) { + if (ALL_IRREGULARS.test(node.value)) { + removeWhitespaceError(node); + } + } + + /** + * Checks the program source for irregular whitespace + * @param {ASTNode} node The program node + * @returns {void} + * @private + */ + function checkForIrregularWhitespace(node) { + const sourceLines = sourceCode.lines; + + sourceLines.forEach((sourceLine, lineIndex) => { + const lineNumber = lineIndex + 1; + let match; + + while ((match = IRREGULAR_WHITESPACE.exec(sourceLine)) !== null) { + const location = { + line: lineNumber, + column: match.index + }; + + errors.push({ + node, + messageId: "noIrregularWhitespace", + loc: location + }); + } + }); + } + + /** + * Checks the program source for irregular line terminators + * @param {ASTNode} node The program node + * @returns {void} + * @private + */ + function checkForIrregularLineTerminators(node) { + const source = sourceCode.getText(), + sourceLines = sourceCode.lines, + linebreaks = source.match(LINE_BREAK); + let lastLineIndex = -1, + match; + + while ((match = IRREGULAR_LINE_TERMINATORS.exec(source)) !== null) { + const lineIndex = linebreaks.indexOf(match[0], lastLineIndex + 1) || 0; + const location = { + line: lineIndex + 1, + column: sourceLines[lineIndex].length + }; + + errors.push({ + node, + messageId: "noIrregularWhitespace", + loc: location + }); + lastLineIndex = lineIndex; + } + } + + /** + * A no-op function to act as placeholder for comment accumulation when the `skipComments` option is `false`. + * @returns {void} + * @private + */ + function noop() {} + + const nodes = {}; + + if (ALL_IRREGULARS.test(sourceCode.getText())) { + nodes.Program = function(node) { + + /* + * As we can easily fire warnings for all white space issues with + * all the source its simpler to fire them here. + * This means we can check all the application code without having + * to worry about issues caused in the parser tokens. + * When writing this code also evaluating per node was missing out + * connecting tokens in some cases. + * We can later filter the errors when they are found to be not an + * issue in nodes we don't care about. + */ + checkForIrregularWhitespace(node); + checkForIrregularLineTerminators(node); + }; + + nodes.Identifier = removeInvalidNodeErrorsInIdentifierOrLiteral; + nodes.Literal = removeInvalidNodeErrorsInIdentifierOrLiteral; + nodes.TemplateElement = skipTemplates ? removeInvalidNodeErrorsInTemplateLiteral : noop; + nodes["Program:exit"] = function() { + if (skipComments) { + + // First strip errors occurring in comment nodes. + commentNodes.forEach(removeInvalidNodeErrorsInComment); + } + + // If we have any errors remaining report on them + errors.forEach(error => context.report(error)); + }; + } else { + nodes.Program = noop; + } + + return nodes; + } +}; diff --git a/eslint/lib/rules/no-iterator.js b/eslint/lib/rules/no-iterator.js new file mode 100644 index 0000000..9ba1e7a --- /dev/null +++ b/eslint/lib/rules/no-iterator.js @@ -0,0 +1,52 @@ +/** + * @fileoverview Rule to flag usage of __iterator__ property + * @author Ian Christian Myers + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const { getStaticPropertyName } = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow the use of the `__iterator__` property", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-iterator" + }, + + schema: [], + + messages: { + noIterator: "Reserved name '__iterator__'." + } + }, + + create(context) { + + return { + + MemberExpression(node) { + + if (getStaticPropertyName(node) === "__iterator__") { + context.report({ + node, + messageId: "noIterator" + }); + } + } + }; + + } +}; diff --git a/eslint/lib/rules/no-label-var.js b/eslint/lib/rules/no-label-var.js new file mode 100644 index 0000000..570db03 --- /dev/null +++ b/eslint/lib/rules/no-label-var.js @@ -0,0 +1,79 @@ +/** + * @fileoverview Rule to flag labels that are the same as an identifier + * @author Ian Christian Myers + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow labels that share a name with a variable", + category: "Variables", + recommended: false, + url: "https://eslint.org/docs/rules/no-label-var" + }, + + schema: [], + + messages: { + identifierClashWithLabel: "Found identifier with same name as label." + } + }, + + create(context) { + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Check if the identifier is present inside current scope + * @param {Object} scope current scope + * @param {string} name To evaluate + * @returns {boolean} True if its present + * @private + */ + function findIdentifier(scope, name) { + return astUtils.getVariableByName(scope, name) !== null; + } + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + + LabeledStatement(node) { + + // Fetch the innermost scope. + const scope = context.getScope(); + + /* + * Recursively find the identifier walking up the scope, starting + * with the innermost scope. + */ + if (findIdentifier(scope, node.label.name)) { + context.report({ + node, + messageId: "identifierClashWithLabel" + }); + } + } + + }; + + } +}; diff --git a/eslint/lib/rules/no-labels.js b/eslint/lib/rules/no-labels.js new file mode 100644 index 0000000..85760d8 --- /dev/null +++ b/eslint/lib/rules/no-labels.js @@ -0,0 +1,149 @@ +/** + * @fileoverview Disallow Labeled Statements + * @author Nicholas C. Zakas + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow labeled statements", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-labels" + }, + + schema: [ + { + type: "object", + properties: { + allowLoop: { + type: "boolean", + default: false + }, + allowSwitch: { + type: "boolean", + default: false + } + }, + additionalProperties: false + } + ], + + messages: { + unexpectedLabel: "Unexpected labeled statement.", + unexpectedLabelInBreak: "Unexpected label in break statement.", + unexpectedLabelInContinue: "Unexpected label in continue statement." + } + }, + + create(context) { + const options = context.options[0]; + const allowLoop = options && options.allowLoop; + const allowSwitch = options && options.allowSwitch; + let scopeInfo = null; + + /** + * Gets the kind of a given node. + * @param {ASTNode} node A node to get. + * @returns {string} The kind of the node. + */ + function getBodyKind(node) { + if (astUtils.isLoop(node)) { + return "loop"; + } + if (node.type === "SwitchStatement") { + return "switch"; + } + return "other"; + } + + /** + * Checks whether the label of a given kind is allowed or not. + * @param {string} kind A kind to check. + * @returns {boolean} `true` if the kind is allowed. + */ + function isAllowed(kind) { + switch (kind) { + case "loop": return allowLoop; + case "switch": return allowSwitch; + default: return false; + } + } + + /** + * Checks whether a given name is a label of a loop or not. + * @param {string} label A name of a label to check. + * @returns {boolean} `true` if the name is a label of a loop. + */ + function getKind(label) { + let info = scopeInfo; + + while (info) { + if (info.label === label) { + return info.kind; + } + info = info.upper; + } + + /* istanbul ignore next: syntax error */ + return "other"; + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + LabeledStatement(node) { + scopeInfo = { + label: node.label.name, + kind: getBodyKind(node.body), + upper: scopeInfo + }; + }, + + "LabeledStatement:exit"(node) { + if (!isAllowed(scopeInfo.kind)) { + context.report({ + node, + messageId: "unexpectedLabel" + }); + } + + scopeInfo = scopeInfo.upper; + }, + + BreakStatement(node) { + if (node.label && !isAllowed(getKind(node.label.name))) { + context.report({ + node, + messageId: "unexpectedLabelInBreak" + }); + } + }, + + ContinueStatement(node) { + if (node.label && !isAllowed(getKind(node.label.name))) { + context.report({ + node, + messageId: "unexpectedLabelInContinue" + }); + } + } + }; + + } +}; diff --git a/eslint/lib/rules/no-lone-blocks.js b/eslint/lib/rules/no-lone-blocks.js new file mode 100644 index 0000000..d706988 --- /dev/null +++ b/eslint/lib/rules/no-lone-blocks.js @@ -0,0 +1,128 @@ +/** + * @fileoverview Rule to flag blocks with no reason to exist + * @author Brandon Mills + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow unnecessary nested blocks", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-lone-blocks" + }, + + schema: [], + + messages: { + redundantBlock: "Block is redundant.", + redundantNestedBlock: "Nested block is redundant." + } + }, + + create(context) { + + // A stack of lone blocks to be checked for block-level bindings + const loneBlocks = []; + let ruleDef; + + /** + * Reports a node as invalid. + * @param {ASTNode} node The node to be reported. + * @returns {void} + */ + function report(node) { + const messageId = node.parent.type === "BlockStatement" ? "redundantNestedBlock" : "redundantBlock"; + + context.report({ + node, + messageId + }); + } + + /** + * Checks for any ocurrence of a BlockStatement in a place where lists of statements can appear + * @param {ASTNode} node The node to check + * @returns {boolean} True if the node is a lone block. + */ + function isLoneBlock(node) { + return node.parent.type === "BlockStatement" || + node.parent.type === "Program" || + + // Don't report blocks in switch cases if the block is the only statement of the case. + node.parent.type === "SwitchCase" && !(node.parent.consequent[0] === node && node.parent.consequent.length === 1); + } + + /** + * Checks the enclosing block of the current node for block-level bindings, + * and "marks it" as valid if any. + * @returns {void} + */ + function markLoneBlock() { + if (loneBlocks.length === 0) { + return; + } + + const block = context.getAncestors().pop(); + + if (loneBlocks[loneBlocks.length - 1] === block) { + loneBlocks.pop(); + } + } + + // Default rule definition: report all lone blocks + ruleDef = { + BlockStatement(node) { + if (isLoneBlock(node)) { + report(node); + } + } + }; + + // ES6: report blocks without block-level bindings, or that's only child of another block + if (context.parserOptions.ecmaVersion >= 6) { + ruleDef = { + BlockStatement(node) { + if (isLoneBlock(node)) { + loneBlocks.push(node); + } + }, + "BlockStatement:exit"(node) { + if (loneBlocks.length > 0 && loneBlocks[loneBlocks.length - 1] === node) { + loneBlocks.pop(); + report(node); + } else if ( + node.parent.type === "BlockStatement" && + node.parent.body.length === 1 + ) { + report(node); + } + } + }; + + ruleDef.VariableDeclaration = function(node) { + if (node.kind === "let" || node.kind === "const") { + markLoneBlock(); + } + }; + + ruleDef.FunctionDeclaration = function() { + if (context.getScope().isStrict) { + markLoneBlock(); + } + }; + + ruleDef.ClassDeclaration = markLoneBlock; + } + + return ruleDef; + } +}; diff --git a/eslint/lib/rules/no-lonely-if.js b/eslint/lib/rules/no-lonely-if.js new file mode 100644 index 0000000..6552adc --- /dev/null +++ b/eslint/lib/rules/no-lonely-if.js @@ -0,0 +1,89 @@ +/** + * @fileoverview Rule to disallow if as the only statement in an else block + * @author Brandon Mills + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow `if` statements as the only statement in `else` blocks", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/no-lonely-if" + }, + + schema: [], + fixable: "code", + + messages: { + unexpectedLonelyIf: "Unexpected if as the only statement in an else block." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + return { + IfStatement(node) { + const ancestors = context.getAncestors(), + parent = ancestors.pop(), + grandparent = ancestors.pop(); + + if (parent && parent.type === "BlockStatement" && + parent.body.length === 1 && grandparent && + grandparent.type === "IfStatement" && + parent === grandparent.alternate) { + context.report({ + node, + messageId: "unexpectedLonelyIf", + fix(fixer) { + const openingElseCurly = sourceCode.getFirstToken(parent); + const closingElseCurly = sourceCode.getLastToken(parent); + const elseKeyword = sourceCode.getTokenBefore(openingElseCurly); + const tokenAfterElseBlock = sourceCode.getTokenAfter(closingElseCurly); + const lastIfToken = sourceCode.getLastToken(node.consequent); + const sourceText = sourceCode.getText(); + + if (sourceText.slice(openingElseCurly.range[1], + node.range[0]).trim() || sourceText.slice(node.range[1], closingElseCurly.range[0]).trim()) { + + // Don't fix if there are any non-whitespace characters interfering (e.g. comments) + return null; + } + + if ( + node.consequent.type !== "BlockStatement" && lastIfToken.value !== ";" && tokenAfterElseBlock && + ( + node.consequent.loc.end.line === tokenAfterElseBlock.loc.start.line || + /^[([/+`-]/u.test(tokenAfterElseBlock.value) || + lastIfToken.value === "++" || + lastIfToken.value === "--" + ) + ) { + + /* + * If the `if` statement has no block, and is not followed by a semicolon, make sure that fixing + * the issue would not change semantics due to ASI. If this would happen, don't do a fix. + */ + return null; + } + + return fixer.replaceTextRange( + [openingElseCurly.range[0], closingElseCurly.range[1]], + (elseKeyword.range[1] === openingElseCurly.range[0] ? " " : "") + sourceCode.getText(node) + ); + } + }); + } + } + }; + + } +}; diff --git a/eslint/lib/rules/no-loop-func.js b/eslint/lib/rules/no-loop-func.js new file mode 100644 index 0000000..13ebd3e --- /dev/null +++ b/eslint/lib/rules/no-loop-func.js @@ -0,0 +1,204 @@ +/** + * @fileoverview Rule to flag creation of function inside a loop + * @author Ilya Volodin + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Gets the containing loop node of a specified node. + * + * We don't need to check nested functions, so this ignores those. + * `Scope.through` contains references of nested functions. + * @param {ASTNode} node An AST node to get. + * @returns {ASTNode|null} The containing loop node of the specified node, or + * `null`. + */ +function getContainingLoopNode(node) { + for (let currentNode = node; currentNode.parent; currentNode = currentNode.parent) { + const parent = currentNode.parent; + + switch (parent.type) { + case "WhileStatement": + case "DoWhileStatement": + return parent; + + case "ForStatement": + + // `init` is outside of the loop. + if (parent.init !== currentNode) { + return parent; + } + break; + + case "ForInStatement": + case "ForOfStatement": + + // `right` is outside of the loop. + if (parent.right !== currentNode) { + return parent; + } + break; + + case "ArrowFunctionExpression": + case "FunctionExpression": + case "FunctionDeclaration": + + // We don't need to check nested functions. + return null; + + default: + break; + } + } + + return null; +} + +/** + * Gets the containing loop node of a given node. + * If the loop was nested, this returns the most outer loop. + * @param {ASTNode} node A node to get. This is a loop node. + * @param {ASTNode|null} excludedNode A node that the result node should not + * include. + * @returns {ASTNode} The most outer loop node. + */ +function getTopLoopNode(node, excludedNode) { + const border = excludedNode ? excludedNode.range[1] : 0; + let retv = node; + let containingLoopNode = node; + + while (containingLoopNode && containingLoopNode.range[0] >= border) { + retv = containingLoopNode; + containingLoopNode = getContainingLoopNode(containingLoopNode); + } + + return retv; +} + +/** + * Checks whether a given reference which refers to an upper scope's variable is + * safe or not. + * @param {ASTNode} loopNode A containing loop node. + * @param {eslint-scope.Reference} reference A reference to check. + * @returns {boolean} `true` if the reference is safe or not. + */ +function isSafe(loopNode, reference) { + const variable = reference.resolved; + const definition = variable && variable.defs[0]; + const declaration = definition && definition.parent; + const kind = (declaration && declaration.type === "VariableDeclaration") + ? declaration.kind + : ""; + + // Variables which are declared by `const` is safe. + if (kind === "const") { + return true; + } + + /* + * Variables which are declared by `let` in the loop is safe. + * It's a different instance from the next loop step's. + */ + if (kind === "let" && + declaration.range[0] > loopNode.range[0] && + declaration.range[1] < loopNode.range[1] + ) { + return true; + } + + /* + * WriteReferences which exist after this border are unsafe because those + * can modify the variable. + */ + const border = getTopLoopNode( + loopNode, + (kind === "let") ? declaration : null + ).range[0]; + + /** + * Checks whether a given reference is safe or not. + * The reference is every reference of the upper scope's variable we are + * looking now. + * + * It's safeafe if the reference matches one of the following condition. + * - is readonly. + * - doesn't exist inside a local function and after the border. + * @param {eslint-scope.Reference} upperRef A reference to check. + * @returns {boolean} `true` if the reference is safe. + */ + function isSafeReference(upperRef) { + const id = upperRef.identifier; + + return ( + !upperRef.isWrite() || + variable.scope.variableScope === upperRef.from.variableScope && + id.range[0] < border + ); + } + + return Boolean(variable) && variable.references.every(isSafeReference); +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow function declarations that contain unsafe references inside loop statements", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-loop-func" + }, + + schema: [], + + messages: { + unsafeRefs: "Function declared in a loop contains unsafe references to variable(s) {{ varNames }}." + } + }, + + create(context) { + + /** + * Reports functions which match the following condition: + * + * - has a loop node in ancestors. + * - has any references which refers to an unsafe variable. + * @param {ASTNode} node The AST node to check. + * @returns {boolean} Whether or not the node is within a loop. + */ + function checkForLoops(node) { + const loopNode = getContainingLoopNode(node); + + if (!loopNode) { + return; + } + + const references = context.getScope().through; + const unsafeRefs = references.filter(r => !isSafe(loopNode, r)).map(r => r.identifier.name); + + if (unsafeRefs.length > 0) { + context.report({ + node, + messageId: "unsafeRefs", + data: { varNames: `'${unsafeRefs.join("', '")}'` } + }); + } + } + + return { + ArrowFunctionExpression: checkForLoops, + FunctionExpression: checkForLoops, + FunctionDeclaration: checkForLoops + }; + } +}; diff --git a/eslint/lib/rules/no-magic-numbers.js b/eslint/lib/rules/no-magic-numbers.js new file mode 100644 index 0000000..cd07f5c --- /dev/null +++ b/eslint/lib/rules/no-magic-numbers.js @@ -0,0 +1,213 @@ +/** + * @fileoverview Rule to flag statements that use magic numbers (adapted from https://github.com/danielstjules/buddy.js) + * @author Vincent Lemeunier + */ + +"use strict"; + +const { isNumericLiteral } = require("./utils/ast-utils"); + +// Maximum array length by the ECMAScript Specification. +const MAX_ARRAY_LENGTH = 2 ** 32 - 1; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +/** + * Convert the value to bigint if it's a string. Otherwise return the value as-is. + * @param {bigint|number|string} x The value to normalize. + * @returns {bigint|number} The normalized value. + */ +function normalizeIgnoreValue(x) { + if (typeof x === "string") { + return BigInt(x.slice(0, -1)); + } + return x; +} + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow magic numbers", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-magic-numbers" + }, + + schema: [{ + type: "object", + properties: { + detectObjects: { + type: "boolean", + default: false + }, + enforceConst: { + type: "boolean", + default: false + }, + ignore: { + type: "array", + items: { + anyOf: [ + { type: "number" }, + { type: "string", pattern: "^[+-]?(?:0|[1-9][0-9]*)n$" } + ] + }, + uniqueItems: true + }, + ignoreArrayIndexes: { + type: "boolean", + default: false + } + }, + additionalProperties: false + }], + + messages: { + useConst: "Number constants declarations must use 'const'.", + noMagic: "No magic number: {{raw}}." + } + }, + + create(context) { + const config = context.options[0] || {}, + detectObjects = !!config.detectObjects, + enforceConst = !!config.enforceConst, + ignore = (config.ignore || []).map(normalizeIgnoreValue), + ignoreArrayIndexes = !!config.ignoreArrayIndexes; + + const okTypes = detectObjects ? [] : ["ObjectExpression", "Property", "AssignmentExpression"]; + + /** + * Returns whether the rule is configured to ignore the given value + * @param {bigint|number} value The value to check + * @returns {boolean} true if the value is ignored + */ + function isIgnoredValue(value) { + return ignore.indexOf(value) !== -1; + } + + /** + * Returns whether the given node is used as a radix within parseInt() or Number.parseInt() + * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node + * @returns {boolean} true if the node is radix + */ + function isParseIntRadix(fullNumberNode) { + const parent = fullNumberNode.parent; + + return parent.type === "CallExpression" && fullNumberNode === parent.arguments[1] && + ( + parent.callee.name === "parseInt" || + ( + parent.callee.type === "MemberExpression" && + parent.callee.object.name === "Number" && + parent.callee.property.name === "parseInt" + ) + ); + } + + /** + * Returns whether the given node is a direct child of a JSX node. + * In particular, it aims to detect numbers used as prop values in JSX tags. + * Example: + * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node + * @returns {boolean} true if the node is a JSX number + */ + function isJSXNumber(fullNumberNode) { + return fullNumberNode.parent.type.indexOf("JSX") === 0; + } + + /** + * Returns whether the given node is used as an array index. + * Value must coerce to a valid array index name: "0", "1", "2" ... "4294967294". + * + * All other values, like "-1", "2.5", or "4294967295", are just "normal" object properties, + * which can be created and accessed on an array in addition to the array index properties, + * but they don't affect array's length and are not considered by methods such as .map(), .forEach() etc. + * + * The maximum array length by the specification is 2 ** 32 - 1 = 4294967295, + * thus the maximum valid index is 2 ** 32 - 2 = 4294967294. + * + * All notations are allowed, as long as the value coerces to one of "0", "1", "2" ... "4294967294". + * + * Valid examples: + * a[0], a[1], a[1.2e1], a[0xAB], a[0n], a[1n] + * a[-0] (same as a[0] because -0 coerces to "0") + * a[-0n] (-0n evaluates to 0n) + * + * Invalid examples: + * a[-1], a[-0xAB], a[-1n], a[2.5], a[1.23e1], a[12e-1] + * a[4294967295] (above the max index, it's an access to a regular property a["4294967295"]) + * a[999999999999999999999] (even if it wasn't above the max index, it would be a["1e+21"]) + * a[1e310] (same as a["Infinity"]) + * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node + * @param {bigint|number} value Value expressed by the fullNumberNode + * @returns {boolean} true if the node is a valid array index + */ + function isArrayIndex(fullNumberNode, value) { + const parent = fullNumberNode.parent; + + return parent.type === "MemberExpression" && parent.property === fullNumberNode && + (Number.isInteger(value) || typeof value === "bigint") && + value >= 0 && value < MAX_ARRAY_LENGTH; + } + + return { + Literal(node) { + if (!isNumericLiteral(node)) { + return; + } + + let fullNumberNode; + let value; + let raw; + + // Treat unary minus as a part of the number + if (node.parent.type === "UnaryExpression" && node.parent.operator === "-") { + fullNumberNode = node.parent; + value = -node.value; + raw = `-${node.raw}`; + } else { + fullNumberNode = node; + value = node.value; + raw = node.raw; + } + + // Always allow radix arguments and JSX props + if ( + isIgnoredValue(value) || + isParseIntRadix(fullNumberNode) || + isJSXNumber(fullNumberNode) || + (ignoreArrayIndexes && isArrayIndex(fullNumberNode, value)) + ) { + return; + } + + const parent = fullNumberNode.parent; + + if (parent.type === "VariableDeclarator") { + if (enforceConst && parent.parent.kind !== "const") { + context.report({ + node: fullNumberNode, + messageId: "useConst" + }); + } + } else if ( + okTypes.indexOf(parent.type) === -1 || + (parent.type === "AssignmentExpression" && parent.left.type === "Identifier") + ) { + context.report({ + node: fullNumberNode, + messageId: "noMagic", + data: { + raw + } + }); + } + } + }; + } +}; diff --git a/eslint/lib/rules/no-misleading-character-class.js b/eslint/lib/rules/no-misleading-character-class.js new file mode 100644 index 0000000..9315ba6 --- /dev/null +++ b/eslint/lib/rules/no-misleading-character-class.js @@ -0,0 +1,200 @@ +/** + * @author Toru Nagashima + */ +"use strict"; + +const { CALL, CONSTRUCT, ReferenceTracker, getStringIfConstant } = require("eslint-utils"); +const { RegExpParser, visitRegExpAST } = require("regexpp"); +const { isCombiningCharacter, isEmojiModifier, isRegionalIndicatorSymbol, isSurrogatePair } = require("./utils/unicode"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Iterate character sequences of a given nodes. + * + * CharacterClassRange syntax can steal a part of character sequence, + * so this function reverts CharacterClassRange syntax and restore the sequence. + * @param {regexpp.AST.CharacterClassElement[]} nodes The node list to iterate character sequences. + * @returns {IterableIterator} The list of character sequences. + */ +function *iterateCharacterSequence(nodes) { + let seq = []; + + for (const node of nodes) { + switch (node.type) { + case "Character": + seq.push(node.value); + break; + + case "CharacterClassRange": + seq.push(node.min.value); + yield seq; + seq = [node.max.value]; + break; + + case "CharacterSet": + if (seq.length > 0) { + yield seq; + seq = []; + } + break; + + // no default + } + } + + if (seq.length > 0) { + yield seq; + } +} + +const hasCharacterSequence = { + surrogatePairWithoutUFlag(chars) { + return chars.some((c, i) => i !== 0 && isSurrogatePair(chars[i - 1], c)); + }, + + combiningClass(chars) { + return chars.some((c, i) => ( + i !== 0 && + isCombiningCharacter(c) && + !isCombiningCharacter(chars[i - 1]) + )); + }, + + emojiModifier(chars) { + return chars.some((c, i) => ( + i !== 0 && + isEmojiModifier(c) && + !isEmojiModifier(chars[i - 1]) + )); + }, + + regionalIndicatorSymbol(chars) { + return chars.some((c, i) => ( + i !== 0 && + isRegionalIndicatorSymbol(c) && + isRegionalIndicatorSymbol(chars[i - 1]) + )); + }, + + zwj(chars) { + const lastIndex = chars.length - 1; + + return chars.some((c, i) => ( + i !== 0 && + i !== lastIndex && + c === 0x200d && + chars[i - 1] !== 0x200d && + chars[i + 1] !== 0x200d + )); + } +}; + +const kinds = Object.keys(hasCharacterSequence); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow characters which are made with multiple code points in character class syntax", + category: "Possible Errors", + recommended: true, + url: "https://eslint.org/docs/rules/no-misleading-character-class" + }, + + schema: [], + + messages: { + surrogatePairWithoutUFlag: "Unexpected surrogate pair in character class. Use 'u' flag.", + combiningClass: "Unexpected combined character in character class.", + emojiModifier: "Unexpected modified Emoji in character class.", + regionalIndicatorSymbol: "Unexpected national flag in character class.", + zwj: "Unexpected joined character sequence in character class." + } + }, + create(context) { + const parser = new RegExpParser(); + + /** + * Verify a given regular expression. + * @param {Node} node The node to report. + * @param {string} pattern The regular expression pattern to verify. + * @param {string} flags The flags of the regular expression. + * @returns {void} + */ + function verify(node, pattern, flags) { + const has = { + surrogatePairWithoutUFlag: false, + combiningClass: false, + variationSelector: false, + emojiModifier: false, + regionalIndicatorSymbol: false, + zwj: false + }; + let patternNode; + + try { + patternNode = parser.parsePattern( + pattern, + 0, + pattern.length, + flags.includes("u") + ); + } catch (e) { + + // Ignore regular expressions with syntax errors + return; + } + + visitRegExpAST(patternNode, { + onCharacterClassEnter(ccNode) { + for (const chars of iterateCharacterSequence(ccNode.elements)) { + for (const kind of kinds) { + has[kind] = has[kind] || hasCharacterSequence[kind](chars); + } + } + } + }); + + for (const kind of kinds) { + if (has[kind]) { + context.report({ node, messageId: kind }); + } + } + } + + return { + "Literal[regex]"(node) { + verify(node, node.regex.pattern, node.regex.flags); + }, + "Program"() { + const scope = context.getScope(); + const tracker = new ReferenceTracker(scope); + + /* + * Iterate calls of RegExp. + * E.g., `new RegExp()`, `RegExp()`, `new window.RegExp()`, + * `const {RegExp: a} = window; new a()`, etc... + */ + for (const { node } of tracker.iterateGlobalReferences({ + RegExp: { [CALL]: true, [CONSTRUCT]: true } + })) { + const [patternNode, flagsNode] = node.arguments; + const pattern = getStringIfConstant(patternNode, scope); + const flags = getStringIfConstant(flagsNode, scope); + + if (typeof pattern === "string") { + verify(node, pattern, flags || ""); + } + } + } + }; + } +}; diff --git a/eslint/lib/rules/no-mixed-operators.js b/eslint/lib/rules/no-mixed-operators.js new file mode 100644 index 0000000..37e8906 --- /dev/null +++ b/eslint/lib/rules/no-mixed-operators.js @@ -0,0 +1,242 @@ +/** + * @fileoverview Rule to disallow mixed binary operators. + * @author Toru Nagashima + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils.js"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const ARITHMETIC_OPERATORS = ["+", "-", "*", "/", "%", "**"]; +const BITWISE_OPERATORS = ["&", "|", "^", "~", "<<", ">>", ">>>"]; +const COMPARISON_OPERATORS = ["==", "!=", "===", "!==", ">", ">=", "<", "<="]; +const LOGICAL_OPERATORS = ["&&", "||"]; +const RELATIONAL_OPERATORS = ["in", "instanceof"]; +const TERNARY_OPERATOR = ["?:"]; +const ALL_OPERATORS = [].concat( + ARITHMETIC_OPERATORS, + BITWISE_OPERATORS, + COMPARISON_OPERATORS, + LOGICAL_OPERATORS, + RELATIONAL_OPERATORS, + TERNARY_OPERATOR +); +const DEFAULT_GROUPS = [ + ARITHMETIC_OPERATORS, + BITWISE_OPERATORS, + COMPARISON_OPERATORS, + LOGICAL_OPERATORS, + RELATIONAL_OPERATORS +]; +const TARGET_NODE_TYPE = /^(?:Binary|Logical|Conditional)Expression$/u; + +/** + * Normalizes options. + * @param {Object|undefined} options A options object to normalize. + * @returns {Object} Normalized option object. + */ +function normalizeOptions(options = {}) { + const hasGroups = options.groups && options.groups.length > 0; + const groups = hasGroups ? options.groups : DEFAULT_GROUPS; + const allowSamePrecedence = options.allowSamePrecedence !== false; + + return { + groups, + allowSamePrecedence + }; +} + +/** + * Checks whether any group which includes both given operator exists or not. + * @param {Array.} groups A list of groups to check. + * @param {string} left An operator. + * @param {string} right Another operator. + * @returns {boolean} `true` if such group existed. + */ +function includesBothInAGroup(groups, left, right) { + return groups.some(group => group.indexOf(left) !== -1 && group.indexOf(right) !== -1); +} + +/** + * Checks whether the given node is a conditional expression and returns the test node else the left node. + * @param {ASTNode} node A node which can be a BinaryExpression or a LogicalExpression node. + * This parent node can be BinaryExpression, LogicalExpression + * , or a ConditionalExpression node + * @returns {ASTNode} node the appropriate node(left or test). + */ +function getChildNode(node) { + return node.type === "ConditionalExpression" ? node.test : node.left; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow mixed binary operators", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/no-mixed-operators" + }, + + schema: [ + { + type: "object", + properties: { + groups: { + type: "array", + items: { + type: "array", + items: { enum: ALL_OPERATORS }, + minItems: 2, + uniqueItems: true + }, + uniqueItems: true + }, + allowSamePrecedence: { + type: "boolean", + default: true + } + }, + additionalProperties: false + } + ], + + messages: { + unexpectedMixedOperator: "Unexpected mix of '{{leftOperator}}' and '{{rightOperator}}'." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + const options = normalizeOptions(context.options[0]); + + /** + * Checks whether a given node should be ignored by options or not. + * @param {ASTNode} node A node to check. This is a BinaryExpression + * node or a LogicalExpression node. This parent node is one of + * them, too. + * @returns {boolean} `true` if the node should be ignored. + */ + function shouldIgnore(node) { + const a = node; + const b = node.parent; + + return ( + !includesBothInAGroup(options.groups, a.operator, b.type === "ConditionalExpression" ? "?:" : b.operator) || + ( + options.allowSamePrecedence && + astUtils.getPrecedence(a) === astUtils.getPrecedence(b) + ) + ); + } + + /** + * Checks whether the operator of a given node is mixed with parent + * node's operator or not. + * @param {ASTNode} node A node to check. This is a BinaryExpression + * node or a LogicalExpression node. This parent node is one of + * them, too. + * @returns {boolean} `true` if the node was mixed. + */ + function isMixedWithParent(node) { + + return ( + node.operator !== node.parent.operator && + !astUtils.isParenthesised(sourceCode, node) + ); + } + + /** + * Checks whether the operator of a given node is mixed with a + * conditional expression. + * @param {ASTNode} node A node to check. This is a conditional + * expression node + * @returns {boolean} `true` if the node was mixed. + */ + function isMixedWithConditionalParent(node) { + return !astUtils.isParenthesised(sourceCode, node) && !astUtils.isParenthesised(sourceCode, node.test); + } + + /** + * Gets the operator token of a given node. + * @param {ASTNode} node A node to check. This is a BinaryExpression + * node or a LogicalExpression node. + * @returns {Token} The operator token of the node. + */ + function getOperatorToken(node) { + return sourceCode.getTokenAfter(getChildNode(node), astUtils.isNotClosingParenToken); + } + + /** + * Reports both the operator of a given node and the operator of the + * parent node. + * @param {ASTNode} node A node to check. This is a BinaryExpression + * node or a LogicalExpression node. This parent node is one of + * them, too. + * @returns {void} + */ + function reportBothOperators(node) { + const parent = node.parent; + const left = (getChildNode(parent) === node) ? node : parent; + const right = (getChildNode(parent) !== node) ? node : parent; + const data = { + leftOperator: left.operator || "?:", + rightOperator: right.operator || "?:" + }; + + context.report({ + node: left, + loc: getOperatorToken(left).loc, + messageId: "unexpectedMixedOperator", + data + }); + context.report({ + node: right, + loc: getOperatorToken(right).loc, + messageId: "unexpectedMixedOperator", + data + }); + } + + /** + * Checks between the operator of this node and the operator of the + * parent node. + * @param {ASTNode} node A node to check. + * @returns {void} + */ + function check(node) { + if (TARGET_NODE_TYPE.test(node.parent.type)) { + if (node.parent.type === "ConditionalExpression" && !shouldIgnore(node) && isMixedWithConditionalParent(node.parent)) { + reportBothOperators(node); + } else { + if (TARGET_NODE_TYPE.test(node.parent.type) && + isMixedWithParent(node) && + !shouldIgnore(node) + ) { + reportBothOperators(node); + } + } + } + + } + + return { + BinaryExpression: check, + LogicalExpression: check + + }; + } +}; diff --git a/eslint/lib/rules/no-mixed-requires.js b/eslint/lib/rules/no-mixed-requires.js new file mode 100644 index 0000000..8e988e3 --- /dev/null +++ b/eslint/lib/rules/no-mixed-requires.js @@ -0,0 +1,233 @@ +/** + * @fileoverview Rule to enforce grouped require statements for Node.JS + * @author Raphael Pigulla + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow `require` calls to be mixed with regular variable declarations", + category: "Node.js and CommonJS", + recommended: false, + url: "https://eslint.org/docs/rules/no-mixed-requires" + }, + + schema: [ + { + oneOf: [ + { + type: "boolean" + }, + { + type: "object", + properties: { + grouping: { + type: "boolean" + }, + allowCall: { + type: "boolean" + } + }, + additionalProperties: false + } + ] + } + ], + + messages: { + noMixRequire: "Do not mix 'require' and other declarations.", + noMixCoreModuleFileComputed: "Do not mix core, module, file and computed requires." + } + }, + + create(context) { + + const options = context.options[0]; + let grouping = false, + allowCall = false; + + if (typeof options === "object") { + grouping = options.grouping; + allowCall = options.allowCall; + } else { + grouping = !!options; + } + + /** + * Returns the list of built-in modules. + * @returns {string[]} An array of built-in Node.js modules. + */ + function getBuiltinModules() { + + /* + * This list is generated using: + * `require("repl")._builtinLibs.concat('repl').sort()` + * This particular list is as per nodejs v0.12.2 and iojs v0.7.1 + */ + return [ + "assert", "buffer", "child_process", "cluster", "crypto", + "dgram", "dns", "domain", "events", "fs", "http", "https", + "net", "os", "path", "punycode", "querystring", "readline", + "repl", "smalloc", "stream", "string_decoder", "tls", "tty", + "url", "util", "v8", "vm", "zlib" + ]; + } + + const BUILTIN_MODULES = getBuiltinModules(); + + const DECL_REQUIRE = "require", + DECL_UNINITIALIZED = "uninitialized", + DECL_OTHER = "other"; + + const REQ_CORE = "core", + REQ_FILE = "file", + REQ_MODULE = "module", + REQ_COMPUTED = "computed"; + + /** + * Determines the type of a declaration statement. + * @param {ASTNode} initExpression The init node of the VariableDeclarator. + * @returns {string} The type of declaration represented by the expression. + */ + function getDeclarationType(initExpression) { + if (!initExpression) { + + // "var x;" + return DECL_UNINITIALIZED; + } + + if (initExpression.type === "CallExpression" && + initExpression.callee.type === "Identifier" && + initExpression.callee.name === "require" + ) { + + // "var x = require('util');" + return DECL_REQUIRE; + } + if (allowCall && + initExpression.type === "CallExpression" && + initExpression.callee.type === "CallExpression" + ) { + + // "var x = require('diagnose')('sub-module');" + return getDeclarationType(initExpression.callee); + } + if (initExpression.type === "MemberExpression") { + + // "var x = require('glob').Glob;" + return getDeclarationType(initExpression.object); + } + + // "var x = 42;" + return DECL_OTHER; + } + + /** + * Determines the type of module that is loaded via require. + * @param {ASTNode} initExpression The init node of the VariableDeclarator. + * @returns {string} The module type. + */ + function inferModuleType(initExpression) { + if (initExpression.type === "MemberExpression") { + + // "var x = require('glob').Glob;" + return inferModuleType(initExpression.object); + } + if (initExpression.arguments.length === 0) { + + // "var x = require();" + return REQ_COMPUTED; + } + + const arg = initExpression.arguments[0]; + + if (arg.type !== "Literal" || typeof arg.value !== "string") { + + // "var x = require(42);" + return REQ_COMPUTED; + } + + if (BUILTIN_MODULES.indexOf(arg.value) !== -1) { + + // "var fs = require('fs');" + return REQ_CORE; + } + if (/^\.{0,2}\//u.test(arg.value)) { + + // "var utils = require('./utils');" + return REQ_FILE; + } + + // "var async = require('async');" + return REQ_MODULE; + + } + + /** + * Check if the list of variable declarations is mixed, i.e. whether it + * contains both require and other declarations. + * @param {ASTNode} declarations The list of VariableDeclarators. + * @returns {boolean} True if the declarations are mixed, false if not. + */ + function isMixed(declarations) { + const contains = {}; + + declarations.forEach(declaration => { + const type = getDeclarationType(declaration.init); + + contains[type] = true; + }); + + return !!( + contains[DECL_REQUIRE] && + (contains[DECL_UNINITIALIZED] || contains[DECL_OTHER]) + ); + } + + /** + * Check if all require declarations in the given list are of the same + * type. + * @param {ASTNode} declarations The list of VariableDeclarators. + * @returns {boolean} True if the declarations are grouped, false if not. + */ + function isGrouped(declarations) { + const found = {}; + + declarations.forEach(declaration => { + if (getDeclarationType(declaration.init) === DECL_REQUIRE) { + found[inferModuleType(declaration.init)] = true; + } + }); + + return Object.keys(found).length <= 1; + } + + + return { + + VariableDeclaration(node) { + + if (isMixed(node.declarations)) { + context.report({ + node, + messageId: "noMixRequire" + }); + } else if (grouping && !isGrouped(node.declarations)) { + context.report({ + node, + messageId: "noMixCoreModuleFileComputed" + }); + } + } + }; + + } +}; diff --git a/eslint/lib/rules/no-mixed-spaces-and-tabs.js b/eslint/lib/rules/no-mixed-spaces-and-tabs.js new file mode 100644 index 0000000..16c2bd4 --- /dev/null +++ b/eslint/lib/rules/no-mixed-spaces-and-tabs.js @@ -0,0 +1,105 @@ +/** + * @fileoverview Disallow mixed spaces and tabs for indentation + * @author Jary Niebur + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "disallow mixed spaces and tabs for indentation", + category: "Stylistic Issues", + recommended: true, + url: "https://eslint.org/docs/rules/no-mixed-spaces-and-tabs" + }, + + schema: [ + { + enum: ["smart-tabs", true, false] + } + ], + + messages: { + mixedSpacesAndTabs: "Mixed spaces and tabs." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + let smartTabs; + + switch (context.options[0]) { + case true: // Support old syntax, maybe add deprecation warning here + case "smart-tabs": + smartTabs = true; + break; + default: + smartTabs = false; + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + + "Program:exit"(node) { + const lines = sourceCode.lines, + comments = sourceCode.getAllComments(), + ignoredCommentLines = new Set(); + + // Add all lines except the first ones. + comments.forEach(comment => { + for (let i = comment.loc.start.line + 1; i <= comment.loc.end.line; i++) { + ignoredCommentLines.add(i); + } + }); + + /* + * At least one space followed by a tab + * or the reverse before non-tab/-space + * characters begin. + */ + let regex = /^(?=[\t ]*(\t | \t))/u; + + if (smartTabs) { + + /* + * At least one space followed by a tab + * before non-tab/-space characters begin. + */ + regex = /^(?=[\t ]* \t)/u; + } + + lines.forEach((line, i) => { + const match = regex.exec(line); + + if (match) { + const lineNumber = i + 1, + column = match.index + 1, + loc = { line: lineNumber, column }; + + if (!ignoredCommentLines.has(lineNumber)) { + const containingNode = sourceCode.getNodeByRangeIndex(sourceCode.getIndexFromLoc(loc)); + + if (!(containingNode && ["Literal", "TemplateElement"].includes(containingNode.type))) { + context.report({ + node, + loc, + messageId: "mixedSpacesAndTabs" + }); + } + } + } + }); + } + }; + } +}; diff --git a/eslint/lib/rules/no-multi-assign.js b/eslint/lib/rules/no-multi-assign.js new file mode 100644 index 0000000..ab6430c --- /dev/null +++ b/eslint/lib/rules/no-multi-assign.js @@ -0,0 +1,49 @@ +/** + * @fileoverview Rule to check use of chained assignment expressions + * @author Stewart Rand + */ + +"use strict"; + + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow use of chained assignment expressions", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/no-multi-assign" + }, + + schema: [], + + messages: { + unexpectedChain: "Unexpected chained assignment." + } + }, + + create(context) { + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + AssignmentExpression(node) { + if (["AssignmentExpression", "VariableDeclarator"].indexOf(node.parent.type) !== -1) { + context.report({ + node, + messageId: "unexpectedChain" + }); + } + } + }; + + } +}; diff --git a/eslint/lib/rules/no-multi-spaces.js b/eslint/lib/rules/no-multi-spaces.js new file mode 100644 index 0000000..d43ed73 --- /dev/null +++ b/eslint/lib/rules/no-multi-spaces.js @@ -0,0 +1,138 @@ +/** + * @fileoverview Disallow use of multiple spaces. + * @author Nicholas C. Zakas + */ + +"use strict"; + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "disallow multiple spaces", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-multi-spaces" + }, + + fixable: "whitespace", + + schema: [ + { + type: "object", + properties: { + exceptions: { + type: "object", + patternProperties: { + "^([A-Z][a-z]*)+$": { + type: "boolean" + } + }, + additionalProperties: false + }, + ignoreEOLComments: { + type: "boolean", + default: false + } + }, + additionalProperties: false + } + ], + + messages: { + multipleSpaces: "Multiple spaces found before '{{displayValue}}'." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + const options = context.options[0] || {}; + const ignoreEOLComments = options.ignoreEOLComments; + const exceptions = Object.assign({ Property: true }, options.exceptions); + const hasExceptions = Object.keys(exceptions).filter(key => exceptions[key]).length > 0; + + /** + * Formats value of given comment token for error message by truncating its length. + * @param {Token} token comment token + * @returns {string} formatted value + * @private + */ + function formatReportedCommentValue(token) { + const valueLines = token.value.split("\n"); + const value = valueLines[0]; + const formattedValue = `${value.slice(0, 12)}...`; + + return valueLines.length === 1 && value.length <= 12 ? value : formattedValue; + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + Program() { + sourceCode.tokensAndComments.forEach((leftToken, leftIndex, tokensAndComments) => { + if (leftIndex === tokensAndComments.length - 1) { + return; + } + const rightToken = tokensAndComments[leftIndex + 1]; + + // Ignore tokens that don't have 2 spaces between them or are on different lines + if ( + !sourceCode.text.slice(leftToken.range[1], rightToken.range[0]).includes(" ") || + leftToken.loc.end.line < rightToken.loc.start.line + ) { + return; + } + + // Ignore comments that are the last token on their line if `ignoreEOLComments` is active. + if ( + ignoreEOLComments && + astUtils.isCommentToken(rightToken) && + ( + leftIndex === tokensAndComments.length - 2 || + rightToken.loc.end.line < tokensAndComments[leftIndex + 2].loc.start.line + ) + ) { + return; + } + + // Ignore tokens that are in a node in the "exceptions" object + if (hasExceptions) { + const parentNode = sourceCode.getNodeByRangeIndex(rightToken.range[0] - 1); + + if (parentNode && exceptions[parentNode.type]) { + return; + } + } + + let displayValue; + + if (rightToken.type === "Block") { + displayValue = `/*${formatReportedCommentValue(rightToken)}*/`; + } else if (rightToken.type === "Line") { + displayValue = `//${formatReportedCommentValue(rightToken)}`; + } else { + displayValue = rightToken.value; + } + + context.report({ + node: rightToken, + loc: { start: leftToken.loc.end, end: rightToken.loc.start }, + messageId: "multipleSpaces", + data: { displayValue }, + fix: fixer => fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], " ") + }); + }); + } + }; + + } +}; diff --git a/eslint/lib/rules/no-multi-str.js b/eslint/lib/rules/no-multi-str.js new file mode 100644 index 0000000..7cf1ae3 --- /dev/null +++ b/eslint/lib/rules/no-multi-str.js @@ -0,0 +1,65 @@ +/** + * @fileoverview Rule to flag when using multiline strings + * @author Ilya Volodin + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow multiline strings", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-multi-str" + }, + + schema: [], + + messages: { + multilineString: "Multiline support is limited to browsers supporting ES5 only." + } + }, + + create(context) { + + /** + * Determines if a given node is part of JSX syntax. + * @param {ASTNode} node The node to check. + * @returns {boolean} True if the node is a JSX node, false if not. + * @private + */ + function isJSXElement(node) { + return node.type.indexOf("JSX") === 0; + } + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + + Literal(node) { + if (astUtils.LINEBREAK_MATCHER.test(node.raw) && !isJSXElement(node.parent)) { + context.report({ + node, + messageId: "multilineString" + }); + } + } + }; + + } +}; diff --git a/eslint/lib/rules/no-multiple-empty-lines.js b/eslint/lib/rules/no-multiple-empty-lines.js new file mode 100644 index 0000000..9cccef3 --- /dev/null +++ b/eslint/lib/rules/no-multiple-empty-lines.js @@ -0,0 +1,151 @@ +/** + * @fileoverview Disallows multiple blank lines. + * implementation adapted from the no-trailing-spaces rule. + * @author Greg Cochard + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "disallow multiple empty lines", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/no-multiple-empty-lines" + }, + + fixable: "whitespace", + + schema: [ + { + type: "object", + properties: { + max: { + type: "integer", + minimum: 0 + }, + maxEOF: { + type: "integer", + minimum: 0 + }, + maxBOF: { + type: "integer", + minimum: 0 + } + }, + required: ["max"], + additionalProperties: false + } + ], + + messages: { + blankBeginningOfFile: "Too many blank lines at the beginning of file. Max of {{max}} allowed.", + blankEndOfFile: "Too many blank lines at the end of file. Max of {{max}} allowed.", + consecutiveBlank: "More than {{max}} blank {{pluralizedLines}} not allowed." + } + }, + + create(context) { + + // Use options.max or 2 as default + let max = 2, + maxEOF = max, + maxBOF = max; + + if (context.options.length) { + max = context.options[0].max; + maxEOF = typeof context.options[0].maxEOF !== "undefined" ? context.options[0].maxEOF : max; + maxBOF = typeof context.options[0].maxBOF !== "undefined" ? context.options[0].maxBOF : max; + } + + const sourceCode = context.getSourceCode(); + + // Swallow the final newline, as some editors add it automatically and we don't want it to cause an issue + const allLines = sourceCode.lines[sourceCode.lines.length - 1] === "" ? sourceCode.lines.slice(0, -1) : sourceCode.lines; + const templateLiteralLines = new Set(); + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + TemplateLiteral(node) { + node.quasis.forEach(literalPart => { + + // Empty lines have a semantic meaning if they're inside template literals. Don't count these as empty lines. + for (let ignoredLine = literalPart.loc.start.line; ignoredLine < literalPart.loc.end.line; ignoredLine++) { + templateLiteralLines.add(ignoredLine); + } + }); + }, + "Program:exit"(node) { + return allLines + + // Given a list of lines, first get a list of line numbers that are non-empty. + .reduce((nonEmptyLineNumbers, line, index) => { + if (line.trim() || templateLiteralLines.has(index + 1)) { + nonEmptyLineNumbers.push(index + 1); + } + return nonEmptyLineNumbers; + }, []) + + // Add a value at the end to allow trailing empty lines to be checked. + .concat(allLines.length + 1) + + // Given two line numbers of non-empty lines, report the lines between if the difference is too large. + .reduce((lastLineNumber, lineNumber) => { + let messageId, maxAllowed; + + if (lastLineNumber === 0) { + messageId = "blankBeginningOfFile"; + maxAllowed = maxBOF; + } else if (lineNumber === allLines.length + 1) { + messageId = "blankEndOfFile"; + maxAllowed = maxEOF; + } else { + messageId = "consecutiveBlank"; + maxAllowed = max; + } + + if (lineNumber - lastLineNumber - 1 > maxAllowed) { + context.report({ + node, + loc: { + start: { line: lastLineNumber + maxAllowed + 1, column: 0 }, + end: { line: lineNumber, column: 0 } + }, + messageId, + data: { + max: maxAllowed, + pluralizedLines: maxAllowed === 1 ? "line" : "lines" + }, + fix(fixer) { + const rangeStart = sourceCode.getIndexFromLoc({ line: lastLineNumber + 1, column: 0 }); + + /* + * The end of the removal range is usually the start index of the next line. + * However, at the end of the file there is no next line, so the end of the + * range is just the length of the text. + */ + const lineNumberAfterRemovedLines = lineNumber - maxAllowed; + const rangeEnd = lineNumberAfterRemovedLines <= allLines.length + ? sourceCode.getIndexFromLoc({ line: lineNumberAfterRemovedLines, column: 0 }) + : sourceCode.text.length; + + return fixer.removeRange([rangeStart, rangeEnd]); + } + }); + } + + return lineNumber; + }, 0); + } + }; + } +}; diff --git a/eslint/lib/rules/no-native-reassign.js b/eslint/lib/rules/no-native-reassign.js new file mode 100644 index 0000000..833e3b7 --- /dev/null +++ b/eslint/lib/rules/no-native-reassign.js @@ -0,0 +1,97 @@ +/** + * @fileoverview Rule to disallow assignments to native objects or read-only global variables + * @author Ilya Volodin + * @deprecated in ESLint v3.3.0 + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow assignments to native objects or read-only global variables", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-native-reassign" + }, + + deprecated: true, + + replacedBy: ["no-global-assign"], + + schema: [ + { + type: "object", + properties: { + exceptions: { + type: "array", + items: { type: "string" }, + uniqueItems: true + } + }, + additionalProperties: false + } + ], + + messages: { + nativeReassign: "Read-only global '{{name}}' should not be modified." + } + }, + + create(context) { + const config = context.options[0]; + const exceptions = (config && config.exceptions) || []; + + /** + * Reports write references. + * @param {Reference} reference A reference to check. + * @param {int} index The index of the reference in the references. + * @param {Reference[]} references The array that the reference belongs to. + * @returns {void} + */ + function checkReference(reference, index, references) { + const identifier = reference.identifier; + + if (reference.init === false && + reference.isWrite() && + + /* + * Destructuring assignments can have multiple default value, + * so possibly there are multiple writeable references for the same identifier. + */ + (index === 0 || references[index - 1].identifier !== identifier) + ) { + context.report({ + node: identifier, + messageId: "nativeReassign", + data: identifier + }); + } + } + + /** + * Reports write references if a given variable is read-only builtin. + * @param {Variable} variable A variable to check. + * @returns {void} + */ + function checkVariable(variable) { + if (variable.writeable === false && exceptions.indexOf(variable.name) === -1) { + variable.references.forEach(checkReference); + } + } + + return { + Program() { + const globalScope = context.getScope(); + + globalScope.variables.forEach(checkVariable); + } + }; + } +}; diff --git a/eslint/lib/rules/no-negated-condition.js b/eslint/lib/rules/no-negated-condition.js new file mode 100644 index 0000000..8a9eba8 --- /dev/null +++ b/eslint/lib/rules/no-negated-condition.js @@ -0,0 +1,95 @@ +/** + * @fileoverview Rule to disallow a negated condition + * @author Alberto Rodríguez + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow negated conditions", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/no-negated-condition" + }, + + schema: [], + + messages: { + unexpectedNegated: "Unexpected negated condition." + } + }, + + create(context) { + + /** + * Determines if a given node is an if-else without a condition on the else + * @param {ASTNode} node The node to check. + * @returns {boolean} True if the node has an else without an if. + * @private + */ + function hasElseWithoutCondition(node) { + return node.alternate && node.alternate.type !== "IfStatement"; + } + + /** + * Determines if a given node is a negated unary expression + * @param {Object} test The test object to check. + * @returns {boolean} True if the node is a negated unary expression. + * @private + */ + function isNegatedUnaryExpression(test) { + return test.type === "UnaryExpression" && test.operator === "!"; + } + + /** + * Determines if a given node is a negated binary expression + * @param {Test} test The test to check. + * @returns {boolean} True if the node is a negated binary expression. + * @private + */ + function isNegatedBinaryExpression(test) { + return test.type === "BinaryExpression" && + (test.operator === "!=" || test.operator === "!=="); + } + + /** + * Determines if a given node has a negated if expression + * @param {ASTNode} node The node to check. + * @returns {boolean} True if the node has a negated if expression. + * @private + */ + function isNegatedIf(node) { + return isNegatedUnaryExpression(node.test) || isNegatedBinaryExpression(node.test); + } + + return { + IfStatement(node) { + if (!hasElseWithoutCondition(node)) { + return; + } + + if (isNegatedIf(node)) { + context.report({ + node, + messageId: "unexpectedNegated" + }); + } + }, + ConditionalExpression(node) { + if (isNegatedIf(node)) { + context.report({ + node, + messageId: "unexpectedNegated" + }); + } + } + }; + } +}; diff --git a/eslint/lib/rules/no-negated-in-lhs.js b/eslint/lib/rules/no-negated-in-lhs.js new file mode 100644 index 0000000..1229ced --- /dev/null +++ b/eslint/lib/rules/no-negated-in-lhs.js @@ -0,0 +1,46 @@ +/** + * @fileoverview A rule to disallow negated left operands of the `in` operator + * @author Michael Ficarra + * @deprecated in ESLint v3.3.0 + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow negating the left operand in `in` expressions", + category: "Possible Errors", + recommended: false, + url: "https://eslint.org/docs/rules/no-negated-in-lhs" + }, + + replacedBy: ["no-unsafe-negation"], + + deprecated: true, + schema: [], + + messages: { + negatedLHS: "The 'in' expression's left operand is negated." + } + }, + + create(context) { + + return { + + BinaryExpression(node) { + if (node.operator === "in" && node.left.type === "UnaryExpression" && node.left.operator === "!") { + context.report({ node, messageId: "negatedLHS" }); + } + } + }; + + } +}; diff --git a/eslint/lib/rules/no-nested-ternary.js b/eslint/lib/rules/no-nested-ternary.js new file mode 100644 index 0000000..383bb23 --- /dev/null +++ b/eslint/lib/rules/no-nested-ternary.js @@ -0,0 +1,44 @@ +/** + * @fileoverview Rule to flag nested ternary expressions + * @author Ian Christian Myers + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow nested ternary expressions", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/no-nested-ternary" + }, + + schema: [], + + messages: { + noNestedTernary: "Do not nest ternary expressions." + } + }, + + create(context) { + + return { + ConditionalExpression(node) { + if (node.alternate.type === "ConditionalExpression" || + node.consequent.type === "ConditionalExpression") { + context.report({ + node, + messageId: "noNestedTernary" + }); + } + } + }; + } +}; diff --git a/eslint/lib/rules/no-new-func.js b/eslint/lib/rules/no-new-func.js new file mode 100644 index 0000000..d1360e9 --- /dev/null +++ b/eslint/lib/rules/no-new-func.js @@ -0,0 +1,55 @@ +/** + * @fileoverview Rule to flag when using new Function + * @author Ilya Volodin + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow `new` operators with the `Function` object", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-new-func" + }, + + schema: [], + + messages: { + noFunctionConstructor: "The Function constructor is eval." + } + }, + + create(context) { + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Reports a node. + * @param {ASTNode} node The node to report + * @returns {void} + * @private + */ + function report(node) { + context.report({ + node, + messageId: "noFunctionConstructor" + }); + } + + return { + "NewExpression[callee.name = 'Function']": report, + "CallExpression[callee.name = 'Function']": report + }; + + } +}; diff --git a/eslint/lib/rules/no-new-object.js b/eslint/lib/rules/no-new-object.js new file mode 100644 index 0000000..f3e99c9 --- /dev/null +++ b/eslint/lib/rules/no-new-object.js @@ -0,0 +1,45 @@ +/** + * @fileoverview A rule to disallow calls to the Object constructor + * @author Matt DuVall + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow `Object` constructors", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/no-new-object" + }, + + schema: [], + + messages: { + preferLiteral: "The object literal notation {} is preferrable." + } + }, + + create(context) { + + return { + + NewExpression(node) { + if (node.callee.name === "Object") { + context.report({ + node, + messageId: "preferLiteral" + }); + } + } + }; + + } +}; diff --git a/eslint/lib/rules/no-new-require.js b/eslint/lib/rules/no-new-require.js new file mode 100644 index 0000000..df12a42 --- /dev/null +++ b/eslint/lib/rules/no-new-require.js @@ -0,0 +1,45 @@ +/** + * @fileoverview Rule to disallow use of new operator with the `require` function + * @author Wil Moore III + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow `new` operators with calls to `require`", + category: "Node.js and CommonJS", + recommended: false, + url: "https://eslint.org/docs/rules/no-new-require" + }, + + schema: [], + + messages: { + noNewRequire: "Unexpected use of new with require." + } + }, + + create(context) { + + return { + + NewExpression(node) { + if (node.callee.type === "Identifier" && node.callee.name === "require") { + context.report({ + node, + messageId: "noNewRequire" + }); + } + } + }; + + } +}; diff --git a/eslint/lib/rules/no-new-symbol.js b/eslint/lib/rules/no-new-symbol.js new file mode 100644 index 0000000..cb7e4f0 --- /dev/null +++ b/eslint/lib/rules/no-new-symbol.js @@ -0,0 +1,53 @@ +/** + * @fileoverview Rule to disallow use of the new operator with the `Symbol` object + * @author Alberto Rodríguez + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow `new` operators with the `Symbol` object", + category: "ECMAScript 6", + recommended: true, + url: "https://eslint.org/docs/rules/no-new-symbol" + }, + + schema: [], + + messages: { + noNewSymbol: "`Symbol` cannot be called as a constructor." + } + }, + + create(context) { + + return { + "Program:exit"() { + const globalScope = context.getScope(); + const variable = globalScope.set.get("Symbol"); + + if (variable && variable.defs.length === 0) { + variable.references.forEach(ref => { + const node = ref.identifier; + + if (node.parent && node.parent.type === "NewExpression") { + context.report({ + node, + messageId: "noNewSymbol" + }); + } + }); + } + } + }; + + } +}; diff --git a/eslint/lib/rules/no-new-wrappers.js b/eslint/lib/rules/no-new-wrappers.js new file mode 100644 index 0000000..d276c48 --- /dev/null +++ b/eslint/lib/rules/no-new-wrappers.js @@ -0,0 +1,48 @@ +/** + * @fileoverview Rule to flag when using constructor for wrapper objects + * @author Ilya Volodin + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow `new` operators with the `String`, `Number`, and `Boolean` objects", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-new-wrappers" + }, + + schema: [], + + messages: { + noConstructor: "Do not use {{fn}} as a constructor." + } + }, + + create(context) { + + return { + + NewExpression(node) { + const wrapperObjects = ["String", "Number", "Boolean"]; + + if (wrapperObjects.indexOf(node.callee.name) > -1) { + context.report({ + node, + messageId: "noConstructor", + data: { fn: node.callee.name } + }); + } + } + }; + + } +}; diff --git a/eslint/lib/rules/no-new.js b/eslint/lib/rules/no-new.js new file mode 100644 index 0000000..aa8a4e2 --- /dev/null +++ b/eslint/lib/rules/no-new.js @@ -0,0 +1,43 @@ +/** + * @fileoverview Rule to flag statements with function invocation preceded by + * "new" and not part of assignment + * @author Ilya Volodin + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow `new` operators outside of assignments or comparisons", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-new" + }, + + schema: [], + + messages: { + noNewStatement: "Do not use 'new' for side effects." + } + }, + + create(context) { + + return { + "ExpressionStatement > NewExpression"(node) { + context.report({ + node: node.parent, + messageId: "noNewStatement" + }); + } + }; + + } +}; diff --git a/eslint/lib/rules/no-obj-calls.js b/eslint/lib/rules/no-obj-calls.js new file mode 100644 index 0000000..6139ba2 --- /dev/null +++ b/eslint/lib/rules/no-obj-calls.js @@ -0,0 +1,81 @@ +/** + * @fileoverview Rule to flag use of an object property of the global object (Math and JSON) as a function + * @author James Allardice + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const { CALL, CONSTRUCT, ReferenceTracker } = require("eslint-utils"); +const getPropertyName = require("./utils/ast-utils").getStaticPropertyName; + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const nonCallableGlobals = ["Atomics", "JSON", "Math", "Reflect"]; + +/** + * Returns the name of the node to report + * @param {ASTNode} node A node to report + * @returns {string} name to report + */ +function getReportNodeName(node) { + if (node.callee.type === "MemberExpression") { + return getPropertyName(node.callee); + } + return node.callee.name; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow calling global object properties as functions", + category: "Possible Errors", + recommended: true, + url: "https://eslint.org/docs/rules/no-obj-calls" + }, + + schema: [], + + messages: { + unexpectedCall: "'{{name}}' is not a function.", + unexpectedRefCall: "'{{name}}' is reference to '{{ref}}', which is not a function." + } + }, + + create(context) { + + return { + Program() { + const scope = context.getScope(); + const tracker = new ReferenceTracker(scope); + const traceMap = {}; + + for (const g of nonCallableGlobals) { + traceMap[g] = { + [CALL]: true, + [CONSTRUCT]: true + }; + } + + for (const { node, path } of tracker.iterateGlobalReferences(traceMap)) { + const name = getReportNodeName(node); + const ref = path[0]; + const messageId = name === ref ? "unexpectedCall" : "unexpectedRefCall"; + + context.report({ node, messageId, data: { name, ref } }); + } + } + }; + } +}; diff --git a/eslint/lib/rules/no-octal-escape.js b/eslint/lib/rules/no-octal-escape.js new file mode 100644 index 0000000..5b4c7b2 --- /dev/null +++ b/eslint/lib/rules/no-octal-escape.js @@ -0,0 +1,56 @@ +/** + * @fileoverview Rule to flag octal escape sequences in string literals. + * @author Ian Christian Myers + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow octal escape sequences in string literals", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-octal-escape" + }, + + schema: [], + + messages: { + octalEscapeSequence: "Don't use octal: '\\{{sequence}}'. Use '\\u....' instead." + } + }, + + create(context) { + + return { + + Literal(node) { + if (typeof node.value !== "string") { + return; + } + + // \0 represents a valid NULL character if it isn't followed by a digit. + const match = node.raw.match( + /^(?:[^\\]|\\.)*?\\([0-3][0-7]{1,2}|[4-7][0-7]|0(?=[89])|[1-7])/su + ); + + if (match) { + context.report({ + node, + messageId: "octalEscapeSequence", + data: { sequence: match[1] } + }); + } + } + + }; + + } +}; diff --git a/eslint/lib/rules/no-octal.js b/eslint/lib/rules/no-octal.js new file mode 100644 index 0000000..e9940be --- /dev/null +++ b/eslint/lib/rules/no-octal.js @@ -0,0 +1,45 @@ +/** + * @fileoverview Rule to flag when initializing octal literal + * @author Ilya Volodin + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow octal literals", + category: "Best Practices", + recommended: true, + url: "https://eslint.org/docs/rules/no-octal" + }, + + schema: [], + + messages: { + noOcatal: "Octal literals should not be used." + } + }, + + create(context) { + + return { + + Literal(node) { + if (typeof node.value === "number" && /^0[0-9]/u.test(node.raw)) { + context.report({ + node, + messageId: "noOcatal" + }); + } + } + }; + + } +}; diff --git a/eslint/lib/rules/no-param-reassign.js b/eslint/lib/rules/no-param-reassign.js new file mode 100644 index 0000000..6874af4 --- /dev/null +++ b/eslint/lib/rules/no-param-reassign.js @@ -0,0 +1,229 @@ +/** + * @fileoverview Disallow reassignment of function parameters. + * @author Nat Burns + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +const stopNodePattern = /(?:Statement|Declaration|Function(?:Expression)?|Program)$/u; + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow reassigning `function` parameters", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-param-reassign" + }, + + schema: [ + { + oneOf: [ + { + type: "object", + properties: { + props: { + enum: [false] + } + }, + additionalProperties: false + }, + { + type: "object", + properties: { + props: { + enum: [true] + }, + ignorePropertyModificationsFor: { + type: "array", + items: { + type: "string" + }, + uniqueItems: true + }, + ignorePropertyModificationsForRegex: { + type: "array", + items: { + type: "string" + }, + uniqueItems: true + } + }, + additionalProperties: false + } + ] + } + ], + + messages: { + assignmentToFunctionParam: "Assignment to function parameter '{{name}}'.", + assignmentToFunctionParamProp: "Assignment to property of function parameter '{{name}}'." + } + }, + + create(context) { + const props = context.options[0] && context.options[0].props; + const ignoredPropertyAssignmentsFor = context.options[0] && context.options[0].ignorePropertyModificationsFor || []; + const ignoredPropertyAssignmentsForRegex = context.options[0] && context.options[0].ignorePropertyModificationsForRegex || []; + + /** + * Checks whether or not the reference modifies properties of its variable. + * @param {Reference} reference A reference to check. + * @returns {boolean} Whether or not the reference modifies properties of its variable. + */ + function isModifyingProp(reference) { + let node = reference.identifier; + let parent = node.parent; + + while (parent && (!stopNodePattern.test(parent.type) || + parent.type === "ForInStatement" || parent.type === "ForOfStatement")) { + switch (parent.type) { + + // e.g. foo.a = 0; + case "AssignmentExpression": + return parent.left === node; + + // e.g. ++foo.a; + case "UpdateExpression": + return true; + + // e.g. delete foo.a; + case "UnaryExpression": + if (parent.operator === "delete") { + return true; + } + break; + + // e.g. for (foo.a in b) {} + case "ForInStatement": + case "ForOfStatement": + if (parent.left === node) { + return true; + } + + // this is a stop node for parent.right and parent.body + return false; + + // EXCLUDES: e.g. cache.get(foo.a).b = 0; + case "CallExpression": + if (parent.callee !== node) { + return false; + } + break; + + // EXCLUDES: e.g. cache[foo.a] = 0; + case "MemberExpression": + if (parent.property === node) { + return false; + } + break; + + // EXCLUDES: e.g. ({ [foo]: a }) = bar; + case "Property": + if (parent.key === node) { + return false; + } + + break; + + // EXCLUDES: e.g. (foo ? a : b).c = bar; + case "ConditionalExpression": + if (parent.test === node) { + return false; + } + + break; + + // no default + } + + node = parent; + parent = node.parent; + } + + return false; + } + + /** + * Tests that an identifier name matches any of the ignored property assignments. + * First we test strings in ignoredPropertyAssignmentsFor. + * Then we instantiate and test RegExp objects from ignoredPropertyAssignmentsForRegex strings. + * @param {string} identifierName A string that describes the name of an identifier to + * ignore property assignments for. + * @returns {boolean} Whether the string matches an ignored property assignment regular expression or not. + */ + function isIgnoredPropertyAssignment(identifierName) { + return ignoredPropertyAssignmentsFor.includes(identifierName) || + ignoredPropertyAssignmentsForRegex.some(ignored => new RegExp(ignored, "u").test(identifierName)); + } + + /** + * Reports a reference if is non initializer and writable. + * @param {Reference} reference A reference to check. + * @param {int} index The index of the reference in the references. + * @param {Reference[]} references The array that the reference belongs to. + * @returns {void} + */ + function checkReference(reference, index, references) { + const identifier = reference.identifier; + + if (identifier && + !reference.init && + + /* + * Destructuring assignments can have multiple default value, + * so possibly there are multiple writeable references for the same identifier. + */ + (index === 0 || references[index - 1].identifier !== identifier) + ) { + if (reference.isWrite()) { + context.report({ + node: identifier, + messageId: "assignmentToFunctionParam", + data: { name: identifier.name } + }); + } else if (props && isModifyingProp(reference) && !isIgnoredPropertyAssignment(identifier.name)) { + context.report({ + node: identifier, + messageId: "assignmentToFunctionParamProp", + data: { name: identifier.name } + }); + } + } + } + + /** + * Finds and reports references that are non initializer and writable. + * @param {Variable} variable A variable to check. + * @returns {void} + */ + function checkVariable(variable) { + if (variable.defs[0].type === "Parameter") { + variable.references.forEach(checkReference); + } + } + + /** + * Checks parameters of a given function node. + * @param {ASTNode} node A function node to check. + * @returns {void} + */ + function checkForFunction(node) { + context.getDeclaredVariables(node).forEach(checkVariable); + } + + return { + + // `:exit` is needed for the `node.parent` property of identifier nodes. + "FunctionDeclaration:exit": checkForFunction, + "FunctionExpression:exit": checkForFunction, + "ArrowFunctionExpression:exit": checkForFunction + }; + + } +}; diff --git a/eslint/lib/rules/no-path-concat.js b/eslint/lib/rules/no-path-concat.js new file mode 100644 index 0000000..9fa8b85 --- /dev/null +++ b/eslint/lib/rules/no-path-concat.js @@ -0,0 +1,59 @@ +/** + * @fileoverview Disallow string concatenation when using __dirname and __filename + * @author Nicholas C. Zakas + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow string concatenation with `__dirname` and `__filename`", + category: "Node.js and CommonJS", + recommended: false, + url: "https://eslint.org/docs/rules/no-path-concat" + }, + + schema: [], + + messages: { + usePathFunctions: "Use path.join() or path.resolve() instead of + to create paths." + } + }, + + create(context) { + + const MATCHER = /^__(?:dir|file)name$/u; + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + + BinaryExpression(node) { + + const left = node.left, + right = node.right; + + if (node.operator === "+" && + ((left.type === "Identifier" && MATCHER.test(left.name)) || + (right.type === "Identifier" && MATCHER.test(right.name))) + ) { + + context.report({ + node, + messageId: "usePathFunctions" + }); + } + } + + }; + + } +}; diff --git a/eslint/lib/rules/no-plusplus.js b/eslint/lib/rules/no-plusplus.js new file mode 100644 index 0000000..84d6c3e --- /dev/null +++ b/eslint/lib/rules/no-plusplus.js @@ -0,0 +1,105 @@ +/** + * @fileoverview Rule to flag use of unary increment and decrement operators. + * @author Ian Christian Myers + * @author Brody McKee (github.com/mrmckeb) + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Determines whether the given node is the update node of a `ForStatement`. + * @param {ASTNode} node The node to check. + * @returns {boolean} `true` if the node is `ForStatement` update. + */ +function isForStatementUpdate(node) { + const parent = node.parent; + + return parent.type === "ForStatement" && parent.update === node; +} + +/** + * Determines whether the given node is considered to be a for loop "afterthought" by the logic of this rule. + * In particular, it returns `true` if the given node is either: + * - The update node of a `ForStatement`: for (;; i++) {} + * - An operand of a sequence expression that is the update node: for (;; foo(), i++) {} + * - An operand of a sequence expression that is child of another sequence expression, etc., + * up to the sequence expression that is the update node: for (;; foo(), (bar(), (baz(), i++))) {} + * @param {ASTNode} node The node to check. + * @returns {boolean} `true` if the node is a for loop afterthought. + */ +function isForLoopAfterthought(node) { + const parent = node.parent; + + if (parent.type === "SequenceExpression") { + return isForLoopAfterthought(parent); + } + + return isForStatementUpdate(node); +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow the unary operators `++` and `--`", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/no-plusplus" + }, + + schema: [ + { + type: "object", + properties: { + allowForLoopAfterthoughts: { + type: "boolean", + default: false + } + }, + additionalProperties: false + } + ], + + messages: { + unexpectedUnaryOp: "Unary operator '{{operator}}' used." + } + }, + + create(context) { + + const config = context.options[0]; + let allowForLoopAfterthoughts = false; + + if (typeof config === "object") { + allowForLoopAfterthoughts = config.allowForLoopAfterthoughts === true; + } + + return { + + UpdateExpression(node) { + if (allowForLoopAfterthoughts && isForLoopAfterthought(node)) { + return; + } + + context.report({ + node, + messageId: "unexpectedUnaryOp", + data: { + operator: node.operator + } + }); + } + + }; + + } +}; diff --git a/eslint/lib/rules/no-process-env.js b/eslint/lib/rules/no-process-env.js new file mode 100644 index 0000000..0f8d7f8 --- /dev/null +++ b/eslint/lib/rules/no-process-env.js @@ -0,0 +1,46 @@ +/** + * @fileoverview Disallow the use of process.env() + * @author Vignesh Anand + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow the use of `process.env`", + category: "Node.js and CommonJS", + recommended: false, + url: "https://eslint.org/docs/rules/no-process-env" + }, + + schema: [], + + messages: { + unexpectedProcessEnv: "Unexpected use of process.env." + } + }, + + create(context) { + + return { + + MemberExpression(node) { + const objectName = node.object.name, + propertyName = node.property.name; + + if (objectName === "process" && !node.computed && propertyName && propertyName === "env") { + context.report({ node, messageId: "unexpectedProcessEnv" }); + } + + } + + }; + + } +}; diff --git a/eslint/lib/rules/no-process-exit.js b/eslint/lib/rules/no-process-exit.js new file mode 100644 index 0000000..2987166 --- /dev/null +++ b/eslint/lib/rules/no-process-exit.js @@ -0,0 +1,42 @@ +/** + * @fileoverview Disallow the use of process.exit() + * @author Nicholas C. Zakas + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow the use of `process.exit()`", + category: "Node.js and CommonJS", + recommended: false, + url: "https://eslint.org/docs/rules/no-process-exit" + }, + + schema: [], + + messages: { + noProcessExit: "Don't use process.exit(); throw an error instead." + } + }, + + create(context) { + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + "CallExpression > MemberExpression.callee[object.name = 'process'][property.name = 'exit']"(node) { + context.report({ node: node.parent, messageId: "noProcessExit" }); + } + }; + + } +}; diff --git a/eslint/lib/rules/no-proto.js b/eslint/lib/rules/no-proto.js new file mode 100644 index 0000000..82ce02f --- /dev/null +++ b/eslint/lib/rules/no-proto.js @@ -0,0 +1,48 @@ +/** + * @fileoverview Rule to flag usage of __proto__ property + * @author Ilya Volodin + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const { getStaticPropertyName } = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow the use of the `__proto__` property", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-proto" + }, + + schema: [], + + messages: { + unexpectedProto: "The '__proto__' property is deprecated." + } + }, + + create(context) { + + return { + + MemberExpression(node) { + if (getStaticPropertyName(node) === "__proto__") { + context.report({ node, messageId: "unexpectedProto" }); + } + } + }; + + } +}; diff --git a/eslint/lib/rules/no-prototype-builtins.js b/eslint/lib/rules/no-prototype-builtins.js new file mode 100644 index 0000000..a00d370 --- /dev/null +++ b/eslint/lib/rules/no-prototype-builtins.js @@ -0,0 +1,61 @@ +/** + * @fileoverview Rule to disallow use of Object.prototype builtins on objects + * @author Andrew Levine + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow calling some `Object.prototype` methods directly on objects", + category: "Possible Errors", + recommended: true, + url: "https://eslint.org/docs/rules/no-prototype-builtins" + }, + + schema: [], + + messages: { + prototypeBuildIn: "Do not access Object.prototype method '{{prop}}' from target object." + } + }, + + create(context) { + const DISALLOWED_PROPS = [ + "hasOwnProperty", + "isPrototypeOf", + "propertyIsEnumerable" + ]; + + /** + * Reports if a disallowed property is used in a CallExpression + * @param {ASTNode} node The CallExpression node. + * @returns {void} + */ + function disallowBuiltIns(node) { + if (node.callee.type !== "MemberExpression" || node.callee.computed) { + return; + } + const propName = node.callee.property.name; + + if (DISALLOWED_PROPS.indexOf(propName) > -1) { + context.report({ + messageId: "prototypeBuildIn", + loc: node.callee.property.loc, + data: { prop: propName }, + node + }); + } + } + + return { + CallExpression: disallowBuiltIns + }; + } +}; diff --git a/eslint/lib/rules/no-redeclare.js b/eslint/lib/rules/no-redeclare.js new file mode 100644 index 0000000..6ddb21c --- /dev/null +++ b/eslint/lib/rules/no-redeclare.js @@ -0,0 +1,172 @@ +/** + * @fileoverview Rule to flag when the same variable is declared more then once. + * @author Ilya Volodin + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow variable redeclaration", + category: "Best Practices", + recommended: true, + url: "https://eslint.org/docs/rules/no-redeclare" + }, + + messages: { + redeclared: "'{{id}}' is already defined.", + redeclaredAsBuiltin: "'{{id}}' is already defined as a built-in global variable.", + redeclaredBySyntax: "'{{id}}' is already defined by a variable declaration." + }, + + schema: [ + { + type: "object", + properties: { + builtinGlobals: { type: "boolean", default: true } + }, + additionalProperties: false + } + ] + }, + + create(context) { + const options = { + builtinGlobals: Boolean( + context.options.length === 0 || + context.options[0].builtinGlobals + ) + }; + const sourceCode = context.getSourceCode(); + + /** + * Iterate declarations of a given variable. + * @param {escope.variable} variable The variable object to iterate declarations. + * @returns {IterableIterator<{type:string,node:ASTNode,loc:SourceLocation}>} The declarations. + */ + function *iterateDeclarations(variable) { + if (options.builtinGlobals && ( + variable.eslintImplicitGlobalSetting === "readonly" || + variable.eslintImplicitGlobalSetting === "writable" + )) { + yield { type: "builtin" }; + } + + for (const id of variable.identifiers) { + yield { type: "syntax", node: id, loc: id.loc }; + } + + if (variable.eslintExplicitGlobalComments) { + for (const comment of variable.eslintExplicitGlobalComments) { + yield { + type: "comment", + node: comment, + loc: astUtils.getNameLocationInGlobalDirectiveComment( + sourceCode, + comment, + variable.name + ) + }; + } + } + } + + /** + * Find variables in a given scope and flag redeclared ones. + * @param {Scope} scope An eslint-scope scope object. + * @returns {void} + * @private + */ + function findVariablesInScope(scope) { + for (const variable of scope.variables) { + const [ + declaration, + ...extraDeclarations + ] = iterateDeclarations(variable); + + if (extraDeclarations.length === 0) { + continue; + } + + /* + * If the type of a declaration is different from the type of + * the first declaration, it shows the location of the first + * declaration. + */ + const detailMessageId = declaration.type === "builtin" + ? "redeclaredAsBuiltin" + : "redeclaredBySyntax"; + const data = { id: variable.name }; + + // Report extra declarations. + for (const { type, node, loc } of extraDeclarations) { + const messageId = type === declaration.type + ? "redeclared" + : detailMessageId; + + context.report({ node, loc, messageId, data }); + } + } + } + + /** + * Find variables in the current scope. + * @param {ASTNode} node The node of the current scope. + * @returns {void} + * @private + */ + function checkForBlock(node) { + const scope = context.getScope(); + + /* + * In ES5, some node type such as `BlockStatement` doesn't have that scope. + * `scope.block` is a different node in such a case. + */ + if (scope.block === node) { + findVariablesInScope(scope); + } + } + + return { + Program() { + const scope = context.getScope(); + + findVariablesInScope(scope); + + // Node.js or ES modules has a special scope. + if ( + scope.type === "global" && + scope.childScopes[0] && + + // The special scope's block is the Program node. + scope.block === scope.childScopes[0].block + ) { + findVariablesInScope(scope.childScopes[0]); + } + }, + + FunctionDeclaration: checkForBlock, + FunctionExpression: checkForBlock, + ArrowFunctionExpression: checkForBlock, + + BlockStatement: checkForBlock, + ForStatement: checkForBlock, + ForInStatement: checkForBlock, + ForOfStatement: checkForBlock, + SwitchStatement: checkForBlock + }; + } +}; diff --git a/eslint/lib/rules/no-regex-spaces.js b/eslint/lib/rules/no-regex-spaces.js new file mode 100644 index 0000000..afb26d7 --- /dev/null +++ b/eslint/lib/rules/no-regex-spaces.js @@ -0,0 +1,180 @@ +/** + * @fileoverview Rule to count multiple spaces in regular expressions + * @author Matt DuVall + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); +const regexpp = require("regexpp"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const regExpParser = new regexpp.RegExpParser(); +const DOUBLE_SPACE = / {2}/u; + +/** + * Check if node is a string + * @param {ASTNode} node node to evaluate + * @returns {boolean} True if its a string + * @private + */ +function isString(node) { + return node && node.type === "Literal" && typeof node.value === "string"; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow multiple spaces in regular expressions", + category: "Possible Errors", + recommended: true, + url: "https://eslint.org/docs/rules/no-regex-spaces" + }, + + schema: [], + fixable: "code", + + messages: { + multipleSpaces: "Spaces are hard to count. Use {{{length}}}." + } + }, + + create(context) { + + /** + * Validate regular expression + * @param {ASTNode} nodeToReport Node to report. + * @param {string} pattern Regular expression pattern to validate. + * @param {string} rawPattern Raw representation of the pattern in the source code. + * @param {number} rawPatternStartRange Start range of the pattern in the source code. + * @param {string} flags Regular expression flags. + * @returns {void} + * @private + */ + function checkRegex(nodeToReport, pattern, rawPattern, rawPatternStartRange, flags) { + + // Skip if there are no consecutive spaces in the source code, to avoid reporting e.g., RegExp(' \ '). + if (!DOUBLE_SPACE.test(rawPattern)) { + return; + } + + const characterClassNodes = []; + let regExpAST; + + try { + regExpAST = regExpParser.parsePattern(pattern, 0, pattern.length, flags.includes("u")); + } catch (e) { + + // Ignore regular expressions with syntax errors + return; + } + + regexpp.visitRegExpAST(regExpAST, { + onCharacterClassEnter(ccNode) { + characterClassNodes.push(ccNode); + } + }); + + const spacesPattern = /( {2,})(?: [+*{?]|[^+*{?]|$)/gu; + let match; + + while ((match = spacesPattern.exec(pattern))) { + const { 1: { length }, index } = match; + + // Report only consecutive spaces that are not in character classes. + if ( + characterClassNodes.every(({ start, end }) => index < start || end <= index) + ) { + context.report({ + node: nodeToReport, + messageId: "multipleSpaces", + data: { length }, + fix(fixer) { + if (pattern !== rawPattern) { + return null; + } + return fixer.replaceTextRange( + [rawPatternStartRange + index, rawPatternStartRange + index + length], + ` {${length}}` + ); + } + }); + + // Report only the first occurrence of consecutive spaces + return; + } + } + } + + /** + * Validate regular expression literals + * @param {ASTNode} node node to validate + * @returns {void} + * @private + */ + function checkLiteral(node) { + if (node.regex) { + const pattern = node.regex.pattern; + const rawPattern = node.raw.slice(1, node.raw.lastIndexOf("/")); + const rawPatternStartRange = node.range[0] + 1; + const flags = node.regex.flags; + + checkRegex( + node, + pattern, + rawPattern, + rawPatternStartRange, + flags + ); + } + } + + /** + * Validate strings passed to the RegExp constructor + * @param {ASTNode} node node to validate + * @returns {void} + * @private + */ + function checkFunction(node) { + const scope = context.getScope(); + const regExpVar = astUtils.getVariableByName(scope, "RegExp"); + const shadowed = regExpVar && regExpVar.defs.length > 0; + const patternNode = node.arguments[0]; + const flagsNode = node.arguments[1]; + + if (node.callee.type === "Identifier" && node.callee.name === "RegExp" && isString(patternNode) && !shadowed) { + const pattern = patternNode.value; + const rawPattern = patternNode.raw.slice(1, -1); + const rawPatternStartRange = patternNode.range[0] + 1; + const flags = isString(flagsNode) ? flagsNode.value : ""; + + checkRegex( + node, + pattern, + rawPattern, + rawPatternStartRange, + flags + ); + } + } + + return { + Literal: checkLiteral, + CallExpression: checkFunction, + NewExpression: checkFunction + }; + } +}; diff --git a/eslint/lib/rules/no-restricted-exports.js b/eslint/lib/rules/no-restricted-exports.js new file mode 100644 index 0000000..5b5c7d9 --- /dev/null +++ b/eslint/lib/rules/no-restricted-exports.js @@ -0,0 +1,84 @@ +/** + * @fileoverview Rule to disallow specified names in exports + * @author Milos Djermanovic + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow specified names in exports", + category: "ECMAScript 6", + recommended: false, + url: "https://eslint.org/docs/rules/no-restricted-exports" + }, + + schema: [{ + type: "object", + properties: { + restrictedNamedExports: { + type: "array", + items: { + type: "string" + }, + uniqueItems: true + } + }, + additionalProperties: false + }], + + messages: { + restrictedNamed: "'{{name}}' is restricted from being used as an exported name." + } + }, + + create(context) { + + const restrictedNames = new Set(context.options[0] && context.options[0].restrictedNamedExports); + + /** + * Checks and reports given exported identifier. + * @param {ASTNode} node exported `Identifer` node to check. + * @returns {void} + */ + function checkExportedName(node) { + const name = node.name; + + if (restrictedNames.has(name)) { + context.report({ + node, + messageId: "restrictedNamed", + data: { name } + }); + } + } + + return { + ExportNamedDeclaration(node) { + const declaration = node.declaration; + + if (declaration) { + if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") { + checkExportedName(declaration.id); + } else if (declaration.type === "VariableDeclaration") { + context.getDeclaredVariables(declaration) + .map(v => v.defs.find(d => d.parent === declaration)) + .map(d => d.name) // Identifier nodes + .forEach(checkExportedName); + } + } else { + node.specifiers + .map(s => s.exported) + .forEach(checkExportedName); + } + } + }; + } +}; diff --git a/eslint/lib/rules/no-restricted-globals.js b/eslint/lib/rules/no-restricted-globals.js new file mode 100644 index 0000000..2c932a7 --- /dev/null +++ b/eslint/lib/rules/no-restricted-globals.js @@ -0,0 +1,122 @@ +/** + * @fileoverview Restrict usage of specified globals. + * @author Benoît Zugmeyer + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow specified global variables", + category: "Variables", + recommended: false, + url: "https://eslint.org/docs/rules/no-restricted-globals" + }, + + schema: { + type: "array", + items: { + oneOf: [ + { + type: "string" + }, + { + type: "object", + properties: { + name: { type: "string" }, + message: { type: "string" } + }, + required: ["name"], + additionalProperties: false + } + ] + }, + uniqueItems: true, + minItems: 0 + }, + + messages: { + defaultMessage: "Unexpected use of '{{name}}'.", + // eslint-disable-next-line eslint-plugin/report-message-format + customMessage: "Unexpected use of '{{name}}'. {{customMessage}}" + } + }, + + create(context) { + + // If no globals are restricted, we don't need to do anything + if (context.options.length === 0) { + return {}; + } + + const restrictedGlobalMessages = context.options.reduce((memo, option) => { + if (typeof option === "string") { + memo[option] = null; + } else { + memo[option.name] = option.message; + } + + return memo; + }, {}); + + /** + * Report a variable to be used as a restricted global. + * @param {Reference} reference the variable reference + * @returns {void} + * @private + */ + function reportReference(reference) { + const name = reference.identifier.name, + customMessage = restrictedGlobalMessages[name], + messageId = customMessage + ? "customMessage" + : "defaultMessage"; + + context.report({ + node: reference.identifier, + messageId, + data: { + name, + customMessage + } + }); + } + + /** + * Check if the given name is a restricted global name. + * @param {string} name name of a variable + * @returns {boolean} whether the variable is a restricted global or not + * @private + */ + function isRestricted(name) { + return Object.prototype.hasOwnProperty.call(restrictedGlobalMessages, name); + } + + return { + Program() { + const scope = context.getScope(); + + // Report variables declared elsewhere (ex: variables defined as "global" by eslint) + scope.variables.forEach(variable => { + if (!variable.defs.length && isRestricted(variable.name)) { + variable.references.forEach(reportReference); + } + }); + + // Report variables not declared at all + scope.through.forEach(reference => { + if (isRestricted(reference.identifier.name)) { + reportReference(reference); + } + }); + + } + }; + } +}; diff --git a/eslint/lib/rules/no-restricted-imports.js b/eslint/lib/rules/no-restricted-imports.js new file mode 100644 index 0000000..c205dad --- /dev/null +++ b/eslint/lib/rules/no-restricted-imports.js @@ -0,0 +1,268 @@ +/** + * @fileoverview Restrict usage of specified node imports. + * @author Guy Ellis + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +const ignore = require("ignore"); + +const arrayOfStrings = { + type: "array", + items: { type: "string" }, + uniqueItems: true +}; + +const arrayOfStringsOrObjects = { + type: "array", + items: { + anyOf: [ + { type: "string" }, + { + type: "object", + properties: { + name: { type: "string" }, + message: { + type: "string", + minLength: 1 + }, + importNames: { + type: "array", + items: { + type: "string" + } + } + }, + additionalProperties: false, + required: ["name"] + } + ] + }, + uniqueItems: true +}; + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow specified modules when loaded by `import`", + category: "ECMAScript 6", + recommended: false, + url: "https://eslint.org/docs/rules/no-restricted-imports" + }, + + messages: { + path: "'{{importSource}}' import is restricted from being used.", + // eslint-disable-next-line eslint-plugin/report-message-format + pathWithCustomMessage: "'{{importSource}}' import is restricted from being used. {{customMessage}}", + + patterns: "'{{importSource}}' import is restricted from being used by a pattern.", + + everything: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted.", + // eslint-disable-next-line eslint-plugin/report-message-format + everythingWithCustomMessage: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted. {{customMessage}}", + + importName: "'{{importName}}' import from '{{importSource}}' is restricted.", + // eslint-disable-next-line eslint-plugin/report-message-format + importNameWithCustomMessage: "'{{importName}}' import from '{{importSource}}' is restricted. {{customMessage}}" + }, + + schema: { + anyOf: [ + arrayOfStringsOrObjects, + { + type: "array", + items: [{ + type: "object", + properties: { + paths: arrayOfStringsOrObjects, + patterns: arrayOfStrings + }, + additionalProperties: false + }], + additionalItems: false + } + ] + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + const options = Array.isArray(context.options) ? context.options : []; + const isPathAndPatternsObject = + typeof options[0] === "object" && + (Object.prototype.hasOwnProperty.call(options[0], "paths") || Object.prototype.hasOwnProperty.call(options[0], "patterns")); + + const restrictedPaths = (isPathAndPatternsObject ? options[0].paths : context.options) || []; + const restrictedPatterns = (isPathAndPatternsObject ? options[0].patterns : []) || []; + + // if no imports are restricted we don"t need to check + if (Object.keys(restrictedPaths).length === 0 && restrictedPatterns.length === 0) { + return {}; + } + + const restrictedPathMessages = restrictedPaths.reduce((memo, importSource) => { + if (typeof importSource === "string") { + memo[importSource] = { message: null }; + } else { + memo[importSource.name] = { + message: importSource.message, + importNames: importSource.importNames + }; + } + return memo; + }, {}); + + const restrictedPatternsMatcher = ignore().add(restrictedPatterns); + + /** + * Report a restricted path. + * @param {string} importSource path of the import + * @param {Map} importNames Map of import names that are being imported + * @param {node} node representing the restricted path reference + * @returns {void} + * @private + */ + function checkRestrictedPathAndReport(importSource, importNames, node) { + if (!Object.prototype.hasOwnProperty.call(restrictedPathMessages, importSource)) { + return; + } + + const customMessage = restrictedPathMessages[importSource].message; + const restrictedImportNames = restrictedPathMessages[importSource].importNames; + + if (restrictedImportNames) { + if (importNames.has("*")) { + const specifierData = importNames.get("*")[0]; + + context.report({ + node, + messageId: customMessage ? "everythingWithCustomMessage" : "everything", + loc: specifierData.loc, + data: { + importSource, + importNames: restrictedImportNames, + customMessage + } + }); + } + + restrictedImportNames.forEach(importName => { + if (importNames.has(importName)) { + const specifiers = importNames.get(importName); + + specifiers.forEach(specifier => { + context.report({ + node, + messageId: customMessage ? "importNameWithCustomMessage" : "importName", + loc: specifier.loc, + data: { + importSource, + customMessage, + importName + } + }); + }); + } + }); + } else { + context.report({ + node, + messageId: customMessage ? "pathWithCustomMessage" : "path", + data: { + importSource, + customMessage + } + }); + } + } + + /** + * Report a restricted path specifically for patterns. + * @param {node} node representing the restricted path reference + * @returns {void} + * @private + */ + function reportPathForPatterns(node) { + const importSource = node.source.value.trim(); + + context.report({ + node, + messageId: "patterns", + data: { + importSource + } + }); + } + + /** + * Check if the given importSource is restricted by a pattern. + * @param {string} importSource path of the import + * @returns {boolean} whether the variable is a restricted pattern or not + * @private + */ + function isRestrictedPattern(importSource) { + return restrictedPatterns.length > 0 && restrictedPatternsMatcher.ignores(importSource); + } + + /** + * Checks a node to see if any problems should be reported. + * @param {ASTNode} node The node to check. + * @returns {void} + * @private + */ + function checkNode(node) { + const importSource = node.source.value.trim(); + const importNames = new Map(); + + if (node.type === "ExportAllDeclaration") { + const starToken = sourceCode.getFirstToken(node, 1); + + importNames.set("*", [{ loc: starToken.loc }]); + } else if (node.specifiers) { + for (const specifier of node.specifiers) { + let name; + const specifierData = { loc: specifier.loc }; + + if (specifier.type === "ImportDefaultSpecifier") { + name = "default"; + } else if (specifier.type === "ImportNamespaceSpecifier") { + name = "*"; + } else if (specifier.imported) { + name = specifier.imported.name; + } else if (specifier.local) { + name = specifier.local.name; + } + + if (name) { + if (importNames.has(name)) { + importNames.get(name).push(specifierData); + } else { + importNames.set(name, [specifierData]); + } + } + } + } + + checkRestrictedPathAndReport(importSource, importNames, node); + + if (isRestrictedPattern(importSource)) { + reportPathForPatterns(node); + } + } + + return { + ImportDeclaration: checkNode, + ExportNamedDeclaration(node) { + if (node.source) { + checkNode(node); + } + }, + ExportAllDeclaration: checkNode + }; + } +}; diff --git a/eslint/lib/rules/no-restricted-modules.js b/eslint/lib/rules/no-restricted-modules.js new file mode 100644 index 0000000..abd8d5c --- /dev/null +++ b/eslint/lib/rules/no-restricted-modules.js @@ -0,0 +1,210 @@ +/** + * @fileoverview Restrict usage of specified node modules. + * @author Christian Schulz + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +const ignore = require("ignore"); + +const arrayOfStrings = { + type: "array", + items: { type: "string" }, + uniqueItems: true +}; + +const arrayOfStringsOrObjects = { + type: "array", + items: { + anyOf: [ + { type: "string" }, + { + type: "object", + properties: { + name: { type: "string" }, + message: { + type: "string", + minLength: 1 + } + }, + additionalProperties: false, + required: ["name"] + } + ] + }, + uniqueItems: true +}; + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow specified modules when loaded by `require`", + category: "Node.js and CommonJS", + recommended: false, + url: "https://eslint.org/docs/rules/no-restricted-modules" + }, + + schema: { + anyOf: [ + arrayOfStringsOrObjects, + { + type: "array", + items: { + type: "object", + properties: { + paths: arrayOfStringsOrObjects, + patterns: arrayOfStrings + }, + additionalProperties: false + }, + additionalItems: false + } + ] + }, + + messages: { + defaultMessage: "'{{name}}' module is restricted from being used.", + // eslint-disable-next-line eslint-plugin/report-message-format + customMessage: "'{{name}}' module is restricted from being used. {{customMessage}}", + patternMessage: "'{{name}}' module is restricted from being used by a pattern." + } + }, + + create(context) { + const options = Array.isArray(context.options) ? context.options : []; + const isPathAndPatternsObject = + typeof options[0] === "object" && + (Object.prototype.hasOwnProperty.call(options[0], "paths") || Object.prototype.hasOwnProperty.call(options[0], "patterns")); + + const restrictedPaths = (isPathAndPatternsObject ? options[0].paths : context.options) || []; + const restrictedPatterns = (isPathAndPatternsObject ? options[0].patterns : []) || []; + + const restrictedPathMessages = restrictedPaths.reduce((memo, importName) => { + if (typeof importName === "string") { + memo[importName] = null; + } else { + memo[importName.name] = importName.message; + } + return memo; + }, {}); + + // if no imports are restricted we don"t need to check + if (Object.keys(restrictedPaths).length === 0 && restrictedPatterns.length === 0) { + return {}; + } + + const ig = ignore().add(restrictedPatterns); + + + /** + * Function to check if a node is a string literal. + * @param {ASTNode} node The node to check. + * @returns {boolean} If the node is a string literal. + */ + function isStringLiteral(node) { + return node && node.type === "Literal" && typeof node.value === "string"; + } + + /** + * Function to check if a node is a static string template literal. + * @param {ASTNode} node The node to check. + * @returns {boolean} If the node is a string template literal. + */ + function isStaticTemplateLiteral(node) { + return node && node.type === "TemplateLiteral" && node.expressions.length === 0; + } + + /** + * Function to check if a node is a require call. + * @param {ASTNode} node The node to check. + * @returns {boolean} If the node is a require call. + */ + function isRequireCall(node) { + return node.callee.type === "Identifier" && node.callee.name === "require"; + } + + /** + * Extract string from Literal or TemplateLiteral node + * @param {ASTNode} node The node to extract from + * @returns {string|null} Extracted string or null if node doesn't represent a string + */ + function getFirstArgumentString(node) { + if (isStringLiteral(node)) { + return node.value.trim(); + } + + if (isStaticTemplateLiteral(node)) { + return node.quasis[0].value.cooked.trim(); + } + + return null; + } + + /** + * Report a restricted path. + * @param {node} node representing the restricted path reference + * @param {string} name restricted path + * @returns {void} + * @private + */ + function reportPath(node, name) { + const customMessage = restrictedPathMessages[name]; + const messageId = customMessage + ? "customMessage" + : "defaultMessage"; + + context.report({ + node, + messageId, + data: { + name, + customMessage + } + }); + } + + /** + * Check if the given name is a restricted path name + * @param {string} name name of a variable + * @returns {boolean} whether the variable is a restricted path or not + * @private + */ + function isRestrictedPath(name) { + return Object.prototype.hasOwnProperty.call(restrictedPathMessages, name); + } + + return { + CallExpression(node) { + if (isRequireCall(node)) { + + // node has arguments + if (node.arguments.length) { + const name = getFirstArgumentString(node.arguments[0]); + + // if first argument is a string literal or a static string template literal + if (name) { + + // check if argument value is in restricted modules array + if (isRestrictedPath(name)) { + reportPath(node, name); + } + + if (restrictedPatterns.length > 0 && ig.ignores(name)) { + context.report({ + node, + messageId: "patternMessage", + data: { name } + }); + } + } + } + } + } + }; + } +}; diff --git a/eslint/lib/rules/no-restricted-properties.js b/eslint/lib/rules/no-restricted-properties.js new file mode 100644 index 0000000..7ab8399 --- /dev/null +++ b/eslint/lib/rules/no-restricted-properties.js @@ -0,0 +1,181 @@ +/** + * @fileoverview Rule to disallow certain object properties + * @author Will Klein & Eli White + */ + +"use strict"; + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow certain properties on certain objects", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-restricted-properties" + }, + + schema: { + type: "array", + items: { + anyOf: [ // `object` and `property` are both optional, but at least one of them must be provided. + { + type: "object", + properties: { + object: { + type: "string" + }, + property: { + type: "string" + }, + message: { + type: "string" + } + }, + additionalProperties: false, + required: ["object"] + }, + { + type: "object", + properties: { + object: { + type: "string" + }, + property: { + type: "string" + }, + message: { + type: "string" + } + }, + additionalProperties: false, + required: ["property"] + } + ] + }, + uniqueItems: true + }, + + messages: { + // eslint-disable-next-line eslint-plugin/report-message-format + restrictedObjectProperty: "'{{objectName}}.{{propertyName}}' is restricted from being used.{{message}}", + // eslint-disable-next-line eslint-plugin/report-message-format + restrictedProperty: "'{{propertyName}}' is restricted from being used.{{message}}" + } + }, + + create(context) { + const restrictedCalls = context.options; + + if (restrictedCalls.length === 0) { + return {}; + } + + const restrictedProperties = new Map(); + const globallyRestrictedObjects = new Map(); + const globallyRestrictedProperties = new Map(); + + restrictedCalls.forEach(option => { + const objectName = option.object; + const propertyName = option.property; + + if (typeof objectName === "undefined") { + globallyRestrictedProperties.set(propertyName, { message: option.message }); + } else if (typeof propertyName === "undefined") { + globallyRestrictedObjects.set(objectName, { message: option.message }); + } else { + if (!restrictedProperties.has(objectName)) { + restrictedProperties.set(objectName, new Map()); + } + + restrictedProperties.get(objectName).set(propertyName, { + message: option.message + }); + } + }); + + /** + * Checks to see whether a property access is restricted, and reports it if so. + * @param {ASTNode} node The node to report + * @param {string} objectName The name of the object + * @param {string} propertyName The name of the property + * @returns {undefined} + */ + function checkPropertyAccess(node, objectName, propertyName) { + if (propertyName === null) { + return; + } + const matchedObject = restrictedProperties.get(objectName); + const matchedObjectProperty = matchedObject ? matchedObject.get(propertyName) : globallyRestrictedObjects.get(objectName); + const globalMatchedProperty = globallyRestrictedProperties.get(propertyName); + + if (matchedObjectProperty) { + const message = matchedObjectProperty.message ? ` ${matchedObjectProperty.message}` : ""; + + context.report({ + node, + messageId: "restrictedObjectProperty", + data: { + objectName, + propertyName, + message + } + }); + } else if (globalMatchedProperty) { + const message = globalMatchedProperty.message ? ` ${globalMatchedProperty.message}` : ""; + + context.report({ + node, + messageId: "restrictedProperty", + data: { + propertyName, + message + } + }); + } + } + + /** + * Checks property accesses in a destructuring assignment expression, e.g. `var foo; ({foo} = bar);` + * @param {ASTNode} node An AssignmentExpression or AssignmentPattern node + * @returns {undefined} + */ + function checkDestructuringAssignment(node) { + if (node.right.type === "Identifier") { + const objectName = node.right.name; + + if (node.left.type === "ObjectPattern") { + node.left.properties.forEach(property => { + checkPropertyAccess(node.left, objectName, astUtils.getStaticPropertyName(property)); + }); + } + } + } + + return { + MemberExpression(node) { + checkPropertyAccess(node, node.object && node.object.name, astUtils.getStaticPropertyName(node)); + }, + VariableDeclarator(node) { + if (node.init && node.init.type === "Identifier") { + const objectName = node.init.name; + + if (node.id.type === "ObjectPattern") { + node.id.properties.forEach(property => { + checkPropertyAccess(node.id, objectName, astUtils.getStaticPropertyName(property)); + }); + } + } + }, + AssignmentExpression: checkDestructuringAssignment, + AssignmentPattern: checkDestructuringAssignment + }; + } +}; diff --git a/eslint/lib/rules/no-restricted-syntax.js b/eslint/lib/rules/no-restricted-syntax.js new file mode 100644 index 0000000..9572603 --- /dev/null +++ b/eslint/lib/rules/no-restricted-syntax.js @@ -0,0 +1,70 @@ +/** + * @fileoverview Rule to flag use of certain node types + * @author Burak Yigit Kaya + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow specified syntax", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/no-restricted-syntax" + }, + + schema: { + type: "array", + items: { + oneOf: [ + { + type: "string" + }, + { + type: "object", + properties: { + selector: { type: "string" }, + message: { type: "string" } + }, + required: ["selector"], + additionalProperties: false + } + ] + }, + uniqueItems: true, + minItems: 0 + }, + + messages: { + // eslint-disable-next-line eslint-plugin/report-message-format + restrictedSyntax: "{{message}}" + } + }, + + create(context) { + return context.options.reduce((result, selectorOrObject) => { + const isStringFormat = (typeof selectorOrObject === "string"); + const hasCustomMessage = !isStringFormat && Boolean(selectorOrObject.message); + + const selector = isStringFormat ? selectorOrObject : selectorOrObject.selector; + const message = hasCustomMessage ? selectorOrObject.message : `Using '${selector}' is not allowed.`; + + return Object.assign(result, { + [selector](node) { + context.report({ + node, + messageId: "restrictedSyntax", + data: { message } + }); + } + }); + }, {}); + + } +}; diff --git a/eslint/lib/rules/no-return-assign.js b/eslint/lib/rules/no-return-assign.js new file mode 100644 index 0000000..4b57d42 --- /dev/null +++ b/eslint/lib/rules/no-return-assign.js @@ -0,0 +1,80 @@ +/** + * @fileoverview Rule to flag when return statement contains assignment + * @author Ilya Volodin + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const SENTINEL_TYPE = /^(?:[a-zA-Z]+?Statement|ArrowFunctionExpression|FunctionExpression|ClassExpression)$/u; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow assignment operators in `return` statements", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-return-assign" + }, + + schema: [ + { + enum: ["except-parens", "always"] + } + ], + + messages: { + returnAssignment: "Return statement should not contain assignment.", + arrowAssignment: "Arrow function should not return assignment." + } + }, + + create(context) { + const always = (context.options[0] || "except-parens") !== "except-parens"; + const sourceCode = context.getSourceCode(); + + return { + AssignmentExpression(node) { + if (!always && astUtils.isParenthesised(sourceCode, node)) { + return; + } + + let currentChild = node; + let parent = currentChild.parent; + + // Find ReturnStatement or ArrowFunctionExpression in ancestors. + while (parent && !SENTINEL_TYPE.test(parent.type)) { + currentChild = parent; + parent = parent.parent; + } + + // Reports. + if (parent && parent.type === "ReturnStatement") { + context.report({ + node: parent, + messageId: "returnAssignment" + }); + } else if (parent && parent.type === "ArrowFunctionExpression" && parent.body === currentChild) { + context.report({ + node: parent, + messageId: "arrowAssignment" + }); + } + } + }; + } +}; diff --git a/eslint/lib/rules/no-return-await.js b/eslint/lib/rules/no-return-await.js new file mode 100644 index 0000000..d1d8982 --- /dev/null +++ b/eslint/lib/rules/no-return-await.js @@ -0,0 +1,103 @@ +/** + * @fileoverview Disallows unnecessary `return await` + * @author Jordan Harband + */ +"use strict"; + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow unnecessary `return await`", + category: "Best Practices", + + recommended: false, + + url: "https://eslint.org/docs/rules/no-return-await" + }, + + fixable: null, + + schema: [ + ], + + messages: { + redundantUseOfAwait: "Redundant use of `await` on a return value." + } + }, + + create(context) { + + /** + * Reports a found unnecessary `await` expression. + * @param {ASTNode} node The node representing the `await` expression to report + * @returns {void} + */ + function reportUnnecessaryAwait(node) { + context.report({ + node: context.getSourceCode().getFirstToken(node), + loc: node.loc, + messageId: "redundantUseOfAwait" + }); + } + + /** + * Determines whether a thrown error from this node will be caught/handled within this function rather than immediately halting + * this function. For example, a statement in a `try` block will always have an error handler. A statement in + * a `catch` block will only have an error handler if there is also a `finally` block. + * @param {ASTNode} node A node representing a location where an could be thrown + * @returns {boolean} `true` if a thrown error will be caught/handled in this function + */ + function hasErrorHandler(node) { + let ancestor = node; + + while (!astUtils.isFunction(ancestor) && ancestor.type !== "Program") { + if (ancestor.parent.type === "TryStatement" && (ancestor === ancestor.parent.block || ancestor === ancestor.parent.handler && ancestor.parent.finalizer)) { + return true; + } + ancestor = ancestor.parent; + } + return false; + } + + /** + * Checks if a node is placed in tail call position. Once `return` arguments (or arrow function expressions) can be a complex expression, + * an `await` expression could or could not be unnecessary by the definition of this rule. So we're looking for `await` expressions that are in tail position. + * @param {ASTNode} node A node representing the `await` expression to check + * @returns {boolean} The checking result + */ + function isInTailCallPosition(node) { + if (node.parent.type === "ArrowFunctionExpression") { + return true; + } + if (node.parent.type === "ReturnStatement") { + return !hasErrorHandler(node.parent); + } + if (node.parent.type === "ConditionalExpression" && (node === node.parent.consequent || node === node.parent.alternate)) { + return isInTailCallPosition(node.parent); + } + if (node.parent.type === "LogicalExpression" && node === node.parent.right) { + return isInTailCallPosition(node.parent); + } + if (node.parent.type === "SequenceExpression" && node === node.parent.expressions[node.parent.expressions.length - 1]) { + return isInTailCallPosition(node.parent); + } + return false; + } + + return { + AwaitExpression(node) { + if (isInTailCallPosition(node) && !hasErrorHandler(node)) { + reportUnnecessaryAwait(node); + } + } + }; + } +}; diff --git a/eslint/lib/rules/no-script-url.js b/eslint/lib/rules/no-script-url.js new file mode 100644 index 0000000..2078fc1 --- /dev/null +++ b/eslint/lib/rules/no-script-url.js @@ -0,0 +1,48 @@ +/** + * @fileoverview Rule to flag when using javascript: urls + * @author Ilya Volodin + */ +/* jshint scripturl: true */ +/* eslint no-script-url: 0 */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow `javascript:` urls", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-script-url" + }, + + schema: [], + + messages: { + unexpectedScriptURL: "Script URL is a form of eval." + } + }, + + create(context) { + + return { + + Literal(node) { + if (node.value && typeof node.value === "string") { + const value = node.value.toLowerCase(); + + if (value.indexOf("javascript:") === 0) { + context.report({ node, messageId: "unexpectedScriptURL" }); + } + } + } + }; + + } +}; diff --git a/eslint/lib/rules/no-self-assign.js b/eslint/lib/rules/no-self-assign.js new file mode 100644 index 0000000..170e46b --- /dev/null +++ b/eslint/lib/rules/no-self-assign.js @@ -0,0 +1,233 @@ +/** + * @fileoverview Rule to disallow assignments where both sides are exactly the same + * @author Toru Nagashima + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const SPACES = /\s+/gu; + +/** + * Checks whether the property of 2 given member expression nodes are the same + * property or not. + * @param {ASTNode} left A member expression node to check. + * @param {ASTNode} right Another member expression node to check. + * @returns {boolean} `true` if the member expressions have the same property. + */ +function isSameProperty(left, right) { + if (left.property.type === "Identifier" && + left.property.type === right.property.type && + left.property.name === right.property.name && + left.computed === right.computed + ) { + return true; + } + + const lname = astUtils.getStaticPropertyName(left); + const rname = astUtils.getStaticPropertyName(right); + + return lname !== null && lname === rname; +} + +/** + * Checks whether 2 given member expression nodes are the reference to the same + * property or not. + * @param {ASTNode} left A member expression node to check. + * @param {ASTNode} right Another member expression node to check. + * @returns {boolean} `true` if the member expressions are the reference to the + * same property or not. + */ +function isSameMember(left, right) { + if (!isSameProperty(left, right)) { + return false; + } + + const lobj = left.object; + const robj = right.object; + + if (lobj.type !== robj.type) { + return false; + } + if (lobj.type === "MemberExpression") { + return isSameMember(lobj, robj); + } + if (lobj.type === "ThisExpression") { + return true; + } + return lobj.type === "Identifier" && lobj.name === robj.name; +} + +/** + * Traverses 2 Pattern nodes in parallel, then reports self-assignments. + * @param {ASTNode|null} left A left node to traverse. This is a Pattern or + * a Property. + * @param {ASTNode|null} right A right node to traverse. This is a Pattern or + * a Property. + * @param {boolean} props The flag to check member expressions as well. + * @param {Function} report A callback function to report. + * @returns {void} + */ +function eachSelfAssignment(left, right, props, report) { + if (!left || !right) { + + // do nothing + } else if ( + left.type === "Identifier" && + right.type === "Identifier" && + left.name === right.name + ) { + report(right); + } else if ( + left.type === "ArrayPattern" && + right.type === "ArrayExpression" + ) { + const end = Math.min(left.elements.length, right.elements.length); + + for (let i = 0; i < end; ++i) { + const leftElement = left.elements[i]; + const rightElement = right.elements[i]; + + // Avoid cases such as [...a] = [...a, 1] + if ( + leftElement && + leftElement.type === "RestElement" && + i < right.elements.length - 1 + ) { + break; + } + + eachSelfAssignment(leftElement, rightElement, props, report); + + // After a spread element, those indices are unknown. + if (rightElement && rightElement.type === "SpreadElement") { + break; + } + } + } else if ( + left.type === "RestElement" && + right.type === "SpreadElement" + ) { + eachSelfAssignment(left.argument, right.argument, props, report); + } else if ( + left.type === "ObjectPattern" && + right.type === "ObjectExpression" && + right.properties.length >= 1 + ) { + + /* + * Gets the index of the last spread property. + * It's possible to overwrite properties followed by it. + */ + let startJ = 0; + + for (let i = right.properties.length - 1; i >= 0; --i) { + const propType = right.properties[i].type; + + if (propType === "SpreadElement" || propType === "ExperimentalSpreadProperty") { + startJ = i + 1; + break; + } + } + + for (let i = 0; i < left.properties.length; ++i) { + for (let j = startJ; j < right.properties.length; ++j) { + eachSelfAssignment( + left.properties[i], + right.properties[j], + props, + report + ); + } + } + } else if ( + left.type === "Property" && + right.type === "Property" && + right.kind === "init" && + !right.method + ) { + const leftName = astUtils.getStaticPropertyName(left); + + if (leftName !== null && leftName === astUtils.getStaticPropertyName(right)) { + eachSelfAssignment(left.value, right.value, props, report); + } + } else if ( + props && + left.type === "MemberExpression" && + right.type === "MemberExpression" && + isSameMember(left, right) + ) { + report(right); + } +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow assignments where both sides are exactly the same", + category: "Best Practices", + recommended: true, + url: "https://eslint.org/docs/rules/no-self-assign" + }, + + schema: [ + { + type: "object", + properties: { + props: { + type: "boolean", + default: true + } + }, + additionalProperties: false + } + ], + + messages: { + selfAssignment: "'{{name}}' is assigned to itself." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + const [{ props = true } = {}] = context.options; + + /** + * Reports a given node as self assignments. + * @param {ASTNode} node A node to report. This is an Identifier node. + * @returns {void} + */ + function report(node) { + context.report({ + node, + messageId: "selfAssignment", + data: { + name: sourceCode.getText(node).replace(SPACES, "") + } + }); + } + + return { + AssignmentExpression(node) { + if (node.operator === "=") { + eachSelfAssignment(node.left, node.right, props, report); + } + } + }; + } +}; diff --git a/eslint/lib/rules/no-self-compare.js b/eslint/lib/rules/no-self-compare.js new file mode 100644 index 0000000..79b6ac7 --- /dev/null +++ b/eslint/lib/rules/no-self-compare.js @@ -0,0 +1,60 @@ +/** + * @fileoverview Rule to flag comparison where left part is the same as the right + * part. + * @author Ilya Volodin + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow comparisons where both sides are exactly the same", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-self-compare" + }, + + schema: [], + + messages: { + comparingToSelf: "Comparing to itself is potentially pointless." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + /** + * Determines whether two nodes are composed of the same tokens. + * @param {ASTNode} nodeA The first node + * @param {ASTNode} nodeB The second node + * @returns {boolean} true if the nodes have identical token representations + */ + function hasSameTokens(nodeA, nodeB) { + const tokensA = sourceCode.getTokens(nodeA); + const tokensB = sourceCode.getTokens(nodeB); + + return tokensA.length === tokensB.length && + tokensA.every((token, index) => token.type === tokensB[index].type && token.value === tokensB[index].value); + } + + return { + + BinaryExpression(node) { + const operators = new Set(["===", "==", "!==", "!=", ">", "<", ">=", "<="]); + + if (operators.has(node.operator) && hasSameTokens(node.left, node.right)) { + context.report({ node, messageId: "comparingToSelf" }); + } + } + }; + + } +}; diff --git a/eslint/lib/rules/no-sequences.js b/eslint/lib/rules/no-sequences.js new file mode 100644 index 0000000..d67635d --- /dev/null +++ b/eslint/lib/rules/no-sequences.js @@ -0,0 +1,119 @@ +/** + * @fileoverview Rule to flag use of comma operator + * @author Brandon Mills + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow comma operators", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-sequences" + }, + + schema: [], + + messages: { + unexpectedCommaExpression: "Unexpected use of comma operator." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + /** + * Parts of the grammar that are required to have parens. + */ + const parenthesized = { + DoWhileStatement: "test", + IfStatement: "test", + SwitchStatement: "discriminant", + WhileStatement: "test", + WithStatement: "object", + ArrowFunctionExpression: "body" + + /* + * Omitting CallExpression - commas are parsed as argument separators + * Omitting NewExpression - commas are parsed as argument separators + * Omitting ForInStatement - parts aren't individually parenthesised + * Omitting ForStatement - parts aren't individually parenthesised + */ + }; + + /** + * Determines whether a node is required by the grammar to be wrapped in + * parens, e.g. the test of an if statement. + * @param {ASTNode} node The AST node + * @returns {boolean} True if parens around node belong to parent node. + */ + function requiresExtraParens(node) { + return node.parent && parenthesized[node.parent.type] && + node === node.parent[parenthesized[node.parent.type]]; + } + + /** + * Check if a node is wrapped in parens. + * @param {ASTNode} node The AST node + * @returns {boolean} True if the node has a paren on each side. + */ + function isParenthesised(node) { + return astUtils.isParenthesised(sourceCode, node); + } + + /** + * Check if a node is wrapped in two levels of parens. + * @param {ASTNode} node The AST node + * @returns {boolean} True if two parens surround the node on each side. + */ + function isParenthesisedTwice(node) { + const previousToken = sourceCode.getTokenBefore(node, 1), + nextToken = sourceCode.getTokenAfter(node, 1); + + return isParenthesised(node) && previousToken && nextToken && + astUtils.isOpeningParenToken(previousToken) && previousToken.range[1] <= node.range[0] && + astUtils.isClosingParenToken(nextToken) && nextToken.range[0] >= node.range[1]; + } + + return { + SequenceExpression(node) { + + // Always allow sequences in for statement update + if (node.parent.type === "ForStatement" && + (node === node.parent.init || node === node.parent.update)) { + return; + } + + // Wrapping a sequence in extra parens indicates intent + if (requiresExtraParens(node)) { + if (isParenthesisedTwice(node)) { + return; + } + } else { + if (isParenthesised(node)) { + return; + } + } + + const firstCommaToken = sourceCode.getTokenAfter(node.expressions[0], astUtils.isCommaToken); + + context.report({ node, loc: firstCommaToken.loc, messageId: "unexpectedCommaExpression" }); + } + }; + + } +}; diff --git a/eslint/lib/rules/no-setter-return.js b/eslint/lib/rules/no-setter-return.js new file mode 100644 index 0000000..a558640 --- /dev/null +++ b/eslint/lib/rules/no-setter-return.js @@ -0,0 +1,227 @@ +/** + * @fileoverview Rule to disallow returning values from setters + * @author Milos Djermanovic + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); +const { findVariable } = require("eslint-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Determines whether the given identifier node is a reference to a global variable. + * @param {ASTNode} node `Identifier` node to check. + * @param {Scope} scope Scope to which the node belongs. + * @returns {boolean} True if the identifier is a reference to a global variable. + */ +function isGlobalReference(node, scope) { + const variable = findVariable(scope, node); + + return variable !== null && variable.scope.type === "global" && variable.defs.length === 0; +} + +/** + * Determines whether the given node is an argument of the specified global method call, at the given `index` position. + * E.g., for given `index === 1`, this function checks for `objectName.methodName(foo, node)`, where objectName is a global variable. + * @param {ASTNode} node The node to check. + * @param {Scope} scope Scope to which the node belongs. + * @param {string} objectName Name of the global object. + * @param {string} methodName Name of the method. + * @param {number} index The given position. + * @returns {boolean} `true` if the node is argument at the given position. + */ +function isArgumentOfGlobalMethodCall(node, scope, objectName, methodName, index) { + const parent = node.parent; + + return parent.type === "CallExpression" && + parent.arguments[index] === node && + parent.callee.type === "MemberExpression" && + astUtils.getStaticPropertyName(parent.callee) === methodName && + parent.callee.object.type === "Identifier" && + parent.callee.object.name === objectName && + isGlobalReference(parent.callee.object, scope); +} + +/** + * Determines whether the given node is used as a property descriptor. + * @param {ASTNode} node The node to check. + * @param {Scope} scope Scope to which the node belongs. + * @returns {boolean} `true` if the node is a property descriptor. + */ +function isPropertyDescriptor(node, scope) { + if ( + isArgumentOfGlobalMethodCall(node, scope, "Object", "defineProperty", 2) || + isArgumentOfGlobalMethodCall(node, scope, "Reflect", "defineProperty", 2) + ) { + return true; + } + + const parent = node.parent; + + if ( + parent.type === "Property" && + parent.value === node + ) { + const grandparent = parent.parent; + + if ( + grandparent.type === "ObjectExpression" && + ( + isArgumentOfGlobalMethodCall(grandparent, scope, "Object", "create", 1) || + isArgumentOfGlobalMethodCall(grandparent, scope, "Object", "defineProperties", 1) + ) + ) { + return true; + } + } + + return false; +} + +/** + * Determines whether the given function node is used as a setter function. + * @param {ASTNode} node The node to check. + * @param {Scope} scope Scope to which the node belongs. + * @returns {boolean} `true` if the node is a setter. + */ +function isSetter(node, scope) { + const parent = node.parent; + + if ( + parent.kind === "set" && + parent.value === node + ) { + + // Setter in an object literal or in a class + return true; + } + + if ( + parent.type === "Property" && + parent.value === node && + astUtils.getStaticPropertyName(parent) === "set" && + parent.parent.type === "ObjectExpression" && + isPropertyDescriptor(parent.parent, scope) + ) { + + // Setter in a property descriptor + return true; + } + + return false; +} + +/** + * Finds function's outer scope. + * @param {Scope} scope Function's own scope. + * @returns {Scope} Function's outer scope. + */ +function getOuterScope(scope) { + const upper = scope.upper; + + if (upper.type === "function-expression-name") { + return upper.upper; + } + + return upper; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow returning values from setters", + category: "Possible Errors", + recommended: true, + url: "https://eslint.org/docs/rules/no-setter-return" + }, + + schema: [], + + messages: { + returnsValue: "Setter cannot return a value." + } + }, + + create(context) { + let funcInfo = null; + + /** + * Creates and pushes to the stack a function info object for the given function node. + * @param {ASTNode} node The function node. + * @returns {void} + */ + function enterFunction(node) { + const outerScope = getOuterScope(context.getScope()); + + funcInfo = { + upper: funcInfo, + isSetter: isSetter(node, outerScope) + }; + } + + /** + * Pops the current function info object from the stack. + * @returns {void} + */ + function exitFunction() { + funcInfo = funcInfo.upper; + } + + /** + * Reports the given node. + * @param {ASTNode} node Node to report. + * @returns {void} + */ + function report(node) { + context.report({ node, messageId: "returnsValue" }); + } + + return { + + /* + * Function declarations cannot be setters, but we still have to track them in the `funcInfo` stack to avoid + * false positives, because a ReturnStatement node can belong to a function declaration inside a setter. + * + * Note: A previously declared function can be referenced and actually used as a setter in a property descriptor, + * but that's out of scope for this rule. + */ + FunctionDeclaration: enterFunction, + FunctionExpression: enterFunction, + ArrowFunctionExpression(node) { + enterFunction(node); + + if (funcInfo.isSetter && node.expression) { + + // { set: foo => bar } property descriptor. Report implicit return 'bar' as the equivalent for a return statement. + report(node.body); + } + }, + + "FunctionDeclaration:exit": exitFunction, + "FunctionExpression:exit": exitFunction, + "ArrowFunctionExpression:exit": exitFunction, + + ReturnStatement(node) { + + // Global returns (e.g., at the top level of a Node module) don't have `funcInfo`. + if (funcInfo && funcInfo.isSetter && node.argument) { + report(node); + } + } + }; + } +}; diff --git a/eslint/lib/rules/no-shadow-restricted-names.js b/eslint/lib/rules/no-shadow-restricted-names.js new file mode 100644 index 0000000..9647e9a --- /dev/null +++ b/eslint/lib/rules/no-shadow-restricted-names.js @@ -0,0 +1,64 @@ +/** + * @fileoverview Disallow shadowing of NaN, undefined, and Infinity (ES5 section 15.1.1) + * @author Michael Ficarra + */ +"use strict"; + +/** + * Determines if a variable safely shadows undefined. + * This is the case when a variable named `undefined` is never assigned to a value (i.e. it always shares the same value + * as the global). + * @param {eslintScope.Variable} variable The variable to check + * @returns {boolean} true if this variable safely shadows `undefined` + */ +function safelyShadowsUndefined(variable) { + return variable.name === "undefined" && + variable.references.every(ref => !ref.isWrite()) && + variable.defs.every(def => def.node.type === "VariableDeclarator" && def.node.init === null); +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow identifiers from shadowing restricted names", + category: "Variables", + recommended: true, + url: "https://eslint.org/docs/rules/no-shadow-restricted-names" + }, + + schema: [], + + messages: { + shadowingRestrictedName: "Shadowing of global property '{{name}}'." + } + }, + + create(context) { + + + const RESTRICTED = new Set(["undefined", "NaN", "Infinity", "arguments", "eval"]); + + return { + "VariableDeclaration, :function, CatchClause"(node) { + for (const variable of context.getDeclaredVariables(node)) { + if (variable.defs.length > 0 && RESTRICTED.has(variable.name) && !safelyShadowsUndefined(variable)) { + context.report({ + node: variable.defs[0].name, + messageId: "shadowingRestrictedName", + data: { + name: variable.name + } + }); + } + } + } + }; + + } +}; diff --git a/eslint/lib/rules/no-shadow.js b/eslint/lib/rules/no-shadow.js new file mode 100644 index 0000000..1be8590 --- /dev/null +++ b/eslint/lib/rules/no-shadow.js @@ -0,0 +1,192 @@ +/** + * @fileoverview Rule to flag on declaring variables already declared in the outer scope + * @author Ilya Volodin + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow variable declarations from shadowing variables declared in the outer scope", + category: "Variables", + recommended: false, + url: "https://eslint.org/docs/rules/no-shadow" + }, + + schema: [ + { + type: "object", + properties: { + builtinGlobals: { type: "boolean", default: false }, + hoist: { enum: ["all", "functions", "never"], default: "functions" }, + allow: { + type: "array", + items: { + type: "string" + } + } + }, + additionalProperties: false + } + ], + + messages: { + noShadow: "'{{name}}' is already declared in the upper scope." + } + }, + + create(context) { + + const options = { + builtinGlobals: context.options[0] && context.options[0].builtinGlobals, + hoist: (context.options[0] && context.options[0].hoist) || "functions", + allow: (context.options[0] && context.options[0].allow) || [] + }; + + /** + * Check if variable name is allowed. + * @param {ASTNode} variable The variable to check. + * @returns {boolean} Whether or not the variable name is allowed. + */ + function isAllowed(variable) { + return options.allow.indexOf(variable.name) !== -1; + } + + /** + * Checks if a variable of the class name in the class scope of ClassDeclaration. + * + * ClassDeclaration creates two variables of its name into its outer scope and its class scope. + * So we should ignore the variable in the class scope. + * @param {Object} variable The variable to check. + * @returns {boolean} Whether or not the variable of the class name in the class scope of ClassDeclaration. + */ + function isDuplicatedClassNameVariable(variable) { + const block = variable.scope.block; + + return block.type === "ClassDeclaration" && block.id === variable.identifiers[0]; + } + + /** + * Checks if a variable is inside the initializer of scopeVar. + * + * To avoid reporting at declarations such as `var a = function a() {};`. + * But it should report `var a = function(a) {};` or `var a = function() { function a() {} };`. + * @param {Object} variable The variable to check. + * @param {Object} scopeVar The scope variable to look for. + * @returns {boolean} Whether or not the variable is inside initializer of scopeVar. + */ + function isOnInitializer(variable, scopeVar) { + const outerScope = scopeVar.scope; + const outerDef = scopeVar.defs[0]; + const outer = outerDef && outerDef.parent && outerDef.parent.range; + const innerScope = variable.scope; + const innerDef = variable.defs[0]; + const inner = innerDef && innerDef.name.range; + + return ( + outer && + inner && + outer[0] < inner[0] && + inner[1] < outer[1] && + ((innerDef.type === "FunctionName" && innerDef.node.type === "FunctionExpression") || innerDef.node.type === "ClassExpression") && + outerScope === innerScope.upper + ); + } + + /** + * Get a range of a variable's identifier node. + * @param {Object} variable The variable to get. + * @returns {Array|undefined} The range of the variable's identifier node. + */ + function getNameRange(variable) { + const def = variable.defs[0]; + + return def && def.name.range; + } + + /** + * Checks if a variable is in TDZ of scopeVar. + * @param {Object} variable The variable to check. + * @param {Object} scopeVar The variable of TDZ. + * @returns {boolean} Whether or not the variable is in TDZ of scopeVar. + */ + function isInTdz(variable, scopeVar) { + const outerDef = scopeVar.defs[0]; + const inner = getNameRange(variable); + const outer = getNameRange(scopeVar); + + return ( + inner && + outer && + inner[1] < outer[0] && + + // Excepts FunctionDeclaration if is {"hoist":"function"}. + (options.hoist !== "functions" || !outerDef || outerDef.node.type !== "FunctionDeclaration") + ); + } + + /** + * Checks the current context for shadowed variables. + * @param {Scope} scope Fixme + * @returns {void} + */ + function checkForShadows(scope) { + const variables = scope.variables; + + for (let i = 0; i < variables.length; ++i) { + const variable = variables[i]; + + // Skips "arguments" or variables of a class name in the class scope of ClassDeclaration. + if (variable.identifiers.length === 0 || + isDuplicatedClassNameVariable(variable) || + isAllowed(variable) + ) { + continue; + } + + // Gets shadowed variable. + const shadowed = astUtils.getVariableByName(scope.upper, variable.name); + + if (shadowed && + (shadowed.identifiers.length > 0 || (options.builtinGlobals && "writeable" in shadowed)) && + !isOnInitializer(variable, shadowed) && + !(options.hoist !== "all" && isInTdz(variable, shadowed)) + ) { + context.report({ + node: variable.identifiers[0], + messageId: "noShadow", + data: variable + }); + } + } + } + + return { + "Program:exit"() { + const globalScope = context.getScope(); + const stack = globalScope.childScopes.slice(); + + while (stack.length) { + const scope = stack.pop(); + + stack.push(...scope.childScopes); + checkForShadows(scope); + } + } + }; + + } +}; diff --git a/eslint/lib/rules/no-spaced-func.js b/eslint/lib/rules/no-spaced-func.js new file mode 100644 index 0000000..961bc68 --- /dev/null +++ b/eslint/lib/rules/no-spaced-func.js @@ -0,0 +1,83 @@ +/** + * @fileoverview Rule to check that spaced function application + * @author Matt DuVall + * @deprecated in ESLint v3.3.0 + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "disallow spacing between function identifiers and their applications (deprecated)", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/no-spaced-func" + }, + + deprecated: true, + + replacedBy: ["func-call-spacing"], + + fixable: "whitespace", + schema: [], + + messages: { + noSpacedFunction: "Unexpected space between function name and paren." + } + }, + + create(context) { + + const sourceCode = context.getSourceCode(); + + /** + * Check if open space is present in a function name + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function detectOpenSpaces(node) { + const lastCalleeToken = sourceCode.getLastToken(node.callee); + let prevToken = lastCalleeToken, + parenToken = sourceCode.getTokenAfter(lastCalleeToken); + + // advances to an open parenthesis. + while ( + parenToken && + parenToken.range[1] < node.range[1] && + parenToken.value !== "(" + ) { + prevToken = parenToken; + parenToken = sourceCode.getTokenAfter(parenToken); + } + + // look for a space between the callee and the open paren + if (parenToken && + parenToken.range[1] < node.range[1] && + sourceCode.isSpaceBetweenTokens(prevToken, parenToken) + ) { + context.report({ + node, + loc: lastCalleeToken.loc.start, + messageId: "noSpacedFunction", + fix(fixer) { + return fixer.removeRange([prevToken.range[1], parenToken.range[0]]); + } + }); + } + } + + return { + CallExpression: detectOpenSpaces, + NewExpression: detectOpenSpaces + }; + + } +}; diff --git a/eslint/lib/rules/no-sparse-arrays.js b/eslint/lib/rules/no-sparse-arrays.js new file mode 100644 index 0000000..e8407c3 --- /dev/null +++ b/eslint/lib/rules/no-sparse-arrays.js @@ -0,0 +1,50 @@ +/** + * @fileoverview Disallow sparse arrays + * @author Nicholas C. Zakas + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow sparse arrays", + category: "Possible Errors", + recommended: true, + url: "https://eslint.org/docs/rules/no-sparse-arrays" + }, + + schema: [], + + messages: { + unexpectedSparseArray: "Unexpected comma in middle of array." + } + }, + + create(context) { + + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + + ArrayExpression(node) { + + const emptySpot = node.elements.indexOf(null) > -1; + + if (emptySpot) { + context.report({ node, messageId: "unexpectedSparseArray" }); + } + } + + }; + + } +}; diff --git a/eslint/lib/rules/no-sync.js b/eslint/lib/rules/no-sync.js new file mode 100644 index 0000000..d811105 --- /dev/null +++ b/eslint/lib/rules/no-sync.js @@ -0,0 +1,61 @@ +/** + * @fileoverview Rule to check for properties whose identifier ends with the string Sync + * @author Matt DuVall + */ + +/* jshint node:true */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow synchronous methods", + category: "Node.js and CommonJS", + recommended: false, + url: "https://eslint.org/docs/rules/no-sync" + }, + + schema: [ + { + type: "object", + properties: { + allowAtRootLevel: { + type: "boolean", + default: false + } + }, + additionalProperties: false + } + ], + + messages: { + noSync: "Unexpected sync method: '{{propertyName}}'." + } + }, + + create(context) { + const selector = context.options[0] && context.options[0].allowAtRootLevel + ? ":function MemberExpression[property.name=/.*Sync$/]" + : "MemberExpression[property.name=/.*Sync$/]"; + + return { + [selector](node) { + context.report({ + node, + messageId: "noSync", + data: { + propertyName: node.property.name + } + }); + } + }; + + } +}; diff --git a/eslint/lib/rules/no-tabs.js b/eslint/lib/rules/no-tabs.js new file mode 100644 index 0000000..ca7be26 --- /dev/null +++ b/eslint/lib/rules/no-tabs.js @@ -0,0 +1,78 @@ +/** + * @fileoverview Rule to check for tabs inside a file + * @author Gyandeep Singh + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const tabRegex = /\t+/gu; +const anyNonWhitespaceRegex = /\S/u; + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "disallow all tabs", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/no-tabs" + }, + schema: [{ + type: "object", + properties: { + allowIndentationTabs: { + type: "boolean", + default: false + } + }, + additionalProperties: false + }], + + messages: { + unexpectedTab: "Unexpected tab character." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + const allowIndentationTabs = context.options && context.options[0] && context.options[0].allowIndentationTabs; + + return { + Program(node) { + sourceCode.getLines().forEach((line, index) => { + let match; + + while ((match = tabRegex.exec(line)) !== null) { + if (allowIndentationTabs && !anyNonWhitespaceRegex.test(line.slice(0, match.index))) { + continue; + } + + context.report({ + node, + loc: { + start: { + line: index + 1, + column: match.index + }, + end: { + line: index + 1, + column: match.index + match[0].length + } + }, + messageId: "unexpectedTab" + }); + } + }); + } + }; + } +}; diff --git a/eslint/lib/rules/no-template-curly-in-string.js b/eslint/lib/rules/no-template-curly-in-string.js new file mode 100644 index 0000000..539cd5b --- /dev/null +++ b/eslint/lib/rules/no-template-curly-in-string.js @@ -0,0 +1,44 @@ +/** + * @fileoverview Warn when using template string syntax in regular strings + * @author Jeroen Engels + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow template literal placeholder syntax in regular strings", + category: "Possible Errors", + recommended: false, + url: "https://eslint.org/docs/rules/no-template-curly-in-string" + }, + + schema: [], + + messages: { + unexpectedTemplateExpression: "Unexpected template string expression." + } + }, + + create(context) { + const regex = /\$\{[^}]+\}/u; + + return { + Literal(node) { + if (typeof node.value === "string" && regex.test(node.value)) { + context.report({ + node, + messageId: "unexpectedTemplateExpression" + }); + } + } + }; + + } +}; diff --git a/eslint/lib/rules/no-ternary.js b/eslint/lib/rules/no-ternary.js new file mode 100644 index 0000000..b3ced86 --- /dev/null +++ b/eslint/lib/rules/no-ternary.js @@ -0,0 +1,41 @@ +/** + * @fileoverview Rule to flag use of ternary operators. + * @author Ian Christian Myers + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow ternary operators", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/no-ternary" + }, + + schema: [], + + messages: { + noTernaryOperator: "Ternary operator used." + } + }, + + create(context) { + + return { + + ConditionalExpression(node) { + context.report({ node, messageId: "noTernaryOperator" }); + } + + }; + + } +}; diff --git a/eslint/lib/rules/no-this-before-super.js b/eslint/lib/rules/no-this-before-super.js new file mode 100644 index 0000000..44288c0 --- /dev/null +++ b/eslint/lib/rules/no-this-before-super.js @@ -0,0 +1,304 @@ +/** + * @fileoverview A rule to disallow using `this`/`super` before `super()`. + * @author Toru Nagashima + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Checks whether or not a given node is a constructor. + * @param {ASTNode} node A node to check. This node type is one of + * `Program`, `FunctionDeclaration`, `FunctionExpression`, and + * `ArrowFunctionExpression`. + * @returns {boolean} `true` if the node is a constructor. + */ +function isConstructorFunction(node) { + return ( + node.type === "FunctionExpression" && + node.parent.type === "MethodDefinition" && + node.parent.kind === "constructor" + ); +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow `this`/`super` before calling `super()` in constructors", + category: "ECMAScript 6", + recommended: true, + url: "https://eslint.org/docs/rules/no-this-before-super" + }, + + schema: [], + + messages: { + noBeforeSuper: "'{{kind}}' is not allowed before 'super()'." + } + }, + + create(context) { + + /* + * Information for each constructor. + * - upper: Information of the upper constructor. + * - hasExtends: A flag which shows whether the owner class has a valid + * `extends` part. + * - scope: The scope of the owner class. + * - codePath: The code path of this constructor. + */ + let funcInfo = null; + + /* + * Information for each code path segment. + * Each key is the id of a code path segment. + * Each value is an object: + * - superCalled: The flag which shows `super()` called in all code paths. + * - invalidNodes: The array of invalid ThisExpression and Super nodes. + */ + let segInfoMap = Object.create(null); + + /** + * Gets whether or not `super()` is called in a given code path segment. + * @param {CodePathSegment} segment A code path segment to get. + * @returns {boolean} `true` if `super()` is called. + */ + function isCalled(segment) { + return !segment.reachable || segInfoMap[segment.id].superCalled; + } + + /** + * Checks whether or not this is in a constructor. + * @returns {boolean} `true` if this is in a constructor. + */ + function isInConstructorOfDerivedClass() { + return Boolean(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends); + } + + /** + * Checks whether or not this is before `super()` is called. + * @returns {boolean} `true` if this is before `super()` is called. + */ + function isBeforeCallOfSuper() { + return ( + isInConstructorOfDerivedClass() && + !funcInfo.codePath.currentSegments.every(isCalled) + ); + } + + /** + * Sets a given node as invalid. + * @param {ASTNode} node A node to set as invalid. This is one of + * a ThisExpression and a Super. + * @returns {void} + */ + function setInvalid(node) { + const segments = funcInfo.codePath.currentSegments; + + for (let i = 0; i < segments.length; ++i) { + const segment = segments[i]; + + if (segment.reachable) { + segInfoMap[segment.id].invalidNodes.push(node); + } + } + } + + /** + * Sets the current segment as `super` was called. + * @returns {void} + */ + function setSuperCalled() { + const segments = funcInfo.codePath.currentSegments; + + for (let i = 0; i < segments.length; ++i) { + const segment = segments[i]; + + if (segment.reachable) { + segInfoMap[segment.id].superCalled = true; + } + } + } + + return { + + /** + * Adds information of a constructor into the stack. + * @param {CodePath} codePath A code path which was started. + * @param {ASTNode} node The current node. + * @returns {void} + */ + onCodePathStart(codePath, node) { + if (isConstructorFunction(node)) { + + // Class > ClassBody > MethodDefinition > FunctionExpression + const classNode = node.parent.parent.parent; + + funcInfo = { + upper: funcInfo, + isConstructor: true, + hasExtends: Boolean( + classNode.superClass && + !astUtils.isNullOrUndefined(classNode.superClass) + ), + codePath + }; + } else { + funcInfo = { + upper: funcInfo, + isConstructor: false, + hasExtends: false, + codePath + }; + } + }, + + /** + * Removes the top of stack item. + * + * And this treverses all segments of this code path then reports every + * invalid node. + * @param {CodePath} codePath A code path which was ended. + * @returns {void} + */ + onCodePathEnd(codePath) { + const isDerivedClass = funcInfo.hasExtends; + + funcInfo = funcInfo.upper; + if (!isDerivedClass) { + return; + } + + codePath.traverseSegments((segment, controller) => { + const info = segInfoMap[segment.id]; + + for (let i = 0; i < info.invalidNodes.length; ++i) { + const invalidNode = info.invalidNodes[i]; + + context.report({ + messageId: "noBeforeSuper", + node: invalidNode, + data: { + kind: invalidNode.type === "Super" ? "super" : "this" + } + }); + } + + if (info.superCalled) { + controller.skip(); + } + }); + }, + + /** + * Initialize information of a given code path segment. + * @param {CodePathSegment} segment A code path segment to initialize. + * @returns {void} + */ + onCodePathSegmentStart(segment) { + if (!isInConstructorOfDerivedClass()) { + return; + } + + // Initialize info. + segInfoMap[segment.id] = { + superCalled: ( + segment.prevSegments.length > 0 && + segment.prevSegments.every(isCalled) + ), + invalidNodes: [] + }; + }, + + /** + * Update information of the code path segment when a code path was + * looped. + * @param {CodePathSegment} fromSegment The code path segment of the + * end of a loop. + * @param {CodePathSegment} toSegment A code path segment of the head + * of a loop. + * @returns {void} + */ + onCodePathSegmentLoop(fromSegment, toSegment) { + if (!isInConstructorOfDerivedClass()) { + return; + } + + // Update information inside of the loop. + funcInfo.codePath.traverseSegments( + { first: toSegment, last: fromSegment }, + (segment, controller) => { + const info = segInfoMap[segment.id]; + + if (info.superCalled) { + info.invalidNodes = []; + controller.skip(); + } else if ( + segment.prevSegments.length > 0 && + segment.prevSegments.every(isCalled) + ) { + info.superCalled = true; + info.invalidNodes = []; + } + } + ); + }, + + /** + * Reports if this is before `super()`. + * @param {ASTNode} node A target node. + * @returns {void} + */ + ThisExpression(node) { + if (isBeforeCallOfSuper()) { + setInvalid(node); + } + }, + + /** + * Reports if this is before `super()`. + * @param {ASTNode} node A target node. + * @returns {void} + */ + Super(node) { + if (!astUtils.isCallee(node) && isBeforeCallOfSuper()) { + setInvalid(node); + } + }, + + /** + * Marks `super()` called. + * @param {ASTNode} node A target node. + * @returns {void} + */ + "CallExpression:exit"(node) { + if (node.callee.type === "Super" && isBeforeCallOfSuper()) { + setSuperCalled(); + } + }, + + /** + * Resets state. + * @returns {void} + */ + "Program:exit"() { + segInfoMap = Object.create(null); + } + }; + } +}; diff --git a/eslint/lib/rules/no-throw-literal.js b/eslint/lib/rules/no-throw-literal.js new file mode 100644 index 0000000..29fb371 --- /dev/null +++ b/eslint/lib/rules/no-throw-literal.js @@ -0,0 +1,51 @@ +/** + * @fileoverview Rule to restrict what can be thrown as an exception. + * @author Dieter Oberkofler + */ + +"use strict"; + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow throwing literals as exceptions", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-throw-literal" + }, + + schema: [], + + messages: { + object: "Expected an error object to be thrown.", + undef: "Do not throw undefined." + } + }, + + create(context) { + + return { + + ThrowStatement(node) { + if (!astUtils.couldBeError(node.argument)) { + context.report({ node, messageId: "object" }); + } else if (node.argument.type === "Identifier") { + if (node.argument.name === "undefined") { + context.report({ node, messageId: "undef" }); + } + } + + } + + }; + + } +}; diff --git a/eslint/lib/rules/no-trailing-spaces.js b/eslint/lib/rules/no-trailing-spaces.js new file mode 100644 index 0000000..98ae62c --- /dev/null +++ b/eslint/lib/rules/no-trailing-spaces.js @@ -0,0 +1,190 @@ +/** + * @fileoverview Disallow trailing spaces at the end of lines. + * @author Nodeca Team + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "disallow trailing whitespace at the end of lines", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/no-trailing-spaces" + }, + + fixable: "whitespace", + + schema: [ + { + type: "object", + properties: { + skipBlankLines: { + type: "boolean", + default: false + }, + ignoreComments: { + type: "boolean", + default: false + } + }, + additionalProperties: false + } + ], + + messages: { + trailingSpace: "Trailing spaces not allowed." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + const BLANK_CLASS = "[ \t\u00a0\u2000-\u200b\u3000]", + SKIP_BLANK = `^${BLANK_CLASS}*$`, + NONBLANK = `${BLANK_CLASS}+$`; + + const options = context.options[0] || {}, + skipBlankLines = options.skipBlankLines || false, + ignoreComments = options.ignoreComments || false; + + /** + * Report the error message + * @param {ASTNode} node node to report + * @param {int[]} location range information + * @param {int[]} fixRange Range based on the whole program + * @returns {void} + */ + function report(node, location, fixRange) { + + /* + * Passing node is a bit dirty, because message data will contain big + * text in `source`. But... who cares :) ? + * One more kludge will not make worse the bloody wizardry of this + * plugin. + */ + context.report({ + node, + loc: location, + messageId: "trailingSpace", + fix(fixer) { + return fixer.removeRange(fixRange); + } + }); + } + + /** + * Given a list of comment nodes, return the line numbers for those comments. + * @param {Array} comments An array of comment nodes. + * @returns {number[]} An array of line numbers containing comments. + */ + function getCommentLineNumbers(comments) { + const lines = new Set(); + + comments.forEach(comment => { + const endLine = comment.type === "Block" + ? comment.loc.end.line - 1 + : comment.loc.end.line; + + for (let i = comment.loc.start.line; i <= endLine; i++) { + lines.add(i); + } + }); + + return lines; + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + + Program: function checkTrailingSpaces(node) { + + /* + * Let's hack. Since Espree does not return whitespace nodes, + * fetch the source code and do matching via regexps. + */ + + const re = new RegExp(NONBLANK, "u"), + skipMatch = new RegExp(SKIP_BLANK, "u"), + lines = sourceCode.lines, + linebreaks = sourceCode.getText().match(astUtils.createGlobalLinebreakMatcher()), + comments = sourceCode.getAllComments(), + commentLineNumbers = getCommentLineNumbers(comments); + + let totalLength = 0, + fixRange = []; + + for (let i = 0, ii = lines.length; i < ii; i++) { + const lineNumber = i + 1; + + /* + * Always add linebreak length to line length to accommodate for line break (\n or \r\n) + * Because during the fix time they also reserve one spot in the array. + * Usually linebreak length is 2 for \r\n (CRLF) and 1 for \n (LF) + */ + const linebreakLength = linebreaks && linebreaks[i] ? linebreaks[i].length : 1; + const lineLength = lines[i].length + linebreakLength; + + const matches = re.exec(lines[i]); + + if (matches) { + const location = { + start: { + line: lineNumber, + column: matches.index + }, + end: { + line: lineNumber, + column: lineLength - linebreakLength + } + }; + + const rangeStart = totalLength + location.start.column; + const rangeEnd = totalLength + location.end.column; + const containingNode = sourceCode.getNodeByRangeIndex(rangeStart); + + if (containingNode && containingNode.type === "TemplateElement" && + rangeStart > containingNode.parent.range[0] && + rangeEnd < containingNode.parent.range[1]) { + totalLength += lineLength; + continue; + } + + /* + * If the line has only whitespace, and skipBlankLines + * is true, don't report it + */ + if (skipBlankLines && skipMatch.test(lines[i])) { + totalLength += lineLength; + continue; + } + + fixRange = [rangeStart, rangeEnd]; + + if (!ignoreComments || !commentLineNumbers.has(lineNumber)) { + report(node, location, fixRange); + } + } + + totalLength += lineLength; + } + } + + }; + } +}; diff --git a/eslint/lib/rules/no-undef-init.js b/eslint/lib/rules/no-undef-init.js new file mode 100644 index 0000000..5c240fe --- /dev/null +++ b/eslint/lib/rules/no-undef-init.js @@ -0,0 +1,75 @@ +/** + * @fileoverview Rule to flag when initializing to undefined + * @author Ilya Volodin + */ + +"use strict"; + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow initializing variables to `undefined`", + category: "Variables", + recommended: false, + url: "https://eslint.org/docs/rules/no-undef-init" + }, + + schema: [], + fixable: "code", + + messages: { + unnecessaryUndefinedInit: "It's not necessary to initialize '{{name}}' to undefined." + } + }, + + create(context) { + + const sourceCode = context.getSourceCode(); + + return { + + VariableDeclarator(node) { + const name = sourceCode.getText(node.id), + init = node.init && node.init.name, + scope = context.getScope(), + undefinedVar = astUtils.getVariableByName(scope, "undefined"), + shadowed = undefinedVar && undefinedVar.defs.length > 0, + lastToken = sourceCode.getLastToken(node); + + if (init === "undefined" && node.parent.kind !== "const" && !shadowed) { + context.report({ + node, + messageId: "unnecessaryUndefinedInit", + data: { name }, + fix(fixer) { + if (node.parent.kind === "var") { + return null; + } + + if (node.id.type === "ArrayPattern" || node.id.type === "ObjectPattern") { + + // Don't fix destructuring assignment to `undefined`. + return null; + } + + if (sourceCode.commentsExistBetween(node.id, lastToken)) { + return null; + } + + return fixer.removeRange([node.id.range[1], node.range[1]]); + } + }); + } + } + }; + + } +}; diff --git a/eslint/lib/rules/no-undef.js b/eslint/lib/rules/no-undef.js new file mode 100644 index 0000000..6b51408 --- /dev/null +++ b/eslint/lib/rules/no-undef.js @@ -0,0 +1,78 @@ +/** + * @fileoverview Rule to flag references to undeclared variables. + * @author Mark Macdonald + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Checks if the given node is the argument of a typeof operator. + * @param {ASTNode} node The AST node being checked. + * @returns {boolean} Whether or not the node is the argument of a typeof operator. + */ +function hasTypeOfOperator(node) { + const parent = node.parent; + + return parent.type === "UnaryExpression" && parent.operator === "typeof"; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow the use of undeclared variables unless mentioned in `/*global */` comments", + category: "Variables", + recommended: true, + url: "https://eslint.org/docs/rules/no-undef" + }, + + schema: [ + { + type: "object", + properties: { + typeof: { + type: "boolean", + default: false + } + }, + additionalProperties: false + } + ], + messages: { + undef: "'{{name}}' is not defined." + } + }, + + create(context) { + const options = context.options[0]; + const considerTypeOf = options && options.typeof === true || false; + + return { + "Program:exit"(/* node */) { + const globalScope = context.getScope(); + + globalScope.through.forEach(ref => { + const identifier = ref.identifier; + + if (!considerTypeOf && hasTypeOfOperator(identifier)) { + return; + } + + context.report({ + node: identifier, + messageId: "undef", + data: identifier + }); + }); + } + }; + } +}; diff --git a/eslint/lib/rules/no-undefined.js b/eslint/lib/rules/no-undefined.js new file mode 100644 index 0000000..a075d90 --- /dev/null +++ b/eslint/lib/rules/no-undefined.js @@ -0,0 +1,84 @@ +/** + * @fileoverview Rule to flag references to the undefined variable. + * @author Michael Ficarra + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow the use of `undefined` as an identifier", + category: "Variables", + recommended: false, + url: "https://eslint.org/docs/rules/no-undefined" + }, + + schema: [], + + messages: { + unexpectedUndefined: "Unexpected use of undefined." + } + }, + + create(context) { + + /** + * Report an invalid "undefined" identifier node. + * @param {ASTNode} node The node to report. + * @returns {void} + */ + function report(node) { + context.report({ + node, + messageId: "unexpectedUndefined" + }); + } + + /** + * Checks the given scope for references to `undefined` and reports + * all references found. + * @param {eslint-scope.Scope} scope The scope to check. + * @returns {void} + */ + function checkScope(scope) { + const undefinedVar = scope.set.get("undefined"); + + if (!undefinedVar) { + return; + } + + const references = undefinedVar.references; + + const defs = undefinedVar.defs; + + // Report non-initializing references (those are covered in defs below) + references + .filter(ref => !ref.init) + .forEach(ref => report(ref.identifier)); + + defs.forEach(def => report(def.name)); + } + + return { + "Program:exit"() { + const globalScope = context.getScope(); + + const stack = [globalScope]; + + while (stack.length) { + const scope = stack.pop(); + + stack.push(...scope.childScopes); + checkScope(scope); + } + } + }; + + } +}; diff --git a/eslint/lib/rules/no-underscore-dangle.js b/eslint/lib/rules/no-underscore-dangle.js new file mode 100644 index 0000000..cac594e --- /dev/null +++ b/eslint/lib/rules/no-underscore-dangle.js @@ -0,0 +1,232 @@ +/** + * @fileoverview Rule to flag trailing underscores in variable declarations. + * @author Matt DuVall + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow dangling underscores in identifiers", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/no-underscore-dangle" + }, + + schema: [ + { + type: "object", + properties: { + allow: { + type: "array", + items: { + type: "string" + } + }, + allowAfterThis: { + type: "boolean", + default: false + }, + allowAfterSuper: { + type: "boolean", + default: false + }, + allowAfterThisConstructor: { + type: "boolean", + default: false + }, + enforceInMethodNames: { + type: "boolean", + default: false + } + }, + additionalProperties: false + } + ], + + messages: { + unexpectedUnderscore: "Unexpected dangling '_' in '{{identifier}}'." + } + }, + + create(context) { + + const options = context.options[0] || {}; + const ALLOWED_VARIABLES = options.allow ? options.allow : []; + const allowAfterThis = typeof options.allowAfterThis !== "undefined" ? options.allowAfterThis : false; + const allowAfterSuper = typeof options.allowAfterSuper !== "undefined" ? options.allowAfterSuper : false; + const allowAfterThisConstructor = typeof options.allowAfterThisConstructor !== "undefined" ? options.allowAfterThisConstructor : false; + const enforceInMethodNames = typeof options.enforceInMethodNames !== "undefined" ? options.enforceInMethodNames : false; + + //------------------------------------------------------------------------- + // Helpers + //------------------------------------------------------------------------- + + /** + * Check if identifier is present inside the allowed option + * @param {string} identifier name of the node + * @returns {boolean} true if its is present + * @private + */ + function isAllowed(identifier) { + return ALLOWED_VARIABLES.some(ident => ident === identifier); + } + + /** + * Check if identifier has a underscore at the end + * @param {string} identifier name of the node + * @returns {boolean} true if its is present + * @private + */ + function hasTrailingUnderscore(identifier) { + const len = identifier.length; + + return identifier !== "_" && (identifier[0] === "_" || identifier[len - 1] === "_"); + } + + /** + * Check if identifier is a special case member expression + * @param {string} identifier name of the node + * @returns {boolean} true if its is a special case + * @private + */ + function isSpecialCaseIdentifierForMemberExpression(identifier) { + return identifier === "__proto__"; + } + + /** + * Check if identifier is a special case variable expression + * @param {string} identifier name of the node + * @returns {boolean} true if its is a special case + * @private + */ + function isSpecialCaseIdentifierInVariableExpression(identifier) { + + // Checks for the underscore library usage here + return identifier === "_"; + } + + /** + * Check if a node is a member reference of this.constructor + * @param {ASTNode} node node to evaluate + * @returns {boolean} true if it is a reference on this.constructor + * @private + */ + function isThisConstructorReference(node) { + return node.object.type === "MemberExpression" && + node.object.property.name === "constructor" && + node.object.object.type === "ThisExpression"; + } + + /** + * Check if function has a underscore at the end + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function checkForTrailingUnderscoreInFunctionDeclaration(node) { + if (node.id) { + const identifier = node.id.name; + + if (typeof identifier !== "undefined" && hasTrailingUnderscore(identifier) && !isAllowed(identifier)) { + context.report({ + node, + messageId: "unexpectedUnderscore", + data: { + identifier + } + }); + } + } + } + + /** + * Check if variable expression has a underscore at the end + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function checkForTrailingUnderscoreInVariableExpression(node) { + const identifier = node.id.name; + + if (typeof identifier !== "undefined" && hasTrailingUnderscore(identifier) && + !isSpecialCaseIdentifierInVariableExpression(identifier) && !isAllowed(identifier)) { + context.report({ + node, + messageId: "unexpectedUnderscore", + data: { + identifier + } + }); + } + } + + /** + * Check if member expression has a underscore at the end + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function checkForTrailingUnderscoreInMemberExpression(node) { + const identifier = node.property.name, + isMemberOfThis = node.object.type === "ThisExpression", + isMemberOfSuper = node.object.type === "Super", + isMemberOfThisConstructor = isThisConstructorReference(node); + + if (typeof identifier !== "undefined" && hasTrailingUnderscore(identifier) && + !(isMemberOfThis && allowAfterThis) && + !(isMemberOfSuper && allowAfterSuper) && + !(isMemberOfThisConstructor && allowAfterThisConstructor) && + !isSpecialCaseIdentifierForMemberExpression(identifier) && !isAllowed(identifier)) { + context.report({ + node, + messageId: "unexpectedUnderscore", + data: { + identifier + } + }); + } + } + + /** + * Check if method declaration or method property has a underscore at the end + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function checkForTrailingUnderscoreInMethod(node) { + const identifier = node.key.name; + const isMethod = node.type === "MethodDefinition" || node.type === "Property" && node.method; + + if (typeof identifier !== "undefined" && enforceInMethodNames && isMethod && hasTrailingUnderscore(identifier) && !isAllowed(identifier)) { + context.report({ + node, + messageId: "unexpectedUnderscore", + data: { + identifier + } + }); + } + } + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + FunctionDeclaration: checkForTrailingUnderscoreInFunctionDeclaration, + VariableDeclarator: checkForTrailingUnderscoreInVariableExpression, + MemberExpression: checkForTrailingUnderscoreInMemberExpression, + MethodDefinition: checkForTrailingUnderscoreInMethod, + Property: checkForTrailingUnderscoreInMethod + }; + + } +}; diff --git a/eslint/lib/rules/no-unexpected-multiline.js b/eslint/lib/rules/no-unexpected-multiline.js new file mode 100644 index 0000000..eb72008 --- /dev/null +++ b/eslint/lib/rules/no-unexpected-multiline.js @@ -0,0 +1,110 @@ +/** + * @fileoverview Rule to spot scenarios where a newline looks like it is ending a statement, but is not. + * @author Glen Mailer + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow confusing multiline expressions", + category: "Possible Errors", + recommended: true, + url: "https://eslint.org/docs/rules/no-unexpected-multiline" + }, + + schema: [], + messages: { + function: "Unexpected newline between function and ( of function call.", + property: "Unexpected newline between object and [ of property access.", + taggedTemplate: "Unexpected newline between template tag and template literal.", + division: "Unexpected newline between numerator and division operator." + } + }, + + create(context) { + + const REGEX_FLAG_MATCHER = /^[gimsuy]+$/u; + + const sourceCode = context.getSourceCode(); + + /** + * Check to see if there is a newline between the node and the following open bracket + * line's expression + * @param {ASTNode} node The node to check. + * @param {string} messageId The error messageId to use. + * @returns {void} + * @private + */ + function checkForBreakAfter(node, messageId) { + const openParen = sourceCode.getTokenAfter(node, astUtils.isNotClosingParenToken); + const nodeExpressionEnd = sourceCode.getTokenBefore(openParen); + + if (openParen.loc.start.line !== nodeExpressionEnd.loc.end.line) { + context.report({ node, loc: openParen.loc.start, messageId, data: { char: openParen.value } }); + } + } + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + + MemberExpression(node) { + if (!node.computed) { + return; + } + checkForBreakAfter(node.object, "property"); + }, + + TaggedTemplateExpression(node) { + if (node.tag.loc.end.line === node.quasi.loc.start.line) { + return; + } + + // handle generics type parameters on template tags + const tokenBefore = sourceCode.getTokenBefore(node.quasi); + + if (tokenBefore.loc.end.line === node.quasi.loc.start.line) { + return; + } + + context.report({ node, loc: node.loc.start, messageId: "taggedTemplate" }); + }, + + CallExpression(node) { + if (node.arguments.length === 0) { + return; + } + checkForBreakAfter(node.callee, "function"); + }, + + "BinaryExpression[operator='/'] > BinaryExpression[operator='/'].left"(node) { + const secondSlash = sourceCode.getTokenAfter(node, token => token.value === "/"); + const tokenAfterOperator = sourceCode.getTokenAfter(secondSlash); + + if ( + tokenAfterOperator.type === "Identifier" && + REGEX_FLAG_MATCHER.test(tokenAfterOperator.value) && + secondSlash.range[1] === tokenAfterOperator.range[0] + ) { + checkForBreakAfter(node.left, "division"); + } + } + }; + + } +}; diff --git a/eslint/lib/rules/no-unmodified-loop-condition.js b/eslint/lib/rules/no-unmodified-loop-condition.js new file mode 100644 index 0000000..7031a4d --- /dev/null +++ b/eslint/lib/rules/no-unmodified-loop-condition.js @@ -0,0 +1,360 @@ +/** + * @fileoverview Rule to disallow use of unmodified expressions in loop conditions + * @author Toru Nagashima + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const Traverser = require("../shared/traverser"), + astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const SENTINEL_PATTERN = /(?:(?:Call|Class|Function|Member|New|Yield)Expression|Statement|Declaration)$/u; +const LOOP_PATTERN = /^(?:DoWhile|For|While)Statement$/u; // for-in/of statements don't have `test` property. +const GROUP_PATTERN = /^(?:BinaryExpression|ConditionalExpression)$/u; +const SKIP_PATTERN = /^(?:ArrowFunction|Class|Function)Expression$/u; +const DYNAMIC_PATTERN = /^(?:Call|Member|New|TaggedTemplate|Yield)Expression$/u; + +/** + * @typedef {Object} LoopConditionInfo + * @property {eslint-scope.Reference} reference - The reference. + * @property {ASTNode} group - BinaryExpression or ConditionalExpression nodes + * that the reference is belonging to. + * @property {Function} isInLoop - The predicate which checks a given reference + * is in this loop. + * @property {boolean} modified - The flag that the reference is modified in + * this loop. + */ + +/** + * Checks whether or not a given reference is a write reference. + * @param {eslint-scope.Reference} reference A reference to check. + * @returns {boolean} `true` if the reference is a write reference. + */ +function isWriteReference(reference) { + if (reference.init) { + const def = reference.resolved && reference.resolved.defs[0]; + + if (!def || def.type !== "Variable" || def.parent.kind !== "var") { + return false; + } + } + return reference.isWrite(); +} + +/** + * Checks whether or not a given loop condition info does not have the modified + * flag. + * @param {LoopConditionInfo} condition A loop condition info to check. + * @returns {boolean} `true` if the loop condition info is "unmodified". + */ +function isUnmodified(condition) { + return !condition.modified; +} + +/** + * Checks whether or not a given loop condition info does not have the modified + * flag and does not have the group this condition belongs to. + * @param {LoopConditionInfo} condition A loop condition info to check. + * @returns {boolean} `true` if the loop condition info is "unmodified". + */ +function isUnmodifiedAndNotBelongToGroup(condition) { + return !(condition.modified || condition.group); +} + +/** + * Checks whether or not a given reference is inside of a given node. + * @param {ASTNode} node A node to check. + * @param {eslint-scope.Reference} reference A reference to check. + * @returns {boolean} `true` if the reference is inside of the node. + */ +function isInRange(node, reference) { + const or = node.range; + const ir = reference.identifier.range; + + return or[0] <= ir[0] && ir[1] <= or[1]; +} + +/** + * Checks whether or not a given reference is inside of a loop node's condition. + * @param {ASTNode} node A node to check. + * @param {eslint-scope.Reference} reference A reference to check. + * @returns {boolean} `true` if the reference is inside of the loop node's + * condition. + */ +const isInLoop = { + WhileStatement: isInRange, + DoWhileStatement: isInRange, + ForStatement(node, reference) { + return ( + isInRange(node, reference) && + !(node.init && isInRange(node.init, reference)) + ); + } +}; + +/** + * Gets the function which encloses a given reference. + * This supports only FunctionDeclaration. + * @param {eslint-scope.Reference} reference A reference to get. + * @returns {ASTNode|null} The function node or null. + */ +function getEncloseFunctionDeclaration(reference) { + let node = reference.identifier; + + while (node) { + if (node.type === "FunctionDeclaration") { + return node.id ? node : null; + } + + node = node.parent; + } + + return null; +} + +/** + * Updates the "modified" flags of given loop conditions with given modifiers. + * @param {LoopConditionInfo[]} conditions The loop conditions to be updated. + * @param {eslint-scope.Reference[]} modifiers The references to update. + * @returns {void} + */ +function updateModifiedFlag(conditions, modifiers) { + + for (let i = 0; i < conditions.length; ++i) { + const condition = conditions[i]; + + for (let j = 0; !condition.modified && j < modifiers.length; ++j) { + const modifier = modifiers[j]; + let funcNode, funcVar; + + /* + * Besides checking for the condition being in the loop, we want to + * check the function that this modifier is belonging to is called + * in the loop. + * FIXME: This should probably be extracted to a function. + */ + const inLoop = condition.isInLoop(modifier) || Boolean( + (funcNode = getEncloseFunctionDeclaration(modifier)) && + (funcVar = astUtils.getVariableByName(modifier.from.upper, funcNode.id.name)) && + funcVar.references.some(condition.isInLoop) + ); + + condition.modified = inLoop; + } + } +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow unmodified loop conditions", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-unmodified-loop-condition" + }, + + schema: [], + + messages: { + loopConditionNotModified: "'{{name}}' is not modified in this loop." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + let groupMap = null; + + /** + * Reports a given condition info. + * @param {LoopConditionInfo} condition A loop condition info to report. + * @returns {void} + */ + function report(condition) { + const node = condition.reference.identifier; + + context.report({ + node, + messageId: "loopConditionNotModified", + data: node + }); + } + + /** + * Registers given conditions to the group the condition belongs to. + * @param {LoopConditionInfo[]} conditions A loop condition info to + * register. + * @returns {void} + */ + function registerConditionsToGroup(conditions) { + for (let i = 0; i < conditions.length; ++i) { + const condition = conditions[i]; + + if (condition.group) { + let group = groupMap.get(condition.group); + + if (!group) { + group = []; + groupMap.set(condition.group, group); + } + group.push(condition); + } + } + } + + /** + * Reports references which are inside of unmodified groups. + * @param {LoopConditionInfo[]} conditions A loop condition info to report. + * @returns {void} + */ + function checkConditionsInGroup(conditions) { + if (conditions.every(isUnmodified)) { + conditions.forEach(report); + } + } + + /** + * Checks whether or not a given group node has any dynamic elements. + * @param {ASTNode} root A node to check. + * This node is one of BinaryExpression or ConditionalExpression. + * @returns {boolean} `true` if the node is dynamic. + */ + function hasDynamicExpressions(root) { + let retv = false; + + Traverser.traverse(root, { + visitorKeys: sourceCode.visitorKeys, + enter(node) { + if (DYNAMIC_PATTERN.test(node.type)) { + retv = true; + this.break(); + } else if (SKIP_PATTERN.test(node.type)) { + this.skip(); + } + } + }); + + return retv; + } + + /** + * Creates the loop condition information from a given reference. + * @param {eslint-scope.Reference} reference A reference to create. + * @returns {LoopConditionInfo|null} Created loop condition info, or null. + */ + function toLoopCondition(reference) { + if (reference.init) { + return null; + } + + let group = null; + let child = reference.identifier; + let node = child.parent; + + while (node) { + if (SENTINEL_PATTERN.test(node.type)) { + if (LOOP_PATTERN.test(node.type) && node.test === child) { + + // This reference is inside of a loop condition. + return { + reference, + group, + isInLoop: isInLoop[node.type].bind(null, node), + modified: false + }; + } + + // This reference is outside of a loop condition. + break; + } + + /* + * If it's inside of a group, OK if either operand is modified. + * So stores the group this reference belongs to. + */ + if (GROUP_PATTERN.test(node.type)) { + + // If this expression is dynamic, no need to check. + if (hasDynamicExpressions(node)) { + break; + } else { + group = node; + } + } + + child = node; + node = node.parent; + } + + return null; + } + + /** + * Finds unmodified references which are inside of a loop condition. + * Then reports the references which are outside of groups. + * @param {eslint-scope.Variable} variable A variable to report. + * @returns {void} + */ + function checkReferences(variable) { + + // Gets references that exist in loop conditions. + const conditions = variable + .references + .map(toLoopCondition) + .filter(Boolean); + + if (conditions.length === 0) { + return; + } + + // Registers the conditions to belonging groups. + registerConditionsToGroup(conditions); + + // Check the conditions are modified. + const modifiers = variable.references.filter(isWriteReference); + + if (modifiers.length > 0) { + updateModifiedFlag(conditions, modifiers); + } + + /* + * Reports the conditions which are not belonging to groups. + * Others will be reported after all variables are done. + */ + conditions + .filter(isUnmodifiedAndNotBelongToGroup) + .forEach(report); + } + + return { + "Program:exit"() { + const queue = [context.getScope()]; + + groupMap = new Map(); + + let scope; + + while ((scope = queue.pop())) { + queue.push(...scope.childScopes); + scope.variables.forEach(checkReferences); + } + + groupMap.forEach(checkConditionsInGroup); + groupMap = null; + } + }; + } +}; diff --git a/eslint/lib/rules/no-unneeded-ternary.js b/eslint/lib/rules/no-unneeded-ternary.js new file mode 100644 index 0000000..d4438e2 --- /dev/null +++ b/eslint/lib/rules/no-unneeded-ternary.js @@ -0,0 +1,166 @@ +/** + * @fileoverview Rule to flag no-unneeded-ternary + * @author Gyandeep Singh + */ + +"use strict"; + +const astUtils = require("./utils/ast-utils"); + +// Operators that always result in a boolean value +const BOOLEAN_OPERATORS = new Set(["==", "===", "!=", "!==", ">", ">=", "<", "<=", "in", "instanceof"]); +const OPERATOR_INVERSES = { + "==": "!=", + "!=": "==", + "===": "!==", + "!==": "===" + + // Operators like < and >= are not true inverses, since both will return false with NaN. +}; +const OR_PRECEDENCE = astUtils.getPrecedence({ type: "LogicalExpression", operator: "||" }); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow ternary operators when simpler alternatives exist", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/no-unneeded-ternary" + }, + + schema: [ + { + type: "object", + properties: { + defaultAssignment: { + type: "boolean", + default: true + } + }, + additionalProperties: false + } + ], + + fixable: "code", + + messages: { + unnecessaryConditionalExpression: "Unnecessary use of boolean literals in conditional expression.", + unnecessaryConditionalAssignment: "Unnecessary use of conditional expression for default assignment." + } + }, + + create(context) { + const options = context.options[0] || {}; + const defaultAssignment = options.defaultAssignment !== false; + const sourceCode = context.getSourceCode(); + + /** + * Test if the node is a boolean literal + * @param {ASTNode} node The node to report. + * @returns {boolean} True if the its a boolean literal + * @private + */ + function isBooleanLiteral(node) { + return node.type === "Literal" && typeof node.value === "boolean"; + } + + /** + * Creates an expression that represents the boolean inverse of the expression represented by the original node + * @param {ASTNode} node A node representing an expression + * @returns {string} A string representing an inverted expression + */ + function invertExpression(node) { + if (node.type === "BinaryExpression" && Object.prototype.hasOwnProperty.call(OPERATOR_INVERSES, node.operator)) { + const operatorToken = sourceCode.getFirstTokenBetween( + node.left, + node.right, + token => token.value === node.operator + ); + const text = sourceCode.getText(); + + return text.slice(node.range[0], + operatorToken.range[0]) + OPERATOR_INVERSES[node.operator] + text.slice(operatorToken.range[1], node.range[1]); + } + + if (astUtils.getPrecedence(node) < astUtils.getPrecedence({ type: "UnaryExpression" })) { + return `!(${astUtils.getParenthesisedText(sourceCode, node)})`; + } + return `!${astUtils.getParenthesisedText(sourceCode, node)}`; + } + + /** + * Tests if a given node always evaluates to a boolean value + * @param {ASTNode} node An expression node + * @returns {boolean} True if it is determined that the node will always evaluate to a boolean value + */ + function isBooleanExpression(node) { + return node.type === "BinaryExpression" && BOOLEAN_OPERATORS.has(node.operator) || + node.type === "UnaryExpression" && node.operator === "!"; + } + + /** + * Test if the node matches the pattern id ? id : expression + * @param {ASTNode} node The ConditionalExpression to check. + * @returns {boolean} True if the pattern is matched, and false otherwise + * @private + */ + function matchesDefaultAssignment(node) { + return node.test.type === "Identifier" && + node.consequent.type === "Identifier" && + node.test.name === node.consequent.name; + } + + return { + + ConditionalExpression(node) { + if (isBooleanLiteral(node.alternate) && isBooleanLiteral(node.consequent)) { + context.report({ + node, + loc: node.consequent.loc.start, + messageId: "unnecessaryConditionalExpression", + fix(fixer) { + if (node.consequent.value === node.alternate.value) { + + // Replace `foo ? true : true` with just `true`, but don't replace `foo() ? true : true` + return node.test.type === "Identifier" ? fixer.replaceText(node, node.consequent.value.toString()) : null; + } + if (node.alternate.value) { + + // Replace `foo() ? false : true` with `!(foo())` + return fixer.replaceText(node, invertExpression(node.test)); + } + + // Replace `foo ? true : false` with `foo` if `foo` is guaranteed to be a boolean, or `!!foo` otherwise. + + return fixer.replaceText(node, isBooleanExpression(node.test) ? astUtils.getParenthesisedText(sourceCode, node.test) : `!${invertExpression(node.test)}`); + } + }); + } else if (!defaultAssignment && matchesDefaultAssignment(node)) { + context.report({ + node, + loc: node.consequent.loc.start, + messageId: "unnecessaryConditionalAssignment", + fix: fixer => { + const shouldParenthesizeAlternate = ( + astUtils.getPrecedence(node.alternate) < OR_PRECEDENCE && + !astUtils.isParenthesised(sourceCode, node.alternate) + ); + const alternateText = shouldParenthesizeAlternate + ? `(${sourceCode.getText(node.alternate)})` + : astUtils.getParenthesisedText(sourceCode, node.alternate); + const testText = astUtils.getParenthesisedText(sourceCode, node.test); + + return fixer.replaceText(node, `${testText} || ${alternateText}`); + } + }); + } + } + }; + } +}; diff --git a/eslint/lib/rules/no-unreachable.js b/eslint/lib/rules/no-unreachable.js new file mode 100644 index 0000000..415631a --- /dev/null +++ b/eslint/lib/rules/no-unreachable.js @@ -0,0 +1,218 @@ +/** + * @fileoverview Checks for unreachable code due to return, throws, break, and continue. + * @author Joel Feenstra + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Checks whether or not a given variable declarator has the initializer. + * @param {ASTNode} node A VariableDeclarator node to check. + * @returns {boolean} `true` if the node has the initializer. + */ +function isInitialized(node) { + return Boolean(node.init); +} + +/** + * Checks whether or not a given code path segment is unreachable. + * @param {CodePathSegment} segment A CodePathSegment to check. + * @returns {boolean} `true` if the segment is unreachable. + */ +function isUnreachable(segment) { + return !segment.reachable; +} + +/** + * The class to distinguish consecutive unreachable statements. + */ +class ConsecutiveRange { + constructor(sourceCode) { + this.sourceCode = sourceCode; + this.startNode = null; + this.endNode = null; + } + + /** + * The location object of this range. + * @type {Object} + */ + get location() { + return { + start: this.startNode.loc.start, + end: this.endNode.loc.end + }; + } + + /** + * `true` if this range is empty. + * @type {boolean} + */ + get isEmpty() { + return !(this.startNode && this.endNode); + } + + /** + * Checks whether the given node is inside of this range. + * @param {ASTNode|Token} node The node to check. + * @returns {boolean} `true` if the node is inside of this range. + */ + contains(node) { + return ( + node.range[0] >= this.startNode.range[0] && + node.range[1] <= this.endNode.range[1] + ); + } + + /** + * Checks whether the given node is consecutive to this range. + * @param {ASTNode} node The node to check. + * @returns {boolean} `true` if the node is consecutive to this range. + */ + isConsecutive(node) { + return this.contains(this.sourceCode.getTokenBefore(node)); + } + + /** + * Merges the given node to this range. + * @param {ASTNode} node The node to merge. + * @returns {void} + */ + merge(node) { + this.endNode = node; + } + + /** + * Resets this range by the given node or null. + * @param {ASTNode|null} node The node to reset, or null. + * @returns {void} + */ + reset(node) { + this.startNode = this.endNode = node; + } +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow unreachable code after `return`, `throw`, `continue`, and `break` statements", + category: "Possible Errors", + recommended: true, + url: "https://eslint.org/docs/rules/no-unreachable" + }, + + schema: [], + + messages: { + unreachableCode: "Unreachable code." + } + }, + + create(context) { + let currentCodePath = null; + + const range = new ConsecutiveRange(context.getSourceCode()); + + /** + * Reports a given node if it's unreachable. + * @param {ASTNode} node A statement node to report. + * @returns {void} + */ + function reportIfUnreachable(node) { + let nextNode = null; + + if (node && currentCodePath.currentSegments.every(isUnreachable)) { + + // Store this statement to distinguish consecutive statements. + if (range.isEmpty) { + range.reset(node); + return; + } + + // Skip if this statement is inside of the current range. + if (range.contains(node)) { + return; + } + + // Merge if this statement is consecutive to the current range. + if (range.isConsecutive(node)) { + range.merge(node); + return; + } + + nextNode = node; + } + + /* + * Report the current range since this statement is reachable or is + * not consecutive to the current range. + */ + if (!range.isEmpty) { + context.report({ + messageId: "unreachableCode", + loc: range.location, + node: range.startNode + }); + } + + // Update the current range. + range.reset(nextNode); + } + + return { + + // Manages the current code path. + onCodePathStart(codePath) { + currentCodePath = codePath; + }, + + onCodePathEnd() { + currentCodePath = currentCodePath.upper; + }, + + // Registers for all statement nodes (excludes FunctionDeclaration). + BlockStatement: reportIfUnreachable, + BreakStatement: reportIfUnreachable, + ClassDeclaration: reportIfUnreachable, + ContinueStatement: reportIfUnreachable, + DebuggerStatement: reportIfUnreachable, + DoWhileStatement: reportIfUnreachable, + ExpressionStatement: reportIfUnreachable, + ForInStatement: reportIfUnreachable, + ForOfStatement: reportIfUnreachable, + ForStatement: reportIfUnreachable, + IfStatement: reportIfUnreachable, + ImportDeclaration: reportIfUnreachable, + LabeledStatement: reportIfUnreachable, + ReturnStatement: reportIfUnreachable, + SwitchStatement: reportIfUnreachable, + ThrowStatement: reportIfUnreachable, + TryStatement: reportIfUnreachable, + + VariableDeclaration(node) { + if (node.kind !== "var" || node.declarations.some(isInitialized)) { + reportIfUnreachable(node); + } + }, + + WhileStatement: reportIfUnreachable, + WithStatement: reportIfUnreachable, + ExportNamedDeclaration: reportIfUnreachable, + ExportDefaultDeclaration: reportIfUnreachable, + ExportAllDeclaration: reportIfUnreachable, + + "Program:exit"() { + reportIfUnreachable(); + } + }; + } +}; diff --git a/eslint/lib/rules/no-unsafe-finally.js b/eslint/lib/rules/no-unsafe-finally.js new file mode 100644 index 0000000..11bf06e --- /dev/null +++ b/eslint/lib/rules/no-unsafe-finally.js @@ -0,0 +1,111 @@ +/** + * @fileoverview Rule to flag unsafe statements in finally block + * @author Onur Temizkan + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const SENTINEL_NODE_TYPE_RETURN_THROW = /^(?:Program|(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression)$/u; +const SENTINEL_NODE_TYPE_BREAK = /^(?:Program|(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|DoWhileStatement|WhileStatement|ForOfStatement|ForInStatement|ForStatement|SwitchStatement)$/u; +const SENTINEL_NODE_TYPE_CONTINUE = /^(?:Program|(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|DoWhileStatement|WhileStatement|ForOfStatement|ForInStatement|ForStatement)$/u; + + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow control flow statements in `finally` blocks", + category: "Possible Errors", + recommended: true, + url: "https://eslint.org/docs/rules/no-unsafe-finally" + }, + + schema: [], + + messages: { + unsafeUsage: "Unsafe usage of {{nodeType}}." + } + }, + create(context) { + + /** + * Checks if the node is the finalizer of a TryStatement + * @param {ASTNode} node node to check. + * @returns {boolean} - true if the node is the finalizer of a TryStatement + */ + function isFinallyBlock(node) { + return node.parent.type === "TryStatement" && node.parent.finalizer === node; + } + + /** + * Climbs up the tree if the node is not a sentinel node + * @param {ASTNode} node node to check. + * @param {string} label label of the break or continue statement + * @returns {boolean} - return whether the node is a finally block or a sentinel node + */ + function isInFinallyBlock(node, label) { + let labelInside = false; + let sentinelNodeType; + + if (node.type === "BreakStatement" && !node.label) { + sentinelNodeType = SENTINEL_NODE_TYPE_BREAK; + } else if (node.type === "ContinueStatement") { + sentinelNodeType = SENTINEL_NODE_TYPE_CONTINUE; + } else { + sentinelNodeType = SENTINEL_NODE_TYPE_RETURN_THROW; + } + + for ( + let currentNode = node; + currentNode && !sentinelNodeType.test(currentNode.type); + currentNode = currentNode.parent + ) { + if (currentNode.parent.label && label && (currentNode.parent.label.name === label.name)) { + labelInside = true; + } + if (isFinallyBlock(currentNode)) { + if (label && labelInside) { + return false; + } + return true; + } + } + return false; + } + + /** + * Checks whether the possibly-unsafe statement is inside a finally block. + * @param {ASTNode} node node to check. + * @returns {void} + */ + function check(node) { + if (isInFinallyBlock(node, node.label)) { + context.report({ + messageId: "unsafeUsage", + data: { + nodeType: node.type + }, + node, + line: node.loc.line, + column: node.loc.column + }); + } + } + + return { + ReturnStatement: check, + ThrowStatement: check, + BreakStatement: check, + ContinueStatement: check + }; + } +}; diff --git a/eslint/lib/rules/no-unsafe-negation.js b/eslint/lib/rules/no-unsafe-negation.js new file mode 100644 index 0000000..a9c2ee7 --- /dev/null +++ b/eslint/lib/rules/no-unsafe-negation.js @@ -0,0 +1,127 @@ +/** + * @fileoverview Rule to disallow negating the left operand of relational operators + * @author Toru Nagashima + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Checks whether the given operator is `in` or `instanceof` + * @param {string} op The operator type to check. + * @returns {boolean} `true` if the operator is `in` or `instanceof` + */ +function isInOrInstanceOfOperator(op) { + return op === "in" || op === "instanceof"; +} + +/** + * Checks whether the given operator is an ordering relational operator or not. + * @param {string} op The operator type to check. + * @returns {boolean} `true` if the operator is an ordering relational operator. + */ +function isOrderingRelationalOperator(op) { + return op === "<" || op === ">" || op === ">=" || op === "<="; +} + +/** + * Checks whether the given node is a logical negation expression or not. + * @param {ASTNode} node The node to check. + * @returns {boolean} `true` if the node is a logical negation expression. + */ +function isNegation(node) { + return node.type === "UnaryExpression" && node.operator === "!"; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow negating the left operand of relational operators", + category: "Possible Errors", + recommended: true, + url: "https://eslint.org/docs/rules/no-unsafe-negation", + suggestion: true + }, + + schema: [ + { + type: "object", + properties: { + enforceForOrderingRelations: { + type: "boolean", + default: false + } + }, + additionalProperties: false + } + ], + + fixable: null, + + messages: { + unexpected: "Unexpected negating the left operand of '{{operator}}' operator.", + suggestNegatedExpression: "Negate '{{operator}}' expression instead of its left operand. This changes the current behavior.", + suggestParenthesisedNegation: "Wrap negation in '()' to make the intention explicit. This preserves the current behavior." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + const options = context.options[0] || {}; + const enforceForOrderingRelations = options.enforceForOrderingRelations === true; + + return { + BinaryExpression(node) { + const operator = node.operator; + const orderingRelationRuleApplies = enforceForOrderingRelations && isOrderingRelationalOperator(operator); + + if ( + (isInOrInstanceOfOperator(operator) || orderingRelationRuleApplies) && + isNegation(node.left) && + !astUtils.isParenthesised(sourceCode, node.left) + ) { + context.report({ + node, + loc: node.left.loc, + messageId: "unexpected", + data: { operator }, + suggest: [ + { + messageId: "suggestNegatedExpression", + data: { operator }, + fix(fixer) { + const negationToken = sourceCode.getFirstToken(node.left); + const fixRange = [negationToken.range[1], node.range[1]]; + const text = sourceCode.text.slice(fixRange[0], fixRange[1]); + + return fixer.replaceTextRange(fixRange, `(${text})`); + } + }, + { + messageId: "suggestParenthesisedNegation", + fix(fixer) { + return fixer.replaceText(node.left, `(${sourceCode.getText(node.left)})`); + } + } + ] + }); + } + } + }; + } +}; diff --git a/eslint/lib/rules/no-unused-expressions.js b/eslint/lib/rules/no-unused-expressions.js new file mode 100644 index 0000000..26a25b7 --- /dev/null +++ b/eslint/lib/rules/no-unused-expressions.js @@ -0,0 +1,140 @@ +/** + * @fileoverview Flag expressions in statement position that do not side effect + * @author Michael Ficarra + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow unused expressions", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-unused-expressions" + }, + + schema: [ + { + type: "object", + properties: { + allowShortCircuit: { + type: "boolean", + default: false + }, + allowTernary: { + type: "boolean", + default: false + }, + allowTaggedTemplates: { + type: "boolean", + default: false + } + }, + additionalProperties: false + } + ], + + messages: { + unusedExpression: "Expected an assignment or function call and instead saw an expression." + } + }, + + create(context) { + const config = context.options[0] || {}, + allowShortCircuit = config.allowShortCircuit || false, + allowTernary = config.allowTernary || false, + allowTaggedTemplates = config.allowTaggedTemplates || false; + + // eslint-disable-next-line jsdoc/require-description + /** + * @param {ASTNode} node any node + * @returns {boolean} whether the given node structurally represents a directive + */ + function looksLikeDirective(node) { + return node.type === "ExpressionStatement" && + node.expression.type === "Literal" && typeof node.expression.value === "string"; + } + + // eslint-disable-next-line jsdoc/require-description + /** + * @param {Function} predicate ([a] -> Boolean) the function used to make the determination + * @param {a[]} list the input list + * @returns {a[]} the leading sequence of members in the given list that pass the given predicate + */ + function takeWhile(predicate, list) { + for (let i = 0; i < list.length; ++i) { + if (!predicate(list[i])) { + return list.slice(0, i); + } + } + return list.slice(); + } + + // eslint-disable-next-line jsdoc/require-description + /** + * @param {ASTNode} node a Program or BlockStatement node + * @returns {ASTNode[]} the leading sequence of directive nodes in the given node's body + */ + function directives(node) { + return takeWhile(looksLikeDirective, node.body); + } + + // eslint-disable-next-line jsdoc/require-description + /** + * @param {ASTNode} node any node + * @param {ASTNode[]} ancestors the given node's ancestors + * @returns {boolean} whether the given node is considered a directive in its current position + */ + function isDirective(node, ancestors) { + const parent = ancestors[ancestors.length - 1], + grandparent = ancestors[ancestors.length - 2]; + + return (parent.type === "Program" || parent.type === "BlockStatement" && + (/Function/u.test(grandparent.type))) && + directives(parent).indexOf(node) >= 0; + } + + /** + * Determines whether or not a given node is a valid expression. Recurses on short circuit eval and ternary nodes if enabled by flags. + * @param {ASTNode} node any node + * @returns {boolean} whether the given node is a valid expression + */ + function isValidExpression(node) { + if (allowTernary) { + + // Recursive check for ternary and logical expressions + if (node.type === "ConditionalExpression") { + return isValidExpression(node.consequent) && isValidExpression(node.alternate); + } + } + + if (allowShortCircuit) { + if (node.type === "LogicalExpression") { + return isValidExpression(node.right); + } + } + + if (allowTaggedTemplates && node.type === "TaggedTemplateExpression") { + return true; + } + + return /^(?:Assignment|Call|New|Update|Yield|Await)Expression$/u.test(node.type) || + (node.type === "UnaryExpression" && ["delete", "void"].indexOf(node.operator) >= 0); + } + + return { + ExpressionStatement(node) { + if (!isValidExpression(node.expression) && !isDirective(node, context.getAncestors())) { + context.report({ node, messageId: "unusedExpression" }); + } + } + }; + + } +}; diff --git a/eslint/lib/rules/no-unused-labels.js b/eslint/lib/rules/no-unused-labels.js new file mode 100644 index 0000000..b33fcb7 --- /dev/null +++ b/eslint/lib/rules/no-unused-labels.js @@ -0,0 +1,110 @@ +/** + * @fileoverview Rule to disallow unused labels. + * @author Toru Nagashima + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow unused labels", + category: "Best Practices", + recommended: true, + url: "https://eslint.org/docs/rules/no-unused-labels" + }, + + schema: [], + + fixable: "code", + + messages: { + unused: "'{{name}}:' is defined but never used." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + let scopeInfo = null; + + /** + * Adds a scope info to the stack. + * @param {ASTNode} node A node to add. This is a LabeledStatement. + * @returns {void} + */ + function enterLabeledScope(node) { + scopeInfo = { + label: node.label.name, + used: false, + upper: scopeInfo + }; + } + + /** + * Removes the top of the stack. + * At the same time, this reports the label if it's never used. + * @param {ASTNode} node A node to report. This is a LabeledStatement. + * @returns {void} + */ + function exitLabeledScope(node) { + if (!scopeInfo.used) { + context.report({ + node: node.label, + messageId: "unused", + data: node.label, + fix(fixer) { + + /* + * Only perform a fix if there are no comments between the label and the body. This will be the case + * when there is exactly one token/comment (the ":") between the label and the body. + */ + if (sourceCode.getTokenAfter(node.label, { includeComments: true }) === + sourceCode.getTokenBefore(node.body, { includeComments: true })) { + return fixer.removeRange([node.range[0], node.body.range[0]]); + } + + return null; + } + }); + } + + scopeInfo = scopeInfo.upper; + } + + /** + * Marks the label of a given node as used. + * @param {ASTNode} node A node to mark. This is a BreakStatement or + * ContinueStatement. + * @returns {void} + */ + function markAsUsed(node) { + if (!node.label) { + return; + } + + const label = node.label.name; + let info = scopeInfo; + + while (info) { + if (info.label === label) { + info.used = true; + break; + } + info = info.upper; + } + } + + return { + LabeledStatement: enterLabeledScope, + "LabeledStatement:exit": exitLabeledScope, + BreakStatement: markAsUsed, + ContinueStatement: markAsUsed + }; + } +}; diff --git a/eslint/lib/rules/no-unused-vars.js b/eslint/lib/rules/no-unused-vars.js new file mode 100644 index 0000000..18c48bf --- /dev/null +++ b/eslint/lib/rules/no-unused-vars.js @@ -0,0 +1,645 @@ +/** + * @fileoverview Rule to flag declared but unused variables + * @author Ilya Volodin + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Typedefs +//------------------------------------------------------------------------------ + +/** + * Bag of data used for formatting the `unusedVar` lint message. + * @typedef {Object} UnusedVarMessageData + * @property {string} varName The name of the unused var. + * @property {'defined'|'assigned a value'} action Description of the vars state. + * @property {string} additional Any additional info to be appended at the end. + */ + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow unused variables", + category: "Variables", + recommended: true, + url: "https://eslint.org/docs/rules/no-unused-vars" + }, + + schema: [ + { + oneOf: [ + { + enum: ["all", "local"] + }, + { + type: "object", + properties: { + vars: { + enum: ["all", "local"] + }, + varsIgnorePattern: { + type: "string" + }, + args: { + enum: ["all", "after-used", "none"] + }, + ignoreRestSiblings: { + type: "boolean" + }, + argsIgnorePattern: { + type: "string" + }, + caughtErrors: { + enum: ["all", "none"] + }, + caughtErrorsIgnorePattern: { + type: "string" + } + } + } + ] + } + ], + + messages: { + unusedVar: "'{{varName}}' is {{action}} but never used{{additional}}." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + const REST_PROPERTY_TYPE = /^(?:RestElement|(?:Experimental)?RestProperty)$/u; + + const config = { + vars: "all", + args: "after-used", + ignoreRestSiblings: false, + caughtErrors: "none" + }; + + const firstOption = context.options[0]; + + if (firstOption) { + if (typeof firstOption === "string") { + config.vars = firstOption; + } else { + config.vars = firstOption.vars || config.vars; + config.args = firstOption.args || config.args; + config.ignoreRestSiblings = firstOption.ignoreRestSiblings || config.ignoreRestSiblings; + config.caughtErrors = firstOption.caughtErrors || config.caughtErrors; + + if (firstOption.varsIgnorePattern) { + config.varsIgnorePattern = new RegExp(firstOption.varsIgnorePattern, "u"); + } + + if (firstOption.argsIgnorePattern) { + config.argsIgnorePattern = new RegExp(firstOption.argsIgnorePattern, "u"); + } + + if (firstOption.caughtErrorsIgnorePattern) { + config.caughtErrorsIgnorePattern = new RegExp(firstOption.caughtErrorsIgnorePattern, "u"); + } + } + } + + /** + * Generates the message data about the variable being defined and unused, + * including the ignore pattern if configured. + * @param {Variable} unusedVar eslint-scope variable object. + * @returns {UnusedVarMessageData} The message data to be used with this unused variable. + */ + function getDefinedMessageData(unusedVar) { + const defType = unusedVar.defs && unusedVar.defs[0] && unusedVar.defs[0].type; + let type; + let pattern; + + if (defType === "CatchClause" && config.caughtErrorsIgnorePattern) { + type = "args"; + pattern = config.caughtErrorsIgnorePattern.toString(); + } else if (defType === "Parameter" && config.argsIgnorePattern) { + type = "args"; + pattern = config.argsIgnorePattern.toString(); + } else if (defType !== "Parameter" && config.varsIgnorePattern) { + type = "vars"; + pattern = config.varsIgnorePattern.toString(); + } + + const additional = type ? `. Allowed unused ${type} must match ${pattern}` : ""; + + return { + varName: unusedVar.name, + action: "defined", + additional + }; + } + + /** + * Generate the warning message about the variable being + * assigned and unused, including the ignore pattern if configured. + * @param {Variable} unusedVar eslint-scope variable object. + * @returns {UnusedVarMessageData} The message data to be used with this unused variable. + */ + function getAssignedMessageData(unusedVar) { + const additional = config.varsIgnorePattern ? `. Allowed unused vars must match ${config.varsIgnorePattern.toString()}` : ""; + + return { + varName: unusedVar.name, + action: "assigned a value", + additional + }; + } + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + const STATEMENT_TYPE = /(?:Statement|Declaration)$/u; + + /** + * Determines if a given variable is being exported from a module. + * @param {Variable} variable eslint-scope variable object. + * @returns {boolean} True if the variable is exported, false if not. + * @private + */ + function isExported(variable) { + + const definition = variable.defs[0]; + + if (definition) { + + let node = definition.node; + + if (node.type === "VariableDeclarator") { + node = node.parent; + } else if (definition.type === "Parameter") { + return false; + } + + return node.parent.type.indexOf("Export") === 0; + } + return false; + + } + + /** + * Determines if a variable has a sibling rest property + * @param {Variable} variable eslint-scope variable object. + * @returns {boolean} True if the variable is exported, false if not. + * @private + */ + function hasRestSpreadSibling(variable) { + if (config.ignoreRestSiblings) { + return variable.defs.some(def => { + const propertyNode = def.name.parent; + const patternNode = propertyNode.parent; + + return ( + propertyNode.type === "Property" && + patternNode.type === "ObjectPattern" && + REST_PROPERTY_TYPE.test(patternNode.properties[patternNode.properties.length - 1].type) + ); + }); + } + + return false; + } + + /** + * Determines if a reference is a read operation. + * @param {Reference} ref An eslint-scope Reference + * @returns {boolean} whether the given reference represents a read operation + * @private + */ + function isReadRef(ref) { + return ref.isRead(); + } + + /** + * Determine if an identifier is referencing an enclosing function name. + * @param {Reference} ref The reference to check. + * @param {ASTNode[]} nodes The candidate function nodes. + * @returns {boolean} True if it's a self-reference, false if not. + * @private + */ + function isSelfReference(ref, nodes) { + let scope = ref.from; + + while (scope) { + if (nodes.indexOf(scope.block) >= 0) { + return true; + } + + scope = scope.upper; + } + + return false; + } + + /** + * Gets a list of function definitions for a specified variable. + * @param {Variable} variable eslint-scope variable object. + * @returns {ASTNode[]} Function nodes. + * @private + */ + function getFunctionDefinitions(variable) { + const functionDefinitions = []; + + variable.defs.forEach(def => { + const { type, node } = def; + + // FunctionDeclarations + if (type === "FunctionName") { + functionDefinitions.push(node); + } + + // FunctionExpressions + if (type === "Variable" && node.init && + (node.init.type === "FunctionExpression" || node.init.type === "ArrowFunctionExpression")) { + functionDefinitions.push(node.init); + } + }); + return functionDefinitions; + } + + /** + * Checks the position of given nodes. + * @param {ASTNode} inner A node which is expected as inside. + * @param {ASTNode} outer A node which is expected as outside. + * @returns {boolean} `true` if the `inner` node exists in the `outer` node. + * @private + */ + function isInside(inner, outer) { + return ( + inner.range[0] >= outer.range[0] && + inner.range[1] <= outer.range[1] + ); + } + + /** + * If a given reference is left-hand side of an assignment, this gets + * the right-hand side node of the assignment. + * + * In the following cases, this returns null. + * + * - The reference is not the LHS of an assignment expression. + * - The reference is inside of a loop. + * - The reference is inside of a function scope which is different from + * the declaration. + * @param {eslint-scope.Reference} ref A reference to check. + * @param {ASTNode} prevRhsNode The previous RHS node. + * @returns {ASTNode|null} The RHS node or null. + * @private + */ + function getRhsNode(ref, prevRhsNode) { + const id = ref.identifier; + const parent = id.parent; + const grandparent = parent.parent; + const refScope = ref.from.variableScope; + const varScope = ref.resolved.scope.variableScope; + const canBeUsedLater = refScope !== varScope || astUtils.isInLoop(id); + + /* + * Inherits the previous node if this reference is in the node. + * This is for `a = a + a`-like code. + */ + if (prevRhsNode && isInside(id, prevRhsNode)) { + return prevRhsNode; + } + + if (parent.type === "AssignmentExpression" && + grandparent.type === "ExpressionStatement" && + id === parent.left && + !canBeUsedLater + ) { + return parent.right; + } + return null; + } + + /** + * Checks whether a given function node is stored to somewhere or not. + * If the function node is stored, the function can be used later. + * @param {ASTNode} funcNode A function node to check. + * @param {ASTNode} rhsNode The RHS node of the previous assignment. + * @returns {boolean} `true` if under the following conditions: + * - the funcNode is assigned to a variable. + * - the funcNode is bound as an argument of a function call. + * - the function is bound to a property and the object satisfies above conditions. + * @private + */ + function isStorableFunction(funcNode, rhsNode) { + let node = funcNode; + let parent = funcNode.parent; + + while (parent && isInside(parent, rhsNode)) { + switch (parent.type) { + case "SequenceExpression": + if (parent.expressions[parent.expressions.length - 1] !== node) { + return false; + } + break; + + case "CallExpression": + case "NewExpression": + return parent.callee !== node; + + case "AssignmentExpression": + case "TaggedTemplateExpression": + case "YieldExpression": + return true; + + default: + if (STATEMENT_TYPE.test(parent.type)) { + + /* + * If it encountered statements, this is a complex pattern. + * Since analyzing complex patterns is hard, this returns `true` to avoid false positive. + */ + return true; + } + } + + node = parent; + parent = parent.parent; + } + + return false; + } + + /** + * Checks whether a given Identifier node exists inside of a function node which can be used later. + * + * "can be used later" means: + * - the function is assigned to a variable. + * - the function is bound to a property and the object can be used later. + * - the function is bound as an argument of a function call. + * + * If a reference exists in a function which can be used later, the reference is read when the function is called. + * @param {ASTNode} id An Identifier node to check. + * @param {ASTNode} rhsNode The RHS node of the previous assignment. + * @returns {boolean} `true` if the `id` node exists inside of a function node which can be used later. + * @private + */ + function isInsideOfStorableFunction(id, rhsNode) { + const funcNode = astUtils.getUpperFunction(id); + + return ( + funcNode && + isInside(funcNode, rhsNode) && + isStorableFunction(funcNode, rhsNode) + ); + } + + /** + * Checks whether a given reference is a read to update itself or not. + * @param {eslint-scope.Reference} ref A reference to check. + * @param {ASTNode} rhsNode The RHS node of the previous assignment. + * @returns {boolean} The reference is a read to update itself. + * @private + */ + function isReadForItself(ref, rhsNode) { + const id = ref.identifier; + const parent = id.parent; + const grandparent = parent.parent; + + return ref.isRead() && ( + + // self update. e.g. `a += 1`, `a++` + (// in RHS of an assignment for itself. e.g. `a = a + 1` + (( + parent.type === "AssignmentExpression" && + grandparent.type === "ExpressionStatement" && + parent.left === id + ) || + ( + parent.type === "UpdateExpression" && + grandparent.type === "ExpressionStatement" + ) || rhsNode && + isInside(id, rhsNode) && + !isInsideOfStorableFunction(id, rhsNode))) + ); + } + + /** + * Determine if an identifier is used either in for-in loops. + * @param {Reference} ref The reference to check. + * @returns {boolean} whether reference is used in the for-in loops + * @private + */ + function isForInRef(ref) { + let target = ref.identifier.parent; + + + // "for (var ...) { return; }" + if (target.type === "VariableDeclarator") { + target = target.parent.parent; + } + + if (target.type !== "ForInStatement") { + return false; + } + + // "for (...) { return; }" + if (target.body.type === "BlockStatement") { + target = target.body.body[0]; + + // "for (...) return;" + } else { + target = target.body; + } + + // For empty loop body + if (!target) { + return false; + } + + return target.type === "ReturnStatement"; + } + + /** + * Determines if the variable is used. + * @param {Variable} variable The variable to check. + * @returns {boolean} True if the variable is used + * @private + */ + function isUsedVariable(variable) { + const functionNodes = getFunctionDefinitions(variable), + isFunctionDefinition = functionNodes.length > 0; + let rhsNode = null; + + return variable.references.some(ref => { + if (isForInRef(ref)) { + return true; + } + + const forItself = isReadForItself(ref, rhsNode); + + rhsNode = getRhsNode(ref, rhsNode); + + return ( + isReadRef(ref) && + !forItself && + !(isFunctionDefinition && isSelfReference(ref, functionNodes)) + ); + }); + } + + /** + * Checks whether the given variable is after the last used parameter. + * @param {eslint-scope.Variable} variable The variable to check. + * @returns {boolean} `true` if the variable is defined after the last + * used parameter. + */ + function isAfterLastUsedArg(variable) { + const def = variable.defs[0]; + const params = context.getDeclaredVariables(def.node); + const posteriorParams = params.slice(params.indexOf(variable) + 1); + + // If any used parameters occur after this parameter, do not report. + return !posteriorParams.some(v => v.references.length > 0 || v.eslintUsed); + } + + /** + * Gets an array of variables without read references. + * @param {Scope} scope an eslint-scope Scope object. + * @param {Variable[]} unusedVars an array that saving result. + * @returns {Variable[]} unused variables of the scope and descendant scopes. + * @private + */ + function collectUnusedVariables(scope, unusedVars) { + const variables = scope.variables; + const childScopes = scope.childScopes; + let i, l; + + if (scope.type !== "global" || config.vars === "all") { + for (i = 0, l = variables.length; i < l; ++i) { + const variable = variables[i]; + + // skip a variable of class itself name in the class scope + if (scope.type === "class" && scope.block.id === variable.identifiers[0]) { + continue; + } + + // skip function expression names and variables marked with markVariableAsUsed() + if (scope.functionExpressionScope || variable.eslintUsed) { + continue; + } + + // skip implicit "arguments" variable + if (scope.type === "function" && variable.name === "arguments" && variable.identifiers.length === 0) { + continue; + } + + // explicit global variables don't have definitions. + const def = variable.defs[0]; + + if (def) { + const type = def.type; + + // skip catch variables + if (type === "CatchClause") { + if (config.caughtErrors === "none") { + continue; + } + + // skip ignored parameters + if (config.caughtErrorsIgnorePattern && config.caughtErrorsIgnorePattern.test(def.name.name)) { + continue; + } + } + + if (type === "Parameter") { + + // skip any setter argument + if ((def.node.parent.type === "Property" || def.node.parent.type === "MethodDefinition") && def.node.parent.kind === "set") { + continue; + } + + // if "args" option is "none", skip any parameter + if (config.args === "none") { + continue; + } + + // skip ignored parameters + if (config.argsIgnorePattern && config.argsIgnorePattern.test(def.name.name)) { + continue; + } + + // if "args" option is "after-used", skip used variables + if (config.args === "after-used" && astUtils.isFunction(def.name.parent) && !isAfterLastUsedArg(variable)) { + continue; + } + } else { + + // skip ignored variables + if (config.varsIgnorePattern && config.varsIgnorePattern.test(def.name.name)) { + continue; + } + } + } + + if (!isUsedVariable(variable) && !isExported(variable) && !hasRestSpreadSibling(variable)) { + unusedVars.push(variable); + } + } + } + + for (i = 0, l = childScopes.length; i < l; ++i) { + collectUnusedVariables(childScopes[i], unusedVars); + } + + return unusedVars; + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + "Program:exit"(programNode) { + const unusedVars = collectUnusedVariables(context.getScope(), []); + + for (let i = 0, l = unusedVars.length; i < l; ++i) { + const unusedVar = unusedVars[i]; + + // Report the first declaration. + if (unusedVar.defs.length > 0) { + context.report({ + node: unusedVar.identifiers[0], + messageId: "unusedVar", + data: unusedVar.references.some(ref => ref.isWrite()) + ? getAssignedMessageData(unusedVar) + : getDefinedMessageData(unusedVar) + }); + + // If there are no regular declaration, report the first `/*globals*/` comment directive. + } else if (unusedVar.eslintExplicitGlobalComments) { + const directiveComment = unusedVar.eslintExplicitGlobalComments[0]; + + context.report({ + node: programNode, + loc: astUtils.getNameLocationInGlobalDirectiveComment(sourceCode, directiveComment, unusedVar.name), + messageId: "unusedVar", + data: getDefinedMessageData(unusedVar) + }); + } + } + } + }; + + } +}; diff --git a/eslint/lib/rules/no-use-before-define.js b/eslint/lib/rules/no-use-before-define.js new file mode 100644 index 0000000..c730056 --- /dev/null +++ b/eslint/lib/rules/no-use-before-define.js @@ -0,0 +1,233 @@ +/** + * @fileoverview Rule to flag use of variables before they are defined + * @author Ilya Volodin + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const SENTINEL_TYPE = /^(?:(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|CatchClause|ImportDeclaration|ExportNamedDeclaration)$/u; +const FOR_IN_OF_TYPE = /^For(?:In|Of)Statement$/u; + +/** + * Parses a given value as options. + * @param {any} options A value to parse. + * @returns {Object} The parsed options. + */ +function parseOptions(options) { + let functions = true; + let classes = true; + let variables = true; + + if (typeof options === "string") { + functions = (options !== "nofunc"); + } else if (typeof options === "object" && options !== null) { + functions = options.functions !== false; + classes = options.classes !== false; + variables = options.variables !== false; + } + + return { functions, classes, variables }; +} + +/** + * Checks whether or not a given variable is a function declaration. + * @param {eslint-scope.Variable} variable A variable to check. + * @returns {boolean} `true` if the variable is a function declaration. + */ +function isFunction(variable) { + return variable.defs[0].type === "FunctionName"; +} + +/** + * Checks whether or not a given variable is a class declaration in an upper function scope. + * @param {eslint-scope.Variable} variable A variable to check. + * @param {eslint-scope.Reference} reference A reference to check. + * @returns {boolean} `true` if the variable is a class declaration. + */ +function isOuterClass(variable, reference) { + return ( + variable.defs[0].type === "ClassName" && + variable.scope.variableScope !== reference.from.variableScope + ); +} + +/** + * Checks whether or not a given variable is a variable declaration in an upper function scope. + * @param {eslint-scope.Variable} variable A variable to check. + * @param {eslint-scope.Reference} reference A reference to check. + * @returns {boolean} `true` if the variable is a variable declaration. + */ +function isOuterVariable(variable, reference) { + return ( + variable.defs[0].type === "Variable" && + variable.scope.variableScope !== reference.from.variableScope + ); +} + +/** + * Checks whether or not a given location is inside of the range of a given node. + * @param {ASTNode} node An node to check. + * @param {number} location A location to check. + * @returns {boolean} `true` if the location is inside of the range of the node. + */ +function isInRange(node, location) { + return node && node.range[0] <= location && location <= node.range[1]; +} + +/** + * Checks whether or not a given reference is inside of the initializers of a given variable. + * + * This returns `true` in the following cases: + * + * var a = a + * var [a = a] = list + * var {a = a} = obj + * for (var a in a) {} + * for (var a of a) {} + * @param {Variable} variable A variable to check. + * @param {Reference} reference A reference to check. + * @returns {boolean} `true` if the reference is inside of the initializers. + */ +function isInInitializer(variable, reference) { + if (variable.scope !== reference.from) { + return false; + } + + let node = variable.identifiers[0].parent; + const location = reference.identifier.range[1]; + + while (node) { + if (node.type === "VariableDeclarator") { + if (isInRange(node.init, location)) { + return true; + } + if (FOR_IN_OF_TYPE.test(node.parent.parent.type) && + isInRange(node.parent.parent.right, location) + ) { + return true; + } + break; + } else if (node.type === "AssignmentPattern") { + if (isInRange(node.right, location)) { + return true; + } + } else if (SENTINEL_TYPE.test(node.type)) { + break; + } + + node = node.parent; + } + + return false; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow the use of variables before they are defined", + category: "Variables", + recommended: false, + url: "https://eslint.org/docs/rules/no-use-before-define" + }, + + schema: [ + { + oneOf: [ + { + enum: ["nofunc"] + }, + { + type: "object", + properties: { + functions: { type: "boolean" }, + classes: { type: "boolean" }, + variables: { type: "boolean" } + }, + additionalProperties: false + } + ] + } + ], + + messages: { + usedBeforeDefined: "'{{name}}' was used before it was defined." + } + }, + + create(context) { + const options = parseOptions(context.options[0]); + + /** + * Determines whether a given use-before-define case should be reported according to the options. + * @param {eslint-scope.Variable} variable The variable that gets used before being defined + * @param {eslint-scope.Reference} reference The reference to the variable + * @returns {boolean} `true` if the usage should be reported + */ + function isForbidden(variable, reference) { + if (isFunction(variable)) { + return options.functions; + } + if (isOuterClass(variable, reference)) { + return options.classes; + } + if (isOuterVariable(variable, reference)) { + return options.variables; + } + return true; + } + + /** + * Finds and validates all variables in a given scope. + * @param {Scope} scope The scope object. + * @returns {void} + * @private + */ + function findVariablesInScope(scope) { + scope.references.forEach(reference => { + const variable = reference.resolved; + + /* + * Skips when the reference is: + * - initialization's. + * - referring to an undefined variable. + * - referring to a global environment variable (there're no identifiers). + * - located preceded by the variable (except in initializers). + * - allowed by options. + */ + if (reference.init || + !variable || + variable.identifiers.length === 0 || + (variable.identifiers[0].range[1] < reference.identifier.range[1] && !isInInitializer(variable, reference)) || + !isForbidden(variable, reference) + ) { + return; + } + + // Reports. + context.report({ + node: reference.identifier, + messageId: "usedBeforeDefined", + data: reference.identifier + }); + }); + + scope.childScopes.forEach(findVariablesInScope); + } + + return { + Program() { + findVariablesInScope(context.getScope()); + } + }; + } +}; diff --git a/eslint/lib/rules/no-useless-backreference.js b/eslint/lib/rules/no-useless-backreference.js new file mode 100644 index 0000000..8a6fbe1 --- /dev/null +++ b/eslint/lib/rules/no-useless-backreference.js @@ -0,0 +1,193 @@ +/** + * @fileoverview Rule to disallow useless backreferences in regular expressions + * @author Milos Djermanovic + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const { CALL, CONSTRUCT, ReferenceTracker, getStringIfConstant } = require("eslint-utils"); +const { RegExpParser, visitRegExpAST } = require("regexpp"); +const lodash = require("lodash"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const parser = new RegExpParser(); + +/** + * Finds the path from the given `regexpp` AST node to the root node. + * @param {regexpp.Node} node Node. + * @returns {regexpp.Node[]} Array that starts with the given node and ends with the root node. + */ +function getPathToRoot(node) { + const path = []; + let current = node; + + do { + path.push(current); + current = current.parent; + } while (current); + + return path; +} + +/** + * Determines whether the given `regexpp` AST node is a lookaround node. + * @param {regexpp.Node} node Node. + * @returns {boolean} `true` if it is a lookaround node. + */ +function isLookaround(node) { + return node.type === "Assertion" && + (node.kind === "lookahead" || node.kind === "lookbehind"); +} + +/** + * Determines whether the given `regexpp` AST node is a negative lookaround node. + * @param {regexpp.Node} node Node. + * @returns {boolean} `true` if it is a negative lookaround node. + */ +function isNegativeLookaround(node) { + return isLookaround(node) && node.negate; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow useless backreferences in regular expressions", + category: "Possible Errors", + recommended: false, + url: "https://eslint.org/docs/rules/no-useless-backreference" + }, + + schema: [], + + messages: { + nested: "Backreference '{{ bref }}' will be ignored. It references group '{{ group }}' from within that group.", + forward: "Backreference '{{ bref }}' will be ignored. It references group '{{ group }}' which appears later in the pattern.", + backward: "Backreference '{{ bref }}' will be ignored. It references group '{{ group }}' which appears before in the same lookbehind.", + disjunctive: "Backreference '{{ bref }}' will be ignored. It references group '{{ group }}' which is in another alternative.", + intoNegativeLookaround: "Backreference '{{ bref }}' will be ignored. It references group '{{ group }}' which is in a negative lookaround." + } + }, + + create(context) { + + /** + * Checks and reports useless backreferences in the given regular expression. + * @param {ASTNode} node Node that represents regular expression. A regex literal or RegExp constructor call. + * @param {string} pattern Regular expression pattern. + * @param {string} flags Regular expression flags. + * @returns {void} + */ + function checkRegex(node, pattern, flags) { + let regExpAST; + + try { + regExpAST = parser.parsePattern(pattern, 0, pattern.length, flags.includes("u")); + } catch (e) { + + // Ignore regular expressions with syntax errors + return; + } + + visitRegExpAST(regExpAST, { + onBackreferenceEnter(bref) { + const group = bref.resolved, + brefPath = getPathToRoot(bref), + groupPath = getPathToRoot(group); + let messageId = null; + + if (brefPath.includes(group)) { + + // group is bref's ancestor => bref is nested ('nested reference') => group hasn't matched yet when bref starts to match. + messageId = "nested"; + } else { + + // Start from the root to find the lowest common ancestor. + let i = brefPath.length - 1, + j = groupPath.length - 1; + + do { + i--; + j--; + } while (brefPath[i] === groupPath[j]); + + const indexOfLowestCommonAncestor = j + 1, + groupCut = groupPath.slice(0, indexOfLowestCommonAncestor), + commonPath = groupPath.slice(indexOfLowestCommonAncestor), + lowestCommonLookaround = commonPath.find(isLookaround), + isMatchingBackward = lowestCommonLookaround && lowestCommonLookaround.kind === "lookbehind"; + + if (!isMatchingBackward && bref.end <= group.start) { + + // bref is left, group is right ('forward reference') => group hasn't matched yet when bref starts to match. + messageId = "forward"; + } else if (isMatchingBackward && group.end <= bref.start) { + + // the opposite of the previous when the regex is matching backward in a lookbehind context. + messageId = "backward"; + } else if (lodash.last(groupCut).type === "Alternative") { + + // group's and bref's ancestor nodes below the lowest common ancestor are sibling alternatives => they're disjunctive. + messageId = "disjunctive"; + } else if (groupCut.some(isNegativeLookaround)) { + + // group is in a negative lookaround which isn't bref's ancestor => group has already failed when bref starts to match. + messageId = "intoNegativeLookaround"; + } + } + + if (messageId) { + context.report({ + node, + messageId, + data: { + bref: bref.raw, + group: group.raw + } + }); + } + } + }); + } + + return { + "Literal[regex]"(node) { + const { pattern, flags } = node.regex; + + checkRegex(node, pattern, flags); + }, + Program() { + const scope = context.getScope(), + tracker = new ReferenceTracker(scope), + traceMap = { + RegExp: { + [CALL]: true, + [CONSTRUCT]: true + } + }; + + for (const { node } of tracker.iterateGlobalReferences(traceMap)) { + const [patternNode, flagsNode] = node.arguments, + pattern = getStringIfConstant(patternNode, scope), + flags = getStringIfConstant(flagsNode, scope); + + if (typeof pattern === "string") { + checkRegex(node, pattern, flags || ""); + } + } + } + }; + } +}; diff --git a/eslint/lib/rules/no-useless-call.js b/eslint/lib/rules/no-useless-call.js new file mode 100644 index 0000000..afc729d --- /dev/null +++ b/eslint/lib/rules/no-useless-call.js @@ -0,0 +1,87 @@ +/** + * @fileoverview A rule to disallow unnecessary `.call()` and `.apply()`. + * @author Toru Nagashima + */ + +"use strict"; + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Checks whether or not a node is a `.call()`/`.apply()`. + * @param {ASTNode} node A CallExpression node to check. + * @returns {boolean} Whether or not the node is a `.call()`/`.apply()`. + */ +function isCallOrNonVariadicApply(node) { + return ( + node.callee.type === "MemberExpression" && + node.callee.property.type === "Identifier" && + node.callee.computed === false && + ( + (node.callee.property.name === "call" && node.arguments.length >= 1) || + (node.callee.property.name === "apply" && node.arguments.length === 2 && node.arguments[1].type === "ArrayExpression") + ) + ); +} + + +/** + * Checks whether or not `thisArg` is not changed by `.call()`/`.apply()`. + * @param {ASTNode|null} expectedThis The node that is the owner of the applied function. + * @param {ASTNode} thisArg The node that is given to the first argument of the `.call()`/`.apply()`. + * @param {SourceCode} sourceCode The ESLint source code object. + * @returns {boolean} Whether or not `thisArg` is not changed by `.call()`/`.apply()`. + */ +function isValidThisArg(expectedThis, thisArg, sourceCode) { + if (!expectedThis) { + return astUtils.isNullOrUndefined(thisArg); + } + return astUtils.equalTokens(expectedThis, thisArg, sourceCode); +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow unnecessary calls to `.call()` and `.apply()`", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-useless-call" + }, + + schema: [], + + messages: { + unnecessaryCall: "Unnecessary '.{{name}}()'." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + return { + CallExpression(node) { + if (!isCallOrNonVariadicApply(node)) { + return; + } + + const applied = node.callee.object; + const expectedThis = (applied.type === "MemberExpression") ? applied.object : null; + const thisArg = node.arguments[0]; + + if (isValidThisArg(expectedThis, thisArg, sourceCode)) { + context.report({ node, messageId: "unnecessaryCall", data: { name: node.callee.property.name } }); + } + } + }; + } +}; diff --git a/eslint/lib/rules/no-useless-catch.js b/eslint/lib/rules/no-useless-catch.js new file mode 100644 index 0000000..f303c27 --- /dev/null +++ b/eslint/lib/rules/no-useless-catch.js @@ -0,0 +1,57 @@ +/** + * @fileoverview Reports useless `catch` clauses that just rethrow their error. + * @author Teddy Katz + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow unnecessary `catch` clauses", + category: "Best Practices", + recommended: true, + url: "https://eslint.org/docs/rules/no-useless-catch" + }, + + schema: [], + + messages: { + unnecessaryCatchClause: "Unnecessary catch clause.", + unnecessaryCatch: "Unnecessary try/catch wrapper." + } + }, + + create(context) { + return { + CatchClause(node) { + if ( + node.param && + node.param.type === "Identifier" && + node.body.body.length && + node.body.body[0].type === "ThrowStatement" && + node.body.body[0].argument.type === "Identifier" && + node.body.body[0].argument.name === node.param.name + ) { + if (node.parent.finalizer) { + context.report({ + node, + messageId: "unnecessaryCatchClause" + }); + } else { + context.report({ + node: node.parent, + messageId: "unnecessaryCatch" + }); + } + } + } + }; + } +}; diff --git a/eslint/lib/rules/no-useless-computed-key.js b/eslint/lib/rules/no-useless-computed-key.js new file mode 100644 index 0000000..e0505a3 --- /dev/null +++ b/eslint/lib/rules/no-useless-computed-key.js @@ -0,0 +1,103 @@ +/** + * @fileoverview Rule to disallow unnecessary computed property keys in object literals + * @author Burak Yigit Kaya + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const lodash = require("lodash"); +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow unnecessary computed property keys in objects and classes", + category: "ECMAScript 6", + recommended: false, + url: "https://eslint.org/docs/rules/no-useless-computed-key" + }, + + schema: [{ + type: "object", + properties: { + enforceForClassMembers: { + type: "boolean", + default: false + } + }, + additionalProperties: false + }], + fixable: "code", + + messages: { + unnecessarilyComputedProperty: "Unnecessarily computed property [{{property}}] found." + } + }, + create(context) { + const sourceCode = context.getSourceCode(); + const enforceForClassMembers = context.options[0] && context.options[0].enforceForClassMembers; + + /** + * Reports a given node if it violated this rule. + * @param {ASTNode} node The node to check. + * @returns {void} + */ + function check(node) { + if (!node.computed) { + return; + } + + const key = node.key, + nodeType = typeof key.value; + + let allowedKey; + + if (node.type === "MethodDefinition") { + allowedKey = node.static ? "prototype" : "constructor"; + } else { + allowedKey = "__proto__"; + } + + if (key.type === "Literal" && (nodeType === "string" || nodeType === "number") && key.value !== allowedKey) { + context.report({ + node, + messageId: "unnecessarilyComputedProperty", + data: { property: sourceCode.getText(key) }, + fix(fixer) { + const leftSquareBracket = sourceCode.getTokenBefore(key, astUtils.isOpeningBracketToken); + const rightSquareBracket = sourceCode.getTokenAfter(key, astUtils.isClosingBracketToken); + + // If there are comments between the brackets and the property name, don't do a fix. + if (sourceCode.commentsExistBetween(leftSquareBracket, rightSquareBracket)) { + return null; + } + + const tokenBeforeLeftBracket = sourceCode.getTokenBefore(leftSquareBracket); + + // Insert a space before the key to avoid changing identifiers, e.g. ({ get[2]() {} }) to ({ get2() {} }) + const needsSpaceBeforeKey = tokenBeforeLeftBracket.range[1] === leftSquareBracket.range[0] && + !astUtils.canTokensBeAdjacent(tokenBeforeLeftBracket, sourceCode.getFirstToken(key)); + + const replacementKey = (needsSpaceBeforeKey ? " " : "") + key.raw; + + return fixer.replaceTextRange([leftSquareBracket.range[0], rightSquareBracket.range[1]], replacementKey); + } + }); + } + } + + return { + Property: check, + MethodDefinition: enforceForClassMembers ? check : lodash.noop + }; + } +}; diff --git a/eslint/lib/rules/no-useless-concat.js b/eslint/lib/rules/no-useless-concat.js new file mode 100644 index 0000000..aa46742 --- /dev/null +++ b/eslint/lib/rules/no-useless-concat.js @@ -0,0 +1,115 @@ +/** + * @fileoverview disallow unnecessary concatenation of template strings + * @author Henry Zhu + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Checks whether or not a given node is a concatenation. + * @param {ASTNode} node A node to check. + * @returns {boolean} `true` if the node is a concatenation. + */ +function isConcatenation(node) { + return node.type === "BinaryExpression" && node.operator === "+"; +} + +/** + * Checks if the given token is a `+` token or not. + * @param {Token} token The token to check. + * @returns {boolean} `true` if the token is a `+` token. + */ +function isConcatOperatorToken(token) { + return token.value === "+" && token.type === "Punctuator"; +} + +/** + * Get's the right most node on the left side of a BinaryExpression with + operator. + * @param {ASTNode} node A BinaryExpression node to check. + * @returns {ASTNode} node + */ +function getLeft(node) { + let left = node.left; + + while (isConcatenation(left)) { + left = left.right; + } + return left; +} + +/** + * Get's the left most node on the right side of a BinaryExpression with + operator. + * @param {ASTNode} node A BinaryExpression node to check. + * @returns {ASTNode} node + */ +function getRight(node) { + let right = node.right; + + while (isConcatenation(right)) { + right = right.left; + } + return right; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow unnecessary concatenation of literals or template literals", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-useless-concat" + }, + + schema: [], + + messages: { + unexpectedConcat: "Unexpected string concatenation of literals." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + return { + BinaryExpression(node) { + + // check if not concatenation + if (node.operator !== "+") { + return; + } + + // account for the `foo + "a" + "b"` case + const left = getLeft(node); + const right = getRight(node); + + if (astUtils.isStringLiteral(left) && + astUtils.isStringLiteral(right) && + astUtils.isTokenOnSameLine(left, right) + ) { + const operatorToken = sourceCode.getFirstTokenBetween(left, right, isConcatOperatorToken); + + context.report({ + node, + loc: operatorToken.loc.start, + messageId: "unexpectedConcat" + }); + } + } + }; + } +}; diff --git a/eslint/lib/rules/no-useless-constructor.js b/eslint/lib/rules/no-useless-constructor.js new file mode 100644 index 0000000..4c34aed --- /dev/null +++ b/eslint/lib/rules/no-useless-constructor.js @@ -0,0 +1,181 @@ +/** + * @fileoverview Rule to flag the use of redundant constructors in classes. + * @author Alberto Rodríguez + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Checks whether a given array of statements is a single call of `super`. + * @param {ASTNode[]} body An array of statements to check. + * @returns {boolean} `true` if the body is a single call of `super`. + */ +function isSingleSuperCall(body) { + return ( + body.length === 1 && + body[0].type === "ExpressionStatement" && + body[0].expression.type === "CallExpression" && + body[0].expression.callee.type === "Super" + ); +} + +/** + * Checks whether a given node is a pattern which doesn't have any side effects. + * Default parameters and Destructuring parameters can have side effects. + * @param {ASTNode} node A pattern node. + * @returns {boolean} `true` if the node doesn't have any side effects. + */ +function isSimple(node) { + return node.type === "Identifier" || node.type === "RestElement"; +} + +/** + * Checks whether a given array of expressions is `...arguments` or not. + * `super(...arguments)` passes all arguments through. + * @param {ASTNode[]} superArgs An array of expressions to check. + * @returns {boolean} `true` if the superArgs is `...arguments`. + */ +function isSpreadArguments(superArgs) { + return ( + superArgs.length === 1 && + superArgs[0].type === "SpreadElement" && + superArgs[0].argument.type === "Identifier" && + superArgs[0].argument.name === "arguments" + ); +} + +/** + * Checks whether given 2 nodes are identifiers which have the same name or not. + * @param {ASTNode} ctorParam A node to check. + * @param {ASTNode} superArg A node to check. + * @returns {boolean} `true` if the nodes are identifiers which have the same + * name. + */ +function isValidIdentifierPair(ctorParam, superArg) { + return ( + ctorParam.type === "Identifier" && + superArg.type === "Identifier" && + ctorParam.name === superArg.name + ); +} + +/** + * Checks whether given 2 nodes are a rest/spread pair which has the same values. + * @param {ASTNode} ctorParam A node to check. + * @param {ASTNode} superArg A node to check. + * @returns {boolean} `true` if the nodes are a rest/spread pair which has the + * same values. + */ +function isValidRestSpreadPair(ctorParam, superArg) { + return ( + ctorParam.type === "RestElement" && + superArg.type === "SpreadElement" && + isValidIdentifierPair(ctorParam.argument, superArg.argument) + ); +} + +/** + * Checks whether given 2 nodes have the same value or not. + * @param {ASTNode} ctorParam A node to check. + * @param {ASTNode} superArg A node to check. + * @returns {boolean} `true` if the nodes have the same value or not. + */ +function isValidPair(ctorParam, superArg) { + return ( + isValidIdentifierPair(ctorParam, superArg) || + isValidRestSpreadPair(ctorParam, superArg) + ); +} + +/** + * Checks whether the parameters of a constructor and the arguments of `super()` + * have the same values or not. + * @param {ASTNode} ctorParams The parameters of a constructor to check. + * @param {ASTNode} superArgs The arguments of `super()` to check. + * @returns {boolean} `true` if those have the same values. + */ +function isPassingThrough(ctorParams, superArgs) { + if (ctorParams.length !== superArgs.length) { + return false; + } + + for (let i = 0; i < ctorParams.length; ++i) { + if (!isValidPair(ctorParams[i], superArgs[i])) { + return false; + } + } + + return true; +} + +/** + * Checks whether the constructor body is a redundant super call. + * @param {Array} body constructor body content. + * @param {Array} ctorParams The params to check against super call. + * @returns {boolean} true if the constructor body is redundant + */ +function isRedundantSuperCall(body, ctorParams) { + return ( + isSingleSuperCall(body) && + ctorParams.every(isSimple) && + ( + isSpreadArguments(body[0].expression.arguments) || + isPassingThrough(ctorParams, body[0].expression.arguments) + ) + ); +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow unnecessary constructors", + category: "ECMAScript 6", + recommended: false, + url: "https://eslint.org/docs/rules/no-useless-constructor" + }, + + schema: [], + + messages: { + noUselessConstructor: "Useless constructor." + } + }, + + create(context) { + + /** + * Checks whether a node is a redundant constructor + * @param {ASTNode} node node to check + * @returns {void} + */ + function checkForConstructor(node) { + if (node.kind !== "constructor") { + return; + } + + const body = node.value.body.body; + const ctorParams = node.value.params; + const superClass = node.parent.parent.superClass; + + if (superClass ? isRedundantSuperCall(body, ctorParams) : (body.length === 0)) { + context.report({ + node, + messageId: "noUselessConstructor" + }); + } + } + + return { + MethodDefinition: checkForConstructor + }; + } +}; diff --git a/eslint/lib/rules/no-useless-escape.js b/eslint/lib/rules/no-useless-escape.js new file mode 100644 index 0000000..8057e44 --- /dev/null +++ b/eslint/lib/rules/no-useless-escape.js @@ -0,0 +1,252 @@ +/** + * @fileoverview Look for useless escapes in strings and regexes + * @author Onur Temizkan + */ + +"use strict"; + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +/** + * Returns the union of two sets. + * @param {Set} setA The first set + * @param {Set} setB The second set + * @returns {Set} The union of the two sets + */ +function union(setA, setB) { + return new Set(function *() { + yield* setA; + yield* setB; + }()); +} + +const VALID_STRING_ESCAPES = union(new Set("\\nrvtbfux"), astUtils.LINEBREAKS); +const REGEX_GENERAL_ESCAPES = new Set("\\bcdDfnpPrsStvwWxu0123456789]"); +const REGEX_NON_CHARCLASS_ESCAPES = union(REGEX_GENERAL_ESCAPES, new Set("^/.$*+?[{}|()Bk")); + +/** + * Parses a regular expression into a list of characters with character class info. + * @param {string} regExpText The raw text used to create the regular expression + * @returns {Object[]} A list of characters, each with info on escaping and whether they're in a character class. + * @example + * + * parseRegExp('a\\b[cd-]') + * + * returns: + * [ + * {text: 'a', index: 0, escaped: false, inCharClass: false, startsCharClass: false, endsCharClass: false}, + * {text: 'b', index: 2, escaped: true, inCharClass: false, startsCharClass: false, endsCharClass: false}, + * {text: 'c', index: 4, escaped: false, inCharClass: true, startsCharClass: true, endsCharClass: false}, + * {text: 'd', index: 5, escaped: false, inCharClass: true, startsCharClass: false, endsCharClass: false}, + * {text: '-', index: 6, escaped: false, inCharClass: true, startsCharClass: false, endsCharClass: false} + * ] + */ +function parseRegExp(regExpText) { + const charList = []; + + regExpText.split("").reduce((state, char, index) => { + if (!state.escapeNextChar) { + if (char === "\\") { + return Object.assign(state, { escapeNextChar: true }); + } + if (char === "[" && !state.inCharClass) { + return Object.assign(state, { inCharClass: true, startingCharClass: true }); + } + if (char === "]" && state.inCharClass) { + if (charList.length && charList[charList.length - 1].inCharClass) { + charList[charList.length - 1].endsCharClass = true; + } + return Object.assign(state, { inCharClass: false, startingCharClass: false }); + } + } + charList.push({ + text: char, + index, + escaped: state.escapeNextChar, + inCharClass: state.inCharClass, + startsCharClass: state.startingCharClass, + endsCharClass: false + }); + return Object.assign(state, { escapeNextChar: false, startingCharClass: false }); + }, { escapeNextChar: false, inCharClass: false, startingCharClass: false }); + + return charList; +} + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow unnecessary escape characters", + category: "Best Practices", + recommended: true, + url: "https://eslint.org/docs/rules/no-useless-escape", + suggestion: true + }, + + messages: { + unnecessaryEscape: "Unnecessary escape character: \\{{character}}.", + removeEscape: "Remove the `\\`. This maintains the current functionality.", + escapeBackslash: "Replace the `\\` with `\\\\` to include the actual backslash character." + }, + + schema: [] + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + /** + * Reports a node + * @param {ASTNode} node The node to report + * @param {number} startOffset The backslash's offset from the start of the node + * @param {string} character The uselessly escaped character (not including the backslash) + * @returns {void} + */ + function report(node, startOffset, character) { + const start = sourceCode.getLocFromIndex(sourceCode.getIndexFromLoc(node.loc.start) + startOffset); + const rangeStart = sourceCode.getIndexFromLoc(node.loc.start) + startOffset; + const range = [rangeStart, rangeStart + 1]; + + context.report({ + node, + loc: { + start, + end: { line: start.line, column: start.column + 1 } + }, + messageId: "unnecessaryEscape", + data: { character }, + suggest: [ + { + messageId: "removeEscape", + fix(fixer) { + return fixer.removeRange(range); + } + }, + { + messageId: "escapeBackslash", + fix(fixer) { + return fixer.insertTextBeforeRange(range, "\\"); + } + } + ] + }); + } + + /** + * Checks if the escape character in given string slice is unnecessary. + * @private + * @param {ASTNode} node node to validate. + * @param {string} match string slice to validate. + * @returns {void} + */ + function validateString(node, match) { + const isTemplateElement = node.type === "TemplateElement"; + const escapedChar = match[0][1]; + let isUnnecessaryEscape = !VALID_STRING_ESCAPES.has(escapedChar); + let isQuoteEscape; + + if (isTemplateElement) { + isQuoteEscape = escapedChar === "`"; + + if (escapedChar === "$") { + + // Warn if `\$` is not followed by `{` + isUnnecessaryEscape = match.input[match.index + 2] !== "{"; + } else if (escapedChar === "{") { + + /* + * Warn if `\{` is not preceded by `$`. If preceded by `$`, escaping + * is necessary and the rule should not warn. If preceded by `/$`, the rule + * will warn for the `/$` instead, as it is the first unnecessarily escaped character. + */ + isUnnecessaryEscape = match.input[match.index - 1] !== "$"; + } + } else { + isQuoteEscape = escapedChar === node.raw[0]; + } + + if (isUnnecessaryEscape && !isQuoteEscape) { + report(node, match.index + 1, match[0].slice(1)); + } + } + + /** + * Checks if a node has an escape. + * @param {ASTNode} node node to check. + * @returns {void} + */ + function check(node) { + const isTemplateElement = node.type === "TemplateElement"; + + if ( + isTemplateElement && + node.parent && + node.parent.parent && + node.parent.parent.type === "TaggedTemplateExpression" && + node.parent === node.parent.parent.quasi + ) { + + // Don't report tagged template literals, because the backslash character is accessible to the tag function. + return; + } + + if (typeof node.value === "string" || isTemplateElement) { + + /* + * JSXAttribute doesn't have any escape sequence: https://facebook.github.io/jsx/. + * In addition, backticks are not supported by JSX yet: https://github.com/facebook/jsx/issues/25. + */ + if (node.parent.type === "JSXAttribute" || node.parent.type === "JSXElement" || node.parent.type === "JSXFragment") { + return; + } + + const value = isTemplateElement ? node.value.raw : node.raw.slice(1, -1); + const pattern = /\\[^\d]/gu; + let match; + + while ((match = pattern.exec(value))) { + validateString(node, match); + } + } else if (node.regex) { + parseRegExp(node.regex.pattern) + + /* + * The '-' character is a special case, because it's only valid to escape it if it's in a character + * class, and is not at either edge of the character class. To account for this, don't consider '-' + * characters to be valid in general, and filter out '-' characters that appear in the middle of a + * character class. + */ + .filter(charInfo => !(charInfo.text === "-" && charInfo.inCharClass && !charInfo.startsCharClass && !charInfo.endsCharClass)) + + /* + * The '^' character is also a special case; it must always be escaped outside of character classes, but + * it only needs to be escaped in character classes if it's at the beginning of the character class. To + * account for this, consider it to be a valid escape character outside of character classes, and filter + * out '^' characters that appear at the start of a character class. + */ + .filter(charInfo => !(charInfo.text === "^" && charInfo.startsCharClass)) + + // Filter out characters that aren't escaped. + .filter(charInfo => charInfo.escaped) + + // Filter out characters that are valid to escape, based on their position in the regular expression. + .filter(charInfo => !(charInfo.inCharClass ? REGEX_GENERAL_ESCAPES : REGEX_NON_CHARCLASS_ESCAPES).has(charInfo.text)) + + // Report all the remaining characters. + .forEach(charInfo => report(node, charInfo.index, charInfo.text)); + } + + } + + return { + Literal: check, + TemplateElement: check + }; + } +}; diff --git a/eslint/lib/rules/no-useless-rename.js b/eslint/lib/rules/no-useless-rename.js new file mode 100644 index 0000000..fa88f37 --- /dev/null +++ b/eslint/lib/rules/no-useless-rename.js @@ -0,0 +1,168 @@ +/** + * @fileoverview Disallow renaming import, export, and destructured assignments to the same name. + * @author Kai Cataldo + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow renaming import, export, and destructured assignments to the same name", + category: "ECMAScript 6", + recommended: false, + url: "https://eslint.org/docs/rules/no-useless-rename" + }, + + fixable: "code", + + schema: [ + { + type: "object", + properties: { + ignoreDestructuring: { type: "boolean", default: false }, + ignoreImport: { type: "boolean", default: false }, + ignoreExport: { type: "boolean", default: false } + }, + additionalProperties: false + } + ], + + messages: { + unnecessarilyRenamed: "{{type}} {{name}} unnecessarily renamed." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(), + options = context.options[0] || {}, + ignoreDestructuring = options.ignoreDestructuring === true, + ignoreImport = options.ignoreImport === true, + ignoreExport = options.ignoreExport === true; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Reports error for unnecessarily renamed assignments + * @param {ASTNode} node node to report + * @param {ASTNode} initial node with initial name value + * @param {ASTNode} result node with new name value + * @param {string} type the type of the offending node + * @returns {void} + */ + function reportError(node, initial, result, type) { + const name = initial.type === "Identifier" ? initial.name : initial.value; + + return context.report({ + node, + messageId: "unnecessarilyRenamed", + data: { + name, + type + }, + fix(fixer) { + if (sourceCode.commentsExistBetween(initial, result)) { + return null; + } + + const replacementText = result.type === "AssignmentPattern" + ? sourceCode.getText(result) + : name; + + return fixer.replaceTextRange([ + initial.range[0], + result.range[1] + ], replacementText); + } + }); + } + + /** + * Checks whether a destructured assignment is unnecessarily renamed + * @param {ASTNode} node node to check + * @returns {void} + */ + function checkDestructured(node) { + if (ignoreDestructuring) { + return; + } + + for (const property of node.properties) { + + /* + * TODO: Remove after babel-eslint removes ExperimentalRestProperty + * https://github.com/eslint/eslint/issues/12335 + */ + if (property.type === "ExperimentalRestProperty") { + continue; + } + + /** + * Properties using shorthand syntax and rest elements can not be renamed. + * If the property is computed, we have no idea if a rename is useless or not. + */ + if (property.shorthand || property.type === "RestElement" || property.computed) { + continue; + } + + const key = (property.key.type === "Identifier" && property.key.name) || (property.key.type === "Literal" && property.key.value); + const renamedKey = property.value.type === "AssignmentPattern" ? property.value.left.name : property.value.name; + + if (key === renamedKey) { + reportError(property, property.key, property.value, "Destructuring assignment"); + } + } + } + + /** + * Checks whether an import is unnecessarily renamed + * @param {ASTNode} node node to check + * @returns {void} + */ + function checkImport(node) { + if (ignoreImport) { + return; + } + + if (node.imported.name === node.local.name && + node.imported.range[0] !== node.local.range[0]) { + reportError(node, node.imported, node.local, "Import"); + } + } + + /** + * Checks whether an export is unnecessarily renamed + * @param {ASTNode} node node to check + * @returns {void} + */ + function checkExport(node) { + if (ignoreExport) { + return; + } + + if (node.local.name === node.exported.name && + node.local.range[0] !== node.exported.range[0]) { + reportError(node, node.local, node.exported, "Export"); + } + + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + ObjectPattern: checkDestructured, + ImportSpecifier: checkImport, + ExportSpecifier: checkExport + }; + } +}; diff --git a/eslint/lib/rules/no-useless-return.js b/eslint/lib/rules/no-useless-return.js new file mode 100644 index 0000000..111cb21 --- /dev/null +++ b/eslint/lib/rules/no-useless-return.js @@ -0,0 +1,305 @@ +/** + * @fileoverview Disallow redundant return statements + * @author Teddy Katz + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"), + FixTracker = require("./utils/fix-tracker"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Removes the given element from the array. + * @param {Array} array The source array to remove. + * @param {any} element The target item to remove. + * @returns {void} + */ +function remove(array, element) { + const index = array.indexOf(element); + + if (index !== -1) { + array.splice(index, 1); + } +} + +/** + * Checks whether it can remove the given return statement or not. + * @param {ASTNode} node The return statement node to check. + * @returns {boolean} `true` if the node is removable. + */ +function isRemovable(node) { + return astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type); +} + +/** + * Checks whether the given return statement is in a `finally` block or not. + * @param {ASTNode} node The return statement node to check. + * @returns {boolean} `true` if the node is in a `finally` block. + */ +function isInFinally(node) { + for ( + let currentNode = node; + currentNode && currentNode.parent && !astUtils.isFunction(currentNode); + currentNode = currentNode.parent + ) { + if (currentNode.parent.type === "TryStatement" && currentNode.parent.finalizer === currentNode) { + return true; + } + } + + return false; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow redundant return statements", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-useless-return" + }, + + fixable: "code", + schema: [], + + messages: { + unnecessaryReturn: "Unnecessary return statement." + } + }, + + create(context) { + const segmentInfoMap = new WeakMap(); + const usedUnreachableSegments = new WeakSet(); + const sourceCode = context.getSourceCode(); + let scopeInfo = null; + + /** + * Checks whether the given segment is terminated by a return statement or not. + * @param {CodePathSegment} segment The segment to check. + * @returns {boolean} `true` if the segment is terminated by a return statement, or if it's still a part of unreachable. + */ + function isReturned(segment) { + const info = segmentInfoMap.get(segment); + + return !info || info.returned; + } + + /** + * Collects useless return statements from the given previous segments. + * + * A previous segment may be an unreachable segment. + * In that case, the information object of the unreachable segment is not + * initialized because `onCodePathSegmentStart` event is not notified for + * unreachable segments. + * This goes to the previous segments of the unreachable segment recursively + * if the unreachable segment was generated by a return statement. Otherwise, + * this ignores the unreachable segment. + * + * This behavior would simulate code paths for the case that the return + * statement does not exist. + * @param {ASTNode[]} uselessReturns The collected return statements. + * @param {CodePathSegment[]} prevSegments The previous segments to traverse. + * @param {WeakSet} [providedTraversedSegments] A set of segments that have already been traversed in this call + * @returns {ASTNode[]} `uselessReturns`. + */ + function getUselessReturns(uselessReturns, prevSegments, providedTraversedSegments) { + const traversedSegments = providedTraversedSegments || new WeakSet(); + + for (const segment of prevSegments) { + if (!segment.reachable) { + if (!traversedSegments.has(segment)) { + traversedSegments.add(segment); + getUselessReturns( + uselessReturns, + segment.allPrevSegments.filter(isReturned), + traversedSegments + ); + } + continue; + } + + uselessReturns.push(...segmentInfoMap.get(segment).uselessReturns); + } + + return uselessReturns; + } + + /** + * Removes the return statements on the given segment from the useless return + * statement list. + * + * This segment may be an unreachable segment. + * In that case, the information object of the unreachable segment is not + * initialized because `onCodePathSegmentStart` event is not notified for + * unreachable segments. + * This goes to the previous segments of the unreachable segment recursively + * if the unreachable segment was generated by a return statement. Otherwise, + * this ignores the unreachable segment. + * + * This behavior would simulate code paths for the case that the return + * statement does not exist. + * @param {CodePathSegment} segment The segment to get return statements. + * @returns {void} + */ + function markReturnStatementsOnSegmentAsUsed(segment) { + if (!segment.reachable) { + usedUnreachableSegments.add(segment); + segment.allPrevSegments + .filter(isReturned) + .filter(prevSegment => !usedUnreachableSegments.has(prevSegment)) + .forEach(markReturnStatementsOnSegmentAsUsed); + return; + } + + const info = segmentInfoMap.get(segment); + + for (const node of info.uselessReturns) { + remove(scopeInfo.uselessReturns, node); + } + info.uselessReturns = []; + } + + /** + * Removes the return statements on the current segments from the useless + * return statement list. + * + * This function will be called at every statement except FunctionDeclaration, + * BlockStatement, and BreakStatement. + * + * - FunctionDeclarations are always executed whether it's returned or not. + * - BlockStatements do nothing. + * - BreakStatements go the next merely. + * @returns {void} + */ + function markReturnStatementsOnCurrentSegmentsAsUsed() { + scopeInfo + .codePath + .currentSegments + .forEach(markReturnStatementsOnSegmentAsUsed); + } + + //---------------------------------------------------------------------- + // Public + //---------------------------------------------------------------------- + + return { + + // Makes and pushs a new scope information. + onCodePathStart(codePath) { + scopeInfo = { + upper: scopeInfo, + uselessReturns: [], + codePath + }; + }, + + // Reports useless return statements if exist. + onCodePathEnd() { + for (const node of scopeInfo.uselessReturns) { + context.report({ + node, + loc: node.loc, + messageId: "unnecessaryReturn", + fix(fixer) { + if (isRemovable(node) && !sourceCode.getCommentsInside(node).length) { + + /* + * Extend the replacement range to include the + * entire function to avoid conflicting with + * no-else-return. + * https://github.com/eslint/eslint/issues/8026 + */ + return new FixTracker(fixer, sourceCode) + .retainEnclosingFunction(node) + .remove(node); + } + return null; + } + }); + } + + scopeInfo = scopeInfo.upper; + }, + + /* + * Initializes segments. + * NOTE: This event is notified for only reachable segments. + */ + onCodePathSegmentStart(segment) { + const info = { + uselessReturns: getUselessReturns([], segment.allPrevSegments), + returned: false + }; + + // Stores the info. + segmentInfoMap.set(segment, info); + }, + + // Adds ReturnStatement node to check whether it's useless or not. + ReturnStatement(node) { + if (node.argument) { + markReturnStatementsOnCurrentSegmentsAsUsed(); + } + if ( + node.argument || + astUtils.isInLoop(node) || + isInFinally(node) || + + // Ignore `return` statements in unreachable places (https://github.com/eslint/eslint/issues/11647). + !scopeInfo.codePath.currentSegments.some(s => s.reachable) + ) { + return; + } + + for (const segment of scopeInfo.codePath.currentSegments) { + const info = segmentInfoMap.get(segment); + + if (info) { + info.uselessReturns.push(node); + info.returned = true; + } + } + scopeInfo.uselessReturns.push(node); + }, + + /* + * Registers for all statement nodes except FunctionDeclaration, BlockStatement, BreakStatement. + * Removes return statements of the current segments from the useless return statement list. + */ + ClassDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed, + ContinueStatement: markReturnStatementsOnCurrentSegmentsAsUsed, + DebuggerStatement: markReturnStatementsOnCurrentSegmentsAsUsed, + DoWhileStatement: markReturnStatementsOnCurrentSegmentsAsUsed, + EmptyStatement: markReturnStatementsOnCurrentSegmentsAsUsed, + ExpressionStatement: markReturnStatementsOnCurrentSegmentsAsUsed, + ForInStatement: markReturnStatementsOnCurrentSegmentsAsUsed, + ForOfStatement: markReturnStatementsOnCurrentSegmentsAsUsed, + ForStatement: markReturnStatementsOnCurrentSegmentsAsUsed, + IfStatement: markReturnStatementsOnCurrentSegmentsAsUsed, + ImportDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed, + LabeledStatement: markReturnStatementsOnCurrentSegmentsAsUsed, + SwitchStatement: markReturnStatementsOnCurrentSegmentsAsUsed, + ThrowStatement: markReturnStatementsOnCurrentSegmentsAsUsed, + TryStatement: markReturnStatementsOnCurrentSegmentsAsUsed, + VariableDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed, + WhileStatement: markReturnStatementsOnCurrentSegmentsAsUsed, + WithStatement: markReturnStatementsOnCurrentSegmentsAsUsed, + ExportNamedDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed, + ExportDefaultDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed, + ExportAllDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed + }; + } +}; diff --git a/eslint/lib/rules/no-var.js b/eslint/lib/rules/no-var.js new file mode 100644 index 0000000..f2cb96b --- /dev/null +++ b/eslint/lib/rules/no-var.js @@ -0,0 +1,334 @@ +/** + * @fileoverview Rule to check for the usage of var. + * @author Jamund Ferguson + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Check whether a given variable is a global variable or not. + * @param {eslint-scope.Variable} variable The variable to check. + * @returns {boolean} `true` if the variable is a global variable. + */ +function isGlobal(variable) { + return Boolean(variable.scope) && variable.scope.type === "global"; +} + +/** + * Finds the nearest function scope or global scope walking up the scope + * hierarchy. + * @param {eslint-scope.Scope} scope The scope to traverse. + * @returns {eslint-scope.Scope} a function scope or global scope containing the given + * scope. + */ +function getEnclosingFunctionScope(scope) { + let currentScope = scope; + + while (currentScope.type !== "function" && currentScope.type !== "global") { + currentScope = currentScope.upper; + } + return currentScope; +} + +/** + * Checks whether the given variable has any references from a more specific + * function expression (i.e. a closure). + * @param {eslint-scope.Variable} variable A variable to check. + * @returns {boolean} `true` if the variable is used from a closure. + */ +function isReferencedInClosure(variable) { + const enclosingFunctionScope = getEnclosingFunctionScope(variable.scope); + + return variable.references.some(reference => + getEnclosingFunctionScope(reference.from) !== enclosingFunctionScope); +} + +/** + * Checks whether the given node is the assignee of a loop. + * @param {ASTNode} node A VariableDeclaration node to check. + * @returns {boolean} `true` if the declaration is assigned as part of loop + * iteration. + */ +function isLoopAssignee(node) { + return (node.parent.type === "ForOfStatement" || node.parent.type === "ForInStatement") && + node === node.parent.left; +} + +/** + * Checks whether the given variable declaration is immediately initialized. + * @param {ASTNode} node A VariableDeclaration node to check. + * @returns {boolean} `true` if the declaration has an initializer. + */ +function isDeclarationInitialized(node) { + return node.declarations.every(declarator => declarator.init !== null); +} + +const SCOPE_NODE_TYPE = /^(?:Program|BlockStatement|SwitchStatement|ForStatement|ForInStatement|ForOfStatement)$/u; + +/** + * Gets the scope node which directly contains a given node. + * @param {ASTNode} node A node to get. This is a `VariableDeclaration` or + * an `Identifier`. + * @returns {ASTNode} A scope node. This is one of `Program`, `BlockStatement`, + * `SwitchStatement`, `ForStatement`, `ForInStatement`, and + * `ForOfStatement`. + */ +function getScopeNode(node) { + for (let currentNode = node; currentNode; currentNode = currentNode.parent) { + if (SCOPE_NODE_TYPE.test(currentNode.type)) { + return currentNode; + } + } + + /* istanbul ignore next : unreachable */ + return null; +} + +/** + * Checks whether a given variable is redeclared or not. + * @param {eslint-scope.Variable} variable A variable to check. + * @returns {boolean} `true` if the variable is redeclared. + */ +function isRedeclared(variable) { + return variable.defs.length >= 2; +} + +/** + * Checks whether a given variable is used from outside of the specified scope. + * @param {ASTNode} scopeNode A scope node to check. + * @returns {Function} The predicate function which checks whether a given + * variable is used from outside of the specified scope. + */ +function isUsedFromOutsideOf(scopeNode) { + + /** + * Checks whether a given reference is inside of the specified scope or not. + * @param {eslint-scope.Reference} reference A reference to check. + * @returns {boolean} `true` if the reference is inside of the specified + * scope. + */ + function isOutsideOfScope(reference) { + const scope = scopeNode.range; + const id = reference.identifier.range; + + return id[0] < scope[0] || id[1] > scope[1]; + } + + return function(variable) { + return variable.references.some(isOutsideOfScope); + }; +} + +/** + * Creates the predicate function which checks whether a variable has their references in TDZ. + * + * The predicate function would return `true`: + * + * - if a reference is before the declarator. E.g. (var a = b, b = 1;)(var {a = b, b} = {};) + * - if a reference is in the expression of their default value. E.g. (var {a = a} = {};) + * - if a reference is in the expression of their initializer. E.g. (var a = a;) + * @param {ASTNode} node The initializer node of VariableDeclarator. + * @returns {Function} The predicate function. + * @private + */ +function hasReferenceInTDZ(node) { + const initStart = node.range[0]; + const initEnd = node.range[1]; + + return variable => { + const id = variable.defs[0].name; + const idStart = id.range[0]; + const defaultValue = (id.parent.type === "AssignmentPattern" ? id.parent.right : null); + const defaultStart = defaultValue && defaultValue.range[0]; + const defaultEnd = defaultValue && defaultValue.range[1]; + + return variable.references.some(reference => { + const start = reference.identifier.range[0]; + const end = reference.identifier.range[1]; + + return !reference.init && ( + start < idStart || + (defaultValue !== null && start >= defaultStart && end <= defaultEnd) || + (start >= initStart && end <= initEnd) + ); + }); + }; +} + +/** + * Checks whether a given variable has name that is allowed for 'var' declarations, + * but disallowed for `let` declarations. + * @param {eslint-scope.Variable} variable The variable to check. + * @returns {boolean} `true` if the variable has a disallowed name. + */ +function hasNameDisallowedForLetDeclarations(variable) { + return variable.name === "let"; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "require `let` or `const` instead of `var`", + category: "ECMAScript 6", + recommended: false, + url: "https://eslint.org/docs/rules/no-var" + }, + + schema: [], + fixable: "code", + + messages: { + unexpectedVar: "Unexpected var, use let or const instead." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + /** + * Checks whether the variables which are defined by the given declarator node have their references in TDZ. + * @param {ASTNode} declarator The VariableDeclarator node to check. + * @returns {boolean} `true` if one of the variables which are defined by the given declarator node have their references in TDZ. + */ + function hasSelfReferenceInTDZ(declarator) { + if (!declarator.init) { + return false; + } + const variables = context.getDeclaredVariables(declarator); + + return variables.some(hasReferenceInTDZ(declarator.init)); + } + + /** + * Checks whether it can fix a given variable declaration or not. + * It cannot fix if the following cases: + * + * - A variable is a global variable. + * - A variable is declared on a SwitchCase node. + * - A variable is redeclared. + * - A variable is used from outside the scope. + * - A variable is used from a closure within a loop. + * - A variable might be used before it is assigned within a loop. + * - A variable might be used in TDZ. + * - A variable is declared in statement position (e.g. a single-line `IfStatement`) + * - A variable has name that is disallowed for `let` declarations. + * + * ## A variable is declared on a SwitchCase node. + * + * If this rule modifies 'var' declarations on a SwitchCase node, it + * would generate the warnings of 'no-case-declarations' rule. And the + * 'eslint:recommended' preset includes 'no-case-declarations' rule, so + * this rule doesn't modify those declarations. + * + * ## A variable is redeclared. + * + * The language spec disallows redeclarations of `let` declarations. + * Those variables would cause syntax errors. + * + * ## A variable is used from outside the scope. + * + * The language spec disallows accesses from outside of the scope for + * `let` declarations. Those variables would cause reference errors. + * + * ## A variable is used from a closure within a loop. + * + * A `var` declaration within a loop shares the same variable instance + * across all loop iterations, while a `let` declaration creates a new + * instance for each iteration. This means if a variable in a loop is + * referenced by any closure, changing it from `var` to `let` would + * change the behavior in a way that is generally unsafe. + * + * ## A variable might be used before it is assigned within a loop. + * + * Within a loop, a `let` declaration without an initializer will be + * initialized to null, while a `var` declaration will retain its value + * from the previous iteration, so it is only safe to change `var` to + * `let` if we can statically determine that the variable is always + * assigned a value before its first access in the loop body. To keep + * the implementation simple, we only convert `var` to `let` within + * loops when the variable is a loop assignee or the declaration has an + * initializer. + * @param {ASTNode} node A variable declaration node to check. + * @returns {boolean} `true` if it can fix the node. + */ + function canFix(node) { + const variables = context.getDeclaredVariables(node); + const scopeNode = getScopeNode(node); + + if (node.parent.type === "SwitchCase" || + node.declarations.some(hasSelfReferenceInTDZ) || + variables.some(isGlobal) || + variables.some(isRedeclared) || + variables.some(isUsedFromOutsideOf(scopeNode)) || + variables.some(hasNameDisallowedForLetDeclarations) + ) { + return false; + } + + if (astUtils.isInLoop(node)) { + if (variables.some(isReferencedInClosure)) { + return false; + } + if (!isLoopAssignee(node) && !isDeclarationInitialized(node)) { + return false; + } + } + + if ( + !isLoopAssignee(node) && + !(node.parent.type === "ForStatement" && node.parent.init === node) && + !astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type) + ) { + + // If the declaration is not in a block, e.g. `if (foo) var bar = 1;`, then it can't be fixed. + return false; + } + + return true; + } + + /** + * Reports a given variable declaration node. + * @param {ASTNode} node A variable declaration node to report. + * @returns {void} + */ + function report(node) { + context.report({ + node, + messageId: "unexpectedVar", + + fix(fixer) { + const varToken = sourceCode.getFirstToken(node, { filter: t => t.value === "var" }); + + return canFix(node) + ? fixer.replaceText(varToken, "let") + : null; + } + }); + } + + return { + "VariableDeclaration:exit"(node) { + if (node.kind === "var") { + report(node); + } + } + }; + } +}; diff --git a/eslint/lib/rules/no-void.js b/eslint/lib/rules/no-void.js new file mode 100644 index 0000000..99c8378 --- /dev/null +++ b/eslint/lib/rules/no-void.js @@ -0,0 +1,64 @@ +/** + * @fileoverview Rule to disallow use of void operator. + * @author Mike Sidorov + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow `void` operators", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-void" + }, + + messages: { + noVoid: "Expected 'undefined' and instead saw 'void'." + }, + + schema: [ + { + type: "object", + properties: { + allowAsStatement: { + type: "boolean", + default: false + } + }, + additionalProperties: false + } + ] + }, + + create(context) { + const allowAsStatement = + context.options[0] && context.options[0].allowAsStatement; + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + 'UnaryExpression[operator="void"]'(node) { + if ( + allowAsStatement && + node.parent && + node.parent.type === "ExpressionStatement" + ) { + return; + } + context.report({ + node, + messageId: "noVoid" + }); + } + }; + } +}; diff --git a/eslint/lib/rules/no-warning-comments.js b/eslint/lib/rules/no-warning-comments.js new file mode 100644 index 0000000..d70bd5d --- /dev/null +++ b/eslint/lib/rules/no-warning-comments.js @@ -0,0 +1,163 @@ +/** + * @fileoverview Rule that warns about used warning comments + * @author Alexander Schmidt + */ + +"use strict"; + +const { escapeRegExp } = require("lodash"); +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow specified warning terms in comments", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/no-warning-comments" + }, + + schema: [ + { + type: "object", + properties: { + terms: { + type: "array", + items: { + type: "string" + } + }, + location: { + enum: ["start", "anywhere"] + } + }, + additionalProperties: false + } + ], + + messages: { + unexpectedComment: "Unexpected '{{matchedTerm}}' comment." + } + }, + + create(context) { + + const sourceCode = context.getSourceCode(), + configuration = context.options[0] || {}, + warningTerms = configuration.terms || ["todo", "fixme", "xxx"], + location = configuration.location || "start", + selfConfigRegEx = /\bno-warning-comments\b/u; + + /** + * Convert a warning term into a RegExp which will match a comment containing that whole word in the specified + * location ("start" or "anywhere"). If the term starts or ends with non word characters, then the match will not + * require word boundaries on that side. + * @param {string} term A term to convert to a RegExp + * @returns {RegExp} The term converted to a RegExp + */ + function convertToRegExp(term) { + const escaped = escapeRegExp(term); + const wordBoundary = "\\b"; + const eitherOrWordBoundary = `|${wordBoundary}`; + let prefix; + + /* + * If the term ends in a word character (a-z0-9_), ensure a word + * boundary at the end, so that substrings do not get falsely + * matched. eg "todo" in a string such as "mastodon". + * If the term ends in a non-word character, then \b won't match on + * the boundary to the next non-word character, which would likely + * be a space. For example `/\bFIX!\b/.test('FIX! blah') === false`. + * In these cases, use no bounding match. Same applies for the + * prefix, handled below. + */ + const suffix = /\w$/u.test(term) ? "\\b" : ""; + + if (location === "start") { + + /* + * When matching at the start, ignore leading whitespace, and + * there's no need to worry about word boundaries. + */ + prefix = "^\\s*"; + } else if (/^\w/u.test(term)) { + prefix = wordBoundary; + } else { + prefix = ""; + } + + if (location === "start") { + + /* + * For location "start" the regex should be + * ^\s*TERM\b. This checks the word boundary + * at the beginning of the comment. + */ + return new RegExp(prefix + escaped + suffix, "iu"); + } + + /* + * For location "anywhere" the regex should be + * \bTERM\b|\bTERM\b, this checks the entire comment + * for the term. + */ + return new RegExp(prefix + escaped + suffix + eitherOrWordBoundary + term + wordBoundary, "iu"); + } + + const warningRegExps = warningTerms.map(convertToRegExp); + + /** + * Checks the specified comment for matches of the configured warning terms and returns the matches. + * @param {string} comment The comment which is checked. + * @returns {Array} All matched warning terms for this comment. + */ + function commentContainsWarningTerm(comment) { + const matches = []; + + warningRegExps.forEach((regex, index) => { + if (regex.test(comment)) { + matches.push(warningTerms[index]); + } + }); + + return matches; + } + + /** + * Checks the specified node for matching warning comments and reports them. + * @param {ASTNode} node The AST node being checked. + * @returns {void} undefined. + */ + function checkComment(node) { + if (astUtils.isDirectiveComment(node) && selfConfigRegEx.test(node.value)) { + return; + } + + const matches = commentContainsWarningTerm(node.value); + + matches.forEach(matchedTerm => { + context.report({ + node, + messageId: "unexpectedComment", + data: { + matchedTerm + } + }); + }); + } + + return { + Program() { + const comments = sourceCode.getAllComments(); + + comments.filter(token => token.type !== "Shebang").forEach(checkComment); + } + }; + } +}; diff --git a/eslint/lib/rules/no-whitespace-before-property.js b/eslint/lib/rules/no-whitespace-before-property.js new file mode 100644 index 0000000..ccd0b09 --- /dev/null +++ b/eslint/lib/rules/no-whitespace-before-property.js @@ -0,0 +1,101 @@ +/** + * @fileoverview Rule to disallow whitespace before properties + * @author Kai Cataldo + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "disallow whitespace before properties", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/no-whitespace-before-property" + }, + + fixable: "whitespace", + schema: [], + + messages: { + unexpectedWhitespace: "Unexpected whitespace before property {{propName}}." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Reports whitespace before property token + * @param {ASTNode} node the node to report in the event of an error + * @param {Token} leftToken the left token + * @param {Token} rightToken the right token + * @returns {void} + * @private + */ + function reportError(node, leftToken, rightToken) { + const replacementText = node.computed ? "" : "."; + + context.report({ + node, + messageId: "unexpectedWhitespace", + data: { + propName: sourceCode.getText(node.property) + }, + fix(fixer) { + if (!node.computed && astUtils.isDecimalInteger(node.object)) { + + /* + * If the object is a number literal, fixing it to something like 5.toString() would cause a SyntaxError. + * Don't fix this case. + */ + return null; + } + return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], replacementText); + } + }); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + MemberExpression(node) { + let rightToken; + let leftToken; + + if (!astUtils.isTokenOnSameLine(node.object, node.property)) { + return; + } + + if (node.computed) { + rightToken = sourceCode.getTokenBefore(node.property, astUtils.isOpeningBracketToken); + leftToken = sourceCode.getTokenBefore(rightToken); + } else { + rightToken = sourceCode.getFirstToken(node.property); + leftToken = sourceCode.getTokenBefore(rightToken, 1); + } + + if (sourceCode.isSpaceBetweenTokens(leftToken, rightToken)) { + reportError(node, leftToken, rightToken); + } + } + }; + } +}; diff --git a/eslint/lib/rules/no-with.js b/eslint/lib/rules/no-with.js new file mode 100644 index 0000000..d3e52e0 --- /dev/null +++ b/eslint/lib/rules/no-with.js @@ -0,0 +1,39 @@ +/** + * @fileoverview Rule to flag use of with statement + * @author Nicholas C. Zakas + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow `with` statements", + category: "Best Practices", + recommended: true, + url: "https://eslint.org/docs/rules/no-with" + }, + + schema: [], + + messages: { + unexpectedWith: "Unexpected use of 'with' statement." + } + }, + + create(context) { + + return { + WithStatement(node) { + context.report({ node, messageId: "unexpectedWith" }); + } + }; + + } +}; diff --git a/eslint/lib/rules/nonblock-statement-body-position.js b/eslint/lib/rules/nonblock-statement-body-position.js new file mode 100644 index 0000000..34e6eea --- /dev/null +++ b/eslint/lib/rules/nonblock-statement-body-position.js @@ -0,0 +1,124 @@ +/** + * @fileoverview enforce the location of single-line statements + * @author Teddy Katz + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +const POSITION_SCHEMA = { enum: ["beside", "below", "any"] }; + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "enforce the location of single-line statements", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/nonblock-statement-body-position" + }, + + fixable: "whitespace", + + schema: [ + POSITION_SCHEMA, + { + properties: { + overrides: { + properties: { + if: POSITION_SCHEMA, + else: POSITION_SCHEMA, + while: POSITION_SCHEMA, + do: POSITION_SCHEMA, + for: POSITION_SCHEMA + }, + additionalProperties: false + } + }, + additionalProperties: false + } + ], + + messages: { + expectNoLinebreak: "Expected no linebreak before this statement.", + expectLinebreak: "Expected a linebreak before this statement." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + //---------------------------------------------------------------------- + // Helpers + //---------------------------------------------------------------------- + + /** + * Gets the applicable preference for a particular keyword + * @param {string} keywordName The name of a keyword, e.g. 'if' + * @returns {string} The applicable option for the keyword, e.g. 'beside' + */ + function getOption(keywordName) { + return context.options[1] && context.options[1].overrides && context.options[1].overrides[keywordName] || + context.options[0] || + "beside"; + } + + /** + * Validates the location of a single-line statement + * @param {ASTNode} node The single-line statement + * @param {string} keywordName The applicable keyword name for the single-line statement + * @returns {void} + */ + function validateStatement(node, keywordName) { + const option = getOption(keywordName); + + if (node.type === "BlockStatement" || option === "any") { + return; + } + + const tokenBefore = sourceCode.getTokenBefore(node); + + if (tokenBefore.loc.end.line === node.loc.start.line && option === "below") { + context.report({ + node, + messageId: "expectLinebreak", + fix: fixer => fixer.insertTextBefore(node, "\n") + }); + } else if (tokenBefore.loc.end.line !== node.loc.start.line && option === "beside") { + context.report({ + node, + messageId: "expectNoLinebreak", + fix(fixer) { + if (sourceCode.getText().slice(tokenBefore.range[1], node.range[0]).trim()) { + return null; + } + return fixer.replaceTextRange([tokenBefore.range[1], node.range[0]], " "); + } + }); + } + } + + //---------------------------------------------------------------------- + // Public + //---------------------------------------------------------------------- + + return { + IfStatement(node) { + validateStatement(node.consequent, "if"); + + // Check the `else` node, but don't check 'else if' statements. + if (node.alternate && node.alternate.type !== "IfStatement") { + validateStatement(node.alternate, "else"); + } + }, + WhileStatement: node => validateStatement(node.body, "while"), + DoWhileStatement: node => validateStatement(node.body, "do"), + ForStatement: node => validateStatement(node.body, "for"), + ForInStatement: node => validateStatement(node.body, "for"), + ForOfStatement: node => validateStatement(node.body, "for") + }; + } +}; diff --git a/eslint/lib/rules/object-curly-newline.js b/eslint/lib/rules/object-curly-newline.js new file mode 100644 index 0000000..b48b252 --- /dev/null +++ b/eslint/lib/rules/object-curly-newline.js @@ -0,0 +1,306 @@ +/** + * @fileoverview Rule to require or disallow line breaks inside braces. + * @author Toru Nagashima + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); +const lodash = require("lodash"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +// Schema objects. +const OPTION_VALUE = { + oneOf: [ + { + enum: ["always", "never"] + }, + { + type: "object", + properties: { + multiline: { + type: "boolean" + }, + minProperties: { + type: "integer", + minimum: 0 + }, + consistent: { + type: "boolean" + } + }, + additionalProperties: false, + minProperties: 1 + } + ] +}; + +/** + * Normalizes a given option value. + * @param {string|Object|undefined} value An option value to parse. + * @returns {{multiline: boolean, minProperties: number, consistent: boolean}} Normalized option object. + */ +function normalizeOptionValue(value) { + let multiline = false; + let minProperties = Number.POSITIVE_INFINITY; + let consistent = false; + + if (value) { + if (value === "always") { + minProperties = 0; + } else if (value === "never") { + minProperties = Number.POSITIVE_INFINITY; + } else { + multiline = Boolean(value.multiline); + minProperties = value.minProperties || Number.POSITIVE_INFINITY; + consistent = Boolean(value.consistent); + } + } else { + consistent = true; + } + + return { multiline, minProperties, consistent }; +} + +/** + * Normalizes a given option value. + * @param {string|Object|undefined} options An option value to parse. + * @returns {{ + * ObjectExpression: {multiline: boolean, minProperties: number, consistent: boolean}, + * ObjectPattern: {multiline: boolean, minProperties: number, consistent: boolean}, + * ImportDeclaration: {multiline: boolean, minProperties: number, consistent: boolean}, + * ExportNamedDeclaration : {multiline: boolean, minProperties: number, consistent: boolean} + * }} Normalized option object. + */ +function normalizeOptions(options) { + const isNodeSpecificOption = lodash.overSome([lodash.isPlainObject, lodash.isString]); + + if (lodash.isPlainObject(options) && lodash.some(options, isNodeSpecificOption)) { + return { + ObjectExpression: normalizeOptionValue(options.ObjectExpression), + ObjectPattern: normalizeOptionValue(options.ObjectPattern), + ImportDeclaration: normalizeOptionValue(options.ImportDeclaration), + ExportNamedDeclaration: normalizeOptionValue(options.ExportDeclaration) + }; + } + + const value = normalizeOptionValue(options); + + return { ObjectExpression: value, ObjectPattern: value, ImportDeclaration: value, ExportNamedDeclaration: value }; +} + +/** + * Determines if ObjectExpression, ObjectPattern, ImportDeclaration or ExportNamedDeclaration + * node needs to be checked for missing line breaks + * @param {ASTNode} node Node under inspection + * @param {Object} options option specific to node type + * @param {Token} first First object property + * @param {Token} last Last object property + * @returns {boolean} `true` if node needs to be checked for missing line breaks + */ +function areLineBreaksRequired(node, options, first, last) { + let objectProperties; + + if (node.type === "ObjectExpression" || node.type === "ObjectPattern") { + objectProperties = node.properties; + } else { + + // is ImportDeclaration or ExportNamedDeclaration + objectProperties = node.specifiers + .filter(s => s.type === "ImportSpecifier" || s.type === "ExportSpecifier"); + } + + return objectProperties.length >= options.minProperties || + ( + options.multiline && + objectProperties.length > 0 && + first.loc.start.line !== last.loc.end.line + ); +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "enforce consistent line breaks inside braces", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/object-curly-newline" + }, + + fixable: "whitespace", + + schema: [ + { + oneOf: [ + OPTION_VALUE, + { + type: "object", + properties: { + ObjectExpression: OPTION_VALUE, + ObjectPattern: OPTION_VALUE, + ImportDeclaration: OPTION_VALUE, + ExportDeclaration: OPTION_VALUE + }, + additionalProperties: false, + minProperties: 1 + } + ] + } + ], + + messages: { + unexpectedLinebreakBeforeClosingBrace: "Unexpected line break before this closing brace.", + unexpectedLinebreakAfterOpeningBrace: "Unexpected line break after this opening brace.", + expectedLinebreakBeforeClosingBrace: "Expected a line break before this closing brace.", + expectedLinebreakAfterOpeningBrace: "Expected a line break after this opening brace." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + const normalizedOptions = normalizeOptions(context.options[0]); + + /** + * Reports a given node if it violated this rule. + * @param {ASTNode} node A node to check. This is an ObjectExpression, ObjectPattern, ImportDeclaration or ExportNamedDeclaration node. + * @returns {void} + */ + function check(node) { + const options = normalizedOptions[node.type]; + + if ( + (node.type === "ImportDeclaration" && + !node.specifiers.some(specifier => specifier.type === "ImportSpecifier")) || + (node.type === "ExportNamedDeclaration" && + !node.specifiers.some(specifier => specifier.type === "ExportSpecifier")) + ) { + return; + } + + const openBrace = sourceCode.getFirstToken(node, token => token.value === "{"); + + let closeBrace; + + if (node.typeAnnotation) { + closeBrace = sourceCode.getTokenBefore(node.typeAnnotation); + } else { + closeBrace = sourceCode.getLastToken(node, token => token.value === "}"); + } + + let first = sourceCode.getTokenAfter(openBrace, { includeComments: true }); + let last = sourceCode.getTokenBefore(closeBrace, { includeComments: true }); + + const needsLineBreaks = areLineBreaksRequired(node, options, first, last); + + const hasCommentsFirstToken = astUtils.isCommentToken(first); + const hasCommentsLastToken = astUtils.isCommentToken(last); + + /* + * Use tokens or comments to check multiline or not. + * But use only tokens to check whether line breaks are needed. + * This allows: + * var obj = { // eslint-disable-line foo + * a: 1 + * } + */ + first = sourceCode.getTokenAfter(openBrace); + last = sourceCode.getTokenBefore(closeBrace); + + if (needsLineBreaks) { + if (astUtils.isTokenOnSameLine(openBrace, first)) { + context.report({ + messageId: "expectedLinebreakAfterOpeningBrace", + node, + loc: openBrace.loc.start, + fix(fixer) { + if (hasCommentsFirstToken) { + return null; + } + + return fixer.insertTextAfter(openBrace, "\n"); + } + }); + } + if (astUtils.isTokenOnSameLine(last, closeBrace)) { + context.report({ + messageId: "expectedLinebreakBeforeClosingBrace", + node, + loc: closeBrace.loc.start, + fix(fixer) { + if (hasCommentsLastToken) { + return null; + } + + return fixer.insertTextBefore(closeBrace, "\n"); + } + }); + } + } else { + const consistent = options.consistent; + const hasLineBreakBetweenOpenBraceAndFirst = !astUtils.isTokenOnSameLine(openBrace, first); + const hasLineBreakBetweenCloseBraceAndLast = !astUtils.isTokenOnSameLine(last, closeBrace); + + if ( + (!consistent && hasLineBreakBetweenOpenBraceAndFirst) || + (consistent && hasLineBreakBetweenOpenBraceAndFirst && !hasLineBreakBetweenCloseBraceAndLast) + ) { + context.report({ + messageId: "unexpectedLinebreakAfterOpeningBrace", + node, + loc: openBrace.loc.start, + fix(fixer) { + if (hasCommentsFirstToken) { + return null; + } + + return fixer.removeRange([ + openBrace.range[1], + first.range[0] + ]); + } + }); + } + if ( + (!consistent && hasLineBreakBetweenCloseBraceAndLast) || + (consistent && !hasLineBreakBetweenOpenBraceAndFirst && hasLineBreakBetweenCloseBraceAndLast) + ) { + context.report({ + messageId: "unexpectedLinebreakBeforeClosingBrace", + node, + loc: closeBrace.loc.start, + fix(fixer) { + if (hasCommentsLastToken) { + return null; + } + + return fixer.removeRange([ + last.range[1], + closeBrace.range[0] + ]); + } + }); + } + } + } + + return { + ObjectExpression: check, + ObjectPattern: check, + ImportDeclaration: check, + ExportNamedDeclaration: check + }; + } +}; diff --git a/eslint/lib/rules/object-curly-spacing.js b/eslint/lib/rules/object-curly-spacing.js new file mode 100644 index 0000000..c0044f5 --- /dev/null +++ b/eslint/lib/rules/object-curly-spacing.js @@ -0,0 +1,308 @@ +/** + * @fileoverview Disallows or enforces spaces inside of object literals. + * @author Jamund Ferguson + */ +"use strict"; + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "enforce consistent spacing inside braces", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/object-curly-spacing" + }, + + fixable: "whitespace", + + schema: [ + { + enum: ["always", "never"] + }, + { + type: "object", + properties: { + arraysInObjects: { + type: "boolean" + }, + objectsInObjects: { + type: "boolean" + } + }, + additionalProperties: false + } + ], + + messages: { + requireSpaceBefore: "A space is required before '{{token}}'.", + requireSpaceAfter: "A space is required after '{{token}}'.", + unexpectedSpaceBefore: "There should be no space before '{{token}}'.", + unexpectedSpaceAfter: "There should be no space after '{{token}}'." + } + }, + + create(context) { + const spaced = context.options[0] === "always", + sourceCode = context.getSourceCode(); + + /** + * Determines whether an option is set, relative to the spacing option. + * If spaced is "always", then check whether option is set to false. + * If spaced is "never", then check whether option is set to true. + * @param {Object} option The option to exclude. + * @returns {boolean} Whether or not the property is excluded. + */ + function isOptionSet(option) { + return context.options[1] ? context.options[1][option] === !spaced : false; + } + + const options = { + spaced, + arraysInObjectsException: isOptionSet("arraysInObjects"), + objectsInObjectsException: isOptionSet("objectsInObjects") + }; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Reports that there shouldn't be a space after the first token + * @param {ASTNode} node The node to report in the event of an error. + * @param {Token} token The token to use for the report. + * @returns {void} + */ + function reportNoBeginningSpace(node, token) { + const nextToken = context.getSourceCode().getTokenAfter(token, { includeComments: true }); + + context.report({ + node, + loc: { start: token.loc.end, end: nextToken.loc.start }, + messageId: "unexpectedSpaceAfter", + data: { + token: token.value + }, + fix(fixer) { + return fixer.removeRange([token.range[1], nextToken.range[0]]); + } + }); + } + + /** + * Reports that there shouldn't be a space before the last token + * @param {ASTNode} node The node to report in the event of an error. + * @param {Token} token The token to use for the report. + * @returns {void} + */ + function reportNoEndingSpace(node, token) { + const previousToken = context.getSourceCode().getTokenBefore(token, { includeComments: true }); + + context.report({ + node, + loc: { start: previousToken.loc.end, end: token.loc.start }, + messageId: "unexpectedSpaceBefore", + data: { + token: token.value + }, + fix(fixer) { + return fixer.removeRange([previousToken.range[1], token.range[0]]); + } + }); + } + + /** + * Reports that there should be a space after the first token + * @param {ASTNode} node The node to report in the event of an error. + * @param {Token} token The token to use for the report. + * @returns {void} + */ + function reportRequiredBeginningSpace(node, token) { + context.report({ + node, + loc: token.loc, + messageId: "requireSpaceAfter", + data: { + token: token.value + }, + fix(fixer) { + return fixer.insertTextAfter(token, " "); + } + }); + } + + /** + * Reports that there should be a space before the last token + * @param {ASTNode} node The node to report in the event of an error. + * @param {Token} token The token to use for the report. + * @returns {void} + */ + function reportRequiredEndingSpace(node, token) { + context.report({ + node, + loc: token.loc, + messageId: "requireSpaceBefore", + data: { + token: token.value + }, + fix(fixer) { + return fixer.insertTextBefore(token, " "); + } + }); + } + + /** + * Determines if spacing in curly braces is valid. + * @param {ASTNode} node The AST node to check. + * @param {Token} first The first token to check (should be the opening brace) + * @param {Token} second The second token to check (should be first after the opening brace) + * @param {Token} penultimate The penultimate token to check (should be last before closing brace) + * @param {Token} last The last token to check (should be closing brace) + * @returns {void} + */ + function validateBraceSpacing(node, first, second, penultimate, last) { + if (astUtils.isTokenOnSameLine(first, second)) { + const firstSpaced = sourceCode.isSpaceBetweenTokens(first, second); + + if (options.spaced && !firstSpaced) { + reportRequiredBeginningSpace(node, first); + } + if (!options.spaced && firstSpaced && second.type !== "Line") { + reportNoBeginningSpace(node, first); + } + } + + if (astUtils.isTokenOnSameLine(penultimate, last)) { + const shouldCheckPenultimate = ( + options.arraysInObjectsException && astUtils.isClosingBracketToken(penultimate) || + options.objectsInObjectsException && astUtils.isClosingBraceToken(penultimate) + ); + const penultimateType = shouldCheckPenultimate && sourceCode.getNodeByRangeIndex(penultimate.range[0]).type; + + const closingCurlyBraceMustBeSpaced = ( + options.arraysInObjectsException && penultimateType === "ArrayExpression" || + options.objectsInObjectsException && (penultimateType === "ObjectExpression" || penultimateType === "ObjectPattern") + ) ? !options.spaced : options.spaced; + + const lastSpaced = sourceCode.isSpaceBetweenTokens(penultimate, last); + + if (closingCurlyBraceMustBeSpaced && !lastSpaced) { + reportRequiredEndingSpace(node, last); + } + if (!closingCurlyBraceMustBeSpaced && lastSpaced) { + reportNoEndingSpace(node, last); + } + } + } + + /** + * Gets '}' token of an object node. + * + * Because the last token of object patterns might be a type annotation, + * this traverses tokens preceded by the last property, then returns the + * first '}' token. + * @param {ASTNode} node The node to get. This node is an + * ObjectExpression or an ObjectPattern. And this node has one or + * more properties. + * @returns {Token} '}' token. + */ + function getClosingBraceOfObject(node) { + const lastProperty = node.properties[node.properties.length - 1]; + + return sourceCode.getTokenAfter(lastProperty, astUtils.isClosingBraceToken); + } + + /** + * Reports a given object node if spacing in curly braces is invalid. + * @param {ASTNode} node An ObjectExpression or ObjectPattern node to check. + * @returns {void} + */ + function checkForObject(node) { + if (node.properties.length === 0) { + return; + } + + const first = sourceCode.getFirstToken(node), + last = getClosingBraceOfObject(node), + second = sourceCode.getTokenAfter(first, { includeComments: true }), + penultimate = sourceCode.getTokenBefore(last, { includeComments: true }); + + validateBraceSpacing(node, first, second, penultimate, last); + } + + /** + * Reports a given import node if spacing in curly braces is invalid. + * @param {ASTNode} node An ImportDeclaration node to check. + * @returns {void} + */ + function checkForImport(node) { + if (node.specifiers.length === 0) { + return; + } + + let firstSpecifier = node.specifiers[0]; + const lastSpecifier = node.specifiers[node.specifiers.length - 1]; + + if (lastSpecifier.type !== "ImportSpecifier") { + return; + } + if (firstSpecifier.type !== "ImportSpecifier") { + firstSpecifier = node.specifiers[1]; + } + + const first = sourceCode.getTokenBefore(firstSpecifier), + last = sourceCode.getTokenAfter(lastSpecifier, astUtils.isNotCommaToken), + second = sourceCode.getTokenAfter(first, { includeComments: true }), + penultimate = sourceCode.getTokenBefore(last, { includeComments: true }); + + validateBraceSpacing(node, first, second, penultimate, last); + } + + /** + * Reports a given export node if spacing in curly braces is invalid. + * @param {ASTNode} node An ExportNamedDeclaration node to check. + * @returns {void} + */ + function checkForExport(node) { + if (node.specifiers.length === 0) { + return; + } + + const firstSpecifier = node.specifiers[0], + lastSpecifier = node.specifiers[node.specifiers.length - 1], + first = sourceCode.getTokenBefore(firstSpecifier), + last = sourceCode.getTokenAfter(lastSpecifier, astUtils.isNotCommaToken), + second = sourceCode.getTokenAfter(first, { includeComments: true }), + penultimate = sourceCode.getTokenBefore(last, { includeComments: true }); + + validateBraceSpacing(node, first, second, penultimate, last); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + + // var {x} = y; + ObjectPattern: checkForObject, + + // var y = {x: 'y'} + ObjectExpression: checkForObject, + + // import {y} from 'x'; + ImportDeclaration: checkForImport, + + // export {name} from 'yo'; + ExportNamedDeclaration: checkForExport + }; + + } +}; diff --git a/eslint/lib/rules/object-property-newline.js b/eslint/lib/rules/object-property-newline.js new file mode 100644 index 0000000..074bc77 --- /dev/null +++ b/eslint/lib/rules/object-property-newline.js @@ -0,0 +1,99 @@ +/** + * @fileoverview Rule to enforce placing object properties on separate lines. + * @author Vitor Balocco + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "enforce placing object properties on separate lines", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/object-property-newline" + }, + + schema: [ + { + type: "object", + properties: { + allowAllPropertiesOnSameLine: { + type: "boolean", + default: false + }, + allowMultiplePropertiesPerLine: { // Deprecated + type: "boolean", + default: false + } + }, + additionalProperties: false + } + ], + + fixable: "whitespace", + + messages: { + propertiesOnNewlineAll: "Object properties must go on a new line if they aren't all on the same line.", + propertiesOnNewline: "Object properties must go on a new line." + } + }, + + create(context) { + const allowSameLine = context.options[0] && ( + (context.options[0].allowAllPropertiesOnSameLine || context.options[0].allowMultiplePropertiesPerLine /* Deprecated */) + ); + const messageId = allowSameLine + ? "propertiesOnNewlineAll" + : "propertiesOnNewline"; + + const sourceCode = context.getSourceCode(); + + return { + ObjectExpression(node) { + if (allowSameLine) { + if (node.properties.length > 1) { + const firstTokenOfFirstProperty = sourceCode.getFirstToken(node.properties[0]); + const lastTokenOfLastProperty = sourceCode.getLastToken(node.properties[node.properties.length - 1]); + + if (firstTokenOfFirstProperty.loc.end.line === lastTokenOfLastProperty.loc.start.line) { + + // All keys and values are on the same line + return; + } + } + } + + for (let i = 1; i < node.properties.length; i++) { + const lastTokenOfPreviousProperty = sourceCode.getLastToken(node.properties[i - 1]); + const firstTokenOfCurrentProperty = sourceCode.getFirstToken(node.properties[i]); + + if (lastTokenOfPreviousProperty.loc.end.line === firstTokenOfCurrentProperty.loc.start.line) { + context.report({ + node, + loc: firstTokenOfCurrentProperty.loc.start, + messageId, + fix(fixer) { + const comma = sourceCode.getTokenBefore(firstTokenOfCurrentProperty); + const rangeAfterComma = [comma.range[1], firstTokenOfCurrentProperty.range[0]]; + + // Don't perform a fix if there are any comments between the comma and the next property. + if (sourceCode.text.slice(rangeAfterComma[0], rangeAfterComma[1]).trim()) { + return null; + } + + return fixer.replaceTextRange(rangeAfterComma, "\n"); + } + }); + } + } + } + }; + } +}; diff --git a/eslint/lib/rules/object-shorthand.js b/eslint/lib/rules/object-shorthand.js new file mode 100644 index 0000000..3999ff8 --- /dev/null +++ b/eslint/lib/rules/object-shorthand.js @@ -0,0 +1,508 @@ +/** + * @fileoverview Rule to enforce concise object methods and properties. + * @author Jamund Ferguson + */ + +"use strict"; + +const OPTIONS = { + always: "always", + never: "never", + methods: "methods", + properties: "properties", + consistent: "consistent", + consistentAsNeeded: "consistent-as-needed" +}; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "require or disallow method and property shorthand syntax for object literals", + category: "ECMAScript 6", + recommended: false, + url: "https://eslint.org/docs/rules/object-shorthand" + }, + + fixable: "code", + + schema: { + anyOf: [ + { + type: "array", + items: [ + { + enum: ["always", "methods", "properties", "never", "consistent", "consistent-as-needed"] + } + ], + minItems: 0, + maxItems: 1 + }, + { + type: "array", + items: [ + { + enum: ["always", "methods", "properties"] + }, + { + type: "object", + properties: { + avoidQuotes: { + type: "boolean" + } + }, + additionalProperties: false + } + ], + minItems: 0, + maxItems: 2 + }, + { + type: "array", + items: [ + { + enum: ["always", "methods"] + }, + { + type: "object", + properties: { + ignoreConstructors: { + type: "boolean" + }, + avoidQuotes: { + type: "boolean" + }, + avoidExplicitReturnArrows: { + type: "boolean" + } + }, + additionalProperties: false + } + ], + minItems: 0, + maxItems: 2 + } + ] + }, + + messages: { + expectedAllPropertiesShorthanded: "Expected shorthand for all properties.", + expectedLiteralMethodLongform: "Expected longform method syntax for string literal keys.", + expectedPropertyShorthand: "Expected property shorthand.", + expectedPropertyLongform: "Expected longform property syntax.", + expectedMethodShorthand: "Expected method shorthand.", + expectedMethodLongform: "Expected longform method syntax.", + unexpectedMix: "Unexpected mix of shorthand and non-shorthand properties." + } + }, + + create(context) { + const APPLY = context.options[0] || OPTIONS.always; + const APPLY_TO_METHODS = APPLY === OPTIONS.methods || APPLY === OPTIONS.always; + const APPLY_TO_PROPS = APPLY === OPTIONS.properties || APPLY === OPTIONS.always; + const APPLY_NEVER = APPLY === OPTIONS.never; + const APPLY_CONSISTENT = APPLY === OPTIONS.consistent; + const APPLY_CONSISTENT_AS_NEEDED = APPLY === OPTIONS.consistentAsNeeded; + + const PARAMS = context.options[1] || {}; + const IGNORE_CONSTRUCTORS = PARAMS.ignoreConstructors; + const AVOID_QUOTES = PARAMS.avoidQuotes; + const AVOID_EXPLICIT_RETURN_ARROWS = !!PARAMS.avoidExplicitReturnArrows; + const sourceCode = context.getSourceCode(); + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + const CTOR_PREFIX_REGEX = /[^_$0-9]/u; + + /** + * Determines if the first character of the name is a capital letter. + * @param {string} name The name of the node to evaluate. + * @returns {boolean} True if the first character of the property name is a capital letter, false if not. + * @private + */ + function isConstructor(name) { + const match = CTOR_PREFIX_REGEX.exec(name); + + // Not a constructor if name has no characters apart from '_', '$' and digits e.g. '_', '$$', '_8' + if (!match) { + return false; + } + + const firstChar = name.charAt(match.index); + + return firstChar === firstChar.toUpperCase(); + } + + /** + * Determines if the property can have a shorthand form. + * @param {ASTNode} property Property AST node + * @returns {boolean} True if the property can have a shorthand form + * @private + * + */ + function canHaveShorthand(property) { + return (property.kind !== "set" && property.kind !== "get" && property.type !== "SpreadElement" && property.type !== "SpreadProperty" && property.type !== "ExperimentalSpreadProperty"); + } + + /** + * Checks whether a node is a string literal. + * @param {ASTNode} node Any AST node. + * @returns {boolean} `true` if it is a string literal. + */ + function isStringLiteral(node) { + return node.type === "Literal" && typeof node.value === "string"; + } + + /** + * Determines if the property is a shorthand or not. + * @param {ASTNode} property Property AST node + * @returns {boolean} True if the property is considered shorthand, false if not. + * @private + * + */ + function isShorthand(property) { + + // property.method is true when `{a(){}}`. + return (property.shorthand || property.method); + } + + /** + * Determines if the property's key and method or value are named equally. + * @param {ASTNode} property Property AST node + * @returns {boolean} True if the key and value are named equally, false if not. + * @private + * + */ + function isRedundant(property) { + const value = property.value; + + if (value.type === "FunctionExpression") { + return !value.id; // Only anonymous should be shorthand method. + } + if (value.type === "Identifier") { + return astUtils.getStaticPropertyName(property) === value.name; + } + + return false; + } + + /** + * Ensures that an object's properties are consistently shorthand, or not shorthand at all. + * @param {ASTNode} node Property AST node + * @param {boolean} checkRedundancy Whether to check longform redundancy + * @returns {void} + * + */ + function checkConsistency(node, checkRedundancy) { + + // We are excluding getters/setters and spread properties as they are considered neither longform nor shorthand. + const properties = node.properties.filter(canHaveShorthand); + + // Do we still have properties left after filtering the getters and setters? + if (properties.length > 0) { + const shorthandProperties = properties.filter(isShorthand); + + /* + * If we do not have an equal number of longform properties as + * shorthand properties, we are using the annotations inconsistently + */ + if (shorthandProperties.length !== properties.length) { + + // We have at least 1 shorthand property + if (shorthandProperties.length > 0) { + context.report({ node, messageId: "unexpectedMix" }); + } else if (checkRedundancy) { + + /* + * If all properties of the object contain a method or value with a name matching it's key, + * all the keys are redundant. + */ + const canAlwaysUseShorthand = properties.every(isRedundant); + + if (canAlwaysUseShorthand) { + context.report({ node, messageId: "expectedAllPropertiesShorthanded" }); + } + } + } + } + } + + /** + * Fixes a FunctionExpression node by making it into a shorthand property. + * @param {SourceCodeFixer} fixer The fixer object + * @param {ASTNode} node A `Property` node that has a `FunctionExpression` or `ArrowFunctionExpression` as its value + * @returns {Object} A fix for this node + */ + function makeFunctionShorthand(fixer, node) { + const firstKeyToken = node.computed + ? sourceCode.getFirstToken(node, astUtils.isOpeningBracketToken) + : sourceCode.getFirstToken(node.key); + const lastKeyToken = node.computed + ? sourceCode.getFirstTokenBetween(node.key, node.value, astUtils.isClosingBracketToken) + : sourceCode.getLastToken(node.key); + const keyText = sourceCode.text.slice(firstKeyToken.range[0], lastKeyToken.range[1]); + let keyPrefix = ""; + + // key: /* */ () => {} + if (sourceCode.commentsExistBetween(lastKeyToken, node.value)) { + return null; + } + + if (node.value.async) { + keyPrefix += "async "; + } + if (node.value.generator) { + keyPrefix += "*"; + } + + const fixRange = [firstKeyToken.range[0], node.range[1]]; + const methodPrefix = keyPrefix + keyText; + + if (node.value.type === "FunctionExpression") { + const functionToken = sourceCode.getTokens(node.value).find(token => token.type === "Keyword" && token.value === "function"); + const tokenBeforeParams = node.value.generator ? sourceCode.getTokenAfter(functionToken) : functionToken; + + return fixer.replaceTextRange( + fixRange, + methodPrefix + sourceCode.text.slice(tokenBeforeParams.range[1], node.value.range[1]) + ); + } + + const arrowToken = sourceCode.getTokenBefore(node.value.body, astUtils.isArrowToken); + const fnBody = sourceCode.text.slice(arrowToken.range[1], node.value.range[1]); + + let shouldAddParensAroundParameters = false; + let tokenBeforeParams; + + if (node.value.params.length === 0) { + tokenBeforeParams = sourceCode.getFirstToken(node.value, astUtils.isOpeningParenToken); + } else { + tokenBeforeParams = sourceCode.getTokenBefore(node.value.params[0]); + } + + if (node.value.params.length === 1) { + const hasParen = astUtils.isOpeningParenToken(tokenBeforeParams); + const isTokenOutsideNode = tokenBeforeParams.range[0] < node.range[0]; + + shouldAddParensAroundParameters = !hasParen || isTokenOutsideNode; + } + + const sliceStart = shouldAddParensAroundParameters + ? node.value.params[0].range[0] + : tokenBeforeParams.range[0]; + const sliceEnd = sourceCode.getTokenBefore(arrowToken).range[1]; + + const oldParamText = sourceCode.text.slice(sliceStart, sliceEnd); + const newParamText = shouldAddParensAroundParameters ? `(${oldParamText})` : oldParamText; + + return fixer.replaceTextRange( + fixRange, + methodPrefix + newParamText + fnBody + ); + + } + + /** + * Fixes a FunctionExpression node by making it into a longform property. + * @param {SourceCodeFixer} fixer The fixer object + * @param {ASTNode} node A `Property` node that has a `FunctionExpression` as its value + * @returns {Object} A fix for this node + */ + function makeFunctionLongform(fixer, node) { + const firstKeyToken = node.computed ? sourceCode.getTokens(node).find(token => token.value === "[") : sourceCode.getFirstToken(node.key); + const lastKeyToken = node.computed ? sourceCode.getTokensBetween(node.key, node.value).find(token => token.value === "]") : sourceCode.getLastToken(node.key); + const keyText = sourceCode.text.slice(firstKeyToken.range[0], lastKeyToken.range[1]); + let functionHeader = "function"; + + if (node.value.async) { + functionHeader = `async ${functionHeader}`; + } + if (node.value.generator) { + functionHeader = `${functionHeader}*`; + } + + return fixer.replaceTextRange([node.range[0], lastKeyToken.range[1]], `${keyText}: ${functionHeader}`); + } + + /* + * To determine whether a given arrow function has a lexical identifier (`this`, `arguments`, `super`, or `new.target`), + * create a stack of functions that define these identifiers (i.e. all functions except arrow functions) as the AST is + * traversed. Whenever a new function is encountered, create a new entry on the stack (corresponding to a different lexical + * scope of `this`), and whenever a function is exited, pop that entry off the stack. When an arrow function is entered, + * keep a reference to it on the current stack entry, and remove that reference when the arrow function is exited. + * When a lexical identifier is encountered, mark all the arrow functions on the current stack entry by adding them + * to an `arrowsWithLexicalIdentifiers` set. Any arrow function in that set will not be reported by this rule, + * because converting it into a method would change the value of one of the lexical identifiers. + */ + const lexicalScopeStack = []; + const arrowsWithLexicalIdentifiers = new WeakSet(); + const argumentsIdentifiers = new WeakSet(); + + /** + * Enters a function. This creates a new lexical identifier scope, so a new Set of arrow functions is pushed onto the stack. + * Also, this marks all `arguments` identifiers so that they can be detected later. + * @returns {void} + */ + function enterFunction() { + lexicalScopeStack.unshift(new Set()); + context.getScope().variables.filter(variable => variable.name === "arguments").forEach(variable => { + variable.references.map(ref => ref.identifier).forEach(identifier => argumentsIdentifiers.add(identifier)); + }); + } + + /** + * Exits a function. This pops the current set of arrow functions off the lexical scope stack. + * @returns {void} + */ + function exitFunction() { + lexicalScopeStack.shift(); + } + + /** + * Marks the current function as having a lexical keyword. This implies that all arrow functions + * in the current lexical scope contain a reference to this lexical keyword. + * @returns {void} + */ + function reportLexicalIdentifier() { + lexicalScopeStack[0].forEach(arrowFunction => arrowsWithLexicalIdentifiers.add(arrowFunction)); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + Program: enterFunction, + FunctionDeclaration: enterFunction, + FunctionExpression: enterFunction, + "Program:exit": exitFunction, + "FunctionDeclaration:exit": exitFunction, + "FunctionExpression:exit": exitFunction, + + ArrowFunctionExpression(node) { + lexicalScopeStack[0].add(node); + }, + "ArrowFunctionExpression:exit"(node) { + lexicalScopeStack[0].delete(node); + }, + + ThisExpression: reportLexicalIdentifier, + Super: reportLexicalIdentifier, + MetaProperty(node) { + if (node.meta.name === "new" && node.property.name === "target") { + reportLexicalIdentifier(); + } + }, + Identifier(node) { + if (argumentsIdentifiers.has(node)) { + reportLexicalIdentifier(); + } + }, + + ObjectExpression(node) { + if (APPLY_CONSISTENT) { + checkConsistency(node, false); + } else if (APPLY_CONSISTENT_AS_NEEDED) { + checkConsistency(node, true); + } + }, + + "Property:exit"(node) { + const isConciseProperty = node.method || node.shorthand; + + // Ignore destructuring assignment + if (node.parent.type === "ObjectPattern") { + return; + } + + // getters and setters are ignored + if (node.kind === "get" || node.kind === "set") { + return; + } + + // only computed methods can fail the following checks + if (node.computed && node.value.type !== "FunctionExpression" && node.value.type !== "ArrowFunctionExpression") { + return; + } + + //-------------------------------------------------------------- + // Checks for property/method shorthand. + if (isConciseProperty) { + if (node.method && (APPLY_NEVER || AVOID_QUOTES && isStringLiteral(node.key))) { + const messageId = APPLY_NEVER ? "expectedMethodLongform" : "expectedLiteralMethodLongform"; + + // { x() {} } should be written as { x: function() {} } + context.report({ + node, + messageId, + fix: fixer => makeFunctionLongform(fixer, node) + }); + } else if (APPLY_NEVER) { + + // { x } should be written as { x: x } + context.report({ + node, + messageId: "expectedPropertyLongform", + fix: fixer => fixer.insertTextAfter(node.key, `: ${node.key.name}`) + }); + } + } else if (APPLY_TO_METHODS && !node.value.id && (node.value.type === "FunctionExpression" || node.value.type === "ArrowFunctionExpression")) { + if (IGNORE_CONSTRUCTORS && node.key.type === "Identifier" && isConstructor(node.key.name)) { + return; + } + if (AVOID_QUOTES && isStringLiteral(node.key)) { + return; + } + + // {[x]: function(){}} should be written as {[x]() {}} + if (node.value.type === "FunctionExpression" || + node.value.type === "ArrowFunctionExpression" && + node.value.body.type === "BlockStatement" && + AVOID_EXPLICIT_RETURN_ARROWS && + !arrowsWithLexicalIdentifiers.has(node.value) + ) { + context.report({ + node, + messageId: "expectedMethodShorthand", + fix: fixer => makeFunctionShorthand(fixer, node) + }); + } + } else if (node.value.type === "Identifier" && node.key.name === node.value.name && APPLY_TO_PROPS) { + + // {x: x} should be written as {x} + context.report({ + node, + messageId: "expectedPropertyShorthand", + fix(fixer) { + return fixer.replaceText(node, node.value.name); + } + }); + } else if (node.value.type === "Identifier" && node.key.type === "Literal" && node.key.value === node.value.name && APPLY_TO_PROPS) { + if (AVOID_QUOTES) { + return; + } + + // {"x": x} should be written as {x} + context.report({ + node, + messageId: "expectedPropertyShorthand", + fix(fixer) { + return fixer.replaceText(node, node.value.name); + } + }); + } + } + }; + } +}; diff --git a/eslint/lib/rules/one-var-declaration-per-line.js b/eslint/lib/rules/one-var-declaration-per-line.js new file mode 100644 index 0000000..30ca2cf --- /dev/null +++ b/eslint/lib/rules/one-var-declaration-per-line.js @@ -0,0 +1,92 @@ +/** + * @fileoverview Rule to check multiple var declarations per line + * @author Alberto Rodríguez + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "require or disallow newlines around variable declarations", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/one-var-declaration-per-line" + }, + + schema: [ + { + enum: ["always", "initializations"] + } + ], + + fixable: "whitespace", + + messages: { + expectVarOnNewline: "Expected variable declaration to be on a new line." + } + }, + + create(context) { + + const always = context.options[0] === "always"; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + + /** + * Determine if provided keyword is a variant of for specifiers + * @private + * @param {string} keyword keyword to test + * @returns {boolean} True if `keyword` is a variant of for specifier + */ + function isForTypeSpecifier(keyword) { + return keyword === "ForStatement" || keyword === "ForInStatement" || keyword === "ForOfStatement"; + } + + /** + * Checks newlines around variable declarations. + * @private + * @param {ASTNode} node `VariableDeclaration` node to test + * @returns {void} + */ + function checkForNewLine(node) { + if (isForTypeSpecifier(node.parent.type)) { + return; + } + + const declarations = node.declarations; + let prev; + + declarations.forEach(current => { + if (prev && prev.loc.end.line === current.loc.start.line) { + if (always || prev.init || current.init) { + context.report({ + node, + messageId: "expectVarOnNewline", + loc: current.loc.start, + fix: fixer => fixer.insertTextBefore(current, "\n") + }); + } + } + prev = current; + }); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + VariableDeclaration: checkForNewLine + }; + + } +}; diff --git a/eslint/lib/rules/one-var.js b/eslint/lib/rules/one-var.js new file mode 100644 index 0000000..c31a0d2 --- /dev/null +++ b/eslint/lib/rules/one-var.js @@ -0,0 +1,535 @@ +/** + * @fileoverview A rule to control the use of single variable declarations. + * @author Ian Christian Myers + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "enforce variables to be declared either together or separately in functions", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/one-var" + }, + + fixable: "code", + + schema: [ + { + oneOf: [ + { + enum: ["always", "never", "consecutive"] + }, + { + type: "object", + properties: { + separateRequires: { + type: "boolean" + }, + var: { + enum: ["always", "never", "consecutive"] + }, + let: { + enum: ["always", "never", "consecutive"] + }, + const: { + enum: ["always", "never", "consecutive"] + } + }, + additionalProperties: false + }, + { + type: "object", + properties: { + initialized: { + enum: ["always", "never", "consecutive"] + }, + uninitialized: { + enum: ["always", "never", "consecutive"] + } + }, + additionalProperties: false + } + ] + } + ], + + messages: { + combineUninitialized: "Combine this with the previous '{{type}}' statement with uninitialized variables.", + combineInitialized: "Combine this with the previous '{{type}}' statement with initialized variables.", + splitUninitialized: "Split uninitialized '{{type}}' declarations into multiple statements.", + splitInitialized: "Split initialized '{{type}}' declarations into multiple statements.", + splitRequires: "Split requires to be separated into a single block.", + combine: "Combine this with the previous '{{type}}' statement.", + split: "Split '{{type}}' declarations into multiple statements." + } + }, + + create(context) { + const MODE_ALWAYS = "always"; + const MODE_NEVER = "never"; + const MODE_CONSECUTIVE = "consecutive"; + const mode = context.options[0] || MODE_ALWAYS; + + const options = {}; + + if (typeof mode === "string") { // simple options configuration with just a string + options.var = { uninitialized: mode, initialized: mode }; + options.let = { uninitialized: mode, initialized: mode }; + options.const = { uninitialized: mode, initialized: mode }; + } else if (typeof mode === "object") { // options configuration is an object + options.separateRequires = !!mode.separateRequires; + options.var = { uninitialized: mode.var, initialized: mode.var }; + options.let = { uninitialized: mode.let, initialized: mode.let }; + options.const = { uninitialized: mode.const, initialized: mode.const }; + if (Object.prototype.hasOwnProperty.call(mode, "uninitialized")) { + options.var.uninitialized = mode.uninitialized; + options.let.uninitialized = mode.uninitialized; + options.const.uninitialized = mode.uninitialized; + } + if (Object.prototype.hasOwnProperty.call(mode, "initialized")) { + options.var.initialized = mode.initialized; + options.let.initialized = mode.initialized; + options.const.initialized = mode.initialized; + } + } + + const sourceCode = context.getSourceCode(); + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + const functionStack = []; + const blockStack = []; + + /** + * Increments the blockStack counter. + * @returns {void} + * @private + */ + function startBlock() { + blockStack.push({ + let: { initialized: false, uninitialized: false }, + const: { initialized: false, uninitialized: false } + }); + } + + /** + * Increments the functionStack counter. + * @returns {void} + * @private + */ + function startFunction() { + functionStack.push({ initialized: false, uninitialized: false }); + startBlock(); + } + + /** + * Decrements the blockStack counter. + * @returns {void} + * @private + */ + function endBlock() { + blockStack.pop(); + } + + /** + * Decrements the functionStack counter. + * @returns {void} + * @private + */ + function endFunction() { + functionStack.pop(); + endBlock(); + } + + /** + * Check if a variable declaration is a require. + * @param {ASTNode} decl variable declaration Node + * @returns {bool} if decl is a require, return true; else return false. + * @private + */ + function isRequire(decl) { + return decl.init && decl.init.type === "CallExpression" && decl.init.callee.name === "require"; + } + + /** + * Records whether initialized/uninitialized/required variables are defined in current scope. + * @param {string} statementType node.kind, one of: "var", "let", or "const" + * @param {ASTNode[]} declarations List of declarations + * @param {Object} currentScope The scope being investigated + * @returns {void} + * @private + */ + function recordTypes(statementType, declarations, currentScope) { + for (let i = 0; i < declarations.length; i++) { + if (declarations[i].init === null) { + if (options[statementType] && options[statementType].uninitialized === MODE_ALWAYS) { + currentScope.uninitialized = true; + } + } else { + if (options[statementType] && options[statementType].initialized === MODE_ALWAYS) { + if (options.separateRequires && isRequire(declarations[i])) { + currentScope.required = true; + } else { + currentScope.initialized = true; + } + } + } + } + } + + /** + * Determines the current scope (function or block) + * @param {string} statementType node.kind, one of: "var", "let", or "const" + * @returns {Object} The scope associated with statementType + */ + function getCurrentScope(statementType) { + let currentScope; + + if (statementType === "var") { + currentScope = functionStack[functionStack.length - 1]; + } else if (statementType === "let") { + currentScope = blockStack[blockStack.length - 1].let; + } else if (statementType === "const") { + currentScope = blockStack[blockStack.length - 1].const; + } + return currentScope; + } + + /** + * Counts the number of initialized and uninitialized declarations in a list of declarations + * @param {ASTNode[]} declarations List of declarations + * @returns {Object} Counts of 'uninitialized' and 'initialized' declarations + * @private + */ + function countDeclarations(declarations) { + const counts = { uninitialized: 0, initialized: 0 }; + + for (let i = 0; i < declarations.length; i++) { + if (declarations[i].init === null) { + counts.uninitialized++; + } else { + counts.initialized++; + } + } + return counts; + } + + /** + * Determines if there is more than one var statement in the current scope. + * @param {string} statementType node.kind, one of: "var", "let", or "const" + * @param {ASTNode[]} declarations List of declarations + * @returns {boolean} Returns true if it is the first var declaration, false if not. + * @private + */ + function hasOnlyOneStatement(statementType, declarations) { + + const declarationCounts = countDeclarations(declarations); + const currentOptions = options[statementType] || {}; + const currentScope = getCurrentScope(statementType); + const hasRequires = declarations.some(isRequire); + + if (currentOptions.uninitialized === MODE_ALWAYS && currentOptions.initialized === MODE_ALWAYS) { + if (currentScope.uninitialized || currentScope.initialized) { + if (!hasRequires) { + return false; + } + } + } + + if (declarationCounts.uninitialized > 0) { + if (currentOptions.uninitialized === MODE_ALWAYS && currentScope.uninitialized) { + return false; + } + } + if (declarationCounts.initialized > 0) { + if (currentOptions.initialized === MODE_ALWAYS && currentScope.initialized) { + if (!hasRequires) { + return false; + } + } + } + if (currentScope.required && hasRequires) { + return false; + } + recordTypes(statementType, declarations, currentScope); + return true; + } + + /** + * Fixer to join VariableDeclaration's into a single declaration + * @param {VariableDeclarator[]} declarations The `VariableDeclaration` to join + * @returns {Function} The fixer function + */ + function joinDeclarations(declarations) { + const declaration = declarations[0]; + const body = Array.isArray(declaration.parent.parent.body) ? declaration.parent.parent.body : []; + const currentIndex = body.findIndex(node => node.range[0] === declaration.parent.range[0]); + const previousNode = body[currentIndex - 1]; + + return fixer => { + const type = sourceCode.getTokenBefore(declaration); + const prevSemi = sourceCode.getTokenBefore(type); + const res = []; + + if (previousNode && previousNode.kind === sourceCode.getText(type)) { + if (prevSemi.value === ";") { + res.push(fixer.replaceText(prevSemi, ",")); + } else { + res.push(fixer.insertTextAfter(prevSemi, ",")); + } + res.push(fixer.replaceText(type, "")); + } + + return res; + }; + } + + /** + * Fixer to split a VariableDeclaration into individual declarations + * @param {VariableDeclaration} declaration The `VariableDeclaration` to split + * @returns {Function} The fixer function + */ + function splitDeclarations(declaration) { + return fixer => declaration.declarations.map(declarator => { + const tokenAfterDeclarator = sourceCode.getTokenAfter(declarator); + + if (tokenAfterDeclarator === null) { + return null; + } + + const afterComma = sourceCode.getTokenAfter(tokenAfterDeclarator, { includeComments: true }); + + if (tokenAfterDeclarator.value !== ",") { + return null; + } + + /* + * `var x,y` + * tokenAfterDeclarator ^^ afterComma + */ + if (afterComma.range[0] === tokenAfterDeclarator.range[1]) { + return fixer.replaceText(tokenAfterDeclarator, `; ${declaration.kind} `); + } + + /* + * `var x, + * tokenAfterDeclarator ^ + * y` + * ^ afterComma + */ + if ( + afterComma.loc.start.line > tokenAfterDeclarator.loc.end.line || + afterComma.type === "Line" || + afterComma.type === "Block" + ) { + let lastComment = afterComma; + + while (lastComment.type === "Line" || lastComment.type === "Block") { + lastComment = sourceCode.getTokenAfter(lastComment, { includeComments: true }); + } + + return fixer.replaceTextRange( + [tokenAfterDeclarator.range[0], lastComment.range[0]], + `;${sourceCode.text.slice(tokenAfterDeclarator.range[1], lastComment.range[0])}${declaration.kind} ` + ); + } + + return fixer.replaceText(tokenAfterDeclarator, `; ${declaration.kind}`); + }).filter(x => x); + } + + /** + * Checks a given VariableDeclaration node for errors. + * @param {ASTNode} node The VariableDeclaration node to check + * @returns {void} + * @private + */ + function checkVariableDeclaration(node) { + const parent = node.parent; + const type = node.kind; + + if (!options[type]) { + return; + } + + const declarations = node.declarations; + const declarationCounts = countDeclarations(declarations); + const mixedRequires = declarations.some(isRequire) && !declarations.every(isRequire); + + if (options[type].initialized === MODE_ALWAYS) { + if (options.separateRequires && mixedRequires) { + context.report({ + node, + messageId: "splitRequires" + }); + } + } + + // consecutive + const nodeIndex = (parent.body && parent.body.length > 0 && parent.body.indexOf(node)) || 0; + + if (nodeIndex > 0) { + const previousNode = parent.body[nodeIndex - 1]; + const isPreviousNodeDeclaration = previousNode.type === "VariableDeclaration"; + const declarationsWithPrevious = declarations.concat(previousNode.declarations || []); + + if ( + isPreviousNodeDeclaration && + previousNode.kind === type && + !(declarationsWithPrevious.some(isRequire) && !declarationsWithPrevious.every(isRequire)) + ) { + const previousDeclCounts = countDeclarations(previousNode.declarations); + + if (options[type].initialized === MODE_CONSECUTIVE && options[type].uninitialized === MODE_CONSECUTIVE) { + context.report({ + node, + messageId: "combine", + data: { + type + }, + fix: joinDeclarations(declarations) + }); + } else if (options[type].initialized === MODE_CONSECUTIVE && declarationCounts.initialized > 0 && previousDeclCounts.initialized > 0) { + context.report({ + node, + messageId: "combineInitialized", + data: { + type + }, + fix: joinDeclarations(declarations) + }); + } else if (options[type].uninitialized === MODE_CONSECUTIVE && + declarationCounts.uninitialized > 0 && + previousDeclCounts.uninitialized > 0) { + context.report({ + node, + messageId: "combineUninitialized", + data: { + type + }, + fix: joinDeclarations(declarations) + }); + } + } + } + + // always + if (!hasOnlyOneStatement(type, declarations)) { + if (options[type].initialized === MODE_ALWAYS && options[type].uninitialized === MODE_ALWAYS) { + context.report({ + node, + messageId: "combine", + data: { + type + }, + fix: joinDeclarations(declarations) + }); + } else { + if (options[type].initialized === MODE_ALWAYS && declarationCounts.initialized > 0) { + context.report({ + node, + messageId: "combineInitialized", + data: { + type + }, + fix: joinDeclarations(declarations) + }); + } + if (options[type].uninitialized === MODE_ALWAYS && declarationCounts.uninitialized > 0) { + if (node.parent.left === node && (node.parent.type === "ForInStatement" || node.parent.type === "ForOfStatement")) { + return; + } + context.report({ + node, + messageId: "combineUninitialized", + data: { + type + }, + fix: joinDeclarations(declarations) + }); + } + } + } + + // never + if (parent.type !== "ForStatement" || parent.init !== node) { + const totalDeclarations = declarationCounts.uninitialized + declarationCounts.initialized; + + if (totalDeclarations > 1) { + if (options[type].initialized === MODE_NEVER && options[type].uninitialized === MODE_NEVER) { + + // both initialized and uninitialized + context.report({ + node, + messageId: "split", + data: { + type + }, + fix: splitDeclarations(node) + }); + } else if (options[type].initialized === MODE_NEVER && declarationCounts.initialized > 0) { + + // initialized + context.report({ + node, + messageId: "splitInitialized", + data: { + type + }, + fix: splitDeclarations(node) + }); + } else if (options[type].uninitialized === MODE_NEVER && declarationCounts.uninitialized > 0) { + + // uninitialized + context.report({ + node, + messageId: "splitUninitialized", + data: { + type + }, + fix: splitDeclarations(node) + }); + } + } + } + } + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + Program: startFunction, + FunctionDeclaration: startFunction, + FunctionExpression: startFunction, + ArrowFunctionExpression: startFunction, + BlockStatement: startBlock, + ForStatement: startBlock, + ForInStatement: startBlock, + ForOfStatement: startBlock, + SwitchStatement: startBlock, + VariableDeclaration: checkVariableDeclaration, + "ForStatement:exit": endBlock, + "ForOfStatement:exit": endBlock, + "ForInStatement:exit": endBlock, + "SwitchStatement:exit": endBlock, + "BlockStatement:exit": endBlock, + "Program:exit": endFunction, + "FunctionDeclaration:exit": endFunction, + "FunctionExpression:exit": endFunction, + "ArrowFunctionExpression:exit": endFunction + }; + + } +}; diff --git a/eslint/lib/rules/operator-assignment.js b/eslint/lib/rules/operator-assignment.js new file mode 100644 index 0000000..6820793 --- /dev/null +++ b/eslint/lib/rules/operator-assignment.js @@ -0,0 +1,243 @@ +/** + * @fileoverview Rule to replace assignment expressions with operator assignment + * @author Brandon Mills + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Checks whether an operator is commutative and has an operator assignment + * shorthand form. + * @param {string} operator Operator to check. + * @returns {boolean} True if the operator is commutative and has a + * shorthand form. + */ +function isCommutativeOperatorWithShorthand(operator) { + return ["*", "&", "^", "|"].indexOf(operator) >= 0; +} + +/** + * Checks whether an operator is not commutative and has an operator assignment + * shorthand form. + * @param {string} operator Operator to check. + * @returns {boolean} True if the operator is not commutative and has + * a shorthand form. + */ +function isNonCommutativeOperatorWithShorthand(operator) { + return ["+", "-", "/", "%", "<<", ">>", ">>>", "**"].indexOf(operator) >= 0; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +/** + * Checks whether two expressions reference the same value. For example: + * a = a + * a.b = a.b + * a[0] = a[0] + * a['b'] = a['b'] + * @param {ASTNode} a Left side of the comparison. + * @param {ASTNode} b Right side of the comparison. + * @returns {boolean} True if both sides match and reference the same value. + */ +function same(a, b) { + if (a.type !== b.type) { + return false; + } + + switch (a.type) { + case "Identifier": + return a.name === b.name; + + case "Literal": + return a.value === b.value; + + case "MemberExpression": + + /* + * x[0] = x[0] + * x[y] = x[y] + * x.y = x.y + */ + return same(a.object, b.object) && same(a.property, b.property); + + case "ThisExpression": + return true; + + default: + return false; + } +} + +/** + * Determines if the left side of a node can be safely fixed (i.e. if it activates the same getters/setters and) + * toString calls regardless of whether assignment shorthand is used) + * @param {ASTNode} node The node on the left side of the expression + * @returns {boolean} `true` if the node can be fixed + */ +function canBeFixed(node) { + return ( + node.type === "Identifier" || + ( + node.type === "MemberExpression" && + (node.object.type === "Identifier" || node.object.type === "ThisExpression") && + (!node.computed || node.property.type === "Literal") + ) + ); +} + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "require or disallow assignment operator shorthand where possible", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/operator-assignment" + }, + + schema: [ + { + enum: ["always", "never"] + } + ], + + fixable: "code", + messages: { + replaced: "Assignment can be replaced with operator assignment.", + unexpected: "Unexpected operator assignment shorthand." + } + }, + + create(context) { + + const sourceCode = context.getSourceCode(); + + /** + * Returns the operator token of an AssignmentExpression or BinaryExpression + * @param {ASTNode} node An AssignmentExpression or BinaryExpression node + * @returns {Token} The operator token in the node + */ + function getOperatorToken(node) { + return sourceCode.getFirstTokenBetween(node.left, node.right, token => token.value === node.operator); + } + + /** + * Ensures that an assignment uses the shorthand form where possible. + * @param {ASTNode} node An AssignmentExpression node. + * @returns {void} + */ + function verify(node) { + if (node.operator !== "=" || node.right.type !== "BinaryExpression") { + return; + } + + const left = node.left; + const expr = node.right; + const operator = expr.operator; + + if (isCommutativeOperatorWithShorthand(operator) || isNonCommutativeOperatorWithShorthand(operator)) { + if (same(left, expr.left)) { + context.report({ + node, + messageId: "replaced", + fix(fixer) { + if (canBeFixed(left)) { + const equalsToken = getOperatorToken(node); + const operatorToken = getOperatorToken(expr); + const leftText = sourceCode.getText().slice(node.range[0], equalsToken.range[0]); + const rightText = sourceCode.getText().slice(operatorToken.range[1], node.right.range[1]); + + // Check for comments that would be removed. + if (sourceCode.commentsExistBetween(equalsToken, operatorToken)) { + return null; + } + + return fixer.replaceText(node, `${leftText}${expr.operator}=${rightText}`); + } + return null; + } + }); + } else if (same(left, expr.right) && isCommutativeOperatorWithShorthand(operator)) { + + /* + * This case can't be fixed safely. + * If `a` and `b` both have custom valueOf() behavior, then fixing `a = b * a` to `a *= b` would + * change the execution order of the valueOf() functions. + */ + context.report({ + node, + messageId: "replaced" + }); + } + } + } + + /** + * Warns if an assignment expression uses operator assignment shorthand. + * @param {ASTNode} node An AssignmentExpression node. + * @returns {void} + */ + function prohibit(node) { + if (node.operator !== "=") { + context.report({ + node, + messageId: "unexpected", + fix(fixer) { + if (canBeFixed(node.left)) { + const firstToken = sourceCode.getFirstToken(node); + const operatorToken = getOperatorToken(node); + const leftText = sourceCode.getText().slice(node.range[0], operatorToken.range[0]); + const newOperator = node.operator.slice(0, -1); + let rightText; + + // Check for comments that would be duplicated. + if (sourceCode.commentsExistBetween(firstToken, operatorToken)) { + return null; + } + + // If this change would modify precedence (e.g. `foo *= bar + 1` => `foo = foo * (bar + 1)`), parenthesize the right side. + if ( + astUtils.getPrecedence(node.right) <= astUtils.getPrecedence({ type: "BinaryExpression", operator: newOperator }) && + !astUtils.isParenthesised(sourceCode, node.right) + ) { + rightText = `${sourceCode.text.slice(operatorToken.range[1], node.right.range[0])}(${sourceCode.getText(node.right)})`; + } else { + const tokenAfterOperator = sourceCode.getTokenAfter(operatorToken, { includeComments: true }); + let rightTextPrefix = ""; + + if ( + operatorToken.range[1] === tokenAfterOperator.range[0] && + !astUtils.canTokensBeAdjacent({ type: "Punctuator", value: newOperator }, tokenAfterOperator) + ) { + rightTextPrefix = " "; // foo+=+bar -> foo= foo+ +bar + } + + rightText = `${rightTextPrefix}${sourceCode.text.slice(operatorToken.range[1], node.range[1])}`; + } + + return fixer.replaceText(node, `${leftText}= ${leftText}${newOperator}${rightText}`); + } + return null; + } + }); + } + } + + return { + AssignmentExpression: context.options[0] !== "never" ? verify : prohibit + }; + + } +}; diff --git a/eslint/lib/rules/operator-linebreak.js b/eslint/lib/rules/operator-linebreak.js new file mode 100644 index 0000000..3395fea --- /dev/null +++ b/eslint/lib/rules/operator-linebreak.js @@ -0,0 +1,250 @@ +/** + * @fileoverview Operator linebreak - enforces operator linebreak style of two types: after and before + * @author Benoît Zugmeyer + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "enforce consistent linebreak style for operators", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/operator-linebreak" + }, + + schema: [ + { + enum: ["after", "before", "none", null] + }, + { + type: "object", + properties: { + overrides: { + type: "object", + properties: { + anyOf: { + type: "string", + enum: ["after", "before", "none", "ignore"] + } + } + } + }, + additionalProperties: false + } + ], + + fixable: "code", + + messages: { + operatorAtBeginning: "'{{operator}}' should be placed at the beginning of the line.", + operatorAtEnd: "'{{operator}}' should be placed at the end of the line.", + badLinebreak: "Bad line breaking before and after '{{operator}}'.", + noLinebreak: "There should be no line break before or after '{{operator}}'." + } + }, + + create(context) { + + const usedDefaultGlobal = !context.options[0]; + const globalStyle = context.options[0] || "after"; + const options = context.options[1] || {}; + const styleOverrides = options.overrides ? Object.assign({}, options.overrides) : {}; + + if (usedDefaultGlobal && !styleOverrides["?"]) { + styleOverrides["?"] = "before"; + } + + if (usedDefaultGlobal && !styleOverrides[":"]) { + styleOverrides[":"] = "before"; + } + + const sourceCode = context.getSourceCode(); + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Gets a fixer function to fix rule issues + * @param {Token} operatorToken The operator token of an expression + * @param {string} desiredStyle The style for the rule. One of 'before', 'after', 'none' + * @returns {Function} A fixer function + */ + function getFixer(operatorToken, desiredStyle) { + return fixer => { + const tokenBefore = sourceCode.getTokenBefore(operatorToken); + const tokenAfter = sourceCode.getTokenAfter(operatorToken); + const textBefore = sourceCode.text.slice(tokenBefore.range[1], operatorToken.range[0]); + const textAfter = sourceCode.text.slice(operatorToken.range[1], tokenAfter.range[0]); + const hasLinebreakBefore = !astUtils.isTokenOnSameLine(tokenBefore, operatorToken); + const hasLinebreakAfter = !astUtils.isTokenOnSameLine(operatorToken, tokenAfter); + let newTextBefore, newTextAfter; + + if (hasLinebreakBefore !== hasLinebreakAfter && desiredStyle !== "none") { + + // If there is a comment before and after the operator, don't do a fix. + if (sourceCode.getTokenBefore(operatorToken, { includeComments: true }) !== tokenBefore && + sourceCode.getTokenAfter(operatorToken, { includeComments: true }) !== tokenAfter) { + + return null; + } + + /* + * If there is only one linebreak and it's on the wrong side of the operator, swap the text before and after the operator. + * foo && + * bar + * would get fixed to + * foo + * && bar + */ + newTextBefore = textAfter; + newTextAfter = textBefore; + } else { + const LINEBREAK_REGEX = astUtils.createGlobalLinebreakMatcher(); + + // Otherwise, if no linebreak is desired and no comments interfere, replace the linebreaks with empty strings. + newTextBefore = desiredStyle === "before" || textBefore.trim() ? textBefore : textBefore.replace(LINEBREAK_REGEX, ""); + newTextAfter = desiredStyle === "after" || textAfter.trim() ? textAfter : textAfter.replace(LINEBREAK_REGEX, ""); + + // If there was no change (due to interfering comments), don't output a fix. + if (newTextBefore === textBefore && newTextAfter === textAfter) { + return null; + } + } + + if (newTextAfter === "" && tokenAfter.type === "Punctuator" && "+-".includes(operatorToken.value) && tokenAfter.value === operatorToken.value) { + + // To avoid accidentally creating a ++ or -- operator, insert a space if the operator is a +/- and the following token is a unary +/-. + newTextAfter += " "; + } + + return fixer.replaceTextRange([tokenBefore.range[1], tokenAfter.range[0]], newTextBefore + operatorToken.value + newTextAfter); + }; + } + + /** + * Checks the operator placement + * @param {ASTNode} node The node to check + * @param {ASTNode} leftSide The node that comes before the operator in `node` + * @private + * @returns {void} + */ + function validateNode(node, leftSide) { + + /* + * When the left part of a binary expression is a single expression wrapped in + * parentheses (ex: `(a) + b`), leftToken will be the last token of the expression + * and operatorToken will be the closing parenthesis. + * The leftToken should be the last closing parenthesis, and the operatorToken + * should be the token right after that. + */ + const operatorToken = sourceCode.getTokenAfter(leftSide, astUtils.isNotClosingParenToken); + const leftToken = sourceCode.getTokenBefore(operatorToken); + const rightToken = sourceCode.getTokenAfter(operatorToken); + const operator = operatorToken.value; + const operatorStyleOverride = styleOverrides[operator]; + const style = operatorStyleOverride || globalStyle; + const fix = getFixer(operatorToken, style); + + // if single line + if (astUtils.isTokenOnSameLine(leftToken, operatorToken) && + astUtils.isTokenOnSameLine(operatorToken, rightToken)) { + + // do nothing. + + } else if (operatorStyleOverride !== "ignore" && !astUtils.isTokenOnSameLine(leftToken, operatorToken) && + !astUtils.isTokenOnSameLine(operatorToken, rightToken)) { + + // lone operator + context.report({ + node, + loc: operatorToken.loc, + messageId: "badLinebreak", + data: { + operator + }, + fix + }); + + } else if (style === "before" && astUtils.isTokenOnSameLine(leftToken, operatorToken)) { + + context.report({ + node, + loc: operatorToken.loc, + messageId: "operatorAtBeginning", + data: { + operator + }, + fix + }); + + } else if (style === "after" && astUtils.isTokenOnSameLine(operatorToken, rightToken)) { + + context.report({ + node, + loc: operatorToken.loc, + messageId: "operatorAtEnd", + data: { + operator + }, + fix + }); + + } else if (style === "none") { + + context.report({ + node, + loc: operatorToken.loc, + messageId: "noLinebreak", + data: { + operator + }, + fix + }); + + } + } + + /** + * Validates a binary expression using `validateNode` + * @param {BinaryExpression|LogicalExpression|AssignmentExpression} node node to be validated + * @returns {void} + */ + function validateBinaryExpression(node) { + validateNode(node, node.left); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + BinaryExpression: validateBinaryExpression, + LogicalExpression: validateBinaryExpression, + AssignmentExpression: validateBinaryExpression, + VariableDeclarator(node) { + if (node.init) { + validateNode(node, node.id); + } + }, + ConditionalExpression(node) { + validateNode(node, node.test); + validateNode(node, node.consequent); + } + }; + } +}; diff --git a/eslint/lib/rules/padded-blocks.js b/eslint/lib/rules/padded-blocks.js new file mode 100644 index 0000000..f58a753 --- /dev/null +++ b/eslint/lib/rules/padded-blocks.js @@ -0,0 +1,284 @@ +/** + * @fileoverview A rule to ensure blank lines within blocks. + * @author Mathias Schreck + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "require or disallow padding within blocks", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/padded-blocks" + }, + + fixable: "whitespace", + + schema: [ + { + oneOf: [ + { + enum: ["always", "never"] + }, + { + type: "object", + properties: { + blocks: { + enum: ["always", "never"] + }, + switches: { + enum: ["always", "never"] + }, + classes: { + enum: ["always", "never"] + } + }, + additionalProperties: false, + minProperties: 1 + } + ] + }, + { + type: "object", + properties: { + allowSingleLineBlocks: { + type: "boolean" + } + } + } + ], + + messages: { + alwaysPadBlock: "Block must be padded by blank lines.", + neverPadBlock: "Block must not be padded by blank lines." + } + }, + + create(context) { + const options = {}; + const typeOptions = context.options[0] || "always"; + const exceptOptions = context.options[1] || {}; + + if (typeof typeOptions === "string") { + const shouldHavePadding = typeOptions === "always"; + + options.blocks = shouldHavePadding; + options.switches = shouldHavePadding; + options.classes = shouldHavePadding; + } else { + if (Object.prototype.hasOwnProperty.call(typeOptions, "blocks")) { + options.blocks = typeOptions.blocks === "always"; + } + if (Object.prototype.hasOwnProperty.call(typeOptions, "switches")) { + options.switches = typeOptions.switches === "always"; + } + if (Object.prototype.hasOwnProperty.call(typeOptions, "classes")) { + options.classes = typeOptions.classes === "always"; + } + } + + if (Object.prototype.hasOwnProperty.call(exceptOptions, "allowSingleLineBlocks")) { + options.allowSingleLineBlocks = exceptOptions.allowSingleLineBlocks === true; + } + + const sourceCode = context.getSourceCode(); + + /** + * Gets the open brace token from a given node. + * @param {ASTNode} node A BlockStatement or SwitchStatement node from which to get the open brace. + * @returns {Token} The token of the open brace. + */ + function getOpenBrace(node) { + if (node.type === "SwitchStatement") { + return sourceCode.getTokenBefore(node.cases[0]); + } + return sourceCode.getFirstToken(node); + } + + /** + * Checks if the given parameter is a comment node + * @param {ASTNode|Token} node An AST node or token + * @returns {boolean} True if node is a comment + */ + function isComment(node) { + return node.type === "Line" || node.type === "Block"; + } + + /** + * Checks if there is padding between two tokens + * @param {Token} first The first token + * @param {Token} second The second token + * @returns {boolean} True if there is at least a line between the tokens + */ + function isPaddingBetweenTokens(first, second) { + return second.loc.start.line - first.loc.end.line >= 2; + } + + + /** + * Checks if the given token has a blank line after it. + * @param {Token} token The token to check. + * @returns {boolean} Whether or not the token is followed by a blank line. + */ + function getFirstBlockToken(token) { + let prev, + first = token; + + do { + prev = first; + first = sourceCode.getTokenAfter(first, { includeComments: true }); + } while (isComment(first) && first.loc.start.line === prev.loc.end.line); + + return first; + } + + /** + * Checks if the given token is preceded by a blank line. + * @param {Token} token The token to check + * @returns {boolean} Whether or not the token is preceded by a blank line + */ + function getLastBlockToken(token) { + let last = token, + next; + + do { + next = last; + last = sourceCode.getTokenBefore(last, { includeComments: true }); + } while (isComment(last) && last.loc.end.line === next.loc.start.line); + + return last; + } + + /** + * Checks if a node should be padded, according to the rule config. + * @param {ASTNode} node The AST node to check. + * @returns {boolean} True if the node should be padded, false otherwise. + */ + function requirePaddingFor(node) { + switch (node.type) { + case "BlockStatement": + return options.blocks; + case "SwitchStatement": + return options.switches; + case "ClassBody": + return options.classes; + + /* istanbul ignore next */ + default: + throw new Error("unreachable"); + } + } + + /** + * Checks the given BlockStatement node to be padded if the block is not empty. + * @param {ASTNode} node The AST node of a BlockStatement. + * @returns {void} undefined. + */ + function checkPadding(node) { + const openBrace = getOpenBrace(node), + firstBlockToken = getFirstBlockToken(openBrace), + tokenBeforeFirst = sourceCode.getTokenBefore(firstBlockToken, { includeComments: true }), + closeBrace = sourceCode.getLastToken(node), + lastBlockToken = getLastBlockToken(closeBrace), + tokenAfterLast = sourceCode.getTokenAfter(lastBlockToken, { includeComments: true }), + blockHasTopPadding = isPaddingBetweenTokens(tokenBeforeFirst, firstBlockToken), + blockHasBottomPadding = isPaddingBetweenTokens(lastBlockToken, tokenAfterLast); + + if (options.allowSingleLineBlocks && astUtils.isTokenOnSameLine(tokenBeforeFirst, tokenAfterLast)) { + return; + } + + if (requirePaddingFor(node)) { + if (!blockHasTopPadding) { + context.report({ + node, + loc: { line: tokenBeforeFirst.loc.start.line, column: tokenBeforeFirst.loc.start.column }, + fix(fixer) { + return fixer.insertTextAfter(tokenBeforeFirst, "\n"); + }, + messageId: "alwaysPadBlock" + }); + } + if (!blockHasBottomPadding) { + context.report({ + node, + loc: { line: tokenAfterLast.loc.end.line, column: tokenAfterLast.loc.end.column - 1 }, + fix(fixer) { + return fixer.insertTextBefore(tokenAfterLast, "\n"); + }, + messageId: "alwaysPadBlock" + }); + } + } else { + if (blockHasTopPadding) { + + context.report({ + node, + loc: { line: tokenBeforeFirst.loc.start.line, column: tokenBeforeFirst.loc.start.column }, + fix(fixer) { + return fixer.replaceTextRange([tokenBeforeFirst.range[1], firstBlockToken.range[0] - firstBlockToken.loc.start.column], "\n"); + }, + messageId: "neverPadBlock" + }); + } + + if (blockHasBottomPadding) { + + context.report({ + node, + loc: { line: tokenAfterLast.loc.end.line, column: tokenAfterLast.loc.end.column - 1 }, + messageId: "neverPadBlock", + fix(fixer) { + return fixer.replaceTextRange([lastBlockToken.range[1], tokenAfterLast.range[0] - tokenAfterLast.loc.start.column], "\n"); + } + }); + } + } + } + + const rule = {}; + + if (Object.prototype.hasOwnProperty.call(options, "switches")) { + rule.SwitchStatement = function(node) { + if (node.cases.length === 0) { + return; + } + checkPadding(node); + }; + } + + if (Object.prototype.hasOwnProperty.call(options, "blocks")) { + rule.BlockStatement = function(node) { + if (node.body.length === 0) { + return; + } + checkPadding(node); + }; + } + + if (Object.prototype.hasOwnProperty.call(options, "classes")) { + rule.ClassBody = function(node) { + if (node.body.length === 0) { + return; + } + checkPadding(node); + }; + } + + return rule; + } +}; diff --git a/eslint/lib/rules/padding-line-between-statements.js b/eslint/lib/rules/padding-line-between-statements.js new file mode 100644 index 0000000..eea19f5 --- /dev/null +++ b/eslint/lib/rules/padding-line-between-statements.js @@ -0,0 +1,632 @@ +/** + * @fileoverview Rule to require or disallow newlines between statements + * @author Toru Nagashima + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const LT = `[${Array.from(astUtils.LINEBREAKS).join("")}]`; +const PADDING_LINE_SEQUENCE = new RegExp( + String.raw`^(\s*?${LT})\s*${LT}(\s*;?)$`, + "u" +); +const CJS_EXPORT = /^(?:module\s*\.\s*)?exports(?:\s*\.|\s*\[|$)/u; +const CJS_IMPORT = /^require\(/u; + +/** + * Creates tester which check if a node starts with specific keyword. + * @param {string} keyword The keyword to test. + * @returns {Object} the created tester. + * @private + */ +function newKeywordTester(keyword) { + return { + test: (node, sourceCode) => + sourceCode.getFirstToken(node).value === keyword + }; +} + +/** + * Creates tester which check if a node starts with specific keyword and spans a single line. + * @param {string} keyword The keyword to test. + * @returns {Object} the created tester. + * @private + */ +function newSinglelineKeywordTester(keyword) { + return { + test: (node, sourceCode) => + node.loc.start.line === node.loc.end.line && + sourceCode.getFirstToken(node).value === keyword + }; +} + +/** + * Creates tester which check if a node starts with specific keyword and spans multiple lines. + * @param {string} keyword The keyword to test. + * @returns {Object} the created tester. + * @private + */ +function newMultilineKeywordTester(keyword) { + return { + test: (node, sourceCode) => + node.loc.start.line !== node.loc.end.line && + sourceCode.getFirstToken(node).value === keyword + }; +} + +/** + * Creates tester which check if a node is specific type. + * @param {string} type The node type to test. + * @returns {Object} the created tester. + * @private + */ +function newNodeTypeTester(type) { + return { + test: node => + node.type === type + }; +} + +/** + * Checks the given node is an expression statement of IIFE. + * @param {ASTNode} node The node to check. + * @returns {boolean} `true` if the node is an expression statement of IIFE. + * @private + */ +function isIIFEStatement(node) { + if (node.type === "ExpressionStatement") { + let call = node.expression; + + if (call.type === "UnaryExpression") { + call = call.argument; + } + return call.type === "CallExpression" && astUtils.isFunction(call.callee); + } + return false; +} + +/** + * Checks whether the given node is a block-like statement. + * This checks the last token of the node is the closing brace of a block. + * @param {SourceCode} sourceCode The source code to get tokens. + * @param {ASTNode} node The node to check. + * @returns {boolean} `true` if the node is a block-like statement. + * @private + */ +function isBlockLikeStatement(sourceCode, node) { + + // do-while with a block is a block-like statement. + if (node.type === "DoWhileStatement" && node.body.type === "BlockStatement") { + return true; + } + + /* + * IIFE is a block-like statement specially from + * JSCS#disallowPaddingNewLinesAfterBlocks. + */ + if (isIIFEStatement(node)) { + return true; + } + + // Checks the last token is a closing brace of blocks. + const lastToken = sourceCode.getLastToken(node, astUtils.isNotSemicolonToken); + const belongingNode = lastToken && astUtils.isClosingBraceToken(lastToken) + ? sourceCode.getNodeByRangeIndex(lastToken.range[0]) + : null; + + return Boolean(belongingNode) && ( + belongingNode.type === "BlockStatement" || + belongingNode.type === "SwitchStatement" + ); +} + +/** + * Check whether the given node is a directive or not. + * @param {ASTNode} node The node to check. + * @param {SourceCode} sourceCode The source code object to get tokens. + * @returns {boolean} `true` if the node is a directive. + */ +function isDirective(node, sourceCode) { + return ( + node.type === "ExpressionStatement" && + ( + node.parent.type === "Program" || + ( + node.parent.type === "BlockStatement" && + astUtils.isFunction(node.parent.parent) + ) + ) && + node.expression.type === "Literal" && + typeof node.expression.value === "string" && + !astUtils.isParenthesised(sourceCode, node.expression) + ); +} + +/** + * Check whether the given node is a part of directive prologue or not. + * @param {ASTNode} node The node to check. + * @param {SourceCode} sourceCode The source code object to get tokens. + * @returns {boolean} `true` if the node is a part of directive prologue. + */ +function isDirectivePrologue(node, sourceCode) { + if (isDirective(node, sourceCode)) { + for (const sibling of node.parent.body) { + if (sibling === node) { + break; + } + if (!isDirective(sibling, sourceCode)) { + return false; + } + } + return true; + } + return false; +} + +/** + * Gets the actual last token. + * + * If a semicolon is semicolon-less style's semicolon, this ignores it. + * For example: + * + * foo() + * ;[1, 2, 3].forEach(bar) + * @param {SourceCode} sourceCode The source code to get tokens. + * @param {ASTNode} node The node to get. + * @returns {Token} The actual last token. + * @private + */ +function getActualLastToken(sourceCode, node) { + const semiToken = sourceCode.getLastToken(node); + const prevToken = sourceCode.getTokenBefore(semiToken); + const nextToken = sourceCode.getTokenAfter(semiToken); + const isSemicolonLessStyle = Boolean( + prevToken && + nextToken && + prevToken.range[0] >= node.range[0] && + astUtils.isSemicolonToken(semiToken) && + semiToken.loc.start.line !== prevToken.loc.end.line && + semiToken.loc.end.line === nextToken.loc.start.line + ); + + return isSemicolonLessStyle ? prevToken : semiToken; +} + +/** + * This returns the concatenation of the first 2 captured strings. + * @param {string} _ Unused. Whole matched string. + * @param {string} trailingSpaces The trailing spaces of the first line. + * @param {string} indentSpaces The indentation spaces of the last line. + * @returns {string} The concatenation of trailingSpaces and indentSpaces. + * @private + */ +function replacerToRemovePaddingLines(_, trailingSpaces, indentSpaces) { + return trailingSpaces + indentSpaces; +} + +/** + * Check and report statements for `any` configuration. + * It does nothing. + * @returns {void} + * @private + */ +function verifyForAny() { +} + +/** + * Check and report statements for `never` configuration. + * This autofix removes blank lines between the given 2 statements. + * However, if comments exist between 2 blank lines, it does not remove those + * blank lines automatically. + * @param {RuleContext} context The rule context to report. + * @param {ASTNode} _ Unused. The previous node to check. + * @param {ASTNode} nextNode The next node to check. + * @param {Array} paddingLines The array of token pairs that blank + * lines exist between the pair. + * @returns {void} + * @private + */ +function verifyForNever(context, _, nextNode, paddingLines) { + if (paddingLines.length === 0) { + return; + } + + context.report({ + node: nextNode, + messageId: "unexpectedBlankLine", + fix(fixer) { + if (paddingLines.length >= 2) { + return null; + } + + const prevToken = paddingLines[0][0]; + const nextToken = paddingLines[0][1]; + const start = prevToken.range[1]; + const end = nextToken.range[0]; + const text = context.getSourceCode().text + .slice(start, end) + .replace(PADDING_LINE_SEQUENCE, replacerToRemovePaddingLines); + + return fixer.replaceTextRange([start, end], text); + } + }); +} + +/** + * Check and report statements for `always` configuration. + * This autofix inserts a blank line between the given 2 statements. + * If the `prevNode` has trailing comments, it inserts a blank line after the + * trailing comments. + * @param {RuleContext} context The rule context to report. + * @param {ASTNode} prevNode The previous node to check. + * @param {ASTNode} nextNode The next node to check. + * @param {Array} paddingLines The array of token pairs that blank + * lines exist between the pair. + * @returns {void} + * @private + */ +function verifyForAlways(context, prevNode, nextNode, paddingLines) { + if (paddingLines.length > 0) { + return; + } + + context.report({ + node: nextNode, + messageId: "expectedBlankLine", + fix(fixer) { + const sourceCode = context.getSourceCode(); + let prevToken = getActualLastToken(sourceCode, prevNode); + const nextToken = sourceCode.getFirstTokenBetween( + prevToken, + nextNode, + { + includeComments: true, + + /** + * Skip the trailing comments of the previous node. + * This inserts a blank line after the last trailing comment. + * + * For example: + * + * foo(); // trailing comment. + * // comment. + * bar(); + * + * Get fixed to: + * + * foo(); // trailing comment. + * + * // comment. + * bar(); + * @param {Token} token The token to check. + * @returns {boolean} `true` if the token is not a trailing comment. + * @private + */ + filter(token) { + if (astUtils.isTokenOnSameLine(prevToken, token)) { + prevToken = token; + return false; + } + return true; + } + } + ) || nextNode; + const insertText = astUtils.isTokenOnSameLine(prevToken, nextToken) + ? "\n\n" + : "\n"; + + return fixer.insertTextAfter(prevToken, insertText); + } + }); +} + +/** + * Types of blank lines. + * `any`, `never`, and `always` are defined. + * Those have `verify` method to check and report statements. + * @private + */ +const PaddingTypes = { + any: { verify: verifyForAny }, + never: { verify: verifyForNever }, + always: { verify: verifyForAlways } +}; + +/** + * Types of statements. + * Those have `test` method to check it matches to the given statement. + * @private + */ +const StatementTypes = { + "*": { test: () => true }, + "block-like": { + test: (node, sourceCode) => isBlockLikeStatement(sourceCode, node) + }, + "cjs-export": { + test: (node, sourceCode) => + node.type === "ExpressionStatement" && + node.expression.type === "AssignmentExpression" && + CJS_EXPORT.test(sourceCode.getText(node.expression.left)) + }, + "cjs-import": { + test: (node, sourceCode) => + node.type === "VariableDeclaration" && + node.declarations.length > 0 && + Boolean(node.declarations[0].init) && + CJS_IMPORT.test(sourceCode.getText(node.declarations[0].init)) + }, + directive: { + test: isDirectivePrologue + }, + expression: { + test: (node, sourceCode) => + node.type === "ExpressionStatement" && + !isDirectivePrologue(node, sourceCode) + }, + iife: { + test: isIIFEStatement + }, + "multiline-block-like": { + test: (node, sourceCode) => + node.loc.start.line !== node.loc.end.line && + isBlockLikeStatement(sourceCode, node) + }, + "multiline-expression": { + test: (node, sourceCode) => + node.loc.start.line !== node.loc.end.line && + node.type === "ExpressionStatement" && + !isDirectivePrologue(node, sourceCode) + }, + + "multiline-const": newMultilineKeywordTester("const"), + "multiline-let": newMultilineKeywordTester("let"), + "multiline-var": newMultilineKeywordTester("var"), + "singleline-const": newSinglelineKeywordTester("const"), + "singleline-let": newSinglelineKeywordTester("let"), + "singleline-var": newSinglelineKeywordTester("var"), + + block: newNodeTypeTester("BlockStatement"), + empty: newNodeTypeTester("EmptyStatement"), + function: newNodeTypeTester("FunctionDeclaration"), + + break: newKeywordTester("break"), + case: newKeywordTester("case"), + class: newKeywordTester("class"), + const: newKeywordTester("const"), + continue: newKeywordTester("continue"), + debugger: newKeywordTester("debugger"), + default: newKeywordTester("default"), + do: newKeywordTester("do"), + export: newKeywordTester("export"), + for: newKeywordTester("for"), + if: newKeywordTester("if"), + import: newKeywordTester("import"), + let: newKeywordTester("let"), + return: newKeywordTester("return"), + switch: newKeywordTester("switch"), + throw: newKeywordTester("throw"), + try: newKeywordTester("try"), + var: newKeywordTester("var"), + while: newKeywordTester("while"), + with: newKeywordTester("with") +}; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "require or disallow padding lines between statements", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/padding-line-between-statements" + }, + + fixable: "whitespace", + + schema: { + definitions: { + paddingType: { + enum: Object.keys(PaddingTypes) + }, + statementType: { + anyOf: [ + { enum: Object.keys(StatementTypes) }, + { + type: "array", + items: { enum: Object.keys(StatementTypes) }, + minItems: 1, + uniqueItems: true, + additionalItems: false + } + ] + } + }, + type: "array", + items: { + type: "object", + properties: { + blankLine: { $ref: "#/definitions/paddingType" }, + prev: { $ref: "#/definitions/statementType" }, + next: { $ref: "#/definitions/statementType" } + }, + additionalProperties: false, + required: ["blankLine", "prev", "next"] + }, + additionalItems: false + }, + + messages: { + unexpectedBlankLine: "Unexpected blank line before this statement.", + expectedBlankLine: "Expected blank line before this statement." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + const configureList = context.options || []; + let scopeInfo = null; + + /** + * Processes to enter to new scope. + * This manages the current previous statement. + * @returns {void} + * @private + */ + function enterScope() { + scopeInfo = { + upper: scopeInfo, + prevNode: null + }; + } + + /** + * Processes to exit from the current scope. + * @returns {void} + * @private + */ + function exitScope() { + scopeInfo = scopeInfo.upper; + } + + /** + * Checks whether the given node matches the given type. + * @param {ASTNode} node The statement node to check. + * @param {string|string[]} type The statement type to check. + * @returns {boolean} `true` if the statement node matched the type. + * @private + */ + function match(node, type) { + let innerStatementNode = node; + + while (innerStatementNode.type === "LabeledStatement") { + innerStatementNode = innerStatementNode.body; + } + if (Array.isArray(type)) { + return type.some(match.bind(null, innerStatementNode)); + } + return StatementTypes[type].test(innerStatementNode, sourceCode); + } + + /** + * Finds the last matched configure from configureList. + * @param {ASTNode} prevNode The previous statement to match. + * @param {ASTNode} nextNode The current statement to match. + * @returns {Object} The tester of the last matched configure. + * @private + */ + function getPaddingType(prevNode, nextNode) { + for (let i = configureList.length - 1; i >= 0; --i) { + const configure = configureList[i]; + const matched = + match(prevNode, configure.prev) && + match(nextNode, configure.next); + + if (matched) { + return PaddingTypes[configure.blankLine]; + } + } + return PaddingTypes.any; + } + + /** + * Gets padding line sequences between the given 2 statements. + * Comments are separators of the padding line sequences. + * @param {ASTNode} prevNode The previous statement to count. + * @param {ASTNode} nextNode The current statement to count. + * @returns {Array} The array of token pairs. + * @private + */ + function getPaddingLineSequences(prevNode, nextNode) { + const pairs = []; + let prevToken = getActualLastToken(sourceCode, prevNode); + + if (nextNode.loc.start.line - prevToken.loc.end.line >= 2) { + do { + const token = sourceCode.getTokenAfter( + prevToken, + { includeComments: true } + ); + + if (token.loc.start.line - prevToken.loc.end.line >= 2) { + pairs.push([prevToken, token]); + } + prevToken = token; + + } while (prevToken.range[0] < nextNode.range[0]); + } + + return pairs; + } + + /** + * Verify padding lines between the given node and the previous node. + * @param {ASTNode} node The node to verify. + * @returns {void} + * @private + */ + function verify(node) { + const parentType = node.parent.type; + const validParent = + astUtils.STATEMENT_LIST_PARENTS.has(parentType) || + parentType === "SwitchStatement"; + + if (!validParent) { + return; + } + + // Save this node as the current previous statement. + const prevNode = scopeInfo.prevNode; + + // Verify. + if (prevNode) { + const type = getPaddingType(prevNode, node); + const paddingLines = getPaddingLineSequences(prevNode, node); + + type.verify(context, prevNode, node, paddingLines); + } + + scopeInfo.prevNode = node; + } + + /** + * Verify padding lines between the given node and the previous node. + * Then process to enter to new scope. + * @param {ASTNode} node The node to verify. + * @returns {void} + * @private + */ + function verifyThenEnterScope(node) { + verify(node); + enterScope(); + } + + return { + Program: enterScope, + BlockStatement: enterScope, + SwitchStatement: enterScope, + "Program:exit": exitScope, + "BlockStatement:exit": exitScope, + "SwitchStatement:exit": exitScope, + + ":statement": verify, + + SwitchCase: verifyThenEnterScope, + "SwitchCase:exit": exitScope + }; + } +}; diff --git a/eslint/lib/rules/prefer-arrow-callback.js b/eslint/lib/rules/prefer-arrow-callback.js new file mode 100644 index 0000000..d4e0251 --- /dev/null +++ b/eslint/lib/rules/prefer-arrow-callback.js @@ -0,0 +1,314 @@ +/** + * @fileoverview A rule to suggest using arrow functions as callbacks. + * @author Toru Nagashima + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Checks whether or not a given variable is a function name. + * @param {eslint-scope.Variable} variable A variable to check. + * @returns {boolean} `true` if the variable is a function name. + */ +function isFunctionName(variable) { + return variable && variable.defs[0].type === "FunctionName"; +} + +/** + * Checks whether or not a given MetaProperty node equals to a given value. + * @param {ASTNode} node A MetaProperty node to check. + * @param {string} metaName The name of `MetaProperty.meta`. + * @param {string} propertyName The name of `MetaProperty.property`. + * @returns {boolean} `true` if the node is the specific value. + */ +function checkMetaProperty(node, metaName, propertyName) { + return node.meta.name === metaName && node.property.name === propertyName; +} + +/** + * Gets the variable object of `arguments` which is defined implicitly. + * @param {eslint-scope.Scope} scope A scope to get. + * @returns {eslint-scope.Variable} The found variable object. + */ +function getVariableOfArguments(scope) { + const variables = scope.variables; + + for (let i = 0; i < variables.length; ++i) { + const variable = variables[i]; + + if (variable.name === "arguments") { + + /* + * If there was a parameter which is named "arguments", the + * implicit "arguments" is not defined. + * So does fast return with null. + */ + return (variable.identifiers.length === 0) ? variable : null; + } + } + + /* istanbul ignore next */ + return null; +} + +/** + * Checks whether or not a given node is a callback. + * @param {ASTNode} node A node to check. + * @returns {Object} + * {boolean} retv.isCallback - `true` if the node is a callback. + * {boolean} retv.isLexicalThis - `true` if the node is with `.bind(this)`. + */ +function getCallbackInfo(node) { + const retv = { isCallback: false, isLexicalThis: false }; + let currentNode = node; + let parent = node.parent; + + while (currentNode) { + switch (parent.type) { + + // Checks parents recursively. + + case "LogicalExpression": + case "ConditionalExpression": + break; + + // Checks whether the parent node is `.bind(this)` call. + case "MemberExpression": + if (parent.object === currentNode && + !parent.property.computed && + parent.property.type === "Identifier" && + parent.property.name === "bind" && + parent.parent.type === "CallExpression" && + parent.parent.callee === parent + ) { + retv.isLexicalThis = ( + parent.parent.arguments.length === 1 && + parent.parent.arguments[0].type === "ThisExpression" + ); + parent = parent.parent; + } else { + return retv; + } + break; + + // Checks whether the node is a callback. + case "CallExpression": + case "NewExpression": + if (parent.callee !== currentNode) { + retv.isCallback = true; + } + return retv; + + default: + return retv; + } + + currentNode = parent; + parent = parent.parent; + } + + /* istanbul ignore next */ + throw new Error("unreachable"); +} + +/** + * Checks whether a simple list of parameters contains any duplicates. This does not handle complex + * parameter lists (e.g. with destructuring), since complex parameter lists are a SyntaxError with duplicate + * parameter names anyway. Instead, it always returns `false` for complex parameter lists. + * @param {ASTNode[]} paramsList The list of parameters for a function + * @returns {boolean} `true` if the list of parameters contains any duplicates + */ +function hasDuplicateParams(paramsList) { + return paramsList.every(param => param.type === "Identifier") && paramsList.length !== new Set(paramsList.map(param => param.name)).size; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "require using arrow functions for callbacks", + category: "ECMAScript 6", + recommended: false, + url: "https://eslint.org/docs/rules/prefer-arrow-callback" + }, + + schema: [ + { + type: "object", + properties: { + allowNamedFunctions: { + type: "boolean", + default: false + }, + allowUnboundThis: { + type: "boolean", + default: true + } + }, + additionalProperties: false + } + ], + + fixable: "code", + + messages: { + preferArrowCallback: "Unexpected function expression." + } + }, + + create(context) { + const options = context.options[0] || {}; + + const allowUnboundThis = options.allowUnboundThis !== false; // default to true + const allowNamedFunctions = options.allowNamedFunctions; + const sourceCode = context.getSourceCode(); + + /* + * {Array<{this: boolean, super: boolean, meta: boolean}>} + * - this - A flag which shows there are one or more ThisExpression. + * - super - A flag which shows there are one or more Super. + * - meta - A flag which shows there are one or more MethProperty. + */ + let stack = []; + + /** + * Pushes new function scope with all `false` flags. + * @returns {void} + */ + function enterScope() { + stack.push({ this: false, super: false, meta: false }); + } + + /** + * Pops a function scope from the stack. + * @returns {{this: boolean, super: boolean, meta: boolean}} The information of the last scope. + */ + function exitScope() { + return stack.pop(); + } + + return { + + // Reset internal state. + Program() { + stack = []; + }, + + // If there are below, it cannot replace with arrow functions merely. + ThisExpression() { + const info = stack[stack.length - 1]; + + if (info) { + info.this = true; + } + }, + + Super() { + const info = stack[stack.length - 1]; + + if (info) { + info.super = true; + } + }, + + MetaProperty(node) { + const info = stack[stack.length - 1]; + + if (info && checkMetaProperty(node, "new", "target")) { + info.meta = true; + } + }, + + // To skip nested scopes. + FunctionDeclaration: enterScope, + "FunctionDeclaration:exit": exitScope, + + // Main. + FunctionExpression: enterScope, + "FunctionExpression:exit"(node) { + const scopeInfo = exitScope(); + + // Skip named function expressions + if (allowNamedFunctions && node.id && node.id.name) { + return; + } + + // Skip generators. + if (node.generator) { + return; + } + + // Skip recursive functions. + const nameVar = context.getDeclaredVariables(node)[0]; + + if (isFunctionName(nameVar) && nameVar.references.length > 0) { + return; + } + + // Skip if it's using arguments. + const variable = getVariableOfArguments(context.getScope()); + + if (variable && variable.references.length > 0) { + return; + } + + // Reports if it's a callback which can replace with arrows. + const callbackInfo = getCallbackInfo(node); + + if (callbackInfo.isCallback && + (!allowUnboundThis || !scopeInfo.this || callbackInfo.isLexicalThis) && + !scopeInfo.super && + !scopeInfo.meta + ) { + context.report({ + node, + messageId: "preferArrowCallback", + fix(fixer) { + if ((!callbackInfo.isLexicalThis && scopeInfo.this) || hasDuplicateParams(node.params)) { + + /* + * If the callback function does not have .bind(this) and contains a reference to `this`, there + * is no way to determine what `this` should be, so don't perform any fixes. + * If the callback function has duplicates in its list of parameters (possible in sloppy mode), + * don't replace it with an arrow function, because this is a SyntaxError with arrow functions. + */ + return null; + } + + const paramsLeftParen = node.params.length ? sourceCode.getTokenBefore(node.params[0]) : sourceCode.getTokenBefore(node.body, 1); + const paramsRightParen = sourceCode.getTokenBefore(node.body); + const asyncKeyword = node.async ? "async " : ""; + const paramsFullText = sourceCode.text.slice(paramsLeftParen.range[0], paramsRightParen.range[1]); + const arrowFunctionText = `${asyncKeyword}${paramsFullText} => ${sourceCode.getText(node.body)}`; + + /* + * If the callback function has `.bind(this)`, replace it with an arrow function and remove the binding. + * Otherwise, just replace the arrow function itself. + */ + const replacedNode = callbackInfo.isLexicalThis ? node.parent.parent : node; + + /* + * If the replaced node is part of a BinaryExpression, LogicalExpression, or MemberExpression, then + * the arrow function needs to be parenthesized, because `foo || () => {}` is invalid syntax even + * though `foo || function() {}` is valid. + */ + const needsParens = replacedNode.parent.type !== "CallExpression" && replacedNode.parent.type !== "ConditionalExpression"; + const replacementText = needsParens ? `(${arrowFunctionText})` : arrowFunctionText; + + return fixer.replaceText(replacedNode, replacementText); + } + }); + } + } + }; + } +}; diff --git a/eslint/lib/rules/prefer-const.js b/eslint/lib/rules/prefer-const.js new file mode 100644 index 0000000..439a4db --- /dev/null +++ b/eslint/lib/rules/prefer-const.js @@ -0,0 +1,476 @@ +/** + * @fileoverview A rule to suggest using of const declaration for variables that are never reassigned after declared. + * @author Toru Nagashima + */ + +"use strict"; + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const PATTERN_TYPE = /^(?:.+?Pattern|RestElement|SpreadProperty|ExperimentalRestProperty|Property)$/u; +const DECLARATION_HOST_TYPE = /^(?:Program|BlockStatement|SwitchCase)$/u; +const DESTRUCTURING_HOST_TYPE = /^(?:VariableDeclarator|AssignmentExpression)$/u; + +/** + * Checks whether a given node is located at `ForStatement.init` or not. + * @param {ASTNode} node A node to check. + * @returns {boolean} `true` if the node is located at `ForStatement.init`. + */ +function isInitOfForStatement(node) { + return node.parent.type === "ForStatement" && node.parent.init === node; +} + +/** + * Checks whether a given Identifier node becomes a VariableDeclaration or not. + * @param {ASTNode} identifier An Identifier node to check. + * @returns {boolean} `true` if the node can become a VariableDeclaration. + */ +function canBecomeVariableDeclaration(identifier) { + let node = identifier.parent; + + while (PATTERN_TYPE.test(node.type)) { + node = node.parent; + } + + return ( + node.type === "VariableDeclarator" || + ( + node.type === "AssignmentExpression" && + node.parent.type === "ExpressionStatement" && + DECLARATION_HOST_TYPE.test(node.parent.parent.type) + ) + ); +} + +/** + * Checks if an property or element is from outer scope or function parameters + * in destructing pattern. + * @param {string} name A variable name to be checked. + * @param {eslint-scope.Scope} initScope A scope to start find. + * @returns {boolean} Indicates if the variable is from outer scope or function parameters. + */ +function isOuterVariableInDestructing(name, initScope) { + + if (initScope.through.find(ref => ref.resolved && ref.resolved.name === name)) { + return true; + } + + const variable = astUtils.getVariableByName(initScope, name); + + if (variable !== null) { + return variable.defs.some(def => def.type === "Parameter"); + } + + return false; +} + +/** + * Gets the VariableDeclarator/AssignmentExpression node that a given reference + * belongs to. + * This is used to detect a mix of reassigned and never reassigned in a + * destructuring. + * @param {eslint-scope.Reference} reference A reference to get. + * @returns {ASTNode|null} A VariableDeclarator/AssignmentExpression node or + * null. + */ +function getDestructuringHost(reference) { + if (!reference.isWrite()) { + return null; + } + let node = reference.identifier.parent; + + while (PATTERN_TYPE.test(node.type)) { + node = node.parent; + } + + if (!DESTRUCTURING_HOST_TYPE.test(node.type)) { + return null; + } + return node; +} + +/** + * Determines if a destructuring assignment node contains + * any MemberExpression nodes. This is used to determine if a + * variable that is only written once using destructuring can be + * safely converted into a const declaration. + * @param {ASTNode} node The ObjectPattern or ArrayPattern node to check. + * @returns {boolean} True if the destructuring pattern contains + * a MemberExpression, false if not. + */ +function hasMemberExpressionAssignment(node) { + switch (node.type) { + case "ObjectPattern": + return node.properties.some(prop => { + if (prop) { + + /* + * Spread elements have an argument property while + * others have a value property. Because different + * parsers use different node types for spread elements, + * we just check if there is an argument property. + */ + return hasMemberExpressionAssignment(prop.argument || prop.value); + } + + return false; + }); + + case "ArrayPattern": + return node.elements.some(element => { + if (element) { + return hasMemberExpressionAssignment(element); + } + + return false; + }); + + case "AssignmentPattern": + return hasMemberExpressionAssignment(node.left); + + case "MemberExpression": + return true; + + // no default + } + + return false; +} + +/** + * Gets an identifier node of a given variable. + * + * If the initialization exists or one or more reading references exist before + * the first assignment, the identifier node is the node of the declaration. + * Otherwise, the identifier node is the node of the first assignment. + * + * If the variable should not change to const, this function returns null. + * - If the variable is reassigned. + * - If the variable is never initialized nor assigned. + * - If the variable is initialized in a different scope from the declaration. + * - If the unique assignment of the variable cannot change to a declaration. + * e.g. `if (a) b = 1` / `return (b = 1)` + * - If the variable is declared in the global scope and `eslintUsed` is `true`. + * `/*exported foo` directive comment makes such variables. This rule does not + * warn such variables because this rule cannot distinguish whether the + * exported variables are reassigned or not. + * @param {eslint-scope.Variable} variable A variable to get. + * @param {boolean} ignoreReadBeforeAssign + * The value of `ignoreReadBeforeAssign` option. + * @returns {ASTNode|null} + * An Identifier node if the variable should change to const. + * Otherwise, null. + */ +function getIdentifierIfShouldBeConst(variable, ignoreReadBeforeAssign) { + if (variable.eslintUsed && variable.scope.type === "global") { + return null; + } + + // Finds the unique WriteReference. + let writer = null; + let isReadBeforeInit = false; + const references = variable.references; + + for (let i = 0; i < references.length; ++i) { + const reference = references[i]; + + if (reference.isWrite()) { + const isReassigned = ( + writer !== null && + writer.identifier !== reference.identifier + ); + + if (isReassigned) { + return null; + } + + const destructuringHost = getDestructuringHost(reference); + + if (destructuringHost !== null && destructuringHost.left !== void 0) { + const leftNode = destructuringHost.left; + let hasOuterVariables = false, + hasNonIdentifiers = false; + + if (leftNode.type === "ObjectPattern") { + const properties = leftNode.properties; + + hasOuterVariables = properties + .filter(prop => prop.value) + .map(prop => prop.value.name) + .some(name => isOuterVariableInDestructing(name, variable.scope)); + + hasNonIdentifiers = hasMemberExpressionAssignment(leftNode); + + } else if (leftNode.type === "ArrayPattern") { + const elements = leftNode.elements; + + hasOuterVariables = elements + .map(element => element && element.name) + .some(name => isOuterVariableInDestructing(name, variable.scope)); + + hasNonIdentifiers = hasMemberExpressionAssignment(leftNode); + } + + if (hasOuterVariables || hasNonIdentifiers) { + return null; + } + + } + + writer = reference; + + } else if (reference.isRead() && writer === null) { + if (ignoreReadBeforeAssign) { + return null; + } + isReadBeforeInit = true; + } + } + + /* + * If the assignment is from a different scope, ignore it. + * If the assignment cannot change to a declaration, ignore it. + */ + const shouldBeConst = ( + writer !== null && + writer.from === variable.scope && + canBecomeVariableDeclaration(writer.identifier) + ); + + if (!shouldBeConst) { + return null; + } + + if (isReadBeforeInit) { + return variable.defs[0].name; + } + + return writer.identifier; +} + +/** + * Groups by the VariableDeclarator/AssignmentExpression node that each + * reference of given variables belongs to. + * This is used to detect a mix of reassigned and never reassigned in a + * destructuring. + * @param {eslint-scope.Variable[]} variables Variables to group by destructuring. + * @param {boolean} ignoreReadBeforeAssign + * The value of `ignoreReadBeforeAssign` option. + * @returns {Map} Grouped identifier nodes. + */ +function groupByDestructuring(variables, ignoreReadBeforeAssign) { + const identifierMap = new Map(); + + for (let i = 0; i < variables.length; ++i) { + const variable = variables[i]; + const references = variable.references; + const identifier = getIdentifierIfShouldBeConst(variable, ignoreReadBeforeAssign); + let prevId = null; + + for (let j = 0; j < references.length; ++j) { + const reference = references[j]; + const id = reference.identifier; + + /* + * Avoid counting a reference twice or more for default values of + * destructuring. + */ + if (id === prevId) { + continue; + } + prevId = id; + + // Add the identifier node into the destructuring group. + const group = getDestructuringHost(reference); + + if (group) { + if (identifierMap.has(group)) { + identifierMap.get(group).push(identifier); + } else { + identifierMap.set(group, [identifier]); + } + } + } + } + + return identifierMap; +} + +/** + * Finds the nearest parent of node with a given type. + * @param {ASTNode} node The node to search from. + * @param {string} type The type field of the parent node. + * @param {Function} shouldStop A predicate that returns true if the traversal should stop, and false otherwise. + * @returns {ASTNode} The closest ancestor with the specified type; null if no such ancestor exists. + */ +function findUp(node, type, shouldStop) { + if (!node || shouldStop(node)) { + return null; + } + if (node.type === type) { + return node; + } + return findUp(node.parent, type, shouldStop); +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "require `const` declarations for variables that are never reassigned after declared", + category: "ECMAScript 6", + recommended: false, + url: "https://eslint.org/docs/rules/prefer-const" + }, + + fixable: "code", + + schema: [ + { + type: "object", + properties: { + destructuring: { enum: ["any", "all"], default: "any" }, + ignoreReadBeforeAssign: { type: "boolean", default: false } + }, + additionalProperties: false + } + ], + messages: { + useConst: "'{{name}}' is never reassigned. Use 'const' instead." + } + }, + + create(context) { + const options = context.options[0] || {}; + const sourceCode = context.getSourceCode(); + const shouldMatchAnyDestructuredVariable = options.destructuring !== "all"; + const ignoreReadBeforeAssign = options.ignoreReadBeforeAssign === true; + const variables = []; + let reportCount = 0; + let checkedId = null; + let checkedName = ""; + + + /** + * Reports given identifier nodes if all of the nodes should be declared + * as const. + * + * The argument 'nodes' is an array of Identifier nodes. + * This node is the result of 'getIdentifierIfShouldBeConst()', so it's + * nullable. In simple declaration or assignment cases, the length of + * the array is 1. In destructuring cases, the length of the array can + * be 2 or more. + * @param {(eslint-scope.Reference|null)[]} nodes + * References which are grouped by destructuring to report. + * @returns {void} + */ + function checkGroup(nodes) { + const nodesToReport = nodes.filter(Boolean); + + if (nodes.length && (shouldMatchAnyDestructuredVariable || nodesToReport.length === nodes.length)) { + const varDeclParent = findUp(nodes[0], "VariableDeclaration", parentNode => parentNode.type.endsWith("Statement")); + const isVarDecParentNull = varDeclParent === null; + + if (!isVarDecParentNull && varDeclParent.declarations.length > 0) { + const firstDeclaration = varDeclParent.declarations[0]; + + if (firstDeclaration.init) { + const firstDecParent = firstDeclaration.init.parent; + + /* + * First we check the declaration type and then depending on + * if the type is a "VariableDeclarator" or its an "ObjectPattern" + * we compare the name and id from the first identifier, if the names are different + * we assign the new name, id and reset the count of reportCount and nodeCount in + * order to check each block for the number of reported errors and base our fix + * based on comparing nodes.length and nodesToReport.length. + */ + + if (firstDecParent.type === "VariableDeclarator") { + + if (firstDecParent.id.name !== checkedName) { + checkedName = firstDecParent.id.name; + reportCount = 0; + } + + if (firstDecParent.id.type === "ObjectPattern") { + if (firstDecParent.init.name !== checkedName) { + checkedName = firstDecParent.init.name; + reportCount = 0; + } + } + + if (firstDecParent.id !== checkedId) { + checkedId = firstDecParent.id; + reportCount = 0; + } + } + } + } + + let shouldFix = varDeclParent && + + // Don't do a fix unless all variables in the declarations are initialized (or it's in a for-in or for-of loop) + (varDeclParent.parent.type === "ForInStatement" || varDeclParent.parent.type === "ForOfStatement" || + varDeclParent.declarations.every(declaration => declaration.init)) && + + /* + * If options.destructuring is "all", then this warning will not occur unless + * every assignment in the destructuring should be const. In that case, it's safe + * to apply the fix. + */ + nodesToReport.length === nodes.length; + + if (!isVarDecParentNull && varDeclParent.declarations && varDeclParent.declarations.length !== 1) { + + if (varDeclParent && varDeclParent.declarations && varDeclParent.declarations.length >= 1) { + + /* + * Add nodesToReport.length to a count, then comparing the count to the length + * of the declarations in the current block. + */ + + reportCount += nodesToReport.length; + + shouldFix = shouldFix && (reportCount === varDeclParent.declarations.length); + } + } + + nodesToReport.forEach(node => { + context.report({ + node, + messageId: "useConst", + data: node, + fix: shouldFix + ? fixer => fixer.replaceText( + sourceCode.getFirstToken(varDeclParent, t => t.value === varDeclParent.kind), + "const" + ) + : null + }); + }); + } + } + + return { + "Program:exit"() { + groupByDestructuring(variables, ignoreReadBeforeAssign).forEach(checkGroup); + }, + + VariableDeclaration(node) { + if (node.kind === "let" && !isInitOfForStatement(node)) { + variables.push(...context.getDeclaredVariables(node)); + } + } + }; + } +}; diff --git a/eslint/lib/rules/prefer-destructuring.js b/eslint/lib/rules/prefer-destructuring.js new file mode 100644 index 0000000..1a51956 --- /dev/null +++ b/eslint/lib/rules/prefer-destructuring.js @@ -0,0 +1,272 @@ +/** + * @fileoverview Prefer destructuring from arrays and objects + * @author Alex LaFroscia + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "require destructuring from arrays and/or objects", + category: "ECMAScript 6", + recommended: false, + url: "https://eslint.org/docs/rules/prefer-destructuring" + }, + + fixable: "code", + + schema: [ + { + + /* + * old support {array: Boolean, object: Boolean} + * new support {VariableDeclarator: {}, AssignmentExpression: {}} + */ + oneOf: [ + { + type: "object", + properties: { + VariableDeclarator: { + type: "object", + properties: { + array: { + type: "boolean" + }, + object: { + type: "boolean" + } + }, + additionalProperties: false + }, + AssignmentExpression: { + type: "object", + properties: { + array: { + type: "boolean" + }, + object: { + type: "boolean" + } + }, + additionalProperties: false + } + }, + additionalProperties: false + }, + { + type: "object", + properties: { + array: { + type: "boolean" + }, + object: { + type: "boolean" + } + }, + additionalProperties: false + } + ] + }, + { + type: "object", + properties: { + enforceForRenamedProperties: { + type: "boolean" + } + }, + additionalProperties: false + } + ], + + messages: { + preferDestructuring: "Use {{type}} destructuring." + } + }, + create(context) { + + const enabledTypes = context.options[0]; + const enforceForRenamedProperties = context.options[1] && context.options[1].enforceForRenamedProperties; + let normalizedOptions = { + VariableDeclarator: { array: true, object: true }, + AssignmentExpression: { array: true, object: true } + }; + + if (enabledTypes) { + normalizedOptions = typeof enabledTypes.array !== "undefined" || typeof enabledTypes.object !== "undefined" + ? { VariableDeclarator: enabledTypes, AssignmentExpression: enabledTypes } + : enabledTypes; + } + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + // eslint-disable-next-line jsdoc/require-description + /** + * @param {string} nodeType "AssignmentExpression" or "VariableDeclarator" + * @param {string} destructuringType "array" or "object" + * @returns {boolean} `true` if the destructuring type should be checked for the given node + */ + function shouldCheck(nodeType, destructuringType) { + return normalizedOptions && + normalizedOptions[nodeType] && + normalizedOptions[nodeType][destructuringType]; + } + + /** + * Determines if the given node is accessing an array index + * + * This is used to differentiate array index access from object property + * access. + * @param {ASTNode} node the node to evaluate + * @returns {boolean} whether or not the node is an integer + */ + function isArrayIndexAccess(node) { + return Number.isInteger(node.property.value); + } + + /** + * Report that the given node should use destructuring + * @param {ASTNode} reportNode the node to report + * @param {string} type the type of destructuring that should have been done + * @param {Function|null} fix the fix function or null to pass to context.report + * @returns {void} + */ + function report(reportNode, type, fix) { + context.report({ + node: reportNode, + messageId: "preferDestructuring", + data: { type }, + fix + }); + } + + /** + * Determines if a node should be fixed into object destructuring + * + * The fixer only fixes the simplest case of object destructuring, + * like: `let x = a.x`; + * + * Assignment expression is not fixed. + * Array destructuring is not fixed. + * Renamed property is not fixed. + * @param {ASTNode} node the the node to evaluate + * @returns {boolean} whether or not the node should be fixed + */ + function shouldFix(node) { + return node.type === "VariableDeclarator" && + node.id.type === "Identifier" && + node.init.type === "MemberExpression" && + node.id.name === node.init.property.name; + } + + /** + * Fix a node into object destructuring. + * This function only handles the simplest case of object destructuring, + * see {@link shouldFix}. + * @param {SourceCodeFixer} fixer the fixer object + * @param {ASTNode} node the node to be fixed. + * @returns {Object} a fix for the node + */ + function fixIntoObjectDestructuring(fixer, node) { + const rightNode = node.init; + const sourceCode = context.getSourceCode(); + + return fixer.replaceText( + node, + `{${rightNode.property.name}} = ${sourceCode.getText(rightNode.object)}` + ); + } + + /** + * Check that the `prefer-destructuring` rules are followed based on the + * given left- and right-hand side of the assignment. + * + * Pulled out into a separate method so that VariableDeclarators and + * AssignmentExpressions can share the same verification logic. + * @param {ASTNode} leftNode the left-hand side of the assignment + * @param {ASTNode} rightNode the right-hand side of the assignment + * @param {ASTNode} reportNode the node to report the error on + * @returns {void} + */ + function performCheck(leftNode, rightNode, reportNode) { + if (rightNode.type !== "MemberExpression" || rightNode.object.type === "Super") { + return; + } + + if (isArrayIndexAccess(rightNode)) { + if (shouldCheck(reportNode.type, "array")) { + report(reportNode, "array", null); + } + return; + } + + const fix = shouldFix(reportNode) + ? fixer => fixIntoObjectDestructuring(fixer, reportNode) + : null; + + if (shouldCheck(reportNode.type, "object") && enforceForRenamedProperties) { + report(reportNode, "object", fix); + return; + } + + if (shouldCheck(reportNode.type, "object")) { + const property = rightNode.property; + + if ( + (property.type === "Literal" && leftNode.name === property.value) || + (property.type === "Identifier" && leftNode.name === property.name && !rightNode.computed) + ) { + report(reportNode, "object", fix); + } + } + } + + /** + * Check if a given variable declarator is coming from an property access + * that should be using destructuring instead + * @param {ASTNode} node the variable declarator to check + * @returns {void} + */ + function checkVariableDeclarator(node) { + + // Skip if variable is declared without assignment + if (!node.init) { + return; + } + + // We only care about member expressions past this point + if (node.init.type !== "MemberExpression") { + return; + } + + performCheck(node.id, node.init, node); + } + + /** + * Run the `prefer-destructuring` check on an AssignmentExpression + * @param {ASTNode} node the AssignmentExpression node + * @returns {void} + */ + function checkAssigmentExpression(node) { + if (node.operator === "=") { + performCheck(node.left, node.right, node); + } + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + VariableDeclarator: checkVariableDeclarator, + AssignmentExpression: checkAssigmentExpression + }; + } +}; diff --git a/eslint/lib/rules/prefer-exponentiation-operator.js b/eslint/lib/rules/prefer-exponentiation-operator.js new file mode 100644 index 0000000..5e75ef4 --- /dev/null +++ b/eslint/lib/rules/prefer-exponentiation-operator.js @@ -0,0 +1,189 @@ +/** + * @fileoverview Rule to disallow Math.pow in favor of the ** operator + * @author Milos Djermanovic + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); +const { CALL, ReferenceTracker } = require("eslint-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const PRECEDENCE_OF_EXPONENTIATION_EXPR = astUtils.getPrecedence({ type: "BinaryExpression", operator: "**" }); + +/** + * Determines whether the given node needs parens if used as the base in an exponentiation binary expression. + * @param {ASTNode} base The node to check. + * @returns {boolean} `true` if the node needs to be parenthesised. + */ +function doesBaseNeedParens(base) { + return ( + + // '**' is right-associative, parens are needed when Math.pow(a ** b, c) is converted to (a ** b) ** c + astUtils.getPrecedence(base) <= PRECEDENCE_OF_EXPONENTIATION_EXPR || + + // An unary operator cannot be used immediately before an exponentiation expression + base.type === "UnaryExpression" + ); +} + +/** + * Determines whether the given node needs parens if used as the exponent in an exponentiation binary expression. + * @param {ASTNode} exponent The node to check. + * @returns {boolean} `true` if the node needs to be parenthesised. + */ +function doesExponentNeedParens(exponent) { + + // '**' is right-associative, there is no need for parens when Math.pow(a, b ** c) is converted to a ** b ** c + return astUtils.getPrecedence(exponent) < PRECEDENCE_OF_EXPONENTIATION_EXPR; +} + +/** + * Determines whether an exponentiation binary expression at the place of the given node would need parens. + * @param {ASTNode} node A node that would be replaced by an exponentiation binary expression. + * @param {SourceCode} sourceCode A SourceCode object. + * @returns {boolean} `true` if the expression needs to be parenthesised. + */ +function doesExponentiationExpressionNeedParens(node, sourceCode) { + const parent = node.parent; + + const needsParens = ( + parent.type === "ClassDeclaration" || + ( + parent.type.endsWith("Expression") && + astUtils.getPrecedence(parent) >= PRECEDENCE_OF_EXPONENTIATION_EXPR && + !(parent.type === "BinaryExpression" && parent.operator === "**" && parent.right === node) && + !((parent.type === "CallExpression" || parent.type === "NewExpression") && parent.arguments.includes(node)) && + !(parent.type === "MemberExpression" && parent.computed && parent.property === node) && + !(parent.type === "ArrayExpression") + ) + ); + + return needsParens && !astUtils.isParenthesised(sourceCode, node); +} + +/** + * Optionally parenthesizes given text. + * @param {string} text The text to parenthesize. + * @param {boolean} shouldParenthesize If `true`, the text will be parenthesised. + * @returns {string} parenthesised or unchanged text. + */ +function parenthesizeIfShould(text, shouldParenthesize) { + return shouldParenthesize ? `(${text})` : text; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow the use of `Math.pow` in favor of the `**` operator", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/prefer-exponentiation-operator" + }, + + schema: [], + fixable: "code", + + messages: { + useExponentiation: "Use the '**' operator instead of 'Math.pow'." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + /** + * Reports the given node. + * @param {ASTNode} node 'Math.pow()' node to report. + * @returns {void} + */ + function report(node) { + context.report({ + node, + messageId: "useExponentiation", + fix(fixer) { + if ( + node.arguments.length !== 2 || + node.arguments.some(arg => arg.type === "SpreadElement") || + sourceCode.getCommentsInside(node).length > 0 + ) { + return null; + } + + const base = node.arguments[0], + exponent = node.arguments[1], + baseText = sourceCode.getText(base), + exponentText = sourceCode.getText(exponent), + shouldParenthesizeBase = doesBaseNeedParens(base), + shouldParenthesizeExponent = doesExponentNeedParens(exponent), + shouldParenthesizeAll = doesExponentiationExpressionNeedParens(node, sourceCode); + + let prefix = "", + suffix = ""; + + if (!shouldParenthesizeAll) { + if (!shouldParenthesizeBase) { + const firstReplacementToken = sourceCode.getFirstToken(base), + tokenBefore = sourceCode.getTokenBefore(node); + + if ( + tokenBefore && + tokenBefore.range[1] === node.range[0] && + !astUtils.canTokensBeAdjacent(tokenBefore, firstReplacementToken) + ) { + prefix = " "; // a+Math.pow(++b, c) -> a+ ++b**c + } + } + if (!shouldParenthesizeExponent) { + const lastReplacementToken = sourceCode.getLastToken(exponent), + tokenAfter = sourceCode.getTokenAfter(node); + + if ( + tokenAfter && + node.range[1] === tokenAfter.range[0] && + !astUtils.canTokensBeAdjacent(lastReplacementToken, tokenAfter) + ) { + suffix = " "; // Math.pow(a, b)in c -> a**b in c + } + } + } + + const baseReplacement = parenthesizeIfShould(baseText, shouldParenthesizeBase), + exponentReplacement = parenthesizeIfShould(exponentText, shouldParenthesizeExponent), + replacement = parenthesizeIfShould(`${baseReplacement}**${exponentReplacement}`, shouldParenthesizeAll); + + return fixer.replaceText(node, `${prefix}${replacement}${suffix}`); + } + }); + } + + return { + Program() { + const scope = context.getScope(); + const tracker = new ReferenceTracker(scope); + const trackMap = { + Math: { + pow: { [CALL]: true } + } + }; + + for (const { node } of tracker.iterateGlobalReferences(trackMap)) { + report(node); + } + } + }; + } +}; diff --git a/eslint/lib/rules/prefer-named-capture-group.js b/eslint/lib/rules/prefer-named-capture-group.js new file mode 100644 index 0000000..c8af043 --- /dev/null +++ b/eslint/lib/rules/prefer-named-capture-group.js @@ -0,0 +1,110 @@ +/** + * @fileoverview Rule to enforce requiring named capture groups in regular expression. + * @author Pig Fang + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const { + CALL, + CONSTRUCT, + ReferenceTracker, + getStringIfConstant +} = require("eslint-utils"); +const regexpp = require("regexpp"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const parser = new regexpp.RegExpParser(); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "enforce using named capture group in regular expression", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/prefer-named-capture-group" + }, + + schema: [], + + messages: { + required: "Capture group '{{group}}' should be converted to a named or non-capturing group." + } + }, + + create(context) { + + /** + * Function to check regular expression. + * @param {string} pattern The regular expression pattern to be check. + * @param {ASTNode} node AST node which contains regular expression. + * @param {boolean} uFlag Flag indicates whether unicode mode is enabled or not. + * @returns {void} + */ + function checkRegex(pattern, node, uFlag) { + let ast; + + try { + ast = parser.parsePattern(pattern, 0, pattern.length, uFlag); + } catch (_) { + + // ignore regex syntax errors + return; + } + + regexpp.visitRegExpAST(ast, { + onCapturingGroupEnter(group) { + if (!group.name) { + context.report({ + node, + messageId: "required", + data: { + group: group.raw + } + }); + } + } + }); + } + + return { + Literal(node) { + if (node.regex) { + checkRegex(node.regex.pattern, node, node.regex.flags.includes("u")); + } + }, + Program() { + const scope = context.getScope(); + const tracker = new ReferenceTracker(scope); + const traceMap = { + RegExp: { + [CALL]: true, + [CONSTRUCT]: true + } + }; + + for (const { node } of tracker.iterateGlobalReferences(traceMap)) { + const regex = getStringIfConstant(node.arguments[0]); + const flags = getStringIfConstant(node.arguments[1]); + + if (regex) { + checkRegex(regex, node, flags && flags.includes("u")); + } + } + } + }; + } +}; diff --git a/eslint/lib/rules/prefer-numeric-literals.js b/eslint/lib/rules/prefer-numeric-literals.js new file mode 100644 index 0000000..2a4fb5d --- /dev/null +++ b/eslint/lib/rules/prefer-numeric-literals.js @@ -0,0 +1,147 @@ +/** + * @fileoverview Rule to disallow `parseInt()` in favor of binary, octal, and hexadecimal literals + * @author Annie Zhang, Henry Zhu + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const radixMap = new Map([ + [2, { system: "binary", literalPrefix: "0b" }], + [8, { system: "octal", literalPrefix: "0o" }], + [16, { system: "hexadecimal", literalPrefix: "0x" }] +]); + +/** + * Checks to see if a CallExpression's callee node is `parseInt` or + * `Number.parseInt`. + * @param {ASTNode} calleeNode The callee node to evaluate. + * @returns {boolean} True if the callee is `parseInt` or `Number.parseInt`, + * false otherwise. + */ +function isParseInt(calleeNode) { + switch (calleeNode.type) { + case "Identifier": + return calleeNode.name === "parseInt"; + case "MemberExpression": + return calleeNode.object.type === "Identifier" && + calleeNode.object.name === "Number" && + calleeNode.property.type === "Identifier" && + calleeNode.property.name === "parseInt"; + + // no default + } + + return false; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow `parseInt()` and `Number.parseInt()` in favor of binary, octal, and hexadecimal literals", + category: "ECMAScript 6", + recommended: false, + url: "https://eslint.org/docs/rules/prefer-numeric-literals" + }, + + schema: [], + + messages: { + useLiteral: "Use {{system}} literals instead of {{functionName}}()." + }, + + fixable: "code" + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + //---------------------------------------------------------------------- + // Public + //---------------------------------------------------------------------- + + return { + + "CallExpression[arguments.length=2]"(node) { + const [strNode, radixNode] = node.arguments, + str = astUtils.getStaticStringValue(strNode), + radix = radixNode.value; + + if ( + str !== null && + astUtils.isStringLiteral(strNode) && + radixNode.type === "Literal" && + typeof radix === "number" && + radixMap.has(radix) && + isParseInt(node.callee) + ) { + + const { system, literalPrefix } = radixMap.get(radix); + + context.report({ + node, + messageId: "useLiteral", + data: { + system, + functionName: sourceCode.getText(node.callee) + }, + fix(fixer) { + if (sourceCode.getCommentsInside(node).length) { + return null; + } + + const replacement = `${literalPrefix}${str}`; + + if (+replacement !== parseInt(str, radix)) { + + /* + * If the newly-produced literal would be invalid, (e.g. 0b1234), + * or it would yield an incorrect parseInt result for some other reason, don't make a fix. + */ + return null; + } + + const tokenBefore = sourceCode.getTokenBefore(node), + tokenAfter = sourceCode.getTokenAfter(node); + let prefix = "", + suffix = ""; + + if ( + tokenBefore && + tokenBefore.range[1] === node.range[0] && + !astUtils.canTokensBeAdjacent(tokenBefore, replacement) + ) { + prefix = " "; + } + + if ( + tokenAfter && + node.range[1] === tokenAfter.range[0] && + !astUtils.canTokensBeAdjacent(replacement, tokenAfter) + ) { + suffix = " "; + } + + return fixer.replaceText(node, `${prefix}${replacement}${suffix}`); + } + }); + } + } + }; + } +}; diff --git a/eslint/lib/rules/prefer-object-spread.js b/eslint/lib/rules/prefer-object-spread.js new file mode 100644 index 0000000..ab252c7 --- /dev/null +++ b/eslint/lib/rules/prefer-object-spread.js @@ -0,0 +1,299 @@ +/** + * @fileoverview Prefers object spread property over Object.assign + * @author Sharmila Jesupaul + * See LICENSE file in root directory for full license. + */ + +"use strict"; + +const { CALL, ReferenceTracker } = require("eslint-utils"); +const { + isCommaToken, + isOpeningParenToken, + isClosingParenToken, + isParenthesised +} = require("./utils/ast-utils"); + +const ANY_SPACE = /\s/u; + +/** + * Helper that checks if the Object.assign call has array spread + * @param {ASTNode} node The node that the rule warns on + * @returns {boolean} - Returns true if the Object.assign call has array spread + */ +function hasArraySpread(node) { + return node.arguments.some(arg => arg.type === "SpreadElement"); +} + +/** + * Determines whether the given node is an accessor property (getter/setter). + * @param {ASTNode} node Node to check. + * @returns {boolean} `true` if the node is a getter or a setter. + */ +function isAccessorProperty(node) { + return node.type === "Property" && + (node.kind === "get" || node.kind === "set"); +} + +/** + * Determines whether the given object expression node has accessor properties (getters/setters). + * @param {ASTNode} node `ObjectExpression` node to check. + * @returns {boolean} `true` if the node has at least one getter/setter. + */ +function hasAccessors(node) { + return node.properties.some(isAccessorProperty); +} + +/** + * Determines whether the given call expression node has object expression arguments with accessor properties (getters/setters). + * @param {ASTNode} node `CallExpression` node to check. + * @returns {boolean} `true` if the node has at least one argument that is an object expression with at least one getter/setter. + */ +function hasArgumentsWithAccessors(node) { + return node.arguments + .filter(arg => arg.type === "ObjectExpression") + .some(hasAccessors); +} + +/** + * Helper that checks if the node needs parentheses to be valid JS. + * The default is to wrap the node in parentheses to avoid parsing errors. + * @param {ASTNode} node The node that the rule warns on + * @param {Object} sourceCode in context sourcecode object + * @returns {boolean} - Returns true if the node needs parentheses + */ +function needsParens(node, sourceCode) { + const parent = node.parent; + + switch (parent.type) { + case "VariableDeclarator": + case "ArrayExpression": + case "ReturnStatement": + case "CallExpression": + case "Property": + return false; + case "AssignmentExpression": + return parent.left === node && !isParenthesised(sourceCode, node); + default: + return !isParenthesised(sourceCode, node); + } +} + +/** + * Determines if an argument needs parentheses. The default is to not add parens. + * @param {ASTNode} node The node to be checked. + * @param {Object} sourceCode in context sourcecode object + * @returns {boolean} True if the node needs parentheses + */ +function argNeedsParens(node, sourceCode) { + switch (node.type) { + case "AssignmentExpression": + case "ArrowFunctionExpression": + case "ConditionalExpression": + return !isParenthesised(sourceCode, node); + default: + return false; + } +} + +/** + * Get the parenthesis tokens of a given ObjectExpression node. + * This includes the braces of the object literal and enclosing parentheses. + * @param {ASTNode} node The node to get. + * @param {Token} leftArgumentListParen The opening paren token of the argument list. + * @param {SourceCode} sourceCode The source code object to get tokens. + * @returns {Token[]} The parenthesis tokens of the node. This is sorted by the location. + */ +function getParenTokens(node, leftArgumentListParen, sourceCode) { + const parens = [sourceCode.getFirstToken(node), sourceCode.getLastToken(node)]; + let leftNext = sourceCode.getTokenBefore(node); + let rightNext = sourceCode.getTokenAfter(node); + + // Note: don't include the parens of the argument list. + while ( + leftNext && + rightNext && + leftNext.range[0] > leftArgumentListParen.range[0] && + isOpeningParenToken(leftNext) && + isClosingParenToken(rightNext) + ) { + parens.push(leftNext, rightNext); + leftNext = sourceCode.getTokenBefore(leftNext); + rightNext = sourceCode.getTokenAfter(rightNext); + } + + return parens.sort((a, b) => a.range[0] - b.range[0]); +} + +/** + * Get the range of a given token and around whitespaces. + * @param {Token} token The token to get range. + * @param {SourceCode} sourceCode The source code object to get tokens. + * @returns {number} The end of the range of the token and around whitespaces. + */ +function getStartWithSpaces(token, sourceCode) { + const text = sourceCode.text; + let start = token.range[0]; + + // If the previous token is a line comment then skip this step to avoid commenting this token out. + { + const prevToken = sourceCode.getTokenBefore(token, { includeComments: true }); + + if (prevToken && prevToken.type === "Line") { + return start; + } + } + + // Detect spaces before the token. + while (ANY_SPACE.test(text[start - 1] || "")) { + start -= 1; + } + + return start; +} + +/** + * Get the range of a given token and around whitespaces. + * @param {Token} token The token to get range. + * @param {SourceCode} sourceCode The source code object to get tokens. + * @returns {number} The start of the range of the token and around whitespaces. + */ +function getEndWithSpaces(token, sourceCode) { + const text = sourceCode.text; + let end = token.range[1]; + + // Detect spaces after the token. + while (ANY_SPACE.test(text[end] || "")) { + end += 1; + } + + return end; +} + +/** + * Autofixes the Object.assign call to use an object spread instead. + * @param {ASTNode|null} node The node that the rule warns on, i.e. the Object.assign call + * @param {string} sourceCode sourceCode of the Object.assign call + * @returns {Function} autofixer - replaces the Object.assign with a spread object. + */ +function defineFixer(node, sourceCode) { + return function *(fixer) { + const leftParen = sourceCode.getTokenAfter(node.callee, isOpeningParenToken); + const rightParen = sourceCode.getLastToken(node); + + // Remove everything before the opening paren: callee `Object.assign`, type arguments, and whitespace between the callee and the paren. + yield fixer.removeRange([node.range[0], leftParen.range[0]]); + + // Replace the parens of argument list to braces. + if (needsParens(node, sourceCode)) { + yield fixer.replaceText(leftParen, "({"); + yield fixer.replaceText(rightParen, "})"); + } else { + yield fixer.replaceText(leftParen, "{"); + yield fixer.replaceText(rightParen, "}"); + } + + // Process arguments. + for (const argNode of node.arguments) { + const innerParens = getParenTokens(argNode, leftParen, sourceCode); + const left = innerParens.shift(); + const right = innerParens.pop(); + + if (argNode.type === "ObjectExpression") { + const maybeTrailingComma = sourceCode.getLastToken(argNode, 1); + const maybeArgumentComma = sourceCode.getTokenAfter(right); + + /* + * Make bare this object literal. + * And remove spaces inside of the braces for better formatting. + */ + for (const innerParen of innerParens) { + yield fixer.remove(innerParen); + } + const leftRange = [left.range[0], getEndWithSpaces(left, sourceCode)]; + const rightRange = [ + Math.max(getStartWithSpaces(right, sourceCode), leftRange[1]), // Ensure ranges don't overlap + right.range[1] + ]; + + yield fixer.removeRange(leftRange); + yield fixer.removeRange(rightRange); + + // Remove the comma of this argument if it's duplication. + if ( + (argNode.properties.length === 0 || isCommaToken(maybeTrailingComma)) && + isCommaToken(maybeArgumentComma) + ) { + yield fixer.remove(maybeArgumentComma); + } + } else { + + // Make spread. + if (argNeedsParens(argNode, sourceCode)) { + yield fixer.insertTextBefore(left, "...("); + yield fixer.insertTextAfter(right, ")"); + } else { + yield fixer.insertTextBefore(left, "..."); + } + } + } + }; +} + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: + "disallow using Object.assign with an object literal as the first argument and prefer the use of object spread instead.", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/prefer-object-spread" + }, + + schema: [], + fixable: "code", + + messages: { + useSpreadMessage: "Use an object spread instead of `Object.assign` eg: `{ ...foo }`.", + useLiteralMessage: "Use an object literal instead of `Object.assign`. eg: `{ foo: bar }`." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + return { + Program() { + const scope = context.getScope(); + const tracker = new ReferenceTracker(scope); + const trackMap = { + Object: { + assign: { [CALL]: true } + } + }; + + // Iterate all calls of `Object.assign` (only of the global variable `Object`). + for (const { node } of tracker.iterateGlobalReferences(trackMap)) { + if ( + node.arguments.length >= 1 && + node.arguments[0].type === "ObjectExpression" && + !hasArraySpread(node) && + !( + node.arguments.length > 1 && + hasArgumentsWithAccessors(node) + ) + ) { + const messageId = node.arguments.length === 1 + ? "useLiteralMessage" + : "useSpreadMessage"; + const fix = defineFixer(node, sourceCode); + + context.report({ node, messageId, fix }); + } + } + } + }; + } +}; diff --git a/eslint/lib/rules/prefer-promise-reject-errors.js b/eslint/lib/rules/prefer-promise-reject-errors.js new file mode 100644 index 0000000..56911b6 --- /dev/null +++ b/eslint/lib/rules/prefer-promise-reject-errors.js @@ -0,0 +1,133 @@ +/** + * @fileoverview restrict values that can be used as Promise rejection reasons + * @author Teddy Katz + */ +"use strict"; + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "require using Error objects as Promise rejection reasons", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/prefer-promise-reject-errors" + }, + + fixable: null, + + schema: [ + { + type: "object", + properties: { + allowEmptyReject: { type: "boolean", default: false } + }, + additionalProperties: false + } + ], + + messages: { + rejectAnError: "Expected the Promise rejection reason to be an Error." + } + }, + + create(context) { + + const ALLOW_EMPTY_REJECT = context.options.length && context.options[0].allowEmptyReject; + + //---------------------------------------------------------------------- + // Helpers + //---------------------------------------------------------------------- + + /** + * Checks the argument of a reject() or Promise.reject() CallExpression, and reports it if it can't be an Error + * @param {ASTNode} callExpression A CallExpression node which is used to reject a Promise + * @returns {void} + */ + function checkRejectCall(callExpression) { + if (!callExpression.arguments.length && ALLOW_EMPTY_REJECT) { + return; + } + if ( + !callExpression.arguments.length || + !astUtils.couldBeError(callExpression.arguments[0]) || + callExpression.arguments[0].type === "Identifier" && callExpression.arguments[0].name === "undefined" + ) { + context.report({ + node: callExpression, + messageId: "rejectAnError" + }); + } + } + + /** + * Determines whether a function call is a Promise.reject() call + * @param {ASTNode} node A CallExpression node + * @returns {boolean} `true` if the call is a Promise.reject() call + */ + function isPromiseRejectCall(node) { + return node.callee.type === "MemberExpression" && + node.callee.object.type === "Identifier" && node.callee.object.name === "Promise" && + node.callee.property.type === "Identifier" && node.callee.property.name === "reject"; + } + + //---------------------------------------------------------------------- + // Public + //---------------------------------------------------------------------- + + return { + + // Check `Promise.reject(value)` calls. + CallExpression(node) { + if (isPromiseRejectCall(node)) { + checkRejectCall(node); + } + }, + + /* + * Check for `new Promise((resolve, reject) => {})`, and check for reject() calls. + * This function is run on "NewExpression:exit" instead of "NewExpression" to ensure that + * the nodes in the expression already have the `parent` property. + */ + "NewExpression:exit"(node) { + if ( + node.callee.type === "Identifier" && node.callee.name === "Promise" && + node.arguments.length && astUtils.isFunction(node.arguments[0]) && + node.arguments[0].params.length > 1 && node.arguments[0].params[1].type === "Identifier" + ) { + context.getDeclaredVariables(node.arguments[0]) + + /* + * Find the first variable that matches the second parameter's name. + * If the first parameter has the same name as the second parameter, then the variable will actually + * be "declared" when the first parameter is evaluated, but then it will be immediately overwritten + * by the second parameter. It's not possible for an expression with the variable to be evaluated before + * the variable is overwritten, because functions with duplicate parameters cannot have destructuring or + * default assignments in their parameter lists. Therefore, it's not necessary to explicitly account for + * this case. + */ + .find(variable => variable.name === node.arguments[0].params[1].name) + + // Get the references to that variable. + .references + + // Only check the references that read the parameter's value. + .filter(ref => ref.isRead()) + + // Only check the references that are used as the callee in a function call, e.g. `reject(foo)`. + .filter(ref => ref.identifier.parent.type === "CallExpression" && ref.identifier === ref.identifier.parent.callee) + + // Check the argument of the function call to determine whether it's an Error. + .forEach(ref => checkRejectCall(ref.identifier.parent)); + } + } + }; + } +}; diff --git a/eslint/lib/rules/prefer-reflect.js b/eslint/lib/rules/prefer-reflect.js new file mode 100644 index 0000000..fb2de92 --- /dev/null +++ b/eslint/lib/rules/prefer-reflect.js @@ -0,0 +1,127 @@ +/** + * @fileoverview Rule to suggest using "Reflect" api over Function/Object methods + * @author Keith Cirkel + * @deprecated in ESLint v3.9.0 + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "require `Reflect` methods where applicable", + category: "ECMAScript 6", + recommended: false, + url: "https://eslint.org/docs/rules/prefer-reflect" + }, + + deprecated: true, + + replacedBy: [], + + schema: [ + { + type: "object", + properties: { + exceptions: { + type: "array", + items: { + enum: [ + "apply", + "call", + "delete", + "defineProperty", + "getOwnPropertyDescriptor", + "getPrototypeOf", + "setPrototypeOf", + "isExtensible", + "getOwnPropertyNames", + "preventExtensions" + ] + }, + uniqueItems: true + } + }, + additionalProperties: false + } + ], + + messages: { + preferReflect: "Avoid using {{existing}}, instead use {{substitute}}." + } + }, + + create(context) { + const existingNames = { + apply: "Function.prototype.apply", + call: "Function.prototype.call", + defineProperty: "Object.defineProperty", + getOwnPropertyDescriptor: "Object.getOwnPropertyDescriptor", + getPrototypeOf: "Object.getPrototypeOf", + setPrototypeOf: "Object.setPrototypeOf", + isExtensible: "Object.isExtensible", + getOwnPropertyNames: "Object.getOwnPropertyNames", + preventExtensions: "Object.preventExtensions" + }; + + const reflectSubstitutes = { + apply: "Reflect.apply", + call: "Reflect.apply", + defineProperty: "Reflect.defineProperty", + getOwnPropertyDescriptor: "Reflect.getOwnPropertyDescriptor", + getPrototypeOf: "Reflect.getPrototypeOf", + setPrototypeOf: "Reflect.setPrototypeOf", + isExtensible: "Reflect.isExtensible", + getOwnPropertyNames: "Reflect.getOwnPropertyNames", + preventExtensions: "Reflect.preventExtensions" + }; + + const exceptions = (context.options[0] || {}).exceptions || []; + + /** + * Reports the Reflect violation based on the `existing` and `substitute` + * @param {Object} node The node that violates the rule. + * @param {string} existing The existing method name that has been used. + * @param {string} substitute The Reflect substitute that should be used. + * @returns {void} + */ + function report(node, existing, substitute) { + context.report({ + node, + messageId: "preferReflect", + data: { + existing, + substitute + } + }); + } + + return { + CallExpression(node) { + const methodName = (node.callee.property || {}).name; + const isReflectCall = (node.callee.object || {}).name === "Reflect"; + const hasReflectSubsitute = Object.prototype.hasOwnProperty.call(reflectSubstitutes, methodName); + const userConfiguredException = exceptions.indexOf(methodName) !== -1; + + if (hasReflectSubsitute && !isReflectCall && !userConfiguredException) { + report(node, existingNames[methodName], reflectSubstitutes[methodName]); + } + }, + UnaryExpression(node) { + const isDeleteOperator = node.operator === "delete"; + const targetsIdentifier = node.argument.type === "Identifier"; + const userConfiguredException = exceptions.indexOf("delete") !== -1; + + if (isDeleteOperator && !targetsIdentifier && !userConfiguredException) { + report(node, "the delete keyword", "Reflect.deleteProperty"); + } + } + }; + + } +}; diff --git a/eslint/lib/rules/prefer-regex-literals.js b/eslint/lib/rules/prefer-regex-literals.js new file mode 100644 index 0000000..47b2b09 --- /dev/null +++ b/eslint/lib/rules/prefer-regex-literals.js @@ -0,0 +1,125 @@ +/** + * @fileoverview Rule to disallow use of the `RegExp` constructor in favor of regular expression literals + * @author Milos Djermanovic + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); +const { CALL, CONSTRUCT, ReferenceTracker, findVariable } = require("eslint-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Determines whether the given node is a string literal. + * @param {ASTNode} node Node to check. + * @returns {boolean} True if the node is a string literal. + */ +function isStringLiteral(node) { + return node.type === "Literal" && typeof node.value === "string"; +} + +/** + * Determines whether the given node is a template literal without expressions. + * @param {ASTNode} node Node to check. + * @returns {boolean} True if the node is a template literal without expressions. + */ +function isStaticTemplateLiteral(node) { + return node.type === "TemplateLiteral" && node.expressions.length === 0; +} + + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow use of the `RegExp` constructor in favor of regular expression literals", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/prefer-regex-literals" + }, + + schema: [], + + messages: { + unexpectedRegExp: "Use a regular expression literal instead of the 'RegExp' constructor." + } + }, + + create(context) { + + /** + * Determines whether the given identifier node is a reference to a global variable. + * @param {ASTNode} node `Identifier` node to check. + * @returns {boolean} True if the identifier is a reference to a global variable. + */ + function isGlobalReference(node) { + const scope = context.getScope(); + const variable = findVariable(scope, node); + + return variable !== null && variable.scope.type === "global" && variable.defs.length === 0; + } + + /** + * Determines whether the given node is a String.raw`` tagged template expression + * with a static template literal. + * @param {ASTNode} node Node to check. + * @returns {boolean} True if the node is String.raw`` with a static template. + */ + function isStringRawTaggedStaticTemplateLiteral(node) { + return node.type === "TaggedTemplateExpression" && + node.tag.type === "MemberExpression" && + node.tag.object.type === "Identifier" && + node.tag.object.name === "String" && + isGlobalReference(node.tag.object) && + astUtils.getStaticPropertyName(node.tag) === "raw" && + isStaticTemplateLiteral(node.quasi); + } + + /** + * Determines whether the given node is considered to be a static string by the logic of this rule. + * @param {ASTNode} node Node to check. + * @returns {boolean} True if the node is a static string. + */ + function isStaticString(node) { + return isStringLiteral(node) || + isStaticTemplateLiteral(node) || + isStringRawTaggedStaticTemplateLiteral(node); + } + + return { + Program() { + const scope = context.getScope(); + const tracker = new ReferenceTracker(scope); + const traceMap = { + RegExp: { + [CALL]: true, + [CONSTRUCT]: true + } + }; + + for (const { node } of tracker.iterateGlobalReferences(traceMap)) { + const args = node.arguments; + + if ( + (args.length === 1 || args.length === 2) && + args.every(isStaticString) + ) { + context.report({ node, messageId: "unexpectedRegExp" }); + } + } + } + }; + } +}; diff --git a/eslint/lib/rules/prefer-rest-params.js b/eslint/lib/rules/prefer-rest-params.js new file mode 100644 index 0000000..3ecea73 --- /dev/null +++ b/eslint/lib/rules/prefer-rest-params.js @@ -0,0 +1,115 @@ +/** + * @fileoverview Rule to + * @author Toru Nagashima + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Gets the variable object of `arguments` which is defined implicitly. + * @param {eslint-scope.Scope} scope A scope to get. + * @returns {eslint-scope.Variable} The found variable object. + */ +function getVariableOfArguments(scope) { + const variables = scope.variables; + + for (let i = 0; i < variables.length; ++i) { + const variable = variables[i]; + + if (variable.name === "arguments") { + + /* + * If there was a parameter which is named "arguments", the implicit "arguments" is not defined. + * So does fast return with null. + */ + return (variable.identifiers.length === 0) ? variable : null; + } + } + + /* istanbul ignore next : unreachable */ + return null; +} + +/** + * Checks if the given reference is not normal member access. + * + * - arguments .... true // not member access + * - arguments[i] .... true // computed member access + * - arguments[0] .... true // computed member access + * - arguments.length .... false // normal member access + * @param {eslint-scope.Reference} reference The reference to check. + * @returns {boolean} `true` if the reference is not normal member access. + */ +function isNotNormalMemberAccess(reference) { + const id = reference.identifier; + const parent = id.parent; + + return !( + parent.type === "MemberExpression" && + parent.object === id && + !parent.computed + ); +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "require rest parameters instead of `arguments`", + category: "ECMAScript 6", + recommended: false, + url: "https://eslint.org/docs/rules/prefer-rest-params" + }, + + schema: [], + + messages: { + preferRestParams: "Use the rest parameters instead of 'arguments'." + } + }, + + create(context) { + + /** + * Reports a given reference. + * @param {eslint-scope.Reference} reference A reference to report. + * @returns {void} + */ + function report(reference) { + context.report({ + node: reference.identifier, + loc: reference.identifier.loc, + messageId: "preferRestParams" + }); + } + + /** + * Reports references of the implicit `arguments` variable if exist. + * @returns {void} + */ + function checkForArguments() { + const argumentsVar = getVariableOfArguments(context.getScope()); + + if (argumentsVar) { + argumentsVar + .references + .filter(isNotNormalMemberAccess) + .forEach(report); + } + } + + return { + "FunctionDeclaration:exit": checkForArguments, + "FunctionExpression:exit": checkForArguments + }; + } +}; diff --git a/eslint/lib/rules/prefer-spread.js b/eslint/lib/rules/prefer-spread.js new file mode 100644 index 0000000..bcb0dc0 --- /dev/null +++ b/eslint/lib/rules/prefer-spread.js @@ -0,0 +1,91 @@ +/** + * @fileoverview A rule to suggest using of the spread operator instead of `.apply()`. + * @author Toru Nagashima + */ + +"use strict"; + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Checks whether or not a node is a `.apply()` for variadic. + * @param {ASTNode} node A CallExpression node to check. + * @returns {boolean} Whether or not the node is a `.apply()` for variadic. + */ +function isVariadicApplyCalling(node) { + return ( + node.callee.type === "MemberExpression" && + node.callee.property.type === "Identifier" && + node.callee.property.name === "apply" && + node.callee.computed === false && + node.arguments.length === 2 && + node.arguments[1].type !== "ArrayExpression" && + node.arguments[1].type !== "SpreadElement" + ); +} + + +/** + * Checks whether or not `thisArg` is not changed by `.apply()`. + * @param {ASTNode|null} expectedThis The node that is the owner of the applied function. + * @param {ASTNode} thisArg The node that is given to the first argument of the `.apply()`. + * @param {RuleContext} context The ESLint rule context object. + * @returns {boolean} Whether or not `thisArg` is not changed by `.apply()`. + */ +function isValidThisArg(expectedThis, thisArg, context) { + if (!expectedThis) { + return astUtils.isNullOrUndefined(thisArg); + } + return astUtils.equalTokens(expectedThis, thisArg, context); +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "require spread operators instead of `.apply()`", + category: "ECMAScript 6", + recommended: false, + url: "https://eslint.org/docs/rules/prefer-spread" + }, + + schema: [], + fixable: null, + + messages: { + preferSpread: "Use the spread operator instead of '.apply()'." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + return { + CallExpression(node) { + if (!isVariadicApplyCalling(node)) { + return; + } + + const applied = node.callee.object; + const expectedThis = (applied.type === "MemberExpression") ? applied.object : null; + const thisArg = node.arguments[0]; + + if (isValidThisArg(expectedThis, thisArg, sourceCode)) { + context.report({ + node, + messageId: "preferSpread" + }); + } + } + }; + } +}; diff --git a/eslint/lib/rules/prefer-template.js b/eslint/lib/rules/prefer-template.js new file mode 100644 index 0000000..e8f980e --- /dev/null +++ b/eslint/lib/rules/prefer-template.js @@ -0,0 +1,283 @@ +/** + * @fileoverview A rule to suggest using template literals instead of string concatenation. + * @author Toru Nagashima + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Checks whether or not a given node is a concatenation. + * @param {ASTNode} node A node to check. + * @returns {boolean} `true` if the node is a concatenation. + */ +function isConcatenation(node) { + return node.type === "BinaryExpression" && node.operator === "+"; +} + +/** + * Gets the top binary expression node for concatenation in parents of a given node. + * @param {ASTNode} node A node to get. + * @returns {ASTNode} the top binary expression node in parents of a given node. + */ +function getTopConcatBinaryExpression(node) { + let currentNode = node; + + while (isConcatenation(currentNode.parent)) { + currentNode = currentNode.parent; + } + return currentNode; +} + +/** + * Determines whether a given node is a octal escape sequence + * @param {ASTNode} node A node to check + * @returns {boolean} `true` if the node is an octal escape sequence + */ +function isOctalEscapeSequence(node) { + + // No need to check TemplateLiterals – would throw error with octal escape + const isStringLiteral = node.type === "Literal" && typeof node.value === "string"; + + if (!isStringLiteral) { + return false; + } + + return astUtils.hasOctalEscapeSequence(node.raw); +} + +/** + * Checks whether or not a node contains a octal escape sequence + * @param {ASTNode} node A node to check + * @returns {boolean} `true` if the node contains an octal escape sequence + */ +function hasOctalEscapeSequence(node) { + if (isConcatenation(node)) { + return hasOctalEscapeSequence(node.left) || hasOctalEscapeSequence(node.right); + } + + return isOctalEscapeSequence(node); +} + +/** + * Checks whether or not a given binary expression has string literals. + * @param {ASTNode} node A node to check. + * @returns {boolean} `true` if the node has string literals. + */ +function hasStringLiteral(node) { + if (isConcatenation(node)) { + + // `left` is deeper than `right` normally. + return hasStringLiteral(node.right) || hasStringLiteral(node.left); + } + return astUtils.isStringLiteral(node); +} + +/** + * Checks whether or not a given binary expression has non string literals. + * @param {ASTNode} node A node to check. + * @returns {boolean} `true` if the node has non string literals. + */ +function hasNonStringLiteral(node) { + if (isConcatenation(node)) { + + // `left` is deeper than `right` normally. + return hasNonStringLiteral(node.right) || hasNonStringLiteral(node.left); + } + return !astUtils.isStringLiteral(node); +} + +/** + * Determines whether a given node will start with a template curly expression (`${}`) when being converted to a template literal. + * @param {ASTNode} node The node that will be fixed to a template literal + * @returns {boolean} `true` if the node will start with a template curly. + */ +function startsWithTemplateCurly(node) { + if (node.type === "BinaryExpression") { + return startsWithTemplateCurly(node.left); + } + if (node.type === "TemplateLiteral") { + return node.expressions.length && node.quasis.length && node.quasis[0].range[0] === node.quasis[0].range[1]; + } + return node.type !== "Literal" || typeof node.value !== "string"; +} + +/** + * Determines whether a given node end with a template curly expression (`${}`) when being converted to a template literal. + * @param {ASTNode} node The node that will be fixed to a template literal + * @returns {boolean} `true` if the node will end with a template curly. + */ +function endsWithTemplateCurly(node) { + if (node.type === "BinaryExpression") { + return startsWithTemplateCurly(node.right); + } + if (node.type === "TemplateLiteral") { + return node.expressions.length && node.quasis.length && node.quasis[node.quasis.length - 1].range[0] === node.quasis[node.quasis.length - 1].range[1]; + } + return node.type !== "Literal" || typeof node.value !== "string"; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "require template literals instead of string concatenation", + category: "ECMAScript 6", + recommended: false, + url: "https://eslint.org/docs/rules/prefer-template" + }, + + schema: [], + fixable: "code", + + messages: { + unexpectedStringConcatenation: "Unexpected string concatenation." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + let done = Object.create(null); + + /** + * Gets the non-token text between two nodes, ignoring any other tokens that appear between the two tokens. + * @param {ASTNode} node1 The first node + * @param {ASTNode} node2 The second node + * @returns {string} The text between the nodes, excluding other tokens + */ + function getTextBetween(node1, node2) { + const allTokens = [node1].concat(sourceCode.getTokensBetween(node1, node2)).concat(node2); + const sourceText = sourceCode.getText(); + + return allTokens.slice(0, -1).reduce((accumulator, token, index) => accumulator + sourceText.slice(token.range[1], allTokens[index + 1].range[0]), ""); + } + + /** + * Returns a template literal form of the given node. + * @param {ASTNode} currentNode A node that should be converted to a template literal + * @param {string} textBeforeNode Text that should appear before the node + * @param {string} textAfterNode Text that should appear after the node + * @returns {string} A string form of this node, represented as a template literal + */ + function getTemplateLiteral(currentNode, textBeforeNode, textAfterNode) { + if (currentNode.type === "Literal" && typeof currentNode.value === "string") { + + /* + * If the current node is a string literal, escape any instances of ${ or ` to prevent them from being interpreted + * as a template placeholder. However, if the code already contains a backslash before the ${ or ` + * for some reason, don't add another backslash, because that would change the meaning of the code (it would cause + * an actual backslash character to appear before the dollar sign). + */ + return `\`${currentNode.raw.slice(1, -1).replace(/\\*(\$\{|`)/gu, matched => { + if (matched.lastIndexOf("\\") % 2) { + return `\\${matched}`; + } + return matched; + + // Unescape any quotes that appear in the original Literal that no longer need to be escaped. + }).replace(new RegExp(`\\\\${currentNode.raw[0]}`, "gu"), currentNode.raw[0])}\``; + } + + if (currentNode.type === "TemplateLiteral") { + return sourceCode.getText(currentNode); + } + + if (isConcatenation(currentNode) && hasStringLiteral(currentNode) && hasNonStringLiteral(currentNode)) { + const plusSign = sourceCode.getFirstTokenBetween(currentNode.left, currentNode.right, token => token.value === "+"); + const textBeforePlus = getTextBetween(currentNode.left, plusSign); + const textAfterPlus = getTextBetween(plusSign, currentNode.right); + const leftEndsWithCurly = endsWithTemplateCurly(currentNode.left); + const rightStartsWithCurly = startsWithTemplateCurly(currentNode.right); + + if (leftEndsWithCurly) { + + // If the left side of the expression ends with a template curly, add the extra text to the end of the curly bracket. + // `foo${bar}` /* comment */ + 'baz' --> `foo${bar /* comment */ }${baz}` + return getTemplateLiteral(currentNode.left, textBeforeNode, textBeforePlus + textAfterPlus).slice(0, -1) + + getTemplateLiteral(currentNode.right, null, textAfterNode).slice(1); + } + if (rightStartsWithCurly) { + + // Otherwise, if the right side of the expression starts with a template curly, add the text there. + // 'foo' /* comment */ + `${bar}baz` --> `foo${ /* comment */ bar}baz` + return getTemplateLiteral(currentNode.left, textBeforeNode, null).slice(0, -1) + + getTemplateLiteral(currentNode.right, textBeforePlus + textAfterPlus, textAfterNode).slice(1); + } + + /* + * Otherwise, these nodes should not be combined into a template curly, since there is nowhere to put + * the text between them. + */ + return `${getTemplateLiteral(currentNode.left, textBeforeNode, null)}${textBeforePlus}+${textAfterPlus}${getTemplateLiteral(currentNode.right, textAfterNode, null)}`; + } + + return `\`\${${textBeforeNode || ""}${sourceCode.getText(currentNode)}${textAfterNode || ""}}\``; + } + + /** + * Returns a fixer object that converts a non-string binary expression to a template literal + * @param {SourceCodeFixer} fixer The fixer object + * @param {ASTNode} node A node that should be converted to a template literal + * @returns {Object} A fix for this binary expression + */ + function fixNonStringBinaryExpression(fixer, node) { + const topBinaryExpr = getTopConcatBinaryExpression(node.parent); + + if (hasOctalEscapeSequence(topBinaryExpr)) { + return null; + } + + return fixer.replaceText(topBinaryExpr, getTemplateLiteral(topBinaryExpr, null, null)); + } + + /** + * Reports if a given node is string concatenation with non string literals. + * @param {ASTNode} node A node to check. + * @returns {void} + */ + function checkForStringConcat(node) { + if (!astUtils.isStringLiteral(node) || !isConcatenation(node.parent)) { + return; + } + + const topBinaryExpr = getTopConcatBinaryExpression(node.parent); + + // Checks whether or not this node had been checked already. + if (done[topBinaryExpr.range[0]]) { + return; + } + done[topBinaryExpr.range[0]] = true; + + if (hasNonStringLiteral(topBinaryExpr)) { + context.report({ + node: topBinaryExpr, + messageId: "unexpectedStringConcatenation", + fix: fixer => fixNonStringBinaryExpression(fixer, node) + }); + } + } + + return { + Program() { + done = Object.create(null); + }, + + Literal: checkForStringConcat, + TemplateLiteral: checkForStringConcat + }; + } +}; diff --git a/eslint/lib/rules/quote-props.js b/eslint/lib/rules/quote-props.js new file mode 100644 index 0000000..a2a4e1d --- /dev/null +++ b/eslint/lib/rules/quote-props.js @@ -0,0 +1,307 @@ +/** + * @fileoverview Rule to flag non-quoted property names in object literals. + * @author Mathias Bynens + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const espree = require("espree"); +const astUtils = require("./utils/ast-utils"); +const keywords = require("./utils/keywords"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "require quotes around object literal property names", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/quote-props" + }, + + schema: { + anyOf: [ + { + type: "array", + items: [ + { + enum: ["always", "as-needed", "consistent", "consistent-as-needed"] + } + ], + minItems: 0, + maxItems: 1 + }, + { + type: "array", + items: [ + { + enum: ["always", "as-needed", "consistent", "consistent-as-needed"] + }, + { + type: "object", + properties: { + keywords: { + type: "boolean" + }, + unnecessary: { + type: "boolean" + }, + numbers: { + type: "boolean" + } + }, + additionalProperties: false + } + ], + minItems: 0, + maxItems: 2 + } + ] + }, + + fixable: "code", + messages: { + requireQuotesDueToReservedWord: "Properties should be quoted as '{{property}}' is a reserved word.", + inconsistentlyQuotedProperty: "Inconsistently quoted property '{{key}}' found.", + unnecessarilyQuotedProperty: "Unnecessarily quoted property '{{property}}' found.", + unquotedReservedProperty: "Unquoted reserved word '{{property}}' used as key.", + unquotedNumericProperty: "Unquoted number literal '{{property}}' used as key.", + unquotedPropertyFound: "Unquoted property '{{property}}' found.", + redundantQuoting: "Properties shouldn't be quoted as all quotes are redundant." + } + }, + + create(context) { + + const MODE = context.options[0], + KEYWORDS = context.options[1] && context.options[1].keywords, + CHECK_UNNECESSARY = !context.options[1] || context.options[1].unnecessary !== false, + NUMBERS = context.options[1] && context.options[1].numbers, + + sourceCode = context.getSourceCode(); + + + /** + * Checks whether a certain string constitutes an ES3 token + * @param {string} tokenStr The string to be checked. + * @returns {boolean} `true` if it is an ES3 token. + */ + function isKeyword(tokenStr) { + return keywords.indexOf(tokenStr) >= 0; + } + + /** + * Checks if an espree-tokenized key has redundant quotes (i.e. whether quotes are unnecessary) + * @param {string} rawKey The raw key value from the source + * @param {espreeTokens} tokens The espree-tokenized node key + * @param {boolean} [skipNumberLiterals=false] Indicates whether number literals should be checked + * @returns {boolean} Whether or not a key has redundant quotes. + * @private + */ + function areQuotesRedundant(rawKey, tokens, skipNumberLiterals) { + return tokens.length === 1 && tokens[0].start === 0 && tokens[0].end === rawKey.length && + (["Identifier", "Keyword", "Null", "Boolean"].indexOf(tokens[0].type) >= 0 || + (tokens[0].type === "Numeric" && !skipNumberLiterals && String(+tokens[0].value) === tokens[0].value)); + } + + /** + * Returns a string representation of a property node with quotes removed + * @param {ASTNode} key Key AST Node, which may or may not be quoted + * @returns {string} A replacement string for this property + */ + function getUnquotedKey(key) { + return key.type === "Identifier" ? key.name : key.value; + } + + /** + * Returns a string representation of a property node with quotes added + * @param {ASTNode} key Key AST Node, which may or may not be quoted + * @returns {string} A replacement string for this property + */ + function getQuotedKey(key) { + if (key.type === "Literal" && typeof key.value === "string") { + + // If the key is already a string literal, don't replace the quotes with double quotes. + return sourceCode.getText(key); + } + + // Otherwise, the key is either an identifier or a number literal. + return `"${key.type === "Identifier" ? key.name : key.value}"`; + } + + /** + * Ensures that a property's key is quoted only when necessary + * @param {ASTNode} node Property AST node + * @returns {void} + */ + function checkUnnecessaryQuotes(node) { + const key = node.key; + + if (node.method || node.computed || node.shorthand) { + return; + } + + if (key.type === "Literal" && typeof key.value === "string") { + let tokens; + + try { + tokens = espree.tokenize(key.value); + } catch (e) { + return; + } + + if (tokens.length !== 1) { + return; + } + + const isKeywordToken = isKeyword(tokens[0].value); + + if (isKeywordToken && KEYWORDS) { + return; + } + + if (CHECK_UNNECESSARY && areQuotesRedundant(key.value, tokens, NUMBERS)) { + context.report({ + node, + messageId: "unnecessarilyQuotedProperty", + data: { property: key.value }, + fix: fixer => fixer.replaceText(key, getUnquotedKey(key)) + }); + } + } else if (KEYWORDS && key.type === "Identifier" && isKeyword(key.name)) { + context.report({ + node, + messageId: "unquotedReservedProperty", + data: { property: key.name }, + fix: fixer => fixer.replaceText(key, getQuotedKey(key)) + }); + } else if (NUMBERS && key.type === "Literal" && astUtils.isNumericLiteral(key)) { + context.report({ + node, + messageId: "unquotedNumericProperty", + data: { property: key.value }, + fix: fixer => fixer.replaceText(key, getQuotedKey(key)) + }); + } + } + + /** + * Ensures that a property's key is quoted + * @param {ASTNode} node Property AST node + * @returns {void} + */ + function checkOmittedQuotes(node) { + const key = node.key; + + if (!node.method && !node.computed && !node.shorthand && !(key.type === "Literal" && typeof key.value === "string")) { + context.report({ + node, + messageId: "unquotedPropertyFound", + data: { property: key.name || key.value }, + fix: fixer => fixer.replaceText(key, getQuotedKey(key)) + }); + } + } + + /** + * Ensures that an object's keys are consistently quoted, optionally checks for redundancy of quotes + * @param {ASTNode} node Property AST node + * @param {boolean} checkQuotesRedundancy Whether to check quotes' redundancy + * @returns {void} + */ + function checkConsistency(node, checkQuotesRedundancy) { + const quotedProps = [], + unquotedProps = []; + let keywordKeyName = null, + necessaryQuotes = false; + + node.properties.forEach(property => { + const key = property.key; + + if (!key || property.method || property.computed || property.shorthand) { + return; + } + + if (key.type === "Literal" && typeof key.value === "string") { + + quotedProps.push(property); + + if (checkQuotesRedundancy) { + let tokens; + + try { + tokens = espree.tokenize(key.value); + } catch (e) { + necessaryQuotes = true; + return; + } + + necessaryQuotes = necessaryQuotes || !areQuotesRedundant(key.value, tokens) || KEYWORDS && isKeyword(tokens[0].value); + } + } else if (KEYWORDS && checkQuotesRedundancy && key.type === "Identifier" && isKeyword(key.name)) { + unquotedProps.push(property); + necessaryQuotes = true; + keywordKeyName = key.name; + } else { + unquotedProps.push(property); + } + }); + + if (checkQuotesRedundancy && quotedProps.length && !necessaryQuotes) { + quotedProps.forEach(property => { + context.report({ + node: property, + messageId: "redundantQuoting", + fix: fixer => fixer.replaceText(property.key, getUnquotedKey(property.key)) + }); + }); + } else if (unquotedProps.length && keywordKeyName) { + unquotedProps.forEach(property => { + context.report({ + node: property, + messageId: "requireQuotesDueToReservedWord", + data: { property: keywordKeyName }, + fix: fixer => fixer.replaceText(property.key, getQuotedKey(property.key)) + }); + }); + } else if (quotedProps.length && unquotedProps.length) { + unquotedProps.forEach(property => { + context.report({ + node: property, + messageId: "inconsistentlyQuotedProperty", + data: { key: property.key.name || property.key.value }, + fix: fixer => fixer.replaceText(property.key, getQuotedKey(property.key)) + }); + }); + } + } + + return { + Property(node) { + if (MODE === "always" || !MODE) { + checkOmittedQuotes(node); + } + if (MODE === "as-needed") { + checkUnnecessaryQuotes(node); + } + }, + ObjectExpression(node) { + if (MODE === "consistent") { + checkConsistency(node, false); + } + if (MODE === "consistent-as-needed") { + checkConsistency(node, true); + } + } + }; + + } +}; diff --git a/eslint/lib/rules/quotes.js b/eslint/lib/rules/quotes.js new file mode 100644 index 0000000..d1f4443 --- /dev/null +++ b/eslint/lib/rules/quotes.js @@ -0,0 +1,332 @@ +/** + * @fileoverview A rule to choose between single and double quote marks + * @author Matt DuVall , Brandon Payton + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Constants +//------------------------------------------------------------------------------ + +const QUOTE_SETTINGS = { + double: { + quote: "\"", + alternateQuote: "'", + description: "doublequote" + }, + single: { + quote: "'", + alternateQuote: "\"", + description: "singlequote" + }, + backtick: { + quote: "`", + alternateQuote: "\"", + description: "backtick" + } +}; + +// An unescaped newline is a newline preceded by an even number of backslashes. +const UNESCAPED_LINEBREAK_PATTERN = new RegExp(String.raw`(^|[^\\])(\\\\)*[${Array.from(astUtils.LINEBREAKS).join("")}]`, "u"); + +/** + * Switches quoting of javascript string between ' " and ` + * escaping and unescaping as necessary. + * Only escaping of the minimal set of characters is changed. + * Note: escaping of newlines when switching from backtick to other quotes is not handled. + * @param {string} str A string to convert. + * @returns {string} The string with changed quotes. + * @private + */ +QUOTE_SETTINGS.double.convert = +QUOTE_SETTINGS.single.convert = +QUOTE_SETTINGS.backtick.convert = function(str) { + const newQuote = this.quote; + const oldQuote = str[0]; + + if (newQuote === oldQuote) { + return str; + } + return newQuote + str.slice(1, -1).replace(/\\(\$\{|\r\n?|\n|.)|["'`]|\$\{|(\r\n?|\n)/gu, (match, escaped, newline) => { + if (escaped === oldQuote || oldQuote === "`" && escaped === "${") { + return escaped; // unescape + } + if (match === newQuote || newQuote === "`" && match === "${") { + return `\\${match}`; // escape + } + if (newline && oldQuote === "`") { + return "\\n"; // escape newlines + } + return match; + }) + newQuote; +}; + +const AVOID_ESCAPE = "avoid-escape"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "enforce the consistent use of either backticks, double, or single quotes", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/quotes" + }, + + fixable: "code", + + schema: [ + { + enum: ["single", "double", "backtick"] + }, + { + anyOf: [ + { + enum: ["avoid-escape"] + }, + { + type: "object", + properties: { + avoidEscape: { + type: "boolean" + }, + allowTemplateLiterals: { + type: "boolean" + } + }, + additionalProperties: false + } + ] + } + ], + + messages: { + wrongQuotes: "Strings must use {{description}}." + } + }, + + create(context) { + + const quoteOption = context.options[0], + settings = QUOTE_SETTINGS[quoteOption || "double"], + options = context.options[1], + allowTemplateLiterals = options && options.allowTemplateLiterals === true, + sourceCode = context.getSourceCode(); + let avoidEscape = options && options.avoidEscape === true; + + // deprecated + if (options === AVOID_ESCAPE) { + avoidEscape = true; + } + + /** + * Determines if a given node is part of JSX syntax. + * + * This function returns `true` in the following cases: + * + * - `
` ... If the literal is an attribute value, the parent of the literal is `JSXAttribute`. + * - `
foo
` ... If the literal is a text content, the parent of the literal is `JSXElement`. + * - `<>foo` ... If the literal is a text content, the parent of the literal is `JSXFragment`. + * + * In particular, this function returns `false` in the following cases: + * + * - `
` + * - `
{"foo"}
` + * + * In both cases, inside of the braces is handled as normal JavaScript. + * The braces are `JSXExpressionContainer` nodes. + * @param {ASTNode} node The Literal node to check. + * @returns {boolean} True if the node is a part of JSX, false if not. + * @private + */ + function isJSXLiteral(node) { + return node.parent.type === "JSXAttribute" || node.parent.type === "JSXElement" || node.parent.type === "JSXFragment"; + } + + /** + * Checks whether or not a given node is a directive. + * The directive is a `ExpressionStatement` which has only a string literal. + * @param {ASTNode} node A node to check. + * @returns {boolean} Whether or not the node is a directive. + * @private + */ + function isDirective(node) { + return ( + node.type === "ExpressionStatement" && + node.expression.type === "Literal" && + typeof node.expression.value === "string" + ); + } + + /** + * Checks whether or not a given node is a part of directive prologues. + * See also: http://www.ecma-international.org/ecma-262/6.0/#sec-directive-prologues-and-the-use-strict-directive + * @param {ASTNode} node A node to check. + * @returns {boolean} Whether or not the node is a part of directive prologues. + * @private + */ + function isPartOfDirectivePrologue(node) { + const block = node.parent.parent; + + if (block.type !== "Program" && (block.type !== "BlockStatement" || !astUtils.isFunction(block.parent))) { + return false; + } + + // Check the node is at a prologue. + for (let i = 0; i < block.body.length; ++i) { + const statement = block.body[i]; + + if (statement === node.parent) { + return true; + } + if (!isDirective(statement)) { + break; + } + } + + return false; + } + + /** + * Checks whether or not a given node is allowed as non backtick. + * @param {ASTNode} node A node to check. + * @returns {boolean} Whether or not the node is allowed as non backtick. + * @private + */ + function isAllowedAsNonBacktick(node) { + const parent = node.parent; + + switch (parent.type) { + + // Directive Prologues. + case "ExpressionStatement": + return isPartOfDirectivePrologue(node); + + // LiteralPropertyName. + case "Property": + case "MethodDefinition": + return parent.key === node && !parent.computed; + + // ModuleSpecifier. + case "ImportDeclaration": + case "ExportNamedDeclaration": + case "ExportAllDeclaration": + return parent.source === node; + + // Others don't allow. + default: + return false; + } + } + + /** + * Checks whether or not a given TemplateLiteral node is actually using any of the special features provided by template literal strings. + * @param {ASTNode} node A TemplateLiteral node to check. + * @returns {boolean} Whether or not the TemplateLiteral node is using any of the special features provided by template literal strings. + * @private + */ + function isUsingFeatureOfTemplateLiteral(node) { + const hasTag = node.parent.type === "TaggedTemplateExpression" && node === node.parent.quasi; + + if (hasTag) { + return true; + } + + const hasStringInterpolation = node.expressions.length > 0; + + if (hasStringInterpolation) { + return true; + } + + const isMultilineString = node.quasis.length >= 1 && UNESCAPED_LINEBREAK_PATTERN.test(node.quasis[0].value.raw); + + if (isMultilineString) { + return true; + } + + return false; + } + + return { + + Literal(node) { + const val = node.value, + rawVal = node.raw; + + if (settings && typeof val === "string") { + let isValid = (quoteOption === "backtick" && isAllowedAsNonBacktick(node)) || + isJSXLiteral(node) || + astUtils.isSurroundedBy(rawVal, settings.quote); + + if (!isValid && avoidEscape) { + isValid = astUtils.isSurroundedBy(rawVal, settings.alternateQuote) && rawVal.indexOf(settings.quote) >= 0; + } + + if (!isValid) { + context.report({ + node, + messageId: "wrongQuotes", + data: { + description: settings.description + }, + fix(fixer) { + if (quoteOption === "backtick" && astUtils.hasOctalEscapeSequence(rawVal)) { + + // An octal escape sequence in a template literal would produce syntax error, even in non-strict mode. + return null; + } + + return fixer.replaceText(node, settings.convert(node.raw)); + } + }); + } + } + }, + + TemplateLiteral(node) { + + // Don't throw an error if backticks are expected or a template literal feature is in use. + if ( + allowTemplateLiterals || + quoteOption === "backtick" || + isUsingFeatureOfTemplateLiteral(node) + ) { + return; + } + + context.report({ + node, + messageId: "wrongQuotes", + data: { + description: settings.description + }, + fix(fixer) { + if (isPartOfDirectivePrologue(node)) { + + /* + * TemplateLiterals in a directive prologue aren't actually directives, but if they're + * in the directive prologue, then fixing them might turn them into directives and change + * the behavior of the code. + */ + return null; + } + return fixer.replaceText(node, settings.convert(sourceCode.getText(node))); + } + }); + } + }; + + } +}; diff --git a/eslint/lib/rules/radix.js b/eslint/lib/rules/radix.js new file mode 100644 index 0000000..3903cb2 --- /dev/null +++ b/eslint/lib/rules/radix.js @@ -0,0 +1,178 @@ +/** + * @fileoverview Rule to flag use of parseInt without a radix argument + * @author James Allardice + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const MODE_ALWAYS = "always", + MODE_AS_NEEDED = "as-needed"; + +const validRadixValues = new Set(Array.from({ length: 37 - 2 }, (_, index) => index + 2)); + +/** + * Checks whether a given variable is shadowed or not. + * @param {eslint-scope.Variable} variable A variable to check. + * @returns {boolean} `true` if the variable is shadowed. + */ +function isShadowed(variable) { + return variable.defs.length >= 1; +} + +/** + * Checks whether a given node is a MemberExpression of `parseInt` method or not. + * @param {ASTNode} node A node to check. + * @returns {boolean} `true` if the node is a MemberExpression of `parseInt` + * method. + */ +function isParseIntMethod(node) { + return ( + node.type === "MemberExpression" && + !node.computed && + node.property.type === "Identifier" && + node.property.name === "parseInt" + ); +} + +/** + * Checks whether a given node is a valid value of radix or not. + * + * The following values are invalid. + * + * - A literal except integers between 2 and 36. + * - undefined. + * @param {ASTNode} radix A node of radix to check. + * @returns {boolean} `true` if the node is valid. + */ +function isValidRadix(radix) { + return !( + (radix.type === "Literal" && !validRadixValues.has(radix.value)) || + (radix.type === "Identifier" && radix.name === "undefined") + ); +} + +/** + * Checks whether a given node is a default value of radix or not. + * @param {ASTNode} radix A node of radix to check. + * @returns {boolean} `true` if the node is the literal node of `10`. + */ +function isDefaultRadix(radix) { + return radix.type === "Literal" && radix.value === 10; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "enforce the consistent use of the radix argument when using `parseInt()`", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/radix" + }, + + schema: [ + { + enum: ["always", "as-needed"] + } + ], + + messages: { + missingParameters: "Missing parameters.", + redundantRadix: "Redundant radix parameter.", + missingRadix: "Missing radix parameter.", + invalidRadix: "Invalid radix parameter, must be an integer between 2 and 36." + } + }, + + create(context) { + const mode = context.options[0] || MODE_ALWAYS; + + /** + * Checks the arguments of a given CallExpression node and reports it if it + * offends this rule. + * @param {ASTNode} node A CallExpression node to check. + * @returns {void} + */ + function checkArguments(node) { + const args = node.arguments; + + switch (args.length) { + case 0: + context.report({ + node, + messageId: "missingParameters" + }); + break; + + case 1: + if (mode === MODE_ALWAYS) { + context.report({ + node, + messageId: "missingRadix" + }); + } + break; + + default: + if (mode === MODE_AS_NEEDED && isDefaultRadix(args[1])) { + context.report({ + node, + messageId: "redundantRadix" + }); + } else if (!isValidRadix(args[1])) { + context.report({ + node, + messageId: "invalidRadix" + }); + } + break; + } + } + + return { + "Program:exit"() { + const scope = context.getScope(); + let variable; + + // Check `parseInt()` + variable = astUtils.getVariableByName(scope, "parseInt"); + if (variable && !isShadowed(variable)) { + variable.references.forEach(reference => { + const node = reference.identifier; + + if (astUtils.isCallee(node)) { + checkArguments(node.parent); + } + }); + } + + // Check `Number.parseInt()` + variable = astUtils.getVariableByName(scope, "Number"); + if (variable && !isShadowed(variable)) { + variable.references.forEach(reference => { + const node = reference.identifier.parent; + + if (isParseIntMethod(node) && astUtils.isCallee(node)) { + checkArguments(node.parent); + } + }); + } + } + }; + } +}; diff --git a/eslint/lib/rules/require-atomic-updates.js b/eslint/lib/rules/require-atomic-updates.js new file mode 100644 index 0000000..4f6acce --- /dev/null +++ b/eslint/lib/rules/require-atomic-updates.js @@ -0,0 +1,283 @@ +/** + * @fileoverview disallow assignments that can lead to race conditions due to usage of `await` or `yield` + * @author Teddy Katz + * @author Toru Nagashima + */ +"use strict"; + +/** + * Make the map from identifiers to each reference. + * @param {escope.Scope} scope The scope to get references. + * @param {Map} [outReferenceMap] The map from identifier nodes to each reference object. + * @returns {Map} `referenceMap`. + */ +function createReferenceMap(scope, outReferenceMap = new Map()) { + for (const reference of scope.references) { + outReferenceMap.set(reference.identifier, reference); + } + for (const childScope of scope.childScopes) { + if (childScope.type !== "function") { + createReferenceMap(childScope, outReferenceMap); + } + } + + return outReferenceMap; +} + +/** + * Get `reference.writeExpr` of a given reference. + * If it's the read reference of MemberExpression in LHS, returns RHS in order to address `a.b = await a` + * @param {escope.Reference} reference The reference to get. + * @returns {Expression|null} The `reference.writeExpr`. + */ +function getWriteExpr(reference) { + if (reference.writeExpr) { + return reference.writeExpr; + } + let node = reference.identifier; + + while (node) { + const t = node.parent.type; + + if (t === "AssignmentExpression" && node.parent.left === node) { + return node.parent.right; + } + if (t === "MemberExpression" && node.parent.object === node) { + node = node.parent; + continue; + } + + break; + } + + return null; +} + +/** + * Checks if an expression is a variable that can only be observed within the given function. + * @param {Variable|null} variable The variable to check + * @param {boolean} isMemberAccess If `true` then this is a member access. + * @returns {boolean} `true` if the variable is local to the given function, and is never referenced in a closure. + */ +function isLocalVariableWithoutEscape(variable, isMemberAccess) { + if (!variable) { + return false; // A global variable which was not defined. + } + + // If the reference is a property access and the variable is a parameter, it handles the variable is not local. + if (isMemberAccess && variable.defs.some(d => d.type === "Parameter")) { + return false; + } + + const functionScope = variable.scope.variableScope; + + return variable.references.every(reference => + reference.from.variableScope === functionScope); +} + +class SegmentInfo { + constructor() { + this.info = new WeakMap(); + } + + /** + * Initialize the segment information. + * @param {PathSegment} segment The segment to initialize. + * @returns {void} + */ + initialize(segment) { + const outdatedReadVariableNames = new Set(); + const freshReadVariableNames = new Set(); + + for (const prevSegment of segment.prevSegments) { + const info = this.info.get(prevSegment); + + if (info) { + info.outdatedReadVariableNames.forEach(Set.prototype.add, outdatedReadVariableNames); + info.freshReadVariableNames.forEach(Set.prototype.add, freshReadVariableNames); + } + } + + this.info.set(segment, { outdatedReadVariableNames, freshReadVariableNames }); + } + + /** + * Mark a given variable as read on given segments. + * @param {PathSegment[]} segments The segments that it read the variable on. + * @param {string} variableName The variable name to be read. + * @returns {void} + */ + markAsRead(segments, variableName) { + for (const segment of segments) { + const info = this.info.get(segment); + + if (info) { + info.freshReadVariableNames.add(variableName); + } + } + } + + /** + * Move `freshReadVariableNames` to `outdatedReadVariableNames`. + * @param {PathSegment[]} segments The segments to process. + * @returns {void} + */ + makeOutdated(segments) { + for (const segment of segments) { + const info = this.info.get(segment); + + if (info) { + info.freshReadVariableNames.forEach(Set.prototype.add, info.outdatedReadVariableNames); + info.freshReadVariableNames.clear(); + } + } + } + + /** + * Check if a given variable is outdated on the current segments. + * @param {PathSegment[]} segments The current segments. + * @param {string} variableName The variable name to check. + * @returns {boolean} `true` if the variable is outdated on the segments. + */ + isOutdated(segments, variableName) { + for (const segment of segments) { + const info = this.info.get(segment); + + if (info && info.outdatedReadVariableNames.has(variableName)) { + return true; + } + } + return false; + } +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow assignments that can lead to race conditions due to usage of `await` or `yield`", + category: "Possible Errors", + recommended: false, + url: "https://eslint.org/docs/rules/require-atomic-updates" + }, + + fixable: null, + schema: [], + + messages: { + nonAtomicUpdate: "Possible race condition: `{{value}}` might be reassigned based on an outdated value of `{{value}}`." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + const assignmentReferences = new Map(); + const segmentInfo = new SegmentInfo(); + let stack = null; + + return { + onCodePathStart(codePath) { + const scope = context.getScope(); + const shouldVerify = + scope.type === "function" && + (scope.block.async || scope.block.generator); + + stack = { + upper: stack, + codePath, + referenceMap: shouldVerify ? createReferenceMap(scope) : null + }; + }, + onCodePathEnd() { + stack = stack.upper; + }, + + // Initialize the segment information. + onCodePathSegmentStart(segment) { + segmentInfo.initialize(segment); + }, + + // Handle references to prepare verification. + Identifier(node) { + const { codePath, referenceMap } = stack; + const reference = referenceMap && referenceMap.get(node); + + // Ignore if this is not a valid variable reference. + if (!reference) { + return; + } + const name = reference.identifier.name; + const variable = reference.resolved; + const writeExpr = getWriteExpr(reference); + const isMemberAccess = reference.identifier.parent.type === "MemberExpression"; + + // Add a fresh read variable. + if (reference.isRead() && !(writeExpr && writeExpr.parent.operator === "=")) { + segmentInfo.markAsRead(codePath.currentSegments, name); + } + + /* + * Register the variable to verify after ESLint traversed the `writeExpr` node + * if this reference is an assignment to a variable which is referred from other closure. + */ + if (writeExpr && + writeExpr.parent.right === writeExpr && // ← exclude variable declarations. + !isLocalVariableWithoutEscape(variable, isMemberAccess) + ) { + let refs = assignmentReferences.get(writeExpr); + + if (!refs) { + refs = []; + assignmentReferences.set(writeExpr, refs); + } + + refs.push(reference); + } + }, + + /* + * Verify assignments. + * If the reference exists in `outdatedReadVariableNames` list, report it. + */ + ":expression:exit"(node) { + const { codePath, referenceMap } = stack; + + // referenceMap exists if this is in a resumable function scope. + if (!referenceMap) { + return; + } + + // Mark the read variables on this code path as outdated. + if (node.type === "AwaitExpression" || node.type === "YieldExpression") { + segmentInfo.makeOutdated(codePath.currentSegments); + } + + // Verify. + const references = assignmentReferences.get(node); + + if (references) { + assignmentReferences.delete(node); + + for (const reference of references) { + const name = reference.identifier.name; + + if (segmentInfo.isOutdated(codePath.currentSegments, name)) { + context.report({ + node: node.parent, + messageId: "nonAtomicUpdate", + data: { + value: sourceCode.getText(node.parent.left) + } + }); + } + } + } + } + }; + } +}; diff --git a/eslint/lib/rules/require-await.js b/eslint/lib/rules/require-await.js new file mode 100644 index 0000000..9b5acc7 --- /dev/null +++ b/eslint/lib/rules/require-await.js @@ -0,0 +1,113 @@ +/** + * @fileoverview Rule to disallow async functions which have no `await` expression. + * @author Toru Nagashima + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Capitalize the 1st letter of the given text. + * @param {string} text The text to capitalize. + * @returns {string} The text that the 1st letter was capitalized. + */ +function capitalizeFirstLetter(text) { + return text[0].toUpperCase() + text.slice(1); +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow async functions which have no `await` expression", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/require-await" + }, + + schema: [], + + messages: { + missingAwait: "{{name}} has no 'await' expression." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + let scopeInfo = null; + + /** + * Push the scope info object to the stack. + * @returns {void} + */ + function enterFunction() { + scopeInfo = { + upper: scopeInfo, + hasAwait: false + }; + } + + /** + * Pop the top scope info object from the stack. + * Also, it reports the function if needed. + * @param {ASTNode} node The node to report. + * @returns {void} + */ + function exitFunction(node) { + if (!node.generator && node.async && !scopeInfo.hasAwait && !astUtils.isEmptyFunction(node)) { + context.report({ + node, + loc: astUtils.getFunctionHeadLoc(node, sourceCode), + messageId: "missingAwait", + data: { + name: capitalizeFirstLetter( + astUtils.getFunctionNameWithKind(node) + ) + } + }); + } + + scopeInfo = scopeInfo.upper; + } + + return { + FunctionDeclaration: enterFunction, + FunctionExpression: enterFunction, + ArrowFunctionExpression: enterFunction, + "FunctionDeclaration:exit": exitFunction, + "FunctionExpression:exit": exitFunction, + "ArrowFunctionExpression:exit": exitFunction, + + AwaitExpression() { + if (!scopeInfo) { + return; + } + + scopeInfo.hasAwait = true; + }, + ForOfStatement(node) { + if (!scopeInfo) { + return; + } + + if (node.await) { + scopeInfo.hasAwait = true; + } + } + }; + } +}; diff --git a/eslint/lib/rules/require-jsdoc.js b/eslint/lib/rules/require-jsdoc.js new file mode 100644 index 0000000..e581b2b --- /dev/null +++ b/eslint/lib/rules/require-jsdoc.js @@ -0,0 +1,121 @@ +/** + * @fileoverview Rule to check for jsdoc presence. + * @author Gyandeep Singh + */ +"use strict"; + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "require JSDoc comments", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/require-jsdoc" + }, + + schema: [ + { + type: "object", + properties: { + require: { + type: "object", + properties: { + ClassDeclaration: { + type: "boolean", + default: false + }, + MethodDefinition: { + type: "boolean", + default: false + }, + FunctionDeclaration: { + type: "boolean", + default: true + }, + ArrowFunctionExpression: { + type: "boolean", + default: false + }, + FunctionExpression: { + type: "boolean", + default: false + } + }, + additionalProperties: false, + default: {} + } + }, + additionalProperties: false + } + ], + + deprecated: true, + replacedBy: [], + + messages: { + missingJSDocComment: "Missing JSDoc comment." + } + }, + + create(context) { + const source = context.getSourceCode(); + const DEFAULT_OPTIONS = { + FunctionDeclaration: true, + MethodDefinition: false, + ClassDeclaration: false, + ArrowFunctionExpression: false, + FunctionExpression: false + }; + const options = Object.assign(DEFAULT_OPTIONS, context.options[0] && context.options[0].require); + + /** + * Report the error message + * @param {ASTNode} node node to report + * @returns {void} + */ + function report(node) { + context.report({ node, messageId: "missingJSDocComment" }); + } + + /** + * Check if the jsdoc comment is present or not. + * @param {ASTNode} node node to examine + * @returns {void} + */ + function checkJsDoc(node) { + const jsdocComment = source.getJSDocComment(node); + + if (!jsdocComment) { + report(node); + } + } + + return { + FunctionDeclaration(node) { + if (options.FunctionDeclaration) { + checkJsDoc(node); + } + }, + FunctionExpression(node) { + if ( + (options.MethodDefinition && node.parent.type === "MethodDefinition") || + (options.FunctionExpression && (node.parent.type === "VariableDeclarator" || (node.parent.type === "Property" && node === node.parent.value))) + ) { + checkJsDoc(node); + } + }, + ClassDeclaration(node) { + if (options.ClassDeclaration) { + checkJsDoc(node); + } + }, + ArrowFunctionExpression(node) { + if (options.ArrowFunctionExpression && node.parent.type === "VariableDeclarator") { + checkJsDoc(node); + } + } + }; + } +}; diff --git a/eslint/lib/rules/require-unicode-regexp.js b/eslint/lib/rules/require-unicode-regexp.js new file mode 100644 index 0000000..880405e --- /dev/null +++ b/eslint/lib/rules/require-unicode-regexp.js @@ -0,0 +1,69 @@ +/** + * @fileoverview Rule to enforce the use of `u` flag on RegExp. + * @author Toru Nagashima + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const { + CALL, + CONSTRUCT, + ReferenceTracker, + getStringIfConstant +} = require("eslint-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "enforce the use of `u` flag on RegExp", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/require-unicode-regexp" + }, + + messages: { + requireUFlag: "Use the 'u' flag." + }, + + schema: [] + }, + + create(context) { + return { + "Literal[regex]"(node) { + const flags = node.regex.flags || ""; + + if (!flags.includes("u")) { + context.report({ node, messageId: "requireUFlag" }); + } + }, + + Program() { + const scope = context.getScope(); + const tracker = new ReferenceTracker(scope); + const trackMap = { + RegExp: { [CALL]: true, [CONSTRUCT]: true } + }; + + for (const { node } of tracker.iterateGlobalReferences(trackMap)) { + const flagsNode = node.arguments[1]; + const flags = getStringIfConstant(flagsNode, scope); + + if (!flagsNode || (typeof flags === "string" && !flags.includes("u"))) { + context.report({ node, messageId: "requireUFlag" }); + } + } + } + }; + } +}; diff --git a/eslint/lib/rules/require-yield.js b/eslint/lib/rules/require-yield.js new file mode 100644 index 0000000..af2344d --- /dev/null +++ b/eslint/lib/rules/require-yield.js @@ -0,0 +1,78 @@ +/** + * @fileoverview Rule to flag the generator functions that does not have yield. + * @author Toru Nagashima + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "require generator functions to contain `yield`", + category: "ECMAScript 6", + recommended: true, + url: "https://eslint.org/docs/rules/require-yield" + }, + + schema: [], + + messages: { + missingYield: "This generator function does not have 'yield'." + } + }, + + create(context) { + const stack = []; + + /** + * If the node is a generator function, start counting `yield` keywords. + * @param {Node} node A function node to check. + * @returns {void} + */ + function beginChecking(node) { + if (node.generator) { + stack.push(0); + } + } + + /** + * If the node is a generator function, end counting `yield` keywords, then + * reports result. + * @param {Node} node A function node to check. + * @returns {void} + */ + function endChecking(node) { + if (!node.generator) { + return; + } + + const countYield = stack.pop(); + + if (countYield === 0 && node.body.body.length > 0) { + context.report({ node, messageId: "missingYield" }); + } + } + + return { + FunctionDeclaration: beginChecking, + "FunctionDeclaration:exit": endChecking, + FunctionExpression: beginChecking, + "FunctionExpression:exit": endChecking, + + // Increases the count of `yield` keyword. + YieldExpression() { + + /* istanbul ignore else */ + if (stack.length > 0) { + stack[stack.length - 1] += 1; + } + } + }; + } +}; diff --git a/eslint/lib/rules/rest-spread-spacing.js b/eslint/lib/rules/rest-spread-spacing.js new file mode 100644 index 0000000..4bb5f78 --- /dev/null +++ b/eslint/lib/rules/rest-spread-spacing.js @@ -0,0 +1,123 @@ +/** + * @fileoverview Enforce spacing between rest and spread operators and their expressions. + * @author Kai Cataldo + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "enforce spacing between rest and spread operators and their expressions", + category: "ECMAScript 6", + recommended: false, + url: "https://eslint.org/docs/rules/rest-spread-spacing" + }, + + fixable: "whitespace", + + schema: [ + { + enum: ["always", "never"] + } + ], + + messages: { + unexpectedWhitespace: "Unexpected whitespace after {{type}} operator.", + expectedWhitespace: "Expected whitespace after {{type}} operator." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(), + alwaysSpace = context.options[0] === "always"; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Checks whitespace between rest/spread operators and their expressions + * @param {ASTNode} node The node to check + * @returns {void} + */ + function checkWhiteSpace(node) { + const operator = sourceCode.getFirstToken(node), + nextToken = sourceCode.getTokenAfter(operator), + hasWhitespace = sourceCode.isSpaceBetweenTokens(operator, nextToken); + let type; + + switch (node.type) { + case "SpreadElement": + type = "spread"; + if (node.parent.type === "ObjectExpression") { + type += " property"; + } + break; + case "RestElement": + type = "rest"; + if (node.parent.type === "ObjectPattern") { + type += " property"; + } + break; + case "ExperimentalSpreadProperty": + type = "spread property"; + break; + case "ExperimentalRestProperty": + type = "rest property"; + break; + default: + return; + } + + if (alwaysSpace && !hasWhitespace) { + context.report({ + node, + loc: { + line: operator.loc.end.line, + column: operator.loc.end.column + }, + messageId: "expectedWhitespace", + data: { + type + }, + fix(fixer) { + return fixer.replaceTextRange([operator.range[1], nextToken.range[0]], " "); + } + }); + } else if (!alwaysSpace && hasWhitespace) { + context.report({ + node, + loc: { + line: operator.loc.end.line, + column: operator.loc.end.column + }, + messageId: "unexpectedWhitespace", + data: { + type + }, + fix(fixer) { + return fixer.removeRange([operator.range[1], nextToken.range[0]]); + } + }); + } + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + SpreadElement: checkWhiteSpace, + RestElement: checkWhiteSpace, + ExperimentalSpreadProperty: checkWhiteSpace, + ExperimentalRestProperty: checkWhiteSpace + }; + } +}; diff --git a/eslint/lib/rules/semi-spacing.js b/eslint/lib/rules/semi-spacing.js new file mode 100644 index 0000000..9294853 --- /dev/null +++ b/eslint/lib/rules/semi-spacing.js @@ -0,0 +1,219 @@ +/** + * @fileoverview Validates spacing before and after semicolon + * @author Mathias Schreck + */ + +"use strict"; + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "enforce consistent spacing before and after semicolons", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/semi-spacing" + }, + + fixable: "whitespace", + + schema: [ + { + type: "object", + properties: { + before: { + type: "boolean", + default: false + }, + after: { + type: "boolean", + default: true + } + }, + additionalProperties: false + } + ], + + messages: { + unexpectedWhitespaceBefore: "Unexpected whitespace before semicolon.", + unexpectedWhitespaceAfter: "Unexpected whitespace after semicolon.", + missingWhitespaceBefore: "Missing whitespace before semicolon.", + missingWhitespaceAfter: "Missing whitespace after semicolon." + } + }, + + create(context) { + + const config = context.options[0], + sourceCode = context.getSourceCode(); + let requireSpaceBefore = false, + requireSpaceAfter = true; + + if (typeof config === "object") { + requireSpaceBefore = config.before; + requireSpaceAfter = config.after; + } + + /** + * Checks if a given token has leading whitespace. + * @param {Object} token The token to check. + * @returns {boolean} True if the given token has leading space, false if not. + */ + function hasLeadingSpace(token) { + const tokenBefore = sourceCode.getTokenBefore(token); + + return tokenBefore && astUtils.isTokenOnSameLine(tokenBefore, token) && sourceCode.isSpaceBetweenTokens(tokenBefore, token); + } + + /** + * Checks if a given token has trailing whitespace. + * @param {Object} token The token to check. + * @returns {boolean} True if the given token has trailing space, false if not. + */ + function hasTrailingSpace(token) { + const tokenAfter = sourceCode.getTokenAfter(token); + + return tokenAfter && astUtils.isTokenOnSameLine(token, tokenAfter) && sourceCode.isSpaceBetweenTokens(token, tokenAfter); + } + + /** + * Checks if the given token is the last token in its line. + * @param {Token} token The token to check. + * @returns {boolean} Whether or not the token is the last in its line. + */ + function isLastTokenInCurrentLine(token) { + const tokenAfter = sourceCode.getTokenAfter(token); + + return !(tokenAfter && astUtils.isTokenOnSameLine(token, tokenAfter)); + } + + /** + * Checks if the given token is the first token in its line + * @param {Token} token The token to check. + * @returns {boolean} Whether or not the token is the first in its line. + */ + function isFirstTokenInCurrentLine(token) { + const tokenBefore = sourceCode.getTokenBefore(token); + + return !(tokenBefore && astUtils.isTokenOnSameLine(token, tokenBefore)); + } + + /** + * Checks if the next token of a given token is a closing parenthesis. + * @param {Token} token The token to check. + * @returns {boolean} Whether or not the next token of a given token is a closing parenthesis. + */ + function isBeforeClosingParen(token) { + const nextToken = sourceCode.getTokenAfter(token); + + return (nextToken && astUtils.isClosingBraceToken(nextToken) || astUtils.isClosingParenToken(nextToken)); + } + + /** + * Reports if the given token has invalid spacing. + * @param {Token} token The semicolon token to check. + * @param {ASTNode} node The corresponding node of the token. + * @returns {void} + */ + function checkSemicolonSpacing(token, node) { + if (astUtils.isSemicolonToken(token)) { + const location = token.loc.start; + + if (hasLeadingSpace(token)) { + if (!requireSpaceBefore) { + context.report({ + node, + loc: location, + messageId: "unexpectedWhitespaceBefore", + fix(fixer) { + const tokenBefore = sourceCode.getTokenBefore(token); + + return fixer.removeRange([tokenBefore.range[1], token.range[0]]); + } + }); + } + } else { + if (requireSpaceBefore) { + context.report({ + node, + loc: location, + messageId: "missingWhitespaceBefore", + fix(fixer) { + return fixer.insertTextBefore(token, " "); + } + }); + } + } + + if (!isFirstTokenInCurrentLine(token) && !isLastTokenInCurrentLine(token) && !isBeforeClosingParen(token)) { + if (hasTrailingSpace(token)) { + if (!requireSpaceAfter) { + context.report({ + node, + loc: location, + messageId: "unexpectedWhitespaceAfter", + fix(fixer) { + const tokenAfter = sourceCode.getTokenAfter(token); + + return fixer.removeRange([token.range[1], tokenAfter.range[0]]); + } + }); + } + } else { + if (requireSpaceAfter) { + context.report({ + node, + loc: location, + messageId: "missingWhitespaceAfter", + fix(fixer) { + return fixer.insertTextAfter(token, " "); + } + }); + } + } + } + } + } + + /** + * Checks the spacing of the semicolon with the assumption that the last token is the semicolon. + * @param {ASTNode} node The node to check. + * @returns {void} + */ + function checkNode(node) { + const token = sourceCode.getLastToken(node); + + checkSemicolonSpacing(token, node); + } + + return { + VariableDeclaration: checkNode, + ExpressionStatement: checkNode, + BreakStatement: checkNode, + ContinueStatement: checkNode, + DebuggerStatement: checkNode, + ReturnStatement: checkNode, + ThrowStatement: checkNode, + ImportDeclaration: checkNode, + ExportNamedDeclaration: checkNode, + ExportAllDeclaration: checkNode, + ExportDefaultDeclaration: checkNode, + ForStatement(node) { + if (node.init) { + checkSemicolonSpacing(sourceCode.getTokenAfter(node.init), node); + } + + if (node.test) { + checkSemicolonSpacing(sourceCode.getTokenAfter(node.test), node); + } + } + }; + } +}; diff --git a/eslint/lib/rules/semi-style.js b/eslint/lib/rules/semi-style.js new file mode 100644 index 0000000..0c9bec4 --- /dev/null +++ b/eslint/lib/rules/semi-style.js @@ -0,0 +1,151 @@ +/** + * @fileoverview Rule to enforce location of semicolons. + * @author Toru Nagashima + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +const SELECTOR = `:matches(${ + [ + "BreakStatement", "ContinueStatement", "DebuggerStatement", + "DoWhileStatement", "ExportAllDeclaration", + "ExportDefaultDeclaration", "ExportNamedDeclaration", + "ExpressionStatement", "ImportDeclaration", "ReturnStatement", + "ThrowStatement", "VariableDeclaration" + ].join(",") +})`; + +/** + * Get the child node list of a given node. + * This returns `Program#body`, `BlockStatement#body`, or `SwitchCase#consequent`. + * This is used to check whether a node is the first/last child. + * @param {Node} node A node to get child node list. + * @returns {Node[]|null} The child node list. + */ +function getChildren(node) { + const t = node.type; + + if (t === "BlockStatement" || t === "Program") { + return node.body; + } + if (t === "SwitchCase") { + return node.consequent; + } + return null; +} + +/** + * Check whether a given node is the last statement in the parent block. + * @param {Node} node A node to check. + * @returns {boolean} `true` if the node is the last statement in the parent block. + */ +function isLastChild(node) { + const t = node.parent.type; + + if (t === "IfStatement" && node.parent.consequent === node && node.parent.alternate) { // before `else` keyword. + return true; + } + if (t === "DoWhileStatement") { // before `while` keyword. + return true; + } + const nodeList = getChildren(node.parent); + + return nodeList !== null && nodeList[nodeList.length - 1] === node; // before `}` or etc. +} + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "enforce location of semicolons", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/semi-style" + }, + + schema: [{ enum: ["last", "first"] }], + fixable: "whitespace", + + messages: { + expectedSemiColon: "Expected this semicolon to be at {{pos}}." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + const option = context.options[0] || "last"; + + /** + * Check the given semicolon token. + * @param {Token} semiToken The semicolon token to check. + * @param {"first"|"last"} expected The expected location to check. + * @returns {void} + */ + function check(semiToken, expected) { + const prevToken = sourceCode.getTokenBefore(semiToken); + const nextToken = sourceCode.getTokenAfter(semiToken); + const prevIsSameLine = !prevToken || astUtils.isTokenOnSameLine(prevToken, semiToken); + const nextIsSameLine = !nextToken || astUtils.isTokenOnSameLine(semiToken, nextToken); + + if ((expected === "last" && !prevIsSameLine) || (expected === "first" && !nextIsSameLine)) { + context.report({ + loc: semiToken.loc, + messageId: "expectedSemiColon", + data: { + pos: (expected === "last") + ? "the end of the previous line" + : "the beginning of the next line" + }, + fix(fixer) { + if (prevToken && nextToken && sourceCode.commentsExistBetween(prevToken, nextToken)) { + return null; + } + + const start = prevToken ? prevToken.range[1] : semiToken.range[0]; + const end = nextToken ? nextToken.range[0] : semiToken.range[1]; + const text = (expected === "last") ? ";\n" : "\n;"; + + return fixer.replaceTextRange([start, end], text); + } + }); + } + } + + return { + [SELECTOR](node) { + if (option === "first" && isLastChild(node)) { + return; + } + + const lastToken = sourceCode.getLastToken(node); + + if (astUtils.isSemicolonToken(lastToken)) { + check(lastToken, option); + } + }, + + ForStatement(node) { + const firstSemi = node.init && sourceCode.getTokenAfter(node.init, astUtils.isSemicolonToken); + const secondSemi = node.test && sourceCode.getTokenAfter(node.test, astUtils.isSemicolonToken); + + if (firstSemi) { + check(firstSemi, "last"); + } + if (secondSemi) { + check(secondSemi, "last"); + } + } + }; + } +}; diff --git a/eslint/lib/rules/semi.js b/eslint/lib/rules/semi.js new file mode 100644 index 0000000..d2f0670 --- /dev/null +++ b/eslint/lib/rules/semi.js @@ -0,0 +1,336 @@ +/** + * @fileoverview Rule to flag missing semicolons. + * @author Nicholas C. Zakas + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const FixTracker = require("./utils/fix-tracker"); +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "require or disallow semicolons instead of ASI", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/semi" + }, + + fixable: "code", + + schema: { + anyOf: [ + { + type: "array", + items: [ + { + enum: ["never"] + }, + { + type: "object", + properties: { + beforeStatementContinuationChars: { + enum: ["always", "any", "never"] + } + }, + additionalProperties: false + } + ], + minItems: 0, + maxItems: 2 + }, + { + type: "array", + items: [ + { + enum: ["always"] + }, + { + type: "object", + properties: { + omitLastInOneLineBlock: { type: "boolean" } + }, + additionalProperties: false + } + ], + minItems: 0, + maxItems: 2 + } + ] + }, + + messages: { + missingSemi: "Missing semicolon.", + extraSemi: "Extra semicolon." + } + }, + + create(context) { + + const OPT_OUT_PATTERN = /^[-[(/+`]/u; // One of [(/+-` + const options = context.options[1]; + const never = context.options[0] === "never"; + const exceptOneLine = Boolean(options && options.omitLastInOneLineBlock); + const beforeStatementContinuationChars = options && options.beforeStatementContinuationChars || "any"; + const sourceCode = context.getSourceCode(); + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Reports a semicolon error with appropriate location and message. + * @param {ASTNode} node The node with an extra or missing semicolon. + * @param {boolean} missing True if the semicolon is missing. + * @returns {void} + */ + function report(node, missing) { + const lastToken = sourceCode.getLastToken(node); + let messageId, + fix, + loc; + + if (!missing) { + messageId = "missingSemi"; + loc = { + start: lastToken.loc.end, + end: astUtils.getNextLocation(sourceCode, lastToken.loc.end) + }; + fix = function(fixer) { + return fixer.insertTextAfter(lastToken, ";"); + }; + } else { + messageId = "extraSemi"; + loc = lastToken.loc; + fix = function(fixer) { + + /* + * Expand the replacement range to include the surrounding + * tokens to avoid conflicting with no-extra-semi. + * https://github.com/eslint/eslint/issues/7928 + */ + return new FixTracker(fixer, sourceCode) + .retainSurroundingTokens(lastToken) + .remove(lastToken); + }; + } + + context.report({ + node, + loc, + messageId, + fix + }); + + } + + /** + * Check whether a given semicolon token is redundant. + * @param {Token} semiToken A semicolon token to check. + * @returns {boolean} `true` if the next token is `;` or `}`. + */ + function isRedundantSemi(semiToken) { + const nextToken = sourceCode.getTokenAfter(semiToken); + + return ( + !nextToken || + astUtils.isClosingBraceToken(nextToken) || + astUtils.isSemicolonToken(nextToken) + ); + } + + /** + * Check whether a given token is the closing brace of an arrow function. + * @param {Token} lastToken A token to check. + * @returns {boolean} `true` if the token is the closing brace of an arrow function. + */ + function isEndOfArrowBlock(lastToken) { + if (!astUtils.isClosingBraceToken(lastToken)) { + return false; + } + const node = sourceCode.getNodeByRangeIndex(lastToken.range[0]); + + return ( + node.type === "BlockStatement" && + node.parent.type === "ArrowFunctionExpression" + ); + } + + /** + * Check whether a given node is on the same line with the next token. + * @param {Node} node A statement node to check. + * @returns {boolean} `true` if the node is on the same line with the next token. + */ + function isOnSameLineWithNextToken(node) { + const prevToken = sourceCode.getLastToken(node, 1); + const nextToken = sourceCode.getTokenAfter(node); + + return !!nextToken && astUtils.isTokenOnSameLine(prevToken, nextToken); + } + + /** + * Check whether a given node can connect the next line if the next line is unreliable. + * @param {Node} node A statement node to check. + * @returns {boolean} `true` if the node can connect the next line. + */ + function maybeAsiHazardAfter(node) { + const t = node.type; + + if (t === "DoWhileStatement" || + t === "BreakStatement" || + t === "ContinueStatement" || + t === "DebuggerStatement" || + t === "ImportDeclaration" || + t === "ExportAllDeclaration" + ) { + return false; + } + if (t === "ReturnStatement") { + return Boolean(node.argument); + } + if (t === "ExportNamedDeclaration") { + return Boolean(node.declaration); + } + if (isEndOfArrowBlock(sourceCode.getLastToken(node, 1))) { + return false; + } + + return true; + } + + /** + * Check whether a given token can connect the previous statement. + * @param {Token} token A token to check. + * @returns {boolean} `true` if the token is one of `[`, `(`, `/`, `+`, `-`, ```, `++`, and `--`. + */ + function maybeAsiHazardBefore(token) { + return ( + Boolean(token) && + OPT_OUT_PATTERN.test(token.value) && + token.value !== "++" && + token.value !== "--" + ); + } + + /** + * Check if the semicolon of a given node is unnecessary, only true if: + * - next token is a valid statement divider (`;` or `}`). + * - next token is on a new line and the node is not connectable to the new line. + * @param {Node} node A statement node to check. + * @returns {boolean} whether the semicolon is unnecessary. + */ + function canRemoveSemicolon(node) { + if (isRedundantSemi(sourceCode.getLastToken(node))) { + return true; // `;;` or `;}` + } + if (isOnSameLineWithNextToken(node)) { + return false; // One liner. + } + if (beforeStatementContinuationChars === "never" && !maybeAsiHazardAfter(node)) { + return true; // ASI works. This statement doesn't connect to the next. + } + if (!maybeAsiHazardBefore(sourceCode.getTokenAfter(node))) { + return true; // ASI works. The next token doesn't connect to this statement. + } + + return false; + } + + /** + * Checks a node to see if it's in a one-liner block statement. + * @param {ASTNode} node The node to check. + * @returns {boolean} whether the node is in a one-liner block statement. + */ + function isOneLinerBlock(node) { + const parent = node.parent; + const nextToken = sourceCode.getTokenAfter(node); + + if (!nextToken || nextToken.value !== "}") { + return false; + } + return ( + !!parent && + parent.type === "BlockStatement" && + parent.loc.start.line === parent.loc.end.line + ); + } + + /** + * Checks a node to see if it's followed by a semicolon. + * @param {ASTNode} node The node to check. + * @returns {void} + */ + function checkForSemicolon(node) { + const isSemi = astUtils.isSemicolonToken(sourceCode.getLastToken(node)); + + if (never) { + if (isSemi && canRemoveSemicolon(node)) { + report(node, true); + } else if (!isSemi && beforeStatementContinuationChars === "always" && maybeAsiHazardBefore(sourceCode.getTokenAfter(node))) { + report(node); + } + } else { + const oneLinerBlock = (exceptOneLine && isOneLinerBlock(node)); + + if (isSemi && oneLinerBlock) { + report(node, true); + } else if (!isSemi && !oneLinerBlock) { + report(node); + } + } + } + + /** + * Checks to see if there's a semicolon after a variable declaration. + * @param {ASTNode} node The node to check. + * @returns {void} + */ + function checkForSemicolonForVariableDeclaration(node) { + const parent = node.parent; + + if ((parent.type !== "ForStatement" || parent.init !== node) && + (!/^For(?:In|Of)Statement/u.test(parent.type) || parent.left !== node) + ) { + checkForSemicolon(node); + } + } + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + VariableDeclaration: checkForSemicolonForVariableDeclaration, + ExpressionStatement: checkForSemicolon, + ReturnStatement: checkForSemicolon, + ThrowStatement: checkForSemicolon, + DoWhileStatement: checkForSemicolon, + DebuggerStatement: checkForSemicolon, + BreakStatement: checkForSemicolon, + ContinueStatement: checkForSemicolon, + ImportDeclaration: checkForSemicolon, + ExportAllDeclaration: checkForSemicolon, + ExportNamedDeclaration(node) { + if (!node.declaration) { + checkForSemicolon(node); + } + }, + ExportDefaultDeclaration(node) { + if (!/(?:Class|Function)Declaration/u.test(node.declaration.type)) { + checkForSemicolon(node); + } + } + }; + + } +}; diff --git a/eslint/lib/rules/sort-imports.js b/eslint/lib/rules/sort-imports.js new file mode 100644 index 0000000..65ad9a1 --- /dev/null +++ b/eslint/lib/rules/sort-imports.js @@ -0,0 +1,213 @@ +/** + * @fileoverview Rule to require sorting of import declarations + * @author Christian Schuller + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "enforce sorted import declarations within modules", + category: "ECMAScript 6", + recommended: false, + url: "https://eslint.org/docs/rules/sort-imports" + }, + + schema: [ + { + type: "object", + properties: { + ignoreCase: { + type: "boolean", + default: false + }, + memberSyntaxSortOrder: { + type: "array", + items: { + enum: ["none", "all", "multiple", "single"] + }, + uniqueItems: true, + minItems: 4, + maxItems: 4 + }, + ignoreDeclarationSort: { + type: "boolean", + default: false + }, + ignoreMemberSort: { + type: "boolean", + default: false + } + }, + additionalProperties: false + } + ], + + fixable: "code", + + messages: { + sortImportsAlphabetically: "Imports should be sorted alphabetically.", + sortMembersAlphabetically: "Member '{{memberName}}' of the import declaration should be sorted alphabetically.", + unexpectedSyntaxOrder: "Expected '{{syntaxA}}' syntax before '{{syntaxB}}' syntax." + } + }, + + create(context) { + + const configuration = context.options[0] || {}, + ignoreCase = configuration.ignoreCase || false, + ignoreDeclarationSort = configuration.ignoreDeclarationSort || false, + ignoreMemberSort = configuration.ignoreMemberSort || false, + memberSyntaxSortOrder = configuration.memberSyntaxSortOrder || ["none", "all", "multiple", "single"], + sourceCode = context.getSourceCode(); + let previousDeclaration = null; + + /** + * Gets the used member syntax style. + * + * import "my-module.js" --> none + * import * as myModule from "my-module.js" --> all + * import {myMember} from "my-module.js" --> single + * import {foo, bar} from "my-module.js" --> multiple + * @param {ASTNode} node the ImportDeclaration node. + * @returns {string} used member parameter style, ["all", "multiple", "single"] + */ + function usedMemberSyntax(node) { + if (node.specifiers.length === 0) { + return "none"; + } + if (node.specifiers[0].type === "ImportNamespaceSpecifier") { + return "all"; + } + if (node.specifiers.length === 1) { + return "single"; + } + return "multiple"; + + } + + /** + * Gets the group by member parameter index for given declaration. + * @param {ASTNode} node the ImportDeclaration node. + * @returns {number} the declaration group by member index. + */ + function getMemberParameterGroupIndex(node) { + return memberSyntaxSortOrder.indexOf(usedMemberSyntax(node)); + } + + /** + * Gets the local name of the first imported module. + * @param {ASTNode} node the ImportDeclaration node. + * @returns {?string} the local name of the first imported module. + */ + function getFirstLocalMemberName(node) { + if (node.specifiers[0]) { + return node.specifiers[0].local.name; + } + return null; + + } + + return { + ImportDeclaration(node) { + if (!ignoreDeclarationSort) { + if (previousDeclaration) { + const currentMemberSyntaxGroupIndex = getMemberParameterGroupIndex(node), + previousMemberSyntaxGroupIndex = getMemberParameterGroupIndex(previousDeclaration); + let currentLocalMemberName = getFirstLocalMemberName(node), + previousLocalMemberName = getFirstLocalMemberName(previousDeclaration); + + if (ignoreCase) { + previousLocalMemberName = previousLocalMemberName && previousLocalMemberName.toLowerCase(); + currentLocalMemberName = currentLocalMemberName && currentLocalMemberName.toLowerCase(); + } + + /* + * When the current declaration uses a different member syntax, + * then check if the ordering is correct. + * Otherwise, make a default string compare (like rule sort-vars to be consistent) of the first used local member name. + */ + if (currentMemberSyntaxGroupIndex !== previousMemberSyntaxGroupIndex) { + if (currentMemberSyntaxGroupIndex < previousMemberSyntaxGroupIndex) { + context.report({ + node, + messageId: "unexpectedSyntaxOrder", + data: { + syntaxA: memberSyntaxSortOrder[currentMemberSyntaxGroupIndex], + syntaxB: memberSyntaxSortOrder[previousMemberSyntaxGroupIndex] + } + }); + } + } else { + if (previousLocalMemberName && + currentLocalMemberName && + currentLocalMemberName < previousLocalMemberName + ) { + context.report({ + node, + messageId: "sortImportsAlphabetically" + }); + } + } + } + + previousDeclaration = node; + } + + if (!ignoreMemberSort) { + const importSpecifiers = node.specifiers.filter(specifier => specifier.type === "ImportSpecifier"); + const getSortableName = ignoreCase ? specifier => specifier.local.name.toLowerCase() : specifier => specifier.local.name; + const firstUnsortedIndex = importSpecifiers.map(getSortableName).findIndex((name, index, array) => array[index - 1] > name); + + if (firstUnsortedIndex !== -1) { + context.report({ + node: importSpecifiers[firstUnsortedIndex], + messageId: "sortMembersAlphabetically", + data: { memberName: importSpecifiers[firstUnsortedIndex].local.name }, + fix(fixer) { + if (importSpecifiers.some(specifier => + sourceCode.getCommentsBefore(specifier).length || sourceCode.getCommentsAfter(specifier).length)) { + + // If there are comments in the ImportSpecifier list, don't rearrange the specifiers. + return null; + } + + return fixer.replaceTextRange( + [importSpecifiers[0].range[0], importSpecifiers[importSpecifiers.length - 1].range[1]], + importSpecifiers + + // Clone the importSpecifiers array to avoid mutating it + .slice() + + // Sort the array into the desired order + .sort((specifierA, specifierB) => { + const aName = getSortableName(specifierA); + const bName = getSortableName(specifierB); + + return aName > bName ? 1 : -1; + }) + + // Build a string out of the sorted list of import specifiers and the text between the originals + .reduce((sourceText, specifier, index) => { + const textAfterSpecifier = index === importSpecifiers.length - 1 + ? "" + : sourceCode.getText().slice(importSpecifiers[index].range[1], importSpecifiers[index + 1].range[0]); + + return sourceText + sourceCode.getText(specifier) + textAfterSpecifier; + }, "") + ); + } + }); + } + } + } + }; + } +}; diff --git a/eslint/lib/rules/sort-keys.js b/eslint/lib/rules/sort-keys.js new file mode 100644 index 0000000..8a95ee2 --- /dev/null +++ b/eslint/lib/rules/sort-keys.js @@ -0,0 +1,187 @@ +/** + * @fileoverview Rule to require object keys to be sorted + * @author Toru Nagashima + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"), + naturalCompare = require("natural-compare"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Gets the property name of the given `Property` node. + * + * - If the property's key is an `Identifier` node, this returns the key's name + * whether it's a computed property or not. + * - If the property has a static name, this returns the static name. + * - Otherwise, this returns null. + * @param {ASTNode} node The `Property` node to get. + * @returns {string|null} The property name or null. + * @private + */ +function getPropertyName(node) { + const staticName = astUtils.getStaticPropertyName(node); + + if (staticName !== null) { + return staticName; + } + + return node.key.name || null; +} + +/** + * Functions which check that the given 2 names are in specific order. + * + * Postfix `I` is meant insensitive. + * Postfix `N` is meant natural. + * @private + */ +const isValidOrders = { + asc(a, b) { + return a <= b; + }, + ascI(a, b) { + return a.toLowerCase() <= b.toLowerCase(); + }, + ascN(a, b) { + return naturalCompare(a, b) <= 0; + }, + ascIN(a, b) { + return naturalCompare(a.toLowerCase(), b.toLowerCase()) <= 0; + }, + desc(a, b) { + return isValidOrders.asc(b, a); + }, + descI(a, b) { + return isValidOrders.ascI(b, a); + }, + descN(a, b) { + return isValidOrders.ascN(b, a); + }, + descIN(a, b) { + return isValidOrders.ascIN(b, a); + } +}; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "require object keys to be sorted", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/sort-keys" + }, + + schema: [ + { + enum: ["asc", "desc"] + }, + { + type: "object", + properties: { + caseSensitive: { + type: "boolean", + default: true + }, + natural: { + type: "boolean", + default: false + }, + minKeys: { + type: "integer", + minimum: 2, + default: 2 + } + }, + additionalProperties: false + } + ], + + messages: { + sortKeys: "Expected object keys to be in {{natural}}{{insensitive}}{{order}}ending order. '{{thisName}}' should be before '{{prevName}}'." + } + }, + + create(context) { + + // Parse options. + const order = context.options[0] || "asc"; + const options = context.options[1]; + const insensitive = options && options.caseSensitive === false; + const natural = options && options.natural; + const minKeys = options && options.minKeys; + const isValidOrder = isValidOrders[ + order + (insensitive ? "I" : "") + (natural ? "N" : "") + ]; + + // The stack to save the previous property's name for each object literals. + let stack = null; + + return { + ObjectExpression(node) { + stack = { + upper: stack, + prevName: null, + numKeys: node.properties.length + }; + }, + + "ObjectExpression:exit"() { + stack = stack.upper; + }, + + SpreadElement(node) { + if (node.parent.type === "ObjectExpression") { + stack.prevName = null; + } + }, + + Property(node) { + if (node.parent.type === "ObjectPattern") { + return; + } + + const prevName = stack.prevName; + const numKeys = stack.numKeys; + const thisName = getPropertyName(node); + + if (thisName !== null) { + stack.prevName = thisName; + } + + if (prevName === null || thisName === null || numKeys < minKeys) { + return; + } + + if (!isValidOrder(prevName, thisName)) { + context.report({ + node, + loc: node.key.loc, + messageId: "sortKeys", + data: { + thisName, + prevName, + order, + insensitive: insensitive ? "insensitive " : "", + natural: natural ? "natural " : "" + } + }); + } + } + }; + } +}; diff --git a/eslint/lib/rules/sort-vars.js b/eslint/lib/rules/sort-vars.js new file mode 100644 index 0000000..7add2cf --- /dev/null +++ b/eslint/lib/rules/sort-vars.js @@ -0,0 +1,104 @@ +/** + * @fileoverview Rule to require sorting of variables within a single Variable Declaration block + * @author Ilya Volodin + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "require variables within the same declaration block to be sorted", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/sort-vars" + }, + + schema: [ + { + type: "object", + properties: { + ignoreCase: { + type: "boolean", + default: false + } + }, + additionalProperties: false + } + ], + + fixable: "code", + + messages: { + sortVars: "Variables within the same declaration block should be sorted alphabetically." + } + }, + + create(context) { + + const configuration = context.options[0] || {}, + ignoreCase = configuration.ignoreCase || false, + sourceCode = context.getSourceCode(); + + return { + VariableDeclaration(node) { + const idDeclarations = node.declarations.filter(decl => decl.id.type === "Identifier"); + const getSortableName = ignoreCase ? decl => decl.id.name.toLowerCase() : decl => decl.id.name; + const unfixable = idDeclarations.some(decl => decl.init !== null && decl.init.type !== "Literal"); + let fixed = false; + + idDeclarations.slice(1).reduce((memo, decl) => { + const lastVariableName = getSortableName(memo), + currentVariableName = getSortableName(decl); + + if (currentVariableName < lastVariableName) { + context.report({ + node: decl, + messageId: "sortVars", + fix(fixer) { + if (unfixable || fixed) { + return null; + } + return fixer.replaceTextRange( + [idDeclarations[0].range[0], idDeclarations[idDeclarations.length - 1].range[1]], + idDeclarations + + // Clone the idDeclarations array to avoid mutating it + .slice() + + // Sort the array into the desired order + .sort((declA, declB) => { + const aName = getSortableName(declA); + const bName = getSortableName(declB); + + return aName > bName ? 1 : -1; + }) + + // Build a string out of the sorted list of identifier declarations and the text between the originals + .reduce((sourceText, identifier, index) => { + const textAfterIdentifier = index === idDeclarations.length - 1 + ? "" + : sourceCode.getText().slice(idDeclarations[index].range[1], idDeclarations[index + 1].range[0]); + + return sourceText + sourceCode.getText(identifier) + textAfterIdentifier; + }, "") + + ); + } + }); + fixed = true; + return memo; + } + return decl; + + }, idDeclarations[0]); + } + }; + } +}; diff --git a/eslint/lib/rules/space-before-blocks.js b/eslint/lib/rules/space-before-blocks.js new file mode 100644 index 0000000..9b56481 --- /dev/null +++ b/eslint/lib/rules/space-before-blocks.js @@ -0,0 +1,164 @@ +/** + * @fileoverview A rule to ensure whitespace before blocks. + * @author Mathias Schreck + */ + +"use strict"; + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "enforce consistent spacing before blocks", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/space-before-blocks" + }, + + fixable: "whitespace", + + schema: [ + { + oneOf: [ + { + enum: ["always", "never"] + }, + { + type: "object", + properties: { + keywords: { + enum: ["always", "never", "off"] + }, + functions: { + enum: ["always", "never", "off"] + }, + classes: { + enum: ["always", "never", "off"] + } + }, + additionalProperties: false + } + ] + } + ], + + messages: { + unexpectedSpace: "Unexpected space before opening brace.", + missingSpace: "Missing space before opening brace." + } + }, + + create(context) { + const config = context.options[0], + sourceCode = context.getSourceCode(); + let alwaysFunctions = true, + alwaysKeywords = true, + alwaysClasses = true, + neverFunctions = false, + neverKeywords = false, + neverClasses = false; + + if (typeof config === "object") { + alwaysFunctions = config.functions === "always"; + alwaysKeywords = config.keywords === "always"; + alwaysClasses = config.classes === "always"; + neverFunctions = config.functions === "never"; + neverKeywords = config.keywords === "never"; + neverClasses = config.classes === "never"; + } else if (config === "never") { + alwaysFunctions = false; + alwaysKeywords = false; + alwaysClasses = false; + neverFunctions = true; + neverKeywords = true; + neverClasses = true; + } + + /** + * Checks whether or not a given token is an arrow operator (=>) or a keyword + * in order to avoid to conflict with `arrow-spacing` and `keyword-spacing`. + * @param {Token} token A token to check. + * @returns {boolean} `true` if the token is an arrow operator. + */ + function isConflicted(token) { + return (token.type === "Punctuator" && token.value === "=>") || token.type === "Keyword"; + } + + /** + * Checks the given BlockStatement node has a preceding space if it doesn’t start on a new line. + * @param {ASTNode|Token} node The AST node of a BlockStatement. + * @returns {void} undefined. + */ + function checkPrecedingSpace(node) { + const precedingToken = sourceCode.getTokenBefore(node); + + if (precedingToken && !isConflicted(precedingToken) && astUtils.isTokenOnSameLine(precedingToken, node)) { + const hasSpace = sourceCode.isSpaceBetweenTokens(precedingToken, node); + const parent = context.getAncestors().pop(); + let requireSpace; + let requireNoSpace; + + if (parent.type === "FunctionExpression" || parent.type === "FunctionDeclaration") { + requireSpace = alwaysFunctions; + requireNoSpace = neverFunctions; + } else if (node.type === "ClassBody") { + requireSpace = alwaysClasses; + requireNoSpace = neverClasses; + } else { + requireSpace = alwaysKeywords; + requireNoSpace = neverKeywords; + } + + if (requireSpace && !hasSpace) { + context.report({ + node, + messageId: "missingSpace", + fix(fixer) { + return fixer.insertTextBefore(node, " "); + } + }); + } else if (requireNoSpace && hasSpace) { + context.report({ + node, + messageId: "unexpectedSpace", + fix(fixer) { + return fixer.removeRange([precedingToken.range[1], node.range[0]]); + } + }); + } + } + } + + /** + * Checks if the CaseBlock of an given SwitchStatement node has a preceding space. + * @param {ASTNode} node The node of a SwitchStatement. + * @returns {void} undefined. + */ + function checkSpaceBeforeCaseBlock(node) { + const cases = node.cases; + let openingBrace; + + if (cases.length > 0) { + openingBrace = sourceCode.getTokenBefore(cases[0]); + } else { + openingBrace = sourceCode.getLastToken(node, 1); + } + + checkPrecedingSpace(openingBrace); + } + + return { + BlockStatement: checkPrecedingSpace, + ClassBody: checkPrecedingSpace, + SwitchStatement: checkSpaceBeforeCaseBlock + }; + + } +}; diff --git a/eslint/lib/rules/space-before-function-paren.js b/eslint/lib/rules/space-before-function-paren.js new file mode 100644 index 0000000..af609c2 --- /dev/null +++ b/eslint/lib/rules/space-before-function-paren.js @@ -0,0 +1,161 @@ +/** + * @fileoverview Rule to validate spacing before function paren. + * @author Mathias Schreck + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "enforce consistent spacing before `function` definition opening parenthesis", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/space-before-function-paren" + }, + + fixable: "whitespace", + + schema: [ + { + oneOf: [ + { + enum: ["always", "never"] + }, + { + type: "object", + properties: { + anonymous: { + enum: ["always", "never", "ignore"] + }, + named: { + enum: ["always", "never", "ignore"] + }, + asyncArrow: { + enum: ["always", "never", "ignore"] + } + }, + additionalProperties: false + } + ] + } + ], + + messages: { + unexpectedSpace: "Unexpected space before function parentheses.", + missingSpace: "Missing space before function parentheses." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + const baseConfig = typeof context.options[0] === "string" ? context.options[0] : "always"; + const overrideConfig = typeof context.options[0] === "object" ? context.options[0] : {}; + + /** + * Determines whether a function has a name. + * @param {ASTNode} node The function node. + * @returns {boolean} Whether the function has a name. + */ + function isNamedFunction(node) { + if (node.id) { + return true; + } + + const parent = node.parent; + + return parent.type === "MethodDefinition" || + (parent.type === "Property" && + ( + parent.kind === "get" || + parent.kind === "set" || + parent.method + ) + ); + } + + /** + * Gets the config for a given function + * @param {ASTNode} node The function node + * @returns {string} "always", "never", or "ignore" + */ + function getConfigForFunction(node) { + if (node.type === "ArrowFunctionExpression") { + + // Always ignore non-async functions and arrow functions without parens, e.g. async foo => bar + if (node.async && astUtils.isOpeningParenToken(sourceCode.getFirstToken(node, { skip: 1 }))) { + return overrideConfig.asyncArrow || baseConfig; + } + } else if (isNamedFunction(node)) { + return overrideConfig.named || baseConfig; + + // `generator-star-spacing` should warn anonymous generators. E.g. `function* () {}` + } else if (!node.generator) { + return overrideConfig.anonymous || baseConfig; + } + + return "ignore"; + } + + /** + * Checks the parens of a function node + * @param {ASTNode} node A function node + * @returns {void} + */ + function checkFunction(node) { + const functionConfig = getConfigForFunction(node); + + if (functionConfig === "ignore") { + return; + } + + const rightToken = sourceCode.getFirstToken(node, astUtils.isOpeningParenToken); + const leftToken = sourceCode.getTokenBefore(rightToken); + const hasSpacing = sourceCode.isSpaceBetweenTokens(leftToken, rightToken); + + if (hasSpacing && functionConfig === "never") { + context.report({ + node, + loc: leftToken.loc.end, + messageId: "unexpectedSpace", + fix(fixer) { + const comments = sourceCode.getCommentsBefore(rightToken); + + // Don't fix anything if there's a single line comment between the left and the right token + if (comments.some(comment => comment.type === "Line")) { + return null; + } + return fixer.replaceTextRange( + [leftToken.range[1], rightToken.range[0]], + comments.reduce((text, comment) => text + sourceCode.getText(comment), "") + ); + } + }); + } else if (!hasSpacing && functionConfig === "always") { + context.report({ + node, + loc: leftToken.loc.end, + messageId: "missingSpace", + fix: fixer => fixer.insertTextAfter(leftToken, " ") + }); + } + } + + return { + ArrowFunctionExpression: checkFunction, + FunctionDeclaration: checkFunction, + FunctionExpression: checkFunction + }; + } +}; diff --git a/eslint/lib/rules/space-in-parens.js b/eslint/lib/rules/space-in-parens.js new file mode 100644 index 0000000..b0a604d --- /dev/null +++ b/eslint/lib/rules/space-in-parens.js @@ -0,0 +1,282 @@ +/** + * @fileoverview Disallows or enforces spaces inside of parentheses. + * @author Jonathan Rajavuori + */ +"use strict"; + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "enforce consistent spacing inside parentheses", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/space-in-parens" + }, + + fixable: "whitespace", + + schema: [ + { + enum: ["always", "never"] + }, + { + type: "object", + properties: { + exceptions: { + type: "array", + items: { + enum: ["{}", "[]", "()", "empty"] + }, + uniqueItems: true + } + }, + additionalProperties: false + } + ], + + messages: { + missingOpeningSpace: "There must be a space after this paren.", + missingClosingSpace: "There must be a space before this paren.", + rejectedOpeningSpace: "There should be no space after this paren.", + rejectedClosingSpace: "There should be no space before this paren." + } + }, + + create(context) { + const ALWAYS = context.options[0] === "always", + exceptionsArrayOptions = (context.options[1] && context.options[1].exceptions) || [], + options = {}; + + let exceptions; + + if (exceptionsArrayOptions.length) { + options.braceException = exceptionsArrayOptions.includes("{}"); + options.bracketException = exceptionsArrayOptions.includes("[]"); + options.parenException = exceptionsArrayOptions.includes("()"); + options.empty = exceptionsArrayOptions.includes("empty"); + } + + /** + * Produces an object with the opener and closer exception values + * @returns {Object} `openers` and `closers` exception values + * @private + */ + function getExceptions() { + const openers = [], + closers = []; + + if (options.braceException) { + openers.push("{"); + closers.push("}"); + } + + if (options.bracketException) { + openers.push("["); + closers.push("]"); + } + + if (options.parenException) { + openers.push("("); + closers.push(")"); + } + + if (options.empty) { + openers.push(")"); + closers.push("("); + } + + return { + openers, + closers + }; + } + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + const sourceCode = context.getSourceCode(); + + /** + * Determines if a token is one of the exceptions for the opener paren + * @param {Object} token The token to check + * @returns {boolean} True if the token is one of the exceptions for the opener paren + */ + function isOpenerException(token) { + return exceptions.openers.includes(token.value); + } + + /** + * Determines if a token is one of the exceptions for the closer paren + * @param {Object} token The token to check + * @returns {boolean} True if the token is one of the exceptions for the closer paren + */ + function isCloserException(token) { + return exceptions.closers.includes(token.value); + } + + /** + * Determines if an opening paren is immediately followed by a required space + * @param {Object} openingParenToken The paren token + * @param {Object} tokenAfterOpeningParen The token after it + * @returns {boolean} True if the opening paren is missing a required space + */ + function openerMissingSpace(openingParenToken, tokenAfterOpeningParen) { + if (sourceCode.isSpaceBetweenTokens(openingParenToken, tokenAfterOpeningParen)) { + return false; + } + + if (!options.empty && astUtils.isClosingParenToken(tokenAfterOpeningParen)) { + return false; + } + + if (ALWAYS) { + return !isOpenerException(tokenAfterOpeningParen); + } + return isOpenerException(tokenAfterOpeningParen); + } + + /** + * Determines if an opening paren is immediately followed by a disallowed space + * @param {Object} openingParenToken The paren token + * @param {Object} tokenAfterOpeningParen The token after it + * @returns {boolean} True if the opening paren has a disallowed space + */ + function openerRejectsSpace(openingParenToken, tokenAfterOpeningParen) { + if (!astUtils.isTokenOnSameLine(openingParenToken, tokenAfterOpeningParen)) { + return false; + } + + if (tokenAfterOpeningParen.type === "Line") { + return false; + } + + if (!sourceCode.isSpaceBetweenTokens(openingParenToken, tokenAfterOpeningParen)) { + return false; + } + + if (ALWAYS) { + return isOpenerException(tokenAfterOpeningParen); + } + return !isOpenerException(tokenAfterOpeningParen); + } + + /** + * Determines if a closing paren is immediately preceded by a required space + * @param {Object} tokenBeforeClosingParen The token before the paren + * @param {Object} closingParenToken The paren token + * @returns {boolean} True if the closing paren is missing a required space + */ + function closerMissingSpace(tokenBeforeClosingParen, closingParenToken) { + if (sourceCode.isSpaceBetweenTokens(tokenBeforeClosingParen, closingParenToken)) { + return false; + } + + if (!options.empty && astUtils.isOpeningParenToken(tokenBeforeClosingParen)) { + return false; + } + + if (ALWAYS) { + return !isCloserException(tokenBeforeClosingParen); + } + return isCloserException(tokenBeforeClosingParen); + } + + /** + * Determines if a closer paren is immediately preceded by a disallowed space + * @param {Object} tokenBeforeClosingParen The token before the paren + * @param {Object} closingParenToken The paren token + * @returns {boolean} True if the closing paren has a disallowed space + */ + function closerRejectsSpace(tokenBeforeClosingParen, closingParenToken) { + if (!astUtils.isTokenOnSameLine(tokenBeforeClosingParen, closingParenToken)) { + return false; + } + + if (!sourceCode.isSpaceBetweenTokens(tokenBeforeClosingParen, closingParenToken)) { + return false; + } + + if (ALWAYS) { + return isCloserException(tokenBeforeClosingParen); + } + return !isCloserException(tokenBeforeClosingParen); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + Program: function checkParenSpaces(node) { + exceptions = getExceptions(); + const tokens = sourceCode.tokensAndComments; + + tokens.forEach((token, i) => { + const prevToken = tokens[i - 1]; + const nextToken = tokens[i + 1]; + + // if token is not an opening or closing paren token, do nothing + if (!astUtils.isOpeningParenToken(token) && !astUtils.isClosingParenToken(token)) { + return; + } + + // if token is an opening paren and is not followed by a required space + if (token.value === "(" && openerMissingSpace(token, nextToken)) { + context.report({ + node, + loc: token.loc, + messageId: "missingOpeningSpace", + fix(fixer) { + return fixer.insertTextAfter(token, " "); + } + }); + } + + // if token is an opening paren and is followed by a disallowed space + if (token.value === "(" && openerRejectsSpace(token, nextToken)) { + context.report({ + node, + loc: { start: token.loc.end, end: nextToken.loc.start }, + messageId: "rejectedOpeningSpace", + fix(fixer) { + return fixer.removeRange([token.range[1], nextToken.range[0]]); + } + }); + } + + // if token is a closing paren and is not preceded by a required space + if (token.value === ")" && closerMissingSpace(prevToken, token)) { + context.report({ + node, + loc: token.loc, + messageId: "missingClosingSpace", + fix(fixer) { + return fixer.insertTextBefore(token, " "); + } + }); + } + + // if token is a closing paren and is preceded by a disallowed space + if (token.value === ")" && closerRejectsSpace(prevToken, token)) { + context.report({ + node, + loc: { start: prevToken.loc.end, end: token.loc.start }, + messageId: "rejectedClosingSpace", + fix(fixer) { + return fixer.removeRange([prevToken.range[1], token.range[0]]); + } + }); + } + }); + } + }; + } +}; diff --git a/eslint/lib/rules/space-infix-ops.js b/eslint/lib/rules/space-infix-ops.js new file mode 100644 index 0000000..471c222 --- /dev/null +++ b/eslint/lib/rules/space-infix-ops.js @@ -0,0 +1,169 @@ +/** + * @fileoverview Require spaces around infix operators + * @author Michael Ficarra + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "require spacing around infix operators", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/space-infix-ops" + }, + + fixable: "whitespace", + + schema: [ + { + type: "object", + properties: { + int32Hint: { + type: "boolean", + default: false + } + }, + additionalProperties: false + } + ], + + messages: { + missingSpace: "Operator '{{operator}}' must be spaced." + } + }, + + create(context) { + const int32Hint = context.options[0] ? context.options[0].int32Hint === true : false; + const sourceCode = context.getSourceCode(); + + /** + * Returns the first token which violates the rule + * @param {ASTNode} left The left node of the main node + * @param {ASTNode} right The right node of the main node + * @param {string} op The operator of the main node + * @returns {Object} The violator token or null + * @private + */ + function getFirstNonSpacedToken(left, right, op) { + const operator = sourceCode.getFirstTokenBetween(left, right, token => token.value === op); + const prev = sourceCode.getTokenBefore(operator); + const next = sourceCode.getTokenAfter(operator); + + if (!sourceCode.isSpaceBetweenTokens(prev, operator) || !sourceCode.isSpaceBetweenTokens(operator, next)) { + return operator; + } + + return null; + } + + /** + * Reports an AST node as a rule violation + * @param {ASTNode} mainNode The node to report + * @param {Object} culpritToken The token which has a problem + * @returns {void} + * @private + */ + function report(mainNode, culpritToken) { + context.report({ + node: mainNode, + loc: culpritToken.loc, + messageId: "missingSpace", + data: { + operator: culpritToken.value + }, + fix(fixer) { + const previousToken = sourceCode.getTokenBefore(culpritToken); + const afterToken = sourceCode.getTokenAfter(culpritToken); + let fixString = ""; + + if (culpritToken.range[0] - previousToken.range[1] === 0) { + fixString = " "; + } + + fixString += culpritToken.value; + + if (afterToken.range[0] - culpritToken.range[1] === 0) { + fixString += " "; + } + + return fixer.replaceText(culpritToken, fixString); + } + }); + } + + /** + * Check if the node is binary then report + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function checkBinary(node) { + const leftNode = (node.left.typeAnnotation) ? node.left.typeAnnotation : node.left; + const rightNode = node.right; + + // search for = in AssignmentPattern nodes + const operator = node.operator || "="; + + const nonSpacedNode = getFirstNonSpacedToken(leftNode, rightNode, operator); + + if (nonSpacedNode) { + if (!(int32Hint && sourceCode.getText(node).endsWith("|0"))) { + report(node, nonSpacedNode); + } + } + } + + /** + * Check if the node is conditional + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function checkConditional(node) { + const nonSpacedConsequentNode = getFirstNonSpacedToken(node.test, node.consequent, "?"); + const nonSpacedAlternateNode = getFirstNonSpacedToken(node.consequent, node.alternate, ":"); + + if (nonSpacedConsequentNode) { + report(node, nonSpacedConsequentNode); + } else if (nonSpacedAlternateNode) { + report(node, nonSpacedAlternateNode); + } + } + + /** + * Check if the node is a variable + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function checkVar(node) { + const leftNode = (node.id.typeAnnotation) ? node.id.typeAnnotation : node.id; + const rightNode = node.init; + + if (rightNode) { + const nonSpacedNode = getFirstNonSpacedToken(leftNode, rightNode, "="); + + if (nonSpacedNode) { + report(node, nonSpacedNode); + } + } + } + + return { + AssignmentExpression: checkBinary, + AssignmentPattern: checkBinary, + BinaryExpression: checkBinary, + LogicalExpression: checkBinary, + ConditionalExpression: checkConditional, + VariableDeclarator: checkVar + }; + + } +}; diff --git a/eslint/lib/rules/space-unary-ops.js b/eslint/lib/rules/space-unary-ops.js new file mode 100644 index 0000000..f417eea --- /dev/null +++ b/eslint/lib/rules/space-unary-ops.js @@ -0,0 +1,321 @@ +/** + * @fileoverview This rule shoud require or disallow spaces before or after unary operations. + * @author Marcin Kumorek + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "enforce consistent spacing before or after unary operators", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/space-unary-ops" + }, + + fixable: "whitespace", + + schema: [ + { + type: "object", + properties: { + words: { + type: "boolean", + default: true + }, + nonwords: { + type: "boolean", + default: false + }, + overrides: { + type: "object", + additionalProperties: { + type: "boolean" + } + } + }, + additionalProperties: false + } + ], + messages: { + unexpectedBefore: "Unexpected space before unary operator '{{operator}}'.", + unexpectedAfter: "Unexpected space after unary operator '{{operator}}'.", + unexpectedAfterWord: "Unexpected space after unary word operator '{{word}}'.", + wordOperator: "Unary word operator '{{word}}' must be followed by whitespace.", + operator: "Unary operator '{{operator}}' must be followed by whitespace.", + beforeUnaryExpressions: "Space is required before unary expressions '{{token}}'." + } + }, + + create(context) { + const options = context.options[0] || { words: true, nonwords: false }; + + const sourceCode = context.getSourceCode(); + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Check if the node is the first "!" in a "!!" convert to Boolean expression + * @param {ASTnode} node AST node + * @returns {boolean} Whether or not the node is first "!" in "!!" + */ + function isFirstBangInBangBangExpression(node) { + return node && node.type === "UnaryExpression" && node.argument.operator === "!" && + node.argument && node.argument.type === "UnaryExpression" && node.argument.operator === "!"; + } + + /** + * Checks if an override exists for a given operator. + * @param {string} operator Operator + * @returns {boolean} Whether or not an override has been provided for the operator + */ + function overrideExistsForOperator(operator) { + return options.overrides && Object.prototype.hasOwnProperty.call(options.overrides, operator); + } + + /** + * Gets the value that the override was set to for this operator + * @param {string} operator Operator + * @returns {boolean} Whether or not an override enforces a space with this operator + */ + function overrideEnforcesSpaces(operator) { + return options.overrides[operator]; + } + + /** + * Verify Unary Word Operator has spaces after the word operator + * @param {ASTnode} node AST node + * @param {Object} firstToken first token from the AST node + * @param {Object} secondToken second token from the AST node + * @param {string} word The word to be used for reporting + * @returns {void} + */ + function verifyWordHasSpaces(node, firstToken, secondToken, word) { + if (secondToken.range[0] === firstToken.range[1]) { + context.report({ + node, + messageId: "wordOperator", + data: { + word + }, + fix(fixer) { + return fixer.insertTextAfter(firstToken, " "); + } + }); + } + } + + /** + * Verify Unary Word Operator doesn't have spaces after the word operator + * @param {ASTnode} node AST node + * @param {Object} firstToken first token from the AST node + * @param {Object} secondToken second token from the AST node + * @param {string} word The word to be used for reporting + * @returns {void} + */ + function verifyWordDoesntHaveSpaces(node, firstToken, secondToken, word) { + if (astUtils.canTokensBeAdjacent(firstToken, secondToken)) { + if (secondToken.range[0] > firstToken.range[1]) { + context.report({ + node, + messageId: "unexpectedAfterWord", + data: { + word + }, + fix(fixer) { + return fixer.removeRange([firstToken.range[1], secondToken.range[0]]); + } + }); + } + } + } + + /** + * Check Unary Word Operators for spaces after the word operator + * @param {ASTnode} node AST node + * @param {Object} firstToken first token from the AST node + * @param {Object} secondToken second token from the AST node + * @param {string} word The word to be used for reporting + * @returns {void} + */ + function checkUnaryWordOperatorForSpaces(node, firstToken, secondToken, word) { + if (overrideExistsForOperator(word)) { + if (overrideEnforcesSpaces(word)) { + verifyWordHasSpaces(node, firstToken, secondToken, word); + } else { + verifyWordDoesntHaveSpaces(node, firstToken, secondToken, word); + } + } else if (options.words) { + verifyWordHasSpaces(node, firstToken, secondToken, word); + } else { + verifyWordDoesntHaveSpaces(node, firstToken, secondToken, word); + } + } + + /** + * Verifies YieldExpressions satisfy spacing requirements + * @param {ASTnode} node AST node + * @returns {void} + */ + function checkForSpacesAfterYield(node) { + const tokens = sourceCode.getFirstTokens(node, 3), + word = "yield"; + + if (!node.argument || node.delegate) { + return; + } + + checkUnaryWordOperatorForSpaces(node, tokens[0], tokens[1], word); + } + + /** + * Verifies AwaitExpressions satisfy spacing requirements + * @param {ASTNode} node AwaitExpression AST node + * @returns {void} + */ + function checkForSpacesAfterAwait(node) { + const tokens = sourceCode.getFirstTokens(node, 3); + + checkUnaryWordOperatorForSpaces(node, tokens[0], tokens[1], "await"); + } + + /** + * Verifies UnaryExpression, UpdateExpression and NewExpression have spaces before or after the operator + * @param {ASTnode} node AST node + * @param {Object} firstToken First token in the expression + * @param {Object} secondToken Second token in the expression + * @returns {void} + */ + function verifyNonWordsHaveSpaces(node, firstToken, secondToken) { + if (node.prefix) { + if (isFirstBangInBangBangExpression(node)) { + return; + } + if (firstToken.range[1] === secondToken.range[0]) { + context.report({ + node, + messageId: "operator", + data: { + operator: firstToken.value + }, + fix(fixer) { + return fixer.insertTextAfter(firstToken, " "); + } + }); + } + } else { + if (firstToken.range[1] === secondToken.range[0]) { + context.report({ + node, + messageId: "beforeUnaryExpressions", + data: { + token: secondToken.value + }, + fix(fixer) { + return fixer.insertTextBefore(secondToken, " "); + } + }); + } + } + } + + /** + * Verifies UnaryExpression, UpdateExpression and NewExpression don't have spaces before or after the operator + * @param {ASTnode} node AST node + * @param {Object} firstToken First token in the expression + * @param {Object} secondToken Second token in the expression + * @returns {void} + */ + function verifyNonWordsDontHaveSpaces(node, firstToken, secondToken) { + if (node.prefix) { + if (secondToken.range[0] > firstToken.range[1]) { + context.report({ + node, + messageId: "unexpectedAfter", + data: { + operator: firstToken.value + }, + fix(fixer) { + if (astUtils.canTokensBeAdjacent(firstToken, secondToken)) { + return fixer.removeRange([firstToken.range[1], secondToken.range[0]]); + } + return null; + } + }); + } + } else { + if (secondToken.range[0] > firstToken.range[1]) { + context.report({ + node, + messageId: "unexpectedBefore", + data: { + operator: secondToken.value + }, + fix(fixer) { + return fixer.removeRange([firstToken.range[1], secondToken.range[0]]); + } + }); + } + } + } + + /** + * Verifies UnaryExpression, UpdateExpression and NewExpression satisfy spacing requirements + * @param {ASTnode} node AST node + * @returns {void} + */ + function checkForSpaces(node) { + const tokens = node.type === "UpdateExpression" && !node.prefix + ? sourceCode.getLastTokens(node, 2) + : sourceCode.getFirstTokens(node, 2); + const firstToken = tokens[0]; + const secondToken = tokens[1]; + + if ((node.type === "NewExpression" || node.prefix) && firstToken.type === "Keyword") { + checkUnaryWordOperatorForSpaces(node, firstToken, secondToken, firstToken.value); + return; + } + + const operator = node.prefix ? tokens[0].value : tokens[1].value; + + if (overrideExistsForOperator(operator)) { + if (overrideEnforcesSpaces(operator)) { + verifyNonWordsHaveSpaces(node, firstToken, secondToken); + } else { + verifyNonWordsDontHaveSpaces(node, firstToken, secondToken); + } + } else if (options.nonwords) { + verifyNonWordsHaveSpaces(node, firstToken, secondToken); + } else { + verifyNonWordsDontHaveSpaces(node, firstToken, secondToken); + } + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + UnaryExpression: checkForSpaces, + UpdateExpression: checkForSpaces, + NewExpression: checkForSpaces, + YieldExpression: checkForSpacesAfterYield, + AwaitExpression: checkForSpacesAfterAwait + }; + + } +}; diff --git a/eslint/lib/rules/spaced-comment.js b/eslint/lib/rules/spaced-comment.js new file mode 100644 index 0000000..d3221f0 --- /dev/null +++ b/eslint/lib/rules/spaced-comment.js @@ -0,0 +1,382 @@ +/** + * @fileoverview Source code for spaced-comments rule + * @author Gyandeep Singh + */ +"use strict"; + +const lodash = require("lodash"); +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Escapes the control characters of a given string. + * @param {string} s A string to escape. + * @returns {string} An escaped string. + */ +function escape(s) { + return `(?:${lodash.escapeRegExp(s)})`; +} + +/** + * Escapes the control characters of a given string. + * And adds a repeat flag. + * @param {string} s A string to escape. + * @returns {string} An escaped string. + */ +function escapeAndRepeat(s) { + return `${escape(s)}+`; +} + +/** + * Parses `markers` option. + * If markers don't include `"*"`, this adds `"*"` to allow JSDoc comments. + * @param {string[]} [markers] A marker list. + * @returns {string[]} A marker list. + */ +function parseMarkersOption(markers) { + + // `*` is a marker for JSDoc comments. + if (markers.indexOf("*") === -1) { + return markers.concat("*"); + } + + return markers; +} + +/** + * Creates string pattern for exceptions. + * Generated pattern: + * + * 1. A space or an exception pattern sequence. + * @param {string[]} exceptions An exception pattern list. + * @returns {string} A regular expression string for exceptions. + */ +function createExceptionsPattern(exceptions) { + let pattern = ""; + + /* + * A space or an exception pattern sequence. + * [] ==> "\s" + * ["-"] ==> "(?:\s|\-+$)" + * ["-", "="] ==> "(?:\s|(?:\-+|=+)$)" + * ["-", "=", "--=="] ==> "(?:\s|(?:\-+|=+|(?:\-\-==)+)$)" ==> https://jex.im/regulex/#!embed=false&flags=&re=(%3F%3A%5Cs%7C(%3F%3A%5C-%2B%7C%3D%2B%7C(%3F%3A%5C-%5C-%3D%3D)%2B)%24) + */ + if (exceptions.length === 0) { + + // a space. + pattern += "\\s"; + } else { + + // a space or... + pattern += "(?:\\s|"; + + if (exceptions.length === 1) { + + // a sequence of the exception pattern. + pattern += escapeAndRepeat(exceptions[0]); + } else { + + // a sequence of one of the exception patterns. + pattern += "(?:"; + pattern += exceptions.map(escapeAndRepeat).join("|"); + pattern += ")"; + } + pattern += `(?:$|[${Array.from(astUtils.LINEBREAKS).join("")}]))`; + } + + return pattern; +} + +/** + * Creates RegExp object for `always` mode. + * Generated pattern for beginning of comment: + * + * 1. First, a marker or nothing. + * 2. Next, a space or an exception pattern sequence. + * @param {string[]} markers A marker list. + * @param {string[]} exceptions An exception pattern list. + * @returns {RegExp} A RegExp object for the beginning of a comment in `always` mode. + */ +function createAlwaysStylePattern(markers, exceptions) { + let pattern = "^"; + + /* + * A marker or nothing. + * ["*"] ==> "\*?" + * ["*", "!"] ==> "(?:\*|!)?" + * ["*", "/", "!<"] ==> "(?:\*|\/|(?:!<))?" ==> https://jex.im/regulex/#!embed=false&flags=&re=(%3F%3A%5C*%7C%5C%2F%7C(%3F%3A!%3C))%3F + */ + if (markers.length === 1) { + + // the marker. + pattern += escape(markers[0]); + } else { + + // one of markers. + pattern += "(?:"; + pattern += markers.map(escape).join("|"); + pattern += ")"; + } + + pattern += "?"; // or nothing. + pattern += createExceptionsPattern(exceptions); + + return new RegExp(pattern, "u"); +} + +/** + * Creates RegExp object for `never` mode. + * Generated pattern for beginning of comment: + * + * 1. First, a marker or nothing (captured). + * 2. Next, a space or a tab. + * @param {string[]} markers A marker list. + * @returns {RegExp} A RegExp object for `never` mode. + */ +function createNeverStylePattern(markers) { + const pattern = `^(${markers.map(escape).join("|")})?[ \t]+`; + + return new RegExp(pattern, "u"); +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "enforce consistent spacing after the `//` or `/*` in a comment", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/spaced-comment" + }, + + fixable: "whitespace", + + schema: [ + { + enum: ["always", "never"] + }, + { + type: "object", + properties: { + exceptions: { + type: "array", + items: { + type: "string" + } + }, + markers: { + type: "array", + items: { + type: "string" + } + }, + line: { + type: "object", + properties: { + exceptions: { + type: "array", + items: { + type: "string" + } + }, + markers: { + type: "array", + items: { + type: "string" + } + } + }, + additionalProperties: false + }, + block: { + type: "object", + properties: { + exceptions: { + type: "array", + items: { + type: "string" + } + }, + markers: { + type: "array", + items: { + type: "string" + } + }, + balanced: { + type: "boolean", + default: false + } + }, + additionalProperties: false + } + }, + additionalProperties: false + } + ], + + messages: { + unexpectedSpaceAfterMarker: "Unexpected space or tab after marker ({{refChar}}) in comment.", + expectedExceptionAfter: "Expected exception block, space or tab after '{{refChar}}' in comment.", + unexpectedSpaceBefore: "Unexpected space or tab before '*/' in comment.", + unexpectedSpaceAfter: "Unexpected space or tab after '{{refChar}}' in comment.", + expectedSpaceBefore: "Expected space or tab before '*/' in comment.", + expectedSpaceAfter: "Expected space or tab after '{{refChar}}' in comment." + } + }, + + create(context) { + + const sourceCode = context.getSourceCode(); + + // Unless the first option is never, require a space + const requireSpace = context.options[0] !== "never"; + + /* + * Parse the second options. + * If markers don't include `"*"`, it's added automatically for JSDoc + * comments. + */ + const config = context.options[1] || {}; + const balanced = config.block && config.block.balanced; + + const styleRules = ["block", "line"].reduce((rule, type) => { + const markers = parseMarkersOption(config[type] && config[type].markers || config.markers || []); + const exceptions = config[type] && config[type].exceptions || config.exceptions || []; + const endNeverPattern = "[ \t]+$"; + + // Create RegExp object for valid patterns. + rule[type] = { + beginRegex: requireSpace ? createAlwaysStylePattern(markers, exceptions) : createNeverStylePattern(markers), + endRegex: balanced && requireSpace ? new RegExp(`${createExceptionsPattern(exceptions)}$`, "u") : new RegExp(endNeverPattern, "u"), + hasExceptions: exceptions.length > 0, + captureMarker: new RegExp(`^(${markers.map(escape).join("|")})`, "u"), + markers: new Set(markers) + }; + + return rule; + }, {}); + + /** + * Reports a beginning spacing error with an appropriate message. + * @param {ASTNode} node A comment node to check. + * @param {string} messageId An error message to report. + * @param {Array} match An array of match results for markers. + * @param {string} refChar Character used for reference in the error message. + * @returns {void} + */ + function reportBegin(node, messageId, match, refChar) { + const type = node.type.toLowerCase(), + commentIdentifier = type === "block" ? "/*" : "//"; + + context.report({ + node, + fix(fixer) { + const start = node.range[0]; + let end = start + 2; + + if (requireSpace) { + if (match) { + end += match[0].length; + } + return fixer.insertTextAfterRange([start, end], " "); + } + end += match[0].length; + return fixer.replaceTextRange([start, end], commentIdentifier + (match[1] ? match[1] : "")); + + }, + messageId, + data: { refChar } + }); + } + + /** + * Reports an ending spacing error with an appropriate message. + * @param {ASTNode} node A comment node to check. + * @param {string} messageId An error message to report. + * @param {string} match An array of the matched whitespace characters. + * @returns {void} + */ + function reportEnd(node, messageId, match) { + context.report({ + node, + fix(fixer) { + if (requireSpace) { + return fixer.insertTextAfterRange([node.range[0], node.range[1] - 2], " "); + } + const end = node.range[1] - 2, + start = end - match[0].length; + + return fixer.replaceTextRange([start, end], ""); + + }, + messageId + }); + } + + /** + * Reports a given comment if it's invalid. + * @param {ASTNode} node a comment node to check. + * @returns {void} + */ + function checkCommentForSpace(node) { + const type = node.type.toLowerCase(), + rule = styleRules[type], + commentIdentifier = type === "block" ? "/*" : "//"; + + // Ignores empty comments and comments that consist only of a marker. + if (node.value.length === 0 || rule.markers.has(node.value)) { + return; + } + + const beginMatch = rule.beginRegex.exec(node.value); + const endMatch = rule.endRegex.exec(node.value); + + // Checks. + if (requireSpace) { + if (!beginMatch) { + const hasMarker = rule.captureMarker.exec(node.value); + const marker = hasMarker ? commentIdentifier + hasMarker[0] : commentIdentifier; + + if (rule.hasExceptions) { + reportBegin(node, "expectedExceptionAfter", hasMarker, marker); + } else { + reportBegin(node, "expectedSpaceAfter", hasMarker, marker); + } + } + + if (balanced && type === "block" && !endMatch) { + reportEnd(node, "expectedSpaceBefore"); + } + } else { + if (beginMatch) { + if (!beginMatch[1]) { + reportBegin(node, "unexpectedSpaceAfter", beginMatch, commentIdentifier); + } else { + reportBegin(node, "unexpectedSpaceAfterMarker", beginMatch, beginMatch[1]); + } + } + + if (balanced && type === "block" && endMatch) { + reportEnd(node, "unexpectedSpaceBefore", endMatch); + } + } + } + + return { + Program() { + const comments = sourceCode.getAllComments(); + + comments.filter(token => token.type !== "Shebang").forEach(checkCommentForSpace); + } + }; + } +}; diff --git a/eslint/lib/rules/strict.js b/eslint/lib/rules/strict.js new file mode 100644 index 0000000..b0d6cf9 --- /dev/null +++ b/eslint/lib/rules/strict.js @@ -0,0 +1,277 @@ +/** + * @fileoverview Rule to control usage of strict mode directives. + * @author Brandon Mills + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Gets all of the Use Strict Directives in the Directive Prologue of a group of + * statements. + * @param {ASTNode[]} statements Statements in the program or function body. + * @returns {ASTNode[]} All of the Use Strict Directives. + */ +function getUseStrictDirectives(statements) { + const directives = []; + + for (let i = 0; i < statements.length; i++) { + const statement = statements[i]; + + if ( + statement.type === "ExpressionStatement" && + statement.expression.type === "Literal" && + statement.expression.value === "use strict" + ) { + directives[i] = statement; + } else { + break; + } + } + + return directives; +} + +/** + * Checks whether a given parameter is a simple parameter. + * @param {ASTNode} node A pattern node to check. + * @returns {boolean} `true` if the node is an Identifier node. + */ +function isSimpleParameter(node) { + return node.type === "Identifier"; +} + +/** + * Checks whether a given parameter list is a simple parameter list. + * @param {ASTNode[]} params A parameter list to check. + * @returns {boolean} `true` if the every parameter is an Identifier node. + */ +function isSimpleParameterList(params) { + return params.every(isSimpleParameter); +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "require or disallow strict mode directives", + category: "Strict Mode", + recommended: false, + url: "https://eslint.org/docs/rules/strict" + }, + + schema: [ + { + enum: ["never", "global", "function", "safe"] + } + ], + + fixable: "code", + messages: { + function: "Use the function form of 'use strict'.", + global: "Use the global form of 'use strict'.", + multiple: "Multiple 'use strict' directives.", + never: "Strict mode is not permitted.", + unnecessary: "Unnecessary 'use strict' directive.", + module: "'use strict' is unnecessary inside of modules.", + implied: "'use strict' is unnecessary when implied strict mode is enabled.", + unnecessaryInClasses: "'use strict' is unnecessary inside of classes.", + nonSimpleParameterList: "'use strict' directive inside a function with non-simple parameter list throws a syntax error since ES2016.", + wrap: "Wrap {{name}} in a function with 'use strict' directive." + } + }, + + create(context) { + + const ecmaFeatures = context.parserOptions.ecmaFeatures || {}, + scopes = [], + classScopes = []; + let mode = context.options[0] || "safe"; + + if (ecmaFeatures.impliedStrict) { + mode = "implied"; + } else if (mode === "safe") { + mode = ecmaFeatures.globalReturn ? "global" : "function"; + } + + /** + * Determines whether a reported error should be fixed, depending on the error type. + * @param {string} errorType The type of error + * @returns {boolean} `true` if the reported error should be fixed + */ + function shouldFix(errorType) { + return errorType === "multiple" || errorType === "unnecessary" || errorType === "module" || errorType === "implied" || errorType === "unnecessaryInClasses"; + } + + /** + * Gets a fixer function to remove a given 'use strict' directive. + * @param {ASTNode} node The directive that should be removed + * @returns {Function} A fixer function + */ + function getFixFunction(node) { + return fixer => fixer.remove(node); + } + + /** + * Report a slice of an array of nodes with a given message. + * @param {ASTNode[]} nodes Nodes. + * @param {string} start Index to start from. + * @param {string} end Index to end before. + * @param {string} messageId Message to display. + * @param {boolean} fix `true` if the directive should be fixed (i.e. removed) + * @returns {void} + */ + function reportSlice(nodes, start, end, messageId, fix) { + nodes.slice(start, end).forEach(node => { + context.report({ node, messageId, fix: fix ? getFixFunction(node) : null }); + }); + } + + /** + * Report all nodes in an array with a given message. + * @param {ASTNode[]} nodes Nodes. + * @param {string} messageId Message id to display. + * @param {boolean} fix `true` if the directive should be fixed (i.e. removed) + * @returns {void} + */ + function reportAll(nodes, messageId, fix) { + reportSlice(nodes, 0, nodes.length, messageId, fix); + } + + /** + * Report all nodes in an array, except the first, with a given message. + * @param {ASTNode[]} nodes Nodes. + * @param {string} messageId Message id to display. + * @param {boolean} fix `true` if the directive should be fixed (i.e. removed) + * @returns {void} + */ + function reportAllExceptFirst(nodes, messageId, fix) { + reportSlice(nodes, 1, nodes.length, messageId, fix); + } + + /** + * Entering a function in 'function' mode pushes a new nested scope onto the + * stack. The new scope is true if the nested function is strict mode code. + * @param {ASTNode} node The function declaration or expression. + * @param {ASTNode[]} useStrictDirectives The Use Strict Directives of the node. + * @returns {void} + */ + function enterFunctionInFunctionMode(node, useStrictDirectives) { + const isInClass = classScopes.length > 0, + isParentGlobal = scopes.length === 0 && classScopes.length === 0, + isParentStrict = scopes.length > 0 && scopes[scopes.length - 1], + isStrict = useStrictDirectives.length > 0; + + if (isStrict) { + if (!isSimpleParameterList(node.params)) { + context.report({ node: useStrictDirectives[0], messageId: "nonSimpleParameterList" }); + } else if (isParentStrict) { + context.report({ node: useStrictDirectives[0], messageId: "unnecessary", fix: getFixFunction(useStrictDirectives[0]) }); + } else if (isInClass) { + context.report({ node: useStrictDirectives[0], messageId: "unnecessaryInClasses", fix: getFixFunction(useStrictDirectives[0]) }); + } + + reportAllExceptFirst(useStrictDirectives, "multiple", true); + } else if (isParentGlobal) { + if (isSimpleParameterList(node.params)) { + context.report({ node, messageId: "function" }); + } else { + context.report({ + node, + messageId: "wrap", + data: { name: astUtils.getFunctionNameWithKind(node) } + }); + } + } + + scopes.push(isParentStrict || isStrict); + } + + /** + * Exiting a function in 'function' mode pops its scope off the stack. + * @returns {void} + */ + function exitFunctionInFunctionMode() { + scopes.pop(); + } + + /** + * Enter a function and either: + * - Push a new nested scope onto the stack (in 'function' mode). + * - Report all the Use Strict Directives (in the other modes). + * @param {ASTNode} node The function declaration or expression. + * @returns {void} + */ + function enterFunction(node) { + const isBlock = node.body.type === "BlockStatement", + useStrictDirectives = isBlock + ? getUseStrictDirectives(node.body.body) : []; + + if (mode === "function") { + enterFunctionInFunctionMode(node, useStrictDirectives); + } else if (useStrictDirectives.length > 0) { + if (isSimpleParameterList(node.params)) { + reportAll(useStrictDirectives, mode, shouldFix(mode)); + } else { + context.report({ node: useStrictDirectives[0], messageId: "nonSimpleParameterList" }); + reportAllExceptFirst(useStrictDirectives, "multiple", true); + } + } + } + + const rule = { + Program(node) { + const useStrictDirectives = getUseStrictDirectives(node.body); + + if (node.sourceType === "module") { + mode = "module"; + } + + if (mode === "global") { + if (node.body.length > 0 && useStrictDirectives.length === 0) { + context.report({ node, messageId: "global" }); + } + reportAllExceptFirst(useStrictDirectives, "multiple", true); + } else { + reportAll(useStrictDirectives, mode, shouldFix(mode)); + } + }, + FunctionDeclaration: enterFunction, + FunctionExpression: enterFunction, + ArrowFunctionExpression: enterFunction + }; + + if (mode === "function") { + Object.assign(rule, { + + // Inside of class bodies are always strict mode. + ClassBody() { + classScopes.push(true); + }, + "ClassBody:exit"() { + classScopes.pop(); + }, + + "FunctionDeclaration:exit": exitFunctionInFunctionMode, + "FunctionExpression:exit": exitFunctionInFunctionMode, + "ArrowFunctionExpression:exit": exitFunctionInFunctionMode + }); + } + + return rule; + } +}; diff --git a/eslint/lib/rules/switch-colon-spacing.js b/eslint/lib/rules/switch-colon-spacing.js new file mode 100644 index 0000000..c906415 --- /dev/null +++ b/eslint/lib/rules/switch-colon-spacing.js @@ -0,0 +1,141 @@ +/** + * @fileoverview Rule to enforce spacing around colons of switch statements. + * @author Toru Nagashima + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "enforce spacing around colons of switch statements", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/switch-colon-spacing" + }, + + schema: [ + { + type: "object", + properties: { + before: { type: "boolean", default: false }, + after: { type: "boolean", default: true } + }, + additionalProperties: false + } + ], + fixable: "whitespace", + messages: { + expectedBefore: "Expected space(s) before this colon.", + expectedAfter: "Expected space(s) after this colon.", + unexpectedBefore: "Unexpected space(s) before this colon.", + unexpectedAfter: "Unexpected space(s) after this colon." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + const options = context.options[0] || {}; + const beforeSpacing = options.before === true; // false by default + const afterSpacing = options.after !== false; // true by default + + /** + * Get the colon token of the given SwitchCase node. + * @param {ASTNode} node The SwitchCase node to get. + * @returns {Token} The colon token of the node. + */ + function getColonToken(node) { + if (node.test) { + return sourceCode.getTokenAfter(node.test, astUtils.isColonToken); + } + return sourceCode.getFirstToken(node, 1); + } + + /** + * Check whether the spacing between the given 2 tokens is valid or not. + * @param {Token} left The left token to check. + * @param {Token} right The right token to check. + * @param {boolean} expected The expected spacing to check. `true` if there should be a space. + * @returns {boolean} `true` if the spacing between the tokens is valid. + */ + function isValidSpacing(left, right, expected) { + return ( + astUtils.isClosingBraceToken(right) || + !astUtils.isTokenOnSameLine(left, right) || + sourceCode.isSpaceBetweenTokens(left, right) === expected + ); + } + + /** + * Check whether comments exist between the given 2 tokens. + * @param {Token} left The left token to check. + * @param {Token} right The right token to check. + * @returns {boolean} `true` if comments exist between the given 2 tokens. + */ + function commentsExistBetween(left, right) { + return sourceCode.getFirstTokenBetween( + left, + right, + { + includeComments: true, + filter: astUtils.isCommentToken + } + ) !== null; + } + + /** + * Fix the spacing between the given 2 tokens. + * @param {RuleFixer} fixer The fixer to fix. + * @param {Token} left The left token of fix range. + * @param {Token} right The right token of fix range. + * @param {boolean} spacing The spacing style. `true` if there should be a space. + * @returns {Fix|null} The fix object. + */ + function fix(fixer, left, right, spacing) { + if (commentsExistBetween(left, right)) { + return null; + } + if (spacing) { + return fixer.insertTextAfter(left, " "); + } + return fixer.removeRange([left.range[1], right.range[0]]); + } + + return { + SwitchCase(node) { + const colonToken = getColonToken(node); + const beforeToken = sourceCode.getTokenBefore(colonToken); + const afterToken = sourceCode.getTokenAfter(colonToken); + + if (!isValidSpacing(beforeToken, colonToken, beforeSpacing)) { + context.report({ + node, + loc: colonToken.loc, + messageId: beforeSpacing ? "expectedBefore" : "unexpectedBefore", + fix: fixer => fix(fixer, beforeToken, colonToken, beforeSpacing) + }); + } + if (!isValidSpacing(colonToken, afterToken, afterSpacing)) { + context.report({ + node, + loc: colonToken.loc, + messageId: afterSpacing ? "expectedAfter" : "unexpectedAfter", + fix: fixer => fix(fixer, colonToken, afterToken, afterSpacing) + }); + } + } + }; + } +}; diff --git a/eslint/lib/rules/symbol-description.js b/eslint/lib/rules/symbol-description.js new file mode 100644 index 0000000..155cea4 --- /dev/null +++ b/eslint/lib/rules/symbol-description.js @@ -0,0 +1,71 @@ +/** + * @fileoverview Rule to enforce description with the `Symbol` object + * @author Jarek Rencz + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "require symbol descriptions", + category: "ECMAScript 6", + recommended: false, + url: "https://eslint.org/docs/rules/symbol-description" + }, + fixable: null, + schema: [], + messages: { + expected: "Expected Symbol to have a description." + } + }, + + create(context) { + + /** + * Reports if node does not conform the rule in case rule is set to + * report missing description + * @param {ASTNode} node A CallExpression node to check. + * @returns {void} + */ + function checkArgument(node) { + if (node.arguments.length === 0) { + context.report({ + node, + messageId: "expected" + }); + } + } + + return { + "Program:exit"() { + const scope = context.getScope(); + const variable = astUtils.getVariableByName(scope, "Symbol"); + + if (variable && variable.defs.length === 0) { + variable.references.forEach(reference => { + const node = reference.identifier; + + if (astUtils.isCallee(node)) { + checkArgument(node.parent); + } + }); + } + } + }; + + } +}; diff --git a/eslint/lib/rules/template-curly-spacing.js b/eslint/lib/rules/template-curly-spacing.js new file mode 100644 index 0000000..26043bc --- /dev/null +++ b/eslint/lib/rules/template-curly-spacing.js @@ -0,0 +1,141 @@ +/** + * @fileoverview Rule to enforce spacing around embedded expressions of template strings + * @author Toru Nagashima + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "require or disallow spacing around embedded expressions of template strings", + category: "ECMAScript 6", + recommended: false, + url: "https://eslint.org/docs/rules/template-curly-spacing" + }, + + fixable: "whitespace", + + schema: [ + { enum: ["always", "never"] } + ], + messages: { + expectedBefore: "Expected space(s) before '}'.", + expectedAfter: "Expected space(s) after '${'.", + unexpectedBefore: "Unexpected space(s) before '}'.", + unexpectedAfter: "Unexpected space(s) after '${'." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + const always = context.options[0] === "always"; + + /** + * Checks spacing before `}` of a given token. + * @param {Token} token A token to check. This is a Template token. + * @returns {void} + */ + function checkSpacingBefore(token) { + if (!token.value.startsWith("}")) { + return; // starts with a backtick, this is the first template element in the template literal + } + + const prevToken = sourceCode.getTokenBefore(token, { includeComments: true }), + hasSpace = sourceCode.isSpaceBetween(prevToken, token); + + if (!astUtils.isTokenOnSameLine(prevToken, token)) { + return; + } + + if (always && !hasSpace) { + context.report({ + loc: { + start: token.loc.start, + end: { + line: token.loc.start.line, + column: token.loc.start.column + 1 + } + }, + messageId: "expectedBefore", + fix: fixer => fixer.insertTextBefore(token, " ") + }); + } + + if (!always && hasSpace) { + context.report({ + loc: { + start: prevToken.loc.end, + end: token.loc.start + }, + messageId: "unexpectedBefore", + fix: fixer => fixer.removeRange([prevToken.range[1], token.range[0]]) + }); + } + } + + /** + * Checks spacing after `${` of a given token. + * @param {Token} token A token to check. This is a Template token. + * @returns {void} + */ + function checkSpacingAfter(token) { + if (!token.value.endsWith("${")) { + return; // ends with a backtick, this is the last template element in the template literal + } + + const nextToken = sourceCode.getTokenAfter(token, { includeComments: true }), + hasSpace = sourceCode.isSpaceBetween(token, nextToken); + + if (!astUtils.isTokenOnSameLine(token, nextToken)) { + return; + } + + if (always && !hasSpace) { + context.report({ + loc: { + start: { + line: token.loc.end.line, + column: token.loc.end.column - 2 + }, + end: token.loc.end + }, + messageId: "expectedAfter", + fix: fixer => fixer.insertTextAfter(token, " ") + }); + } + + if (!always && hasSpace) { + context.report({ + loc: { + start: token.loc.end, + end: nextToken.loc.start + }, + messageId: "unexpectedAfter", + fix: fixer => fixer.removeRange([token.range[1], nextToken.range[0]]) + }); + } + } + + return { + TemplateElement(node) { + const token = sourceCode.getFirstToken(node); + + checkSpacingBefore(token); + checkSpacingAfter(token); + } + }; + } +}; diff --git a/eslint/lib/rules/template-tag-spacing.js b/eslint/lib/rules/template-tag-spacing.js new file mode 100644 index 0000000..9eb6d86 --- /dev/null +++ b/eslint/lib/rules/template-tag-spacing.js @@ -0,0 +1,84 @@ +/** + * @fileoverview Rule to check spacing between template tags and their literals + * @author Jonathan Wilsson + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "require or disallow spacing between template tags and their literals", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/template-tag-spacing" + }, + + fixable: "whitespace", + + schema: [ + { enum: ["always", "never"] } + ], + messages: { + unexpected: "Unexpected space between template tag and template literal.", + missing: "Missing space between template tag and template literal." + } + }, + + create(context) { + const never = context.options[0] !== "always"; + const sourceCode = context.getSourceCode(); + + /** + * Check if a space is present between a template tag and its literal + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function checkSpacing(node) { + const tagToken = sourceCode.getTokenBefore(node.quasi); + const literalToken = sourceCode.getFirstToken(node.quasi); + const hasWhitespace = sourceCode.isSpaceBetweenTokens(tagToken, literalToken); + + if (never && hasWhitespace) { + context.report({ + node, + loc: tagToken.loc.start, + messageId: "unexpected", + fix(fixer) { + const comments = sourceCode.getCommentsBefore(node.quasi); + + // Don't fix anything if there's a single line comment after the template tag + if (comments.some(comment => comment.type === "Line")) { + return null; + } + + return fixer.replaceTextRange( + [tagToken.range[1], literalToken.range[0]], + comments.reduce((text, comment) => text + sourceCode.getText(comment), "") + ); + } + }); + } else if (!never && !hasWhitespace) { + context.report({ + node, + loc: tagToken.loc.start, + messageId: "missing", + fix(fixer) { + return fixer.insertTextAfter(tagToken, " "); + } + }); + } + } + + return { + TaggedTemplateExpression: checkSpacing + }; + } +}; diff --git a/eslint/lib/rules/unicode-bom.js b/eslint/lib/rules/unicode-bom.js new file mode 100644 index 0000000..39642f8 --- /dev/null +++ b/eslint/lib/rules/unicode-bom.js @@ -0,0 +1,73 @@ +/** + * @fileoverview Require or disallow Unicode BOM + * @author Andrew Johnston + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "require or disallow Unicode byte order mark (BOM)", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/unicode-bom" + }, + + fixable: "whitespace", + + schema: [ + { + enum: ["always", "never"] + } + ], + messages: { + expected: "Expected Unicode BOM (Byte Order Mark).", + unexpected: "Unexpected Unicode BOM (Byte Order Mark)." + } + }, + + create(context) { + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + + Program: function checkUnicodeBOM(node) { + + const sourceCode = context.getSourceCode(), + location = { column: 0, line: 1 }, + requireBOM = context.options[0] || "never"; + + if (!sourceCode.hasBOM && (requireBOM === "always")) { + context.report({ + node, + loc: location, + messageId: "expected", + fix(fixer) { + return fixer.insertTextBeforeRange([0, 1], "\uFEFF"); + } + }); + } else if (sourceCode.hasBOM && (requireBOM === "never")) { + context.report({ + node, + loc: location, + messageId: "unexpected", + fix(fixer) { + return fixer.removeRange([-1, 0]); + } + }); + } + } + + }; + + } +}; diff --git a/eslint/lib/rules/use-isnan.js b/eslint/lib/rules/use-isnan.js new file mode 100644 index 0000000..7b466be --- /dev/null +++ b/eslint/lib/rules/use-isnan.js @@ -0,0 +1,138 @@ +/** + * @fileoverview Rule to flag comparisons to the value NaN + * @author James Allardice + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Determines if the given node is a NaN `Identifier` node. + * @param {ASTNode|null} node The node to check. + * @returns {boolean} `true` if the node is 'NaN' identifier. + */ +function isNaNIdentifier(node) { + return Boolean(node) && node.type === "Identifier" && node.name === "NaN"; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "require calls to `isNaN()` when checking for `NaN`", + category: "Possible Errors", + recommended: true, + url: "https://eslint.org/docs/rules/use-isnan" + }, + + schema: [ + { + type: "object", + properties: { + enforceForSwitchCase: { + type: "boolean", + default: true + }, + enforceForIndexOf: { + type: "boolean", + default: false + } + }, + additionalProperties: false + } + ], + + messages: { + comparisonWithNaN: "Use the isNaN function to compare with NaN.", + switchNaN: "'switch(NaN)' can never match a case clause. Use Number.isNaN instead of the switch.", + caseNaN: "'case NaN' can never match. Use Number.isNaN before the switch.", + indexOfNaN: "Array prototype method '{{ methodName }}' cannot find NaN." + } + }, + + create(context) { + + const enforceForSwitchCase = !context.options[0] || context.options[0].enforceForSwitchCase; + const enforceForIndexOf = context.options[0] && context.options[0].enforceForIndexOf; + + /** + * Checks the given `BinaryExpression` node for `foo === NaN` and other comparisons. + * @param {ASTNode} node The node to check. + * @returns {void} + */ + function checkBinaryExpression(node) { + if ( + /^(?:[<>]|[!=]=)=?$/u.test(node.operator) && + (isNaNIdentifier(node.left) || isNaNIdentifier(node.right)) + ) { + context.report({ node, messageId: "comparisonWithNaN" }); + } + } + + /** + * Checks the discriminant and all case clauses of the given `SwitchStatement` node for `switch(NaN)` and `case NaN:` + * @param {ASTNode} node The node to check. + * @returns {void} + */ + function checkSwitchStatement(node) { + if (isNaNIdentifier(node.discriminant)) { + context.report({ node, messageId: "switchNaN" }); + } + + for (const switchCase of node.cases) { + if (isNaNIdentifier(switchCase.test)) { + context.report({ node: switchCase, messageId: "caseNaN" }); + } + } + } + + /** + * Checks the the given `CallExpression` node for `.indexOf(NaN)` and `.lastIndexOf(NaN)`. + * @param {ASTNode} node The node to check. + * @returns {void} + */ + function checkCallExpression(node) { + const callee = node.callee; + + if (callee.type === "MemberExpression") { + const methodName = astUtils.getStaticPropertyName(callee); + + if ( + (methodName === "indexOf" || methodName === "lastIndexOf") && + node.arguments.length === 1 && + isNaNIdentifier(node.arguments[0]) + ) { + context.report({ node, messageId: "indexOfNaN", data: { methodName } }); + } + } + } + + const listeners = { + BinaryExpression: checkBinaryExpression + }; + + if (enforceForSwitchCase) { + listeners.SwitchStatement = checkSwitchStatement; + } + + if (enforceForIndexOf) { + listeners.CallExpression = checkCallExpression; + } + + return listeners; + } +}; diff --git a/eslint/lib/rules/utils/ast-utils.js b/eslint/lib/rules/utils/ast-utils.js new file mode 100644 index 0000000..e6a3cb4 --- /dev/null +++ b/eslint/lib/rules/utils/ast-utils.js @@ -0,0 +1,1497 @@ +/** + * @fileoverview Common utils for AST. + * @author Gyandeep Singh + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const esutils = require("esutils"); +const espree = require("espree"); +const lodash = require("lodash"); +const { + breakableTypePattern, + createGlobalLinebreakMatcher, + lineBreakPattern, + shebangPattern +} = require("../../shared/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const anyFunctionPattern = /^(?:Function(?:Declaration|Expression)|ArrowFunctionExpression)$/u; +const anyLoopPattern = /^(?:DoWhile|For|ForIn|ForOf|While)Statement$/u; +const arrayOrTypedArrayPattern = /Array$/u; +const arrayMethodPattern = /^(?:every|filter|find|findIndex|forEach|map|some)$/u; +const bindOrCallOrApplyPattern = /^(?:bind|call|apply)$/u; +const thisTagPattern = /^[\s*]*@this/mu; + + +const COMMENTS_IGNORE_PATTERN = /^\s*(?:eslint|jshint\s+|jslint\s+|istanbul\s+|globals?\s+|exported\s+|jscs)/u; +const LINEBREAKS = new Set(["\r\n", "\r", "\n", "\u2028", "\u2029"]); + +// A set of node types that can contain a list of statements +const STATEMENT_LIST_PARENTS = new Set(["Program", "BlockStatement", "SwitchCase"]); + +const DECIMAL_INTEGER_PATTERN = /^(0|[1-9]\d*)$/u; +const OCTAL_ESCAPE_PATTERN = /^(?:[^\\]|\\[^0-7]|\\0(?![0-9]))*\\(?:[1-7]|0[0-9])/u; + +/** + * Checks reference if is non initializer and writable. + * @param {Reference} reference A reference to check. + * @param {int} index The index of the reference in the references. + * @param {Reference[]} references The array that the reference belongs to. + * @returns {boolean} Success/Failure + * @private + */ +function isModifyingReference(reference, index, references) { + const identifier = reference.identifier; + + /* + * Destructuring assignments can have multiple default value, so + * possibly there are multiple writeable references for the same + * identifier. + */ + const modifyingDifferentIdentifier = index === 0 || + references[index - 1].identifier !== identifier; + + return (identifier && + reference.init === false && + reference.isWrite() && + modifyingDifferentIdentifier + ); +} + +/** + * Checks whether the given string starts with uppercase or not. + * @param {string} s The string to check. + * @returns {boolean} `true` if the string starts with uppercase. + */ +function startsWithUpperCase(s) { + return s[0] !== s[0].toLocaleLowerCase(); +} + +/** + * Checks whether or not a node is a constructor. + * @param {ASTNode} node A function node to check. + * @returns {boolean} Wehether or not a node is a constructor. + */ +function isES5Constructor(node) { + return (node.id && startsWithUpperCase(node.id.name)); +} + +/** + * Finds a function node from ancestors of a node. + * @param {ASTNode} node A start node to find. + * @returns {Node|null} A found function node. + */ +function getUpperFunction(node) { + for (let currentNode = node; currentNode; currentNode = currentNode.parent) { + if (anyFunctionPattern.test(currentNode.type)) { + return currentNode; + } + } + return null; +} + +/** + * Checks whether a given node is a function node or not. + * The following types are function nodes: + * + * - ArrowFunctionExpression + * - FunctionDeclaration + * - FunctionExpression + * @param {ASTNode|null} node A node to check. + * @returns {boolean} `true` if the node is a function node. + */ +function isFunction(node) { + return Boolean(node && anyFunctionPattern.test(node.type)); +} + +/** + * Checks whether a given node is a loop node or not. + * The following types are loop nodes: + * + * - DoWhileStatement + * - ForInStatement + * - ForOfStatement + * - ForStatement + * - WhileStatement + * @param {ASTNode|null} node A node to check. + * @returns {boolean} `true` if the node is a loop node. + */ +function isLoop(node) { + return Boolean(node && anyLoopPattern.test(node.type)); +} + +/** + * Checks whether the given node is in a loop or not. + * @param {ASTNode} node The node to check. + * @returns {boolean} `true` if the node is in a loop. + */ +function isInLoop(node) { + for (let currentNode = node; currentNode && !isFunction(currentNode); currentNode = currentNode.parent) { + if (isLoop(currentNode)) { + return true; + } + } + + return false; +} + +/** + * Checks whether or not a node is `null` or `undefined`. + * @param {ASTNode} node A node to check. + * @returns {boolean} Whether or not the node is a `null` or `undefined`. + * @public + */ +function isNullOrUndefined(node) { + return ( + module.exports.isNullLiteral(node) || + (node.type === "Identifier" && node.name === "undefined") || + (node.type === "UnaryExpression" && node.operator === "void") + ); +} + +/** + * Checks whether or not a node is callee. + * @param {ASTNode} node A node to check. + * @returns {boolean} Whether or not the node is callee. + */ +function isCallee(node) { + return node.parent.type === "CallExpression" && node.parent.callee === node; +} + +/** + * Checks whether or not a node is `Reflect.apply`. + * @param {ASTNode} node A node to check. + * @returns {boolean} Whether or not the node is a `Reflect.apply`. + */ +function isReflectApply(node) { + return ( + node.type === "MemberExpression" && + node.object.type === "Identifier" && + node.object.name === "Reflect" && + node.property.type === "Identifier" && + node.property.name === "apply" && + node.computed === false + ); +} + +/** + * Checks whether or not a node is `Array.from`. + * @param {ASTNode} node A node to check. + * @returns {boolean} Whether or not the node is a `Array.from`. + */ +function isArrayFromMethod(node) { + return ( + node.type === "MemberExpression" && + node.object.type === "Identifier" && + arrayOrTypedArrayPattern.test(node.object.name) && + node.property.type === "Identifier" && + node.property.name === "from" && + node.computed === false + ); +} + +/** + * Checks whether or not a node is a method which has `thisArg`. + * @param {ASTNode} node A node to check. + * @returns {boolean} Whether or not the node is a method which has `thisArg`. + */ +function isMethodWhichHasThisArg(node) { + for ( + let currentNode = node; + currentNode.type === "MemberExpression" && !currentNode.computed; + currentNode = currentNode.property + ) { + if (currentNode.property.type === "Identifier") { + return arrayMethodPattern.test(currentNode.property.name); + } + } + + return false; +} + +/** + * Creates the negate function of the given function. + * @param {Function} f The function to negate. + * @returns {Function} Negated function. + */ +function negate(f) { + return token => !f(token); +} + +/** + * Checks whether or not a node has a `@this` tag in its comments. + * @param {ASTNode} node A node to check. + * @param {SourceCode} sourceCode A SourceCode instance to get comments. + * @returns {boolean} Whether or not the node has a `@this` tag in its comments. + */ +function hasJSDocThisTag(node, sourceCode) { + const jsdocComment = sourceCode.getJSDocComment(node); + + if (jsdocComment && thisTagPattern.test(jsdocComment.value)) { + return true; + } + + // Checks `@this` in its leading comments for callbacks, + // because callbacks don't have its JSDoc comment. + // e.g. + // sinon.test(/* @this sinon.Sandbox */function() { this.spy(); }); + return sourceCode.getCommentsBefore(node).some(comment => thisTagPattern.test(comment.value)); +} + +/** + * Determines if a node is surrounded by parentheses. + * @param {SourceCode} sourceCode The ESLint source code object + * @param {ASTNode} node The node to be checked. + * @returns {boolean} True if the node is parenthesised. + * @private + */ +function isParenthesised(sourceCode, node) { + const previousToken = sourceCode.getTokenBefore(node), + nextToken = sourceCode.getTokenAfter(node); + + return Boolean(previousToken && nextToken) && + previousToken.value === "(" && previousToken.range[1] <= node.range[0] && + nextToken.value === ")" && nextToken.range[0] >= node.range[1]; +} + +/** + * Checks if the given token is an arrow token or not. + * @param {Token} token The token to check. + * @returns {boolean} `true` if the token is an arrow token. + */ +function isArrowToken(token) { + return token.value === "=>" && token.type === "Punctuator"; +} + +/** + * Checks if the given token is a comma token or not. + * @param {Token} token The token to check. + * @returns {boolean} `true` if the token is a comma token. + */ +function isCommaToken(token) { + return token.value === "," && token.type === "Punctuator"; +} + +/** + * Checks if the given token is a dot token or not. + * @param {Token} token The token to check. + * @returns {boolean} `true` if the token is a dot token. + */ +function isDotToken(token) { + return token.value === "." && token.type === "Punctuator"; +} + +/** + * Checks if the given token is a semicolon token or not. + * @param {Token} token The token to check. + * @returns {boolean} `true` if the token is a semicolon token. + */ +function isSemicolonToken(token) { + return token.value === ";" && token.type === "Punctuator"; +} + +/** + * Checks if the given token is a colon token or not. + * @param {Token} token The token to check. + * @returns {boolean} `true` if the token is a colon token. + */ +function isColonToken(token) { + return token.value === ":" && token.type === "Punctuator"; +} + +/** + * Checks if the given token is an opening parenthesis token or not. + * @param {Token} token The token to check. + * @returns {boolean} `true` if the token is an opening parenthesis token. + */ +function isOpeningParenToken(token) { + return token.value === "(" && token.type === "Punctuator"; +} + +/** + * Checks if the given token is a closing parenthesis token or not. + * @param {Token} token The token to check. + * @returns {boolean} `true` if the token is a closing parenthesis token. + */ +function isClosingParenToken(token) { + return token.value === ")" && token.type === "Punctuator"; +} + +/** + * Checks if the given token is an opening square bracket token or not. + * @param {Token} token The token to check. + * @returns {boolean} `true` if the token is an opening square bracket token. + */ +function isOpeningBracketToken(token) { + return token.value === "[" && token.type === "Punctuator"; +} + +/** + * Checks if the given token is a closing square bracket token or not. + * @param {Token} token The token to check. + * @returns {boolean} `true` if the token is a closing square bracket token. + */ +function isClosingBracketToken(token) { + return token.value === "]" && token.type === "Punctuator"; +} + +/** + * Checks if the given token is an opening brace token or not. + * @param {Token} token The token to check. + * @returns {boolean} `true` if the token is an opening brace token. + */ +function isOpeningBraceToken(token) { + return token.value === "{" && token.type === "Punctuator"; +} + +/** + * Checks if the given token is a closing brace token or not. + * @param {Token} token The token to check. + * @returns {boolean} `true` if the token is a closing brace token. + */ +function isClosingBraceToken(token) { + return token.value === "}" && token.type === "Punctuator"; +} + +/** + * Checks if the given token is a comment token or not. + * @param {Token} token The token to check. + * @returns {boolean} `true` if the token is a comment token. + */ +function isCommentToken(token) { + return token.type === "Line" || token.type === "Block" || token.type === "Shebang"; +} + +/** + * Checks if the given token is a keyword token or not. + * @param {Token} token The token to check. + * @returns {boolean} `true` if the token is a keyword token. + */ +function isKeywordToken(token) { + return token.type === "Keyword"; +} + +/** + * Gets the `(` token of the given function node. + * @param {ASTNode} node The function node to get. + * @param {SourceCode} sourceCode The source code object to get tokens. + * @returns {Token} `(` token. + */ +function getOpeningParenOfParams(node, sourceCode) { + return node.id + ? sourceCode.getTokenAfter(node.id, isOpeningParenToken) + : sourceCode.getFirstToken(node, isOpeningParenToken); +} + +/** + * Checks whether or not the tokens of two given nodes are same. + * @param {ASTNode} left A node 1 to compare. + * @param {ASTNode} right A node 2 to compare. + * @param {SourceCode} sourceCode The ESLint source code object. + * @returns {boolean} the source code for the given node. + */ +function equalTokens(left, right, sourceCode) { + const tokensL = sourceCode.getTokens(left); + const tokensR = sourceCode.getTokens(right); + + if (tokensL.length !== tokensR.length) { + return false; + } + for (let i = 0; i < tokensL.length; ++i) { + if (tokensL[i].type !== tokensR[i].type || + tokensL[i].value !== tokensR[i].value + ) { + return false; + } + } + + return true; +} + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = { + COMMENTS_IGNORE_PATTERN, + LINEBREAKS, + LINEBREAK_MATCHER: lineBreakPattern, + SHEBANG_MATCHER: shebangPattern, + STATEMENT_LIST_PARENTS, + + /** + * Determines whether two adjacent tokens are on the same line. + * @param {Object} left The left token object. + * @param {Object} right The right token object. + * @returns {boolean} Whether or not the tokens are on the same line. + * @public + */ + isTokenOnSameLine(left, right) { + return left.loc.end.line === right.loc.start.line; + }, + + isNullOrUndefined, + isCallee, + isES5Constructor, + getUpperFunction, + isFunction, + isLoop, + isInLoop, + isArrayFromMethod, + isParenthesised, + createGlobalLinebreakMatcher, + equalTokens, + + isArrowToken, + isClosingBraceToken, + isClosingBracketToken, + isClosingParenToken, + isColonToken, + isCommaToken, + isCommentToken, + isDotToken, + isKeywordToken, + isNotClosingBraceToken: negate(isClosingBraceToken), + isNotClosingBracketToken: negate(isClosingBracketToken), + isNotClosingParenToken: negate(isClosingParenToken), + isNotColonToken: negate(isColonToken), + isNotCommaToken: negate(isCommaToken), + isNotDotToken: negate(isDotToken), + isNotOpeningBraceToken: negate(isOpeningBraceToken), + isNotOpeningBracketToken: negate(isOpeningBracketToken), + isNotOpeningParenToken: negate(isOpeningParenToken), + isNotSemicolonToken: negate(isSemicolonToken), + isOpeningBraceToken, + isOpeningBracketToken, + isOpeningParenToken, + isSemicolonToken, + + /** + * Checks whether or not a given node is a string literal. + * @param {ASTNode} node A node to check. + * @returns {boolean} `true` if the node is a string literal. + */ + isStringLiteral(node) { + return ( + (node.type === "Literal" && typeof node.value === "string") || + node.type === "TemplateLiteral" + ); + }, + + /** + * Checks whether a given node is a breakable statement or not. + * The node is breakable if the node is one of the following type: + * + * - DoWhileStatement + * - ForInStatement + * - ForOfStatement + * - ForStatement + * - SwitchStatement + * - WhileStatement + * @param {ASTNode} node A node to check. + * @returns {boolean} `true` if the node is breakable. + */ + isBreakableStatement(node) { + return breakableTypePattern.test(node.type); + }, + + /** + * Gets references which are non initializer and writable. + * @param {Reference[]} references An array of references. + * @returns {Reference[]} An array of only references which are non initializer and writable. + * @public + */ + getModifyingReferences(references) { + return references.filter(isModifyingReference); + }, + + /** + * Validate that a string passed in is surrounded by the specified character + * @param {string} val The text to check. + * @param {string} character The character to see if it's surrounded by. + * @returns {boolean} True if the text is surrounded by the character, false if not. + * @private + */ + isSurroundedBy(val, character) { + return val[0] === character && val[val.length - 1] === character; + }, + + /** + * Returns whether the provided node is an ESLint directive comment or not + * @param {Line|Block} node The comment token to be checked + * @returns {boolean} `true` if the node is an ESLint directive comment + */ + isDirectiveComment(node) { + const comment = node.value.trim(); + + return ( + node.type === "Line" && comment.indexOf("eslint-") === 0 || + node.type === "Block" && ( + comment.indexOf("global ") === 0 || + comment.indexOf("eslint ") === 0 || + comment.indexOf("eslint-") === 0 + ) + ); + }, + + /** + * Gets the trailing statement of a given node. + * + * if (code) + * consequent; + * + * When taking this `IfStatement`, returns `consequent;` statement. + * @param {ASTNode} A node to get. + * @returns {ASTNode|null} The trailing statement's node. + */ + getTrailingStatement: esutils.ast.trailingStatement, + + /** + * Finds the variable by a given name in a given scope and its upper scopes. + * @param {eslint-scope.Scope} initScope A scope to start find. + * @param {string} name A variable name to find. + * @returns {eslint-scope.Variable|null} A found variable or `null`. + */ + getVariableByName(initScope, name) { + let scope = initScope; + + while (scope) { + const variable = scope.set.get(name); + + if (variable) { + return variable; + } + + scope = scope.upper; + } + + return null; + }, + + /** + * Checks whether or not a given function node is the default `this` binding. + * + * First, this checks the node: + * + * - The function name does not start with uppercase. It's a convention to capitalize the names + * of constructor functions. This check is not performed if `capIsConstructor` is set to `false`. + * - The function does not have a JSDoc comment that has a @this tag. + * + * Next, this checks the location of the node. + * If the location is below, this judges `this` is valid. + * + * - The location is not on an object literal. + * - The location is not assigned to a variable which starts with an uppercase letter. Applies to anonymous + * functions only, as the name of the variable is considered to be the name of the function in this case. + * This check is not performed if `capIsConstructor` is set to `false`. + * - The location is not on an ES2015 class. + * - Its `bind`/`call`/`apply` method is not called directly. + * - The function is not a callback of array methods (such as `.forEach()`) if `thisArg` is given. + * @param {ASTNode} node A function node to check. + * @param {SourceCode} sourceCode A SourceCode instance to get comments. + * @param {boolean} [capIsConstructor = true] `false` disables the assumption that functions which name starts + * with an uppercase or are assigned to a variable which name starts with an uppercase are constructors. + * @returns {boolean} The function node is the default `this` binding. + */ + isDefaultThisBinding(node, sourceCode, { capIsConstructor = true } = {}) { + if ( + (capIsConstructor && isES5Constructor(node)) || + hasJSDocThisTag(node, sourceCode) + ) { + return false; + } + const isAnonymous = node.id === null; + let currentNode = node; + + while (currentNode) { + const parent = currentNode.parent; + + switch (parent.type) { + + /* + * Looks up the destination. + * e.g., obj.foo = nativeFoo || function foo() { ... }; + */ + case "LogicalExpression": + case "ConditionalExpression": + currentNode = parent; + break; + + /* + * If the upper function is IIFE, checks the destination of the return value. + * e.g. + * obj.foo = (function() { + * // setup... + * return function foo() { ... }; + * })(); + * obj.foo = (() => + * function foo() { ... } + * )(); + */ + case "ReturnStatement": { + const func = getUpperFunction(parent); + + if (func === null || !isCallee(func)) { + return true; + } + currentNode = func.parent; + break; + } + case "ArrowFunctionExpression": + if (currentNode !== parent.body || !isCallee(parent)) { + return true; + } + currentNode = parent.parent; + break; + + /* + * e.g. + * var obj = { foo() { ... } }; + * var obj = { foo: function() { ... } }; + * class A { constructor() { ... } } + * class A { foo() { ... } } + * class A { get foo() { ... } } + * class A { set foo() { ... } } + * class A { static foo() { ... } } + */ + case "Property": + case "MethodDefinition": + return parent.value !== currentNode; + + /* + * e.g. + * obj.foo = function foo() { ... }; + * Foo = function() { ... }; + * [obj.foo = function foo() { ... }] = a; + * [Foo = function() { ... }] = a; + */ + case "AssignmentExpression": + case "AssignmentPattern": + if (parent.left.type === "MemberExpression") { + return false; + } + if ( + capIsConstructor && + isAnonymous && + parent.left.type === "Identifier" && + startsWithUpperCase(parent.left.name) + ) { + return false; + } + return true; + + /* + * e.g. + * var Foo = function() { ... }; + */ + case "VariableDeclarator": + return !( + capIsConstructor && + isAnonymous && + parent.init === currentNode && + parent.id.type === "Identifier" && + startsWithUpperCase(parent.id.name) + ); + + /* + * e.g. + * var foo = function foo() { ... }.bind(obj); + * (function foo() { ... }).call(obj); + * (function foo() { ... }).apply(obj, []); + */ + case "MemberExpression": + return ( + parent.object !== currentNode || + parent.property.type !== "Identifier" || + !bindOrCallOrApplyPattern.test(parent.property.name) || + !isCallee(parent) || + parent.parent.arguments.length === 0 || + isNullOrUndefined(parent.parent.arguments[0]) + ); + + /* + * e.g. + * Reflect.apply(function() {}, obj, []); + * Array.from([], function() {}, obj); + * list.forEach(function() {}, obj); + */ + case "CallExpression": + if (isReflectApply(parent.callee)) { + return ( + parent.arguments.length !== 3 || + parent.arguments[0] !== currentNode || + isNullOrUndefined(parent.arguments[1]) + ); + } + if (isArrayFromMethod(parent.callee)) { + return ( + parent.arguments.length !== 3 || + parent.arguments[1] !== currentNode || + isNullOrUndefined(parent.arguments[2]) + ); + } + if (isMethodWhichHasThisArg(parent.callee)) { + return ( + parent.arguments.length !== 2 || + parent.arguments[0] !== currentNode || + isNullOrUndefined(parent.arguments[1]) + ); + } + return true; + + // Otherwise `this` is default. + default: + return true; + } + } + + /* istanbul ignore next */ + return true; + }, + + /** + * Get the precedence level based on the node type + * @param {ASTNode} node node to evaluate + * @returns {int} precedence level + * @private + */ + getPrecedence(node) { + switch (node.type) { + case "SequenceExpression": + return 0; + + case "AssignmentExpression": + case "ArrowFunctionExpression": + case "YieldExpression": + return 1; + + case "ConditionalExpression": + return 3; + + case "LogicalExpression": + switch (node.operator) { + case "||": + return 4; + case "&&": + return 5; + + // no default + } + + /* falls through */ + + case "BinaryExpression": + + switch (node.operator) { + case "|": + return 6; + case "^": + return 7; + case "&": + return 8; + case "==": + case "!=": + case "===": + case "!==": + return 9; + case "<": + case "<=": + case ">": + case ">=": + case "in": + case "instanceof": + return 10; + case "<<": + case ">>": + case ">>>": + return 11; + case "+": + case "-": + return 12; + case "*": + case "/": + case "%": + return 13; + case "**": + return 15; + + // no default + } + + /* falls through */ + + case "UnaryExpression": + case "AwaitExpression": + return 16; + + case "UpdateExpression": + return 17; + + case "CallExpression": + case "ImportExpression": + return 18; + + case "NewExpression": + return 19; + + default: + return 20; + } + }, + + /** + * Checks whether the given node is an empty block node or not. + * @param {ASTNode|null} node The node to check. + * @returns {boolean} `true` if the node is an empty block. + */ + isEmptyBlock(node) { + return Boolean(node && node.type === "BlockStatement" && node.body.length === 0); + }, + + /** + * Checks whether the given node is an empty function node or not. + * @param {ASTNode|null} node The node to check. + * @returns {boolean} `true` if the node is an empty function. + */ + isEmptyFunction(node) { + return isFunction(node) && module.exports.isEmptyBlock(node.body); + }, + + /** + * Returns the result of the string conversion applied to the evaluated value of the given expression node, + * if it can be determined statically. + * + * This function returns a `string` value for all `Literal` nodes and simple `TemplateLiteral` nodes only. + * In all other cases, this function returns `null`. + * @param {ASTNode} node Expression node. + * @returns {string|null} String value if it can be determined. Otherwise, `null`. + */ + getStaticStringValue(node) { + switch (node.type) { + case "Literal": + if (node.value === null) { + if (module.exports.isNullLiteral(node)) { + return String(node.value); // "null" + } + if (node.regex) { + return `/${node.regex.pattern}/${node.regex.flags}`; + } + if (node.bigint) { + return node.bigint; + } + + // Otherwise, this is an unknown literal. The function will return null. + + } else { + return String(node.value); + } + break; + case "TemplateLiteral": + if (node.expressions.length === 0 && node.quasis.length === 1) { + return node.quasis[0].value.cooked; + } + break; + + // no default + } + + return null; + }, + + /** + * Gets the property name of a given node. + * The node can be a MemberExpression, a Property, or a MethodDefinition. + * + * If the name is dynamic, this returns `null`. + * + * For examples: + * + * a.b // => "b" + * a["b"] // => "b" + * a['b'] // => "b" + * a[`b`] // => "b" + * a[100] // => "100" + * a[b] // => null + * a["a" + "b"] // => null + * a[tag`b`] // => null + * a[`${b}`] // => null + * + * let a = {b: 1} // => "b" + * let a = {["b"]: 1} // => "b" + * let a = {['b']: 1} // => "b" + * let a = {[`b`]: 1} // => "b" + * let a = {[100]: 1} // => "100" + * let a = {[b]: 1} // => null + * let a = {["a" + "b"]: 1} // => null + * let a = {[tag`b`]: 1} // => null + * let a = {[`${b}`]: 1} // => null + * @param {ASTNode} node The node to get. + * @returns {string|null} The property name if static. Otherwise, null. + */ + getStaticPropertyName(node) { + let prop; + + switch (node && node.type) { + case "Property": + case "MethodDefinition": + prop = node.key; + break; + + case "MemberExpression": + prop = node.property; + break; + + // no default + } + + if (prop) { + if (prop.type === "Identifier" && !node.computed) { + return prop.name; + } + + return module.exports.getStaticStringValue(prop); + } + + return null; + }, + + /** + * Get directives from directive prologue of a Program or Function node. + * @param {ASTNode} node The node to check. + * @returns {ASTNode[]} The directives found in the directive prologue. + */ + getDirectivePrologue(node) { + const directives = []; + + // Directive prologues only occur at the top of files or functions. + if ( + node.type === "Program" || + node.type === "FunctionDeclaration" || + node.type === "FunctionExpression" || + + /* + * Do not check arrow functions with implicit return. + * `() => "use strict";` returns the string `"use strict"`. + */ + (node.type === "ArrowFunctionExpression" && node.body.type === "BlockStatement") + ) { + const statements = node.type === "Program" ? node.body : node.body.body; + + for (const statement of statements) { + if ( + statement.type === "ExpressionStatement" && + statement.expression.type === "Literal" + ) { + directives.push(statement); + } else { + break; + } + } + } + + return directives; + }, + + + /** + * Determines whether this node is a decimal integer literal. If a node is a decimal integer literal, a dot added + * after the node will be parsed as a decimal point, rather than a property-access dot. + * @param {ASTNode} node The node to check. + * @returns {boolean} `true` if this node is a decimal integer. + * @example + * + * 5 // true + * 5. // false + * 5.0 // false + * 05 // false + * 0x5 // false + * 0b101 // false + * 0o5 // false + * 5e0 // false + * '5' // false + * 5n // false + */ + isDecimalInteger(node) { + return node.type === "Literal" && typeof node.value === "number" && + DECIMAL_INTEGER_PATTERN.test(node.raw); + }, + + /** + * Determines whether this token is a decimal integer numeric token. + * This is similar to isDecimalInteger(), but for tokens. + * @param {Token} token The token to check. + * @returns {boolean} `true` if this token is a decimal integer. + */ + isDecimalIntegerNumericToken(token) { + return token.type === "Numeric" && DECIMAL_INTEGER_PATTERN.test(token.value); + }, + + /** + * Gets the name and kind of the given function node. + * + * - `function foo() {}` .................... `function 'foo'` + * - `(function foo() {})` .................. `function 'foo'` + * - `(function() {})` ...................... `function` + * - `function* foo() {}` ................... `generator function 'foo'` + * - `(function* foo() {})` ................. `generator function 'foo'` + * - `(function*() {})` ..................... `generator function` + * - `() => {}` ............................. `arrow function` + * - `async () => {}` ....................... `async arrow function` + * - `({ foo: function foo() {} })` ......... `method 'foo'` + * - `({ foo: function() {} })` ............. `method 'foo'` + * - `({ ['foo']: function() {} })` ......... `method 'foo'` + * - `({ [foo]: function() {} })` ........... `method` + * - `({ foo() {} })` ....................... `method 'foo'` + * - `({ foo: function* foo() {} })` ........ `generator method 'foo'` + * - `({ foo: function*() {} })` ............ `generator method 'foo'` + * - `({ ['foo']: function*() {} })` ........ `generator method 'foo'` + * - `({ [foo]: function*() {} })` .......... `generator method` + * - `({ *foo() {} })` ...................... `generator method 'foo'` + * - `({ foo: async function foo() {} })` ... `async method 'foo'` + * - `({ foo: async function() {} })` ....... `async method 'foo'` + * - `({ ['foo']: async function() {} })` ... `async method 'foo'` + * - `({ [foo]: async function() {} })` ..... `async method` + * - `({ async foo() {} })` ................. `async method 'foo'` + * - `({ get foo() {} })` ................... `getter 'foo'` + * - `({ set foo(a) {} })` .................. `setter 'foo'` + * - `class A { constructor() {} }` ......... `constructor` + * - `class A { foo() {} }` ................. `method 'foo'` + * - `class A { *foo() {} }` ................ `generator method 'foo'` + * - `class A { async foo() {} }` ........... `async method 'foo'` + * - `class A { ['foo']() {} }` ............. `method 'foo'` + * - `class A { *['foo']() {} }` ............ `generator method 'foo'` + * - `class A { async ['foo']() {} }` ....... `async method 'foo'` + * - `class A { [foo]() {} }` ............... `method` + * - `class A { *[foo]() {} }` .............. `generator method` + * - `class A { async [foo]() {} }` ......... `async method` + * - `class A { get foo() {} }` ............. `getter 'foo'` + * - `class A { set foo(a) {} }` ............ `setter 'foo'` + * - `class A { static foo() {} }` .......... `static method 'foo'` + * - `class A { static *foo() {} }` ......... `static generator method 'foo'` + * - `class A { static async foo() {} }` .... `static async method 'foo'` + * - `class A { static get foo() {} }` ...... `static getter 'foo'` + * - `class A { static set foo(a) {} }` ..... `static setter 'foo'` + * @param {ASTNode} node The function node to get. + * @returns {string} The name and kind of the function node. + */ + getFunctionNameWithKind(node) { + const parent = node.parent; + const tokens = []; + + if (parent.type === "MethodDefinition" && parent.static) { + tokens.push("static"); + } + if (node.async) { + tokens.push("async"); + } + if (node.generator) { + tokens.push("generator"); + } + + if (node.type === "ArrowFunctionExpression") { + tokens.push("arrow", "function"); + } else if (parent.type === "Property" || parent.type === "MethodDefinition") { + if (parent.kind === "constructor") { + return "constructor"; + } + if (parent.kind === "get") { + tokens.push("getter"); + } else if (parent.kind === "set") { + tokens.push("setter"); + } else { + tokens.push("method"); + } + } else { + tokens.push("function"); + } + + if (node.id) { + tokens.push(`'${node.id.name}'`); + } else { + const name = module.exports.getStaticPropertyName(parent); + + if (name !== null) { + tokens.push(`'${name}'`); + } + } + + return tokens.join(" "); + }, + + /** + * Gets the location of the given function node for reporting. + * + * - `function foo() {}` + * ^^^^^^^^^^^^ + * - `(function foo() {})` + * ^^^^^^^^^^^^ + * - `(function() {})` + * ^^^^^^^^ + * - `function* foo() {}` + * ^^^^^^^^^^^^^ + * - `(function* foo() {})` + * ^^^^^^^^^^^^^ + * - `(function*() {})` + * ^^^^^^^^^ + * - `() => {}` + * ^^ + * - `async () => {}` + * ^^ + * - `({ foo: function foo() {} })` + * ^^^^^^^^^^^^^^^^^ + * - `({ foo: function() {} })` + * ^^^^^^^^^^^^^ + * - `({ ['foo']: function() {} })` + * ^^^^^^^^^^^^^^^^^ + * - `({ [foo]: function() {} })` + * ^^^^^^^^^^^^^^^ + * - `({ foo() {} })` + * ^^^ + * - `({ foo: function* foo() {} })` + * ^^^^^^^^^^^^^^^^^^ + * - `({ foo: function*() {} })` + * ^^^^^^^^^^^^^^ + * - `({ ['foo']: function*() {} })` + * ^^^^^^^^^^^^^^^^^^ + * - `({ [foo]: function*() {} })` + * ^^^^^^^^^^^^^^^^ + * - `({ *foo() {} })` + * ^^^^ + * - `({ foo: async function foo() {} })` + * ^^^^^^^^^^^^^^^^^^^^^^^ + * - `({ foo: async function() {} })` + * ^^^^^^^^^^^^^^^^^^^ + * - `({ ['foo']: async function() {} })` + * ^^^^^^^^^^^^^^^^^^^^^^^ + * - `({ [foo]: async function() {} })` + * ^^^^^^^^^^^^^^^^^^^^^ + * - `({ async foo() {} })` + * ^^^^^^^^^ + * - `({ get foo() {} })` + * ^^^^^^^ + * - `({ set foo(a) {} })` + * ^^^^^^^ + * - `class A { constructor() {} }` + * ^^^^^^^^^^^ + * - `class A { foo() {} }` + * ^^^ + * - `class A { *foo() {} }` + * ^^^^ + * - `class A { async foo() {} }` + * ^^^^^^^^^ + * - `class A { ['foo']() {} }` + * ^^^^^^^ + * - `class A { *['foo']() {} }` + * ^^^^^^^^ + * - `class A { async ['foo']() {} }` + * ^^^^^^^^^^^^^ + * - `class A { [foo]() {} }` + * ^^^^^ + * - `class A { *[foo]() {} }` + * ^^^^^^ + * - `class A { async [foo]() {} }` + * ^^^^^^^^^^^ + * - `class A { get foo() {} }` + * ^^^^^^^ + * - `class A { set foo(a) {} }` + * ^^^^^^^ + * - `class A { static foo() {} }` + * ^^^^^^^^^^ + * - `class A { static *foo() {} }` + * ^^^^^^^^^^^ + * - `class A { static async foo() {} }` + * ^^^^^^^^^^^^^^^^ + * - `class A { static get foo() {} }` + * ^^^^^^^^^^^^^^ + * - `class A { static set foo(a) {} }` + * ^^^^^^^^^^^^^^ + * @param {ASTNode} node The function node to get. + * @param {SourceCode} sourceCode The source code object to get tokens. + * @returns {string} The location of the function node for reporting. + */ + getFunctionHeadLoc(node, sourceCode) { + const parent = node.parent; + let start = null; + let end = null; + + if (node.type === "ArrowFunctionExpression") { + const arrowToken = sourceCode.getTokenBefore(node.body, isArrowToken); + + start = arrowToken.loc.start; + end = arrowToken.loc.end; + } else if (parent.type === "Property" || parent.type === "MethodDefinition") { + start = parent.loc.start; + end = getOpeningParenOfParams(node, sourceCode).loc.start; + } else { + start = node.loc.start; + end = getOpeningParenOfParams(node, sourceCode).loc.start; + } + + return { + start: Object.assign({}, start), + end: Object.assign({}, end) + }; + }, + + /** + * Gets next location when the result is not out of bound, otherwise returns null. + * @param {SourceCode} sourceCode The sourceCode + * @param {{line: number, column: number}} location The location + * @returns {{line: number, column: number} | null} Next location + */ + getNextLocation(sourceCode, location) { + const index = sourceCode.getIndexFromLoc(location); + + // Avoid out of bound location + if (index + 1 > sourceCode.text.length) { + return null; + } + + return sourceCode.getLocFromIndex(index + 1); + }, + + /** + * Gets the parenthesized text of a node. This is similar to sourceCode.getText(node), but it also includes any parentheses + * surrounding the node. + * @param {SourceCode} sourceCode The source code object + * @param {ASTNode} node An expression node + * @returns {string} The text representing the node, with all surrounding parentheses included + */ + getParenthesisedText(sourceCode, node) { + let leftToken = sourceCode.getFirstToken(node); + let rightToken = sourceCode.getLastToken(node); + + while ( + sourceCode.getTokenBefore(leftToken) && + sourceCode.getTokenBefore(leftToken).type === "Punctuator" && + sourceCode.getTokenBefore(leftToken).value === "(" && + sourceCode.getTokenAfter(rightToken) && + sourceCode.getTokenAfter(rightToken).type === "Punctuator" && + sourceCode.getTokenAfter(rightToken).value === ")" + ) { + leftToken = sourceCode.getTokenBefore(leftToken); + rightToken = sourceCode.getTokenAfter(rightToken); + } + + return sourceCode.getText().slice(leftToken.range[0], rightToken.range[1]); + }, + + /* + * Determine if a node has a possiblity to be an Error object + * @param {ASTNode} node ASTNode to check + * @returns {boolean} True if there is a chance it contains an Error obj + */ + couldBeError(node) { + switch (node.type) { + case "Identifier": + case "CallExpression": + case "NewExpression": + case "MemberExpression": + case "TaggedTemplateExpression": + case "YieldExpression": + case "AwaitExpression": + return true; // possibly an error object. + + case "AssignmentExpression": + return module.exports.couldBeError(node.right); + + case "SequenceExpression": { + const exprs = node.expressions; + + return exprs.length !== 0 && module.exports.couldBeError(exprs[exprs.length - 1]); + } + + case "LogicalExpression": + return module.exports.couldBeError(node.left) || module.exports.couldBeError(node.right); + + case "ConditionalExpression": + return module.exports.couldBeError(node.consequent) || module.exports.couldBeError(node.alternate); + + default: + return false; + } + }, + + /** + * Determines whether the given node is a `null` literal. + * @param {ASTNode} node The node to check + * @returns {boolean} `true` if the node is a `null` literal + */ + isNullLiteral(node) { + + /* + * Checking `node.value === null` does not guarantee that a literal is a null literal. + * When parsing values that cannot be represented in the current environment (e.g. unicode + * regexes in Node 4), `node.value` is set to `null` because it wouldn't be possible to + * set `node.value` to a unicode regex. To make sure a literal is actually `null`, check + * `node.regex` instead. Also see: https://github.com/eslint/eslint/issues/8020 + */ + return node.type === "Literal" && node.value === null && !node.regex && !node.bigint; + }, + + /** + * Check if a given node is a numeric literal or not. + * @param {ASTNode} node The node to check. + * @returns {boolean} `true` if the node is a number or bigint literal. + */ + isNumericLiteral(node) { + return ( + node.type === "Literal" && + (typeof node.value === "number" || Boolean(node.bigint)) + ); + }, + + /** + * Determines whether two tokens can safely be placed next to each other without merging into a single token + * @param {Token|string} leftValue The left token. If this is a string, it will be tokenized and the last token will be used. + * @param {Token|string} rightValue The right token. If this is a string, it will be tokenized and the first token will be used. + * @returns {boolean} If the tokens cannot be safely placed next to each other, returns `false`. If the tokens can be placed + * next to each other, behavior is undefined (although it should return `true` in most cases). + */ + canTokensBeAdjacent(leftValue, rightValue) { + const espreeOptions = { + ecmaVersion: espree.latestEcmaVersion, + comment: true, + range: true + }; + + let leftToken; + + if (typeof leftValue === "string") { + let tokens; + + try { + tokens = espree.tokenize(leftValue, espreeOptions); + } catch (e) { + return false; + } + + const comments = tokens.comments; + + leftToken = tokens[tokens.length - 1]; + if (comments.length) { + const lastComment = comments[comments.length - 1]; + + if (lastComment.range[0] > leftToken.range[0]) { + leftToken = lastComment; + } + } + } else { + leftToken = leftValue; + } + + if (leftToken.type === "Shebang") { + return false; + } + + let rightToken; + + if (typeof rightValue === "string") { + let tokens; + + try { + tokens = espree.tokenize(rightValue, espreeOptions); + } catch (e) { + return false; + } + + const comments = tokens.comments; + + rightToken = tokens[0]; + if (comments.length) { + const firstComment = comments[0]; + + if (firstComment.range[0] < rightToken.range[0]) { + rightToken = firstComment; + } + } + } else { + rightToken = rightValue; + } + + if (leftToken.type === "Punctuator" || rightToken.type === "Punctuator") { + if (leftToken.type === "Punctuator" && rightToken.type === "Punctuator") { + const PLUS_TOKENS = new Set(["+", "++"]); + const MINUS_TOKENS = new Set(["-", "--"]); + + return !( + PLUS_TOKENS.has(leftToken.value) && PLUS_TOKENS.has(rightToken.value) || + MINUS_TOKENS.has(leftToken.value) && MINUS_TOKENS.has(rightToken.value) + ); + } + if (leftToken.type === "Punctuator" && leftToken.value === "/") { + return !["Block", "Line", "RegularExpression"].includes(rightToken.type); + } + return true; + } + + if ( + leftToken.type === "String" || rightToken.type === "String" || + leftToken.type === "Template" || rightToken.type === "Template" + ) { + return true; + } + + if (leftToken.type !== "Numeric" && rightToken.type === "Numeric" && rightToken.value.startsWith(".")) { + return true; + } + + if (leftToken.type === "Block" || rightToken.type === "Block" || rightToken.type === "Line") { + return true; + } + + return false; + }, + + /** + * Get the `loc` object of a given name in a `/*globals` directive comment. + * @param {SourceCode} sourceCode The source code to convert index to loc. + * @param {Comment} comment The `/*globals` directive comment which include the name. + * @param {string} name The name to find. + * @returns {SourceLocation} The `loc` object. + */ + getNameLocationInGlobalDirectiveComment(sourceCode, comment, name) { + const namePattern = new RegExp(`[\\s,]${lodash.escapeRegExp(name)}(?:$|[\\s,:])`, "gu"); + + // To ignore the first text "global". + namePattern.lastIndex = comment.value.indexOf("global") + 6; + + // Search a given variable name. + const match = namePattern.exec(comment.value); + + // Convert the index to loc. + const start = sourceCode.getLocFromIndex( + comment.range[0] + + "/*".length + + (match ? match.index + 1 : 0) + ); + const end = { + line: start.line, + column: start.column + (match ? name.length : 1) + }; + + return { start, end }; + }, + + /** + * Determines whether the given raw string contains an octal escape sequence. + * + * "\1", "\2" ... "\7" + * "\00", "\01" ... "\09" + * + * "\0", when not followed by a digit, is not an octal escape sequence. + * @param {string} rawString A string in its raw representation. + * @returns {boolean} `true` if the string contains at least one octal escape sequence. + */ + hasOctalEscapeSequence(rawString) { + return OCTAL_ESCAPE_PATTERN.test(rawString); + } +}; diff --git a/eslint/lib/rules/utils/fix-tracker.js b/eslint/lib/rules/utils/fix-tracker.js new file mode 100644 index 0000000..589870b --- /dev/null +++ b/eslint/lib/rules/utils/fix-tracker.js @@ -0,0 +1,114 @@ +/** + * @fileoverview Helper class to aid in constructing fix commands. + * @author Alan Pierce + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./ast-utils"); + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +/** + * A helper class to combine fix options into a fix command. Currently, it + * exposes some "retain" methods that extend the range of the text being + * replaced so that other fixes won't touch that region in the same pass. + */ +class FixTracker { + + /** + * Create a new FixTracker. + * @param {ruleFixer} fixer A ruleFixer instance. + * @param {SourceCode} sourceCode A SourceCode object for the current code. + */ + constructor(fixer, sourceCode) { + this.fixer = fixer; + this.sourceCode = sourceCode; + this.retainedRange = null; + } + + /** + * Mark the given range as "retained", meaning that other fixes may not + * may not modify this region in the same pass. + * @param {int[]} range The range to retain. + * @returns {FixTracker} The same RuleFixer, for chained calls. + */ + retainRange(range) { + this.retainedRange = range; + return this; + } + + /** + * Given a node, find the function containing it (or the entire program) and + * mark it as retained, meaning that other fixes may not modify it in this + * pass. This is useful for avoiding conflicts in fixes that modify control + * flow. + * @param {ASTNode} node The node to use as a starting point. + * @returns {FixTracker} The same RuleFixer, for chained calls. + */ + retainEnclosingFunction(node) { + const functionNode = astUtils.getUpperFunction(node); + + return this.retainRange(functionNode ? functionNode.range : this.sourceCode.ast.range); + } + + /** + * Given a node or token, find the token before and afterward, and mark that + * range as retained, meaning that other fixes may not modify it in this + * pass. This is useful for avoiding conflicts in fixes that make a small + * change to the code where the AST should not be changed. + * @param {ASTNode|Token} nodeOrToken The node or token to use as a starting + * point. The token to the left and right are use in the range. + * @returns {FixTracker} The same RuleFixer, for chained calls. + */ + retainSurroundingTokens(nodeOrToken) { + const tokenBefore = this.sourceCode.getTokenBefore(nodeOrToken) || nodeOrToken; + const tokenAfter = this.sourceCode.getTokenAfter(nodeOrToken) || nodeOrToken; + + return this.retainRange([tokenBefore.range[0], tokenAfter.range[1]]); + } + + /** + * Create a fix command that replaces the given range with the given text, + * accounting for any retained ranges. + * @param {int[]} range The range to remove in the fix. + * @param {string} text The text to insert in place of the range. + * @returns {Object} The fix command. + */ + replaceTextRange(range, text) { + let actualRange; + + if (this.retainedRange) { + actualRange = [ + Math.min(this.retainedRange[0], range[0]), + Math.max(this.retainedRange[1], range[1]) + ]; + } else { + actualRange = range; + } + + return this.fixer.replaceTextRange( + actualRange, + this.sourceCode.text.slice(actualRange[0], range[0]) + + text + + this.sourceCode.text.slice(range[1], actualRange[1]) + ); + } + + /** + * Create a fix command that removes the given node or token, accounting for + * any retained ranges. + * @param {ASTNode|Token} nodeOrToken The node or token to remove. + * @returns {Object} The fix command. + */ + remove(nodeOrToken) { + return this.replaceTextRange(nodeOrToken.range, ""); + } +} + +module.exports = FixTracker; diff --git a/eslint/lib/rules/utils/keywords.js b/eslint/lib/rules/utils/keywords.js new file mode 100644 index 0000000..3fbb777 --- /dev/null +++ b/eslint/lib/rules/utils/keywords.js @@ -0,0 +1,67 @@ +/** + * @fileoverview A shared list of ES3 keywords. + * @author Josh Perez + */ +"use strict"; + +module.exports = [ + "abstract", + "boolean", + "break", + "byte", + "case", + "catch", + "char", + "class", + "const", + "continue", + "debugger", + "default", + "delete", + "do", + "double", + "else", + "enum", + "export", + "extends", + "false", + "final", + "finally", + "float", + "for", + "function", + "goto", + "if", + "implements", + "import", + "in", + "instanceof", + "int", + "interface", + "long", + "native", + "new", + "null", + "package", + "private", + "protected", + "public", + "return", + "short", + "static", + "super", + "switch", + "synchronized", + "this", + "throw", + "throws", + "transient", + "true", + "try", + "typeof", + "var", + "void", + "volatile", + "while", + "with" +]; diff --git a/eslint/lib/rules/utils/lazy-loading-rule-map.js b/eslint/lib/rules/utils/lazy-loading-rule-map.js new file mode 100644 index 0000000..d426d85 --- /dev/null +++ b/eslint/lib/rules/utils/lazy-loading-rule-map.js @@ -0,0 +1,115 @@ +/** + * @fileoverview `Map` to load rules lazily. + * @author Toru Nagashima + */ +"use strict"; + +const debug = require("debug")("eslint:rules"); + +/** @typedef {import("./types").Rule} Rule */ + +/** + * The `Map` object that loads each rule when it's accessed. + * @example + * const rules = new LazyLoadingRuleMap([ + * ["eqeqeq", () => require("eqeqeq")], + * ["semi", () => require("semi")], + * ["no-unused-vars", () => require("no-unused-vars")], + * ]) + * + * rules.get("semi") // call `() => require("semi")` here. + * + * @extends {Map Rule>} + */ +class LazyLoadingRuleMap extends Map { + + /** + * Initialize this map. + * @param {Array<[string, function(): Rule]>} loaders The rule loaders. + */ + constructor(loaders) { + let remaining = loaders.length; + + super( + debug.enabled + ? loaders.map(([ruleId, load]) => { + let cache = null; + + return [ + ruleId, + () => { + if (!cache) { + debug("Loading rule %o (remaining=%d)", ruleId, --remaining); + cache = load(); + } + return cache; + } + ]; + }) + : loaders + ); + + // `super(...iterable)` uses `this.set()`, so disable it here. + Object.defineProperty(LazyLoadingRuleMap.prototype, "set", { + configurable: true, + value: void 0 + }); + } + + /** + * Get a rule. + * Each rule will be loaded on the first access. + * @param {string} ruleId The rule ID to get. + * @returns {Rule|undefined} The rule. + */ + get(ruleId) { + const load = super.get(ruleId); + + return load && load(); + } + + /** + * Iterate rules. + * @returns {IterableIterator} Rules. + */ + *values() { + for (const load of super.values()) { + yield load(); + } + } + + /** + * Iterate rules. + * @returns {IterableIterator<[string, Rule]>} Rules. + */ + *entries() { + for (const [ruleId, load] of super.entries()) { + yield [ruleId, load()]; + } + } + + /** + * Call a function with each rule. + * @param {Function} callbackFn The callback function. + * @param {any} [thisArg] The object to pass to `this` of the callback function. + * @returns {void} + */ + forEach(callbackFn, thisArg) { + for (const [ruleId, load] of super.entries()) { + callbackFn.call(thisArg, load(), ruleId, this); + } + } +} + +// Forbid mutation. +Object.defineProperties(LazyLoadingRuleMap.prototype, { + clear: { configurable: true, value: void 0 }, + delete: { configurable: true, value: void 0 }, + [Symbol.iterator]: { + configurable: true, + writable: true, + value: LazyLoadingRuleMap.prototype.entries + } +}); + +module.exports = { LazyLoadingRuleMap }; diff --git a/eslint/lib/rules/utils/patterns/letters.js b/eslint/lib/rules/utils/patterns/letters.js new file mode 100644 index 0000000..9bb2de3 --- /dev/null +++ b/eslint/lib/rules/utils/patterns/letters.js @@ -0,0 +1,36 @@ +/** + * @fileoverview Pattern for detecting any letter (even letters outside of ASCII). + * NOTE: This file was generated using this script in JSCS based on the Unicode 7.0.0 standard: https://github.com/jscs-dev/node-jscs/blob/f5ed14427deb7e7aac84f3056a5aab2d9f3e563e/publish/helpers/generate-patterns.js + * Do not edit this file by hand-- please use https://github.com/mathiasbynens/regenerate to regenerate the regular expression exported from this file. + * @author Kevin Partington + * @license MIT License (from JSCS). See below. + */ + +/* + * The MIT License (MIT) + * + * Copyright 2013-2016 Dulin Marat and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +"use strict"; + +module.exports = /[A-Za-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B2\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA7AD\uA7B0\uA7B1\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB5F\uAB64\uAB65\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF30-\uDF40\uDF42-\uDF49\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF]|\uD801[\uDC00-\uDC9D\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE33\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48]|\uD804[\uDC03-\uDC37\uDC83-\uDCAF\uDCD0-\uDCE8\uDD03-\uDD26\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDDA\uDE00-\uDE11\uDE13-\uDE2B\uDEB0-\uDEDE\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF5D-\uDF61]|\uD805[\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDD80-\uDDAE\uDE00-\uDE2F\uDE44\uDE80-\uDEAA]|\uD806[\uDCA0-\uDCDF\uDCFF\uDEC0-\uDEF8]|\uD808[\uDC00-\uDF98]|[\uD80C\uD840-\uD868\uD86A-\uD86C][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDF00-\uDF44\uDF50\uDF93-\uDF9F]|\uD82C[\uDC00\uDC01]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB]|\uD83A[\uDC00-\uDCC4]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D]|\uD87E[\uDC00-\uDE1D]/u; diff --git a/eslint/lib/rules/utils/unicode/index.js b/eslint/lib/rules/utils/unicode/index.js new file mode 100644 index 0000000..02eea50 --- /dev/null +++ b/eslint/lib/rules/utils/unicode/index.js @@ -0,0 +1,11 @@ +/** + * @author Toru Nagashima + */ +"use strict"; + +module.exports = { + isCombiningCharacter: require("./is-combining-character"), + isEmojiModifier: require("./is-emoji-modifier"), + isRegionalIndicatorSymbol: require("./is-regional-indicator-symbol"), + isSurrogatePair: require("./is-surrogate-pair") +}; diff --git a/eslint/lib/rules/utils/unicode/is-combining-character.js b/eslint/lib/rules/utils/unicode/is-combining-character.js new file mode 100644 index 0000000..0498b99 --- /dev/null +++ b/eslint/lib/rules/utils/unicode/is-combining-character.js @@ -0,0 +1,13 @@ +/** + * @author Toru Nagashima + */ +"use strict"; + +/** + * Check whether a given character is a combining mark or not. + * @param {number} codePoint The character code to check. + * @returns {boolean} `true` if the character belongs to the category, any of `Mc`, `Me`, and `Mn`. + */ +module.exports = function isCombiningCharacter(codePoint) { + return /^[\p{Mc}\p{Me}\p{Mn}]$/u.test(String.fromCodePoint(codePoint)); +}; diff --git a/eslint/lib/rules/utils/unicode/is-emoji-modifier.js b/eslint/lib/rules/utils/unicode/is-emoji-modifier.js new file mode 100644 index 0000000..1bd5f55 --- /dev/null +++ b/eslint/lib/rules/utils/unicode/is-emoji-modifier.js @@ -0,0 +1,13 @@ +/** + * @author Toru Nagashima + */ +"use strict"; + +/** + * Check whether a given character is an emoji modifier. + * @param {number} code The character code to check. + * @returns {boolean} `true` if the character is an emoji modifier. + */ +module.exports = function isEmojiModifier(code) { + return code >= 0x1F3FB && code <= 0x1F3FF; +}; diff --git a/eslint/lib/rules/utils/unicode/is-regional-indicator-symbol.js b/eslint/lib/rules/utils/unicode/is-regional-indicator-symbol.js new file mode 100644 index 0000000..c48ed46 --- /dev/null +++ b/eslint/lib/rules/utils/unicode/is-regional-indicator-symbol.js @@ -0,0 +1,13 @@ +/** + * @author Toru Nagashima + */ +"use strict"; + +/** + * Check whether a given character is a regional indicator symbol. + * @param {number} code The character code to check. + * @returns {boolean} `true` if the character is a regional indicator symbol. + */ +module.exports = function isRegionalIndicatorSymbol(code) { + return code >= 0x1F1E6 && code <= 0x1F1FF; +}; diff --git a/eslint/lib/rules/utils/unicode/is-surrogate-pair.js b/eslint/lib/rules/utils/unicode/is-surrogate-pair.js new file mode 100644 index 0000000..b8e5c1c --- /dev/null +++ b/eslint/lib/rules/utils/unicode/is-surrogate-pair.js @@ -0,0 +1,14 @@ +/** + * @author Toru Nagashima + */ +"use strict"; + +/** + * Check whether given two characters are a surrogate pair. + * @param {number} lead The code of the lead character. + * @param {number} tail The code of the tail character. + * @returns {boolean} `true` if the character pair is a surrogate pair. + */ +module.exports = function isSurrogatePair(lead, tail) { + return lead >= 0xD800 && lead < 0xDC00 && tail >= 0xDC00 && tail < 0xE000; +}; diff --git a/eslint/lib/rules/valid-jsdoc.js b/eslint/lib/rules/valid-jsdoc.js new file mode 100644 index 0000000..9ec6938 --- /dev/null +++ b/eslint/lib/rules/valid-jsdoc.js @@ -0,0 +1,515 @@ +/** + * @fileoverview Validates JSDoc comments are syntactically correct + * @author Nicholas C. Zakas + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const doctrine = require("doctrine"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "enforce valid JSDoc comments", + category: "Possible Errors", + recommended: false, + url: "https://eslint.org/docs/rules/valid-jsdoc" + }, + + schema: [ + { + type: "object", + properties: { + prefer: { + type: "object", + additionalProperties: { + type: "string" + } + }, + preferType: { + type: "object", + additionalProperties: { + type: "string" + } + }, + requireReturn: { + type: "boolean", + default: true + }, + requireParamDescription: { + type: "boolean", + default: true + }, + requireReturnDescription: { + type: "boolean", + default: true + }, + matchDescription: { + type: "string" + }, + requireReturnType: { + type: "boolean", + default: true + }, + requireParamType: { + type: "boolean", + default: true + } + }, + additionalProperties: false + } + ], + + fixable: "code", + messages: { + unexpectedTag: "Unexpected @{{title}} tag; function has no return statement.", + expected: "Expected JSDoc for '{{name}}' but found '{{jsdocName}}'.", + use: "Use @{{name}} instead.", + useType: "Use '{{expectedTypeName}}' instead of '{{currentTypeName}}'.", + syntaxError: "JSDoc syntax error.", + missingBrace: "JSDoc type missing brace.", + missingParamDesc: "Missing JSDoc parameter description for '{{name}}'.", + missingParamType: "Missing JSDoc parameter type for '{{name}}'.", + missingReturnType: "Missing JSDoc return type.", + missingReturnDesc: "Missing JSDoc return description.", + missingReturn: "Missing JSDoc @{{returns}} for function.", + missingParam: "Missing JSDoc for parameter '{{name}}'.", + duplicateParam: "Duplicate JSDoc parameter '{{name}}'.", + unsatisfiedDesc: "JSDoc description does not satisfy the regex pattern." + }, + + deprecated: true, + replacedBy: [] + }, + + create(context) { + + const options = context.options[0] || {}, + prefer = options.prefer || {}, + sourceCode = context.getSourceCode(), + + // these both default to true, so you have to explicitly make them false + requireReturn = options.requireReturn !== false, + requireParamDescription = options.requireParamDescription !== false, + requireReturnDescription = options.requireReturnDescription !== false, + requireReturnType = options.requireReturnType !== false, + requireParamType = options.requireParamType !== false, + preferType = options.preferType || {}, + checkPreferType = Object.keys(preferType).length !== 0; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + // Using a stack to store if a function returns or not (handling nested functions) + const fns = []; + + /** + * Check if node type is a Class + * @param {ASTNode} node node to check. + * @returns {boolean} True is its a class + * @private + */ + function isTypeClass(node) { + return node.type === "ClassExpression" || node.type === "ClassDeclaration"; + } + + /** + * When parsing a new function, store it in our function stack. + * @param {ASTNode} node A function node to check. + * @returns {void} + * @private + */ + function startFunction(node) { + fns.push({ + returnPresent: (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") || + isTypeClass(node) || node.async + }); + } + + /** + * Indicate that return has been found in the current function. + * @param {ASTNode} node The return node. + * @returns {void} + * @private + */ + function addReturn(node) { + const functionState = fns[fns.length - 1]; + + if (functionState && node.argument !== null) { + functionState.returnPresent = true; + } + } + + /** + * Check if return tag type is void or undefined + * @param {Object} tag JSDoc tag + * @returns {boolean} True if its of type void or undefined + * @private + */ + function isValidReturnType(tag) { + return tag.type === null || tag.type.name === "void" || tag.type.type === "UndefinedLiteral"; + } + + /** + * Check if type should be validated based on some exceptions + * @param {Object} type JSDoc tag + * @returns {boolean} True if it can be validated + * @private + */ + function canTypeBeValidated(type) { + return type !== "UndefinedLiteral" && // {undefined} as there is no name property available. + type !== "NullLiteral" && // {null} + type !== "NullableLiteral" && // {?} + type !== "FunctionType" && // {function(a)} + type !== "AllLiteral"; // {*} + } + + /** + * Extract the current and expected type based on the input type object + * @param {Object} type JSDoc tag + * @returns {{currentType: Doctrine.Type, expectedTypeName: string}} The current type annotation and + * the expected name of the annotation + * @private + */ + function getCurrentExpectedTypes(type) { + let currentType; + + if (type.name) { + currentType = type; + } else if (type.expression) { + currentType = type.expression; + } + + return { + currentType, + expectedTypeName: currentType && preferType[currentType.name] + }; + } + + /** + * Gets the location of a JSDoc node in a file + * @param {Token} jsdocComment The comment that this node is parsed from + * @param {{range: number[]}} parsedJsdocNode A tag or other node which was parsed from this comment + * @returns {{start: SourceLocation, end: SourceLocation}} The 0-based source location for the tag + */ + function getAbsoluteRange(jsdocComment, parsedJsdocNode) { + return { + start: sourceCode.getLocFromIndex(jsdocComment.range[0] + 2 + parsedJsdocNode.range[0]), + end: sourceCode.getLocFromIndex(jsdocComment.range[0] + 2 + parsedJsdocNode.range[1]) + }; + } + + /** + * Validate type for a given JSDoc node + * @param {Object} jsdocNode JSDoc node + * @param {Object} type JSDoc tag + * @returns {void} + * @private + */ + function validateType(jsdocNode, type) { + if (!type || !canTypeBeValidated(type.type)) { + return; + } + + const typesToCheck = []; + let elements = []; + + switch (type.type) { + case "TypeApplication": // {Array.} + elements = type.applications[0].type === "UnionType" ? type.applications[0].elements : type.applications; + typesToCheck.push(getCurrentExpectedTypes(type)); + break; + case "RecordType": // {{20:String}} + elements = type.fields; + break; + case "UnionType": // {String|number|Test} + case "ArrayType": // {[String, number, Test]} + elements = type.elements; + break; + case "FieldType": // Array.<{count: number, votes: number}> + if (type.value) { + typesToCheck.push(getCurrentExpectedTypes(type.value)); + } + break; + default: + typesToCheck.push(getCurrentExpectedTypes(type)); + } + + elements.forEach(validateType.bind(null, jsdocNode)); + + typesToCheck.forEach(typeToCheck => { + if (typeToCheck.expectedTypeName && + typeToCheck.expectedTypeName !== typeToCheck.currentType.name) { + context.report({ + node: jsdocNode, + messageId: "useType", + loc: getAbsoluteRange(jsdocNode, typeToCheck.currentType), + data: { + currentTypeName: typeToCheck.currentType.name, + expectedTypeName: typeToCheck.expectedTypeName + }, + fix(fixer) { + return fixer.replaceTextRange( + typeToCheck.currentType.range.map(indexInComment => jsdocNode.range[0] + 2 + indexInComment), + typeToCheck.expectedTypeName + ); + } + }); + } + }); + } + + /** + * Validate the JSDoc node and output warnings if anything is wrong. + * @param {ASTNode} node The AST node to check. + * @returns {void} + * @private + */ + function checkJSDoc(node) { + const jsdocNode = sourceCode.getJSDocComment(node), + functionData = fns.pop(), + paramTagsByName = Object.create(null), + paramTags = []; + let hasReturns = false, + returnsTag, + hasConstructor = false, + isInterface = false, + isOverride = false, + isAbstract = false; + + // make sure only to validate JSDoc comments + if (jsdocNode) { + let jsdoc; + + try { + jsdoc = doctrine.parse(jsdocNode.value, { + strict: true, + unwrap: true, + sloppy: true, + range: true + }); + } catch (ex) { + + if (/braces/iu.test(ex.message)) { + context.report({ node: jsdocNode, messageId: "missingBrace" }); + } else { + context.report({ node: jsdocNode, messageId: "syntaxError" }); + } + + return; + } + + jsdoc.tags.forEach(tag => { + + switch (tag.title.toLowerCase()) { + + case "param": + case "arg": + case "argument": + paramTags.push(tag); + break; + + case "return": + case "returns": + hasReturns = true; + returnsTag = tag; + break; + + case "constructor": + case "class": + hasConstructor = true; + break; + + case "override": + case "inheritdoc": + isOverride = true; + break; + + case "abstract": + case "virtual": + isAbstract = true; + break; + + case "interface": + isInterface = true; + break; + + // no default + } + + // check tag preferences + if (Object.prototype.hasOwnProperty.call(prefer, tag.title) && tag.title !== prefer[tag.title]) { + const entireTagRange = getAbsoluteRange(jsdocNode, tag); + + context.report({ + node: jsdocNode, + messageId: "use", + loc: { + start: entireTagRange.start, + end: { + line: entireTagRange.start.line, + column: entireTagRange.start.column + `@${tag.title}`.length + } + }, + data: { name: prefer[tag.title] }, + fix(fixer) { + return fixer.replaceTextRange( + [ + jsdocNode.range[0] + tag.range[0] + 3, + jsdocNode.range[0] + tag.range[0] + tag.title.length + 3 + ], + prefer[tag.title] + ); + } + }); + } + + // validate the types + if (checkPreferType && tag.type) { + validateType(jsdocNode, tag.type); + } + }); + + paramTags.forEach(param => { + if (requireParamType && !param.type) { + context.report({ + node: jsdocNode, + messageId: "missingParamType", + loc: getAbsoluteRange(jsdocNode, param), + data: { name: param.name } + }); + } + if (!param.description && requireParamDescription) { + context.report({ + node: jsdocNode, + messageId: "missingParamDesc", + loc: getAbsoluteRange(jsdocNode, param), + data: { name: param.name } + }); + } + if (paramTagsByName[param.name]) { + context.report({ + node: jsdocNode, + messageId: "duplicateParam", + loc: getAbsoluteRange(jsdocNode, param), + data: { name: param.name } + }); + } else if (param.name.indexOf(".") === -1) { + paramTagsByName[param.name] = param; + } + }); + + if (hasReturns) { + if (!requireReturn && !functionData.returnPresent && (returnsTag.type === null || !isValidReturnType(returnsTag)) && !isAbstract) { + context.report({ + node: jsdocNode, + messageId: "unexpectedTag", + loc: getAbsoluteRange(jsdocNode, returnsTag), + data: { + title: returnsTag.title + } + }); + } else { + if (requireReturnType && !returnsTag.type) { + context.report({ node: jsdocNode, messageId: "missingReturnType" }); + } + + if (!isValidReturnType(returnsTag) && !returnsTag.description && requireReturnDescription) { + context.report({ node: jsdocNode, messageId: "missingReturnDesc" }); + } + } + } + + // check for functions missing @returns + if (!isOverride && !hasReturns && !hasConstructor && !isInterface && + node.parent.kind !== "get" && node.parent.kind !== "constructor" && + node.parent.kind !== "set" && !isTypeClass(node)) { + if (requireReturn || (functionData.returnPresent && !node.async)) { + context.report({ + node: jsdocNode, + messageId: "missingReturn", + data: { + returns: prefer.returns || "returns" + } + }); + } + } + + // check the parameters + const jsdocParamNames = Object.keys(paramTagsByName); + + if (node.params) { + node.params.forEach((param, paramsIndex) => { + const bindingParam = param.type === "AssignmentPattern" + ? param.left + : param; + + // TODO(nzakas): Figure out logical things to do with destructured, default, rest params + if (bindingParam.type === "Identifier") { + const name = bindingParam.name; + + if (jsdocParamNames[paramsIndex] && (name !== jsdocParamNames[paramsIndex])) { + context.report({ + node: jsdocNode, + messageId: "expected", + loc: getAbsoluteRange(jsdocNode, paramTagsByName[jsdocParamNames[paramsIndex]]), + data: { + name, + jsdocName: jsdocParamNames[paramsIndex] + } + }); + } else if (!paramTagsByName[name] && !isOverride) { + context.report({ + node: jsdocNode, + messageId: "missingParam", + data: { + name + } + }); + } + } + }); + } + + if (options.matchDescription) { + const regex = new RegExp(options.matchDescription, "u"); + + if (!regex.test(jsdoc.description)) { + context.report({ node: jsdocNode, messageId: "unsatisfiedDesc" }); + } + } + + } + + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + ArrowFunctionExpression: startFunction, + FunctionExpression: startFunction, + FunctionDeclaration: startFunction, + ClassExpression: startFunction, + ClassDeclaration: startFunction, + "ArrowFunctionExpression:exit": checkJSDoc, + "FunctionExpression:exit": checkJSDoc, + "FunctionDeclaration:exit": checkJSDoc, + "ClassExpression:exit": checkJSDoc, + "ClassDeclaration:exit": checkJSDoc, + ReturnStatement: addReturn + }; + + } +}; diff --git a/eslint/lib/rules/valid-typeof.js b/eslint/lib/rules/valid-typeof.js new file mode 100644 index 0000000..a0f20f7 --- /dev/null +++ b/eslint/lib/rules/valid-typeof.js @@ -0,0 +1,85 @@ +/** + * @fileoverview Ensures that the results of typeof are compared against a valid string + * @author Ian Christian Myers + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "enforce comparing `typeof` expressions against valid strings", + category: "Possible Errors", + recommended: true, + url: "https://eslint.org/docs/rules/valid-typeof" + }, + + schema: [ + { + type: "object", + properties: { + requireStringLiterals: { + type: "boolean", + default: false + } + }, + additionalProperties: false + } + ], + messages: { + invalidValue: "Invalid typeof comparison value.", + notString: "Typeof comparisons should be to string literals." + } + }, + + create(context) { + + const VALID_TYPES = ["symbol", "undefined", "object", "boolean", "number", "string", "function", "bigint"], + OPERATORS = ["==", "===", "!=", "!=="]; + + const requireStringLiterals = context.options[0] && context.options[0].requireStringLiterals; + + /** + * Determines whether a node is a typeof expression. + * @param {ASTNode} node The node + * @returns {boolean} `true` if the node is a typeof expression + */ + function isTypeofExpression(node) { + return node.type === "UnaryExpression" && node.operator === "typeof"; + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + + UnaryExpression(node) { + if (isTypeofExpression(node)) { + const parent = context.getAncestors().pop(); + + if (parent.type === "BinaryExpression" && OPERATORS.indexOf(parent.operator) !== -1) { + const sibling = parent.left === node ? parent.right : parent.left; + + if (sibling.type === "Literal" || sibling.type === "TemplateLiteral" && !sibling.expressions.length) { + const value = sibling.type === "Literal" ? sibling.value : sibling.quasis[0].value.cooked; + + if (VALID_TYPES.indexOf(value) === -1) { + context.report({ node: sibling, messageId: "invalidValue" }); + } + } else if (requireStringLiterals && !isTypeofExpression(sibling)) { + context.report({ node: sibling, messageId: "notString" }); + } + } + } + } + + }; + + } +}; diff --git a/eslint/lib/rules/vars-on-top.js b/eslint/lib/rules/vars-on-top.js new file mode 100644 index 0000000..28ddae4 --- /dev/null +++ b/eslint/lib/rules/vars-on-top.js @@ -0,0 +1,144 @@ +/** + * @fileoverview Rule to enforce var declarations are only at the top of a function. + * @author Danny Fritz + * @author Gyandeep Singh + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "require `var` declarations be placed at the top of their containing scope", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/vars-on-top" + }, + + schema: [], + messages: { + top: "All 'var' declarations must be at the top of the function scope." + } + }, + + create(context) { + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + // eslint-disable-next-line jsdoc/require-description + /** + * @param {ASTNode} node any node + * @returns {boolean} whether the given node structurally represents a directive + */ + function looksLikeDirective(node) { + return node.type === "ExpressionStatement" && + node.expression.type === "Literal" && typeof node.expression.value === "string"; + } + + /** + * Check to see if its a ES6 import declaration + * @param {ASTNode} node any node + * @returns {boolean} whether the given node represents a import declaration + */ + function looksLikeImport(node) { + return node.type === "ImportDeclaration" || node.type === "ImportSpecifier" || + node.type === "ImportDefaultSpecifier" || node.type === "ImportNamespaceSpecifier"; + } + + /** + * Checks whether a given node is a variable declaration or not. + * @param {ASTNode} node any node + * @returns {boolean} `true` if the node is a variable declaration. + */ + function isVariableDeclaration(node) { + return ( + node.type === "VariableDeclaration" || + ( + node.type === "ExportNamedDeclaration" && + node.declaration && + node.declaration.type === "VariableDeclaration" + ) + ); + } + + /** + * Checks whether this variable is on top of the block body + * @param {ASTNode} node The node to check + * @param {ASTNode[]} statements collection of ASTNodes for the parent node block + * @returns {boolean} True if var is on top otherwise false + */ + function isVarOnTop(node, statements) { + const l = statements.length; + let i = 0; + + // skip over directives + for (; i < l; ++i) { + if (!looksLikeDirective(statements[i]) && !looksLikeImport(statements[i])) { + break; + } + } + + for (; i < l; ++i) { + if (!isVariableDeclaration(statements[i])) { + return false; + } + if (statements[i] === node) { + return true; + } + } + + return false; + } + + /** + * Checks whether variable is on top at the global level + * @param {ASTNode} node The node to check + * @param {ASTNode} parent Parent of the node + * @returns {void} + */ + function globalVarCheck(node, parent) { + if (!isVarOnTop(node, parent.body)) { + context.report({ node, messageId: "top" }); + } + } + + /** + * Checks whether variable is on top at functional block scope level + * @param {ASTNode} node The node to check + * @param {ASTNode} parent Parent of the node + * @param {ASTNode} grandParent Parent of the node's parent + * @returns {void} + */ + function blockScopeVarCheck(node, parent, grandParent) { + if (!(/Function/u.test(grandParent.type) && + parent.type === "BlockStatement" && + isVarOnTop(node, parent.body))) { + context.report({ node, messageId: "top" }); + } + } + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + "VariableDeclaration[kind='var']"(node) { + if (node.parent.type === "ExportNamedDeclaration") { + globalVarCheck(node.parent, node.parent.parent); + } else if (node.parent.type === "Program") { + globalVarCheck(node, node.parent); + } else { + blockScopeVarCheck(node, node.parent, node.parent.parent); + } + } + }; + + } +}; diff --git a/eslint/lib/rules/wrap-iife.js b/eslint/lib/rules/wrap-iife.js new file mode 100644 index 0000000..896aed6 --- /dev/null +++ b/eslint/lib/rules/wrap-iife.js @@ -0,0 +1,197 @@ +/** + * @fileoverview Rule to flag when IIFE is not wrapped in parens + * @author Ilya Volodin + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); +const eslintUtils = require("eslint-utils"); + +//---------------------------------------------------------------------- +// Helpers +//---------------------------------------------------------------------- + +/** + * Check if the given node is callee of a `NewExpression` node + * @param {ASTNode} node node to check + * @returns {boolean} True if the node is callee of a `NewExpression` node + * @private + */ +function isCalleeOfNewExpression(node) { + return node.parent.type === "NewExpression" && node.parent.callee === node; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "require parentheses around immediate `function` invocations", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/wrap-iife" + }, + + schema: [ + { + enum: ["outside", "inside", "any"] + }, + { + type: "object", + properties: { + functionPrototypeMethods: { + type: "boolean", + default: false + } + }, + additionalProperties: false + } + ], + + fixable: "code", + messages: { + wrapInvocation: "Wrap an immediate function invocation in parentheses.", + wrapExpression: "Wrap only the function expression in parens.", + moveInvocation: "Move the invocation into the parens that contain the function." + } + }, + + create(context) { + + const style = context.options[0] || "outside"; + const includeFunctionPrototypeMethods = context.options[1] && context.options[1].functionPrototypeMethods; + + const sourceCode = context.getSourceCode(); + + /** + * Check if the node is wrapped in any (). All parens count: grouping parens and parens for constructs such as if() + * @param {ASTNode} node node to evaluate + * @returns {boolean} True if it is wrapped in any parens + * @private + */ + function isWrappedInAnyParens(node) { + return astUtils.isParenthesised(sourceCode, node); + } + + /** + * Check if the node is wrapped in grouping (). Parens for constructs such as if() don't count + * @param {ASTNode} node node to evaluate + * @returns {boolean} True if it is wrapped in grouping parens + * @private + */ + function isWrappedInGroupingParens(node) { + return eslintUtils.isParenthesized(1, node, sourceCode); + } + + /** + * Get the function node from an IIFE + * @param {ASTNode} node node to evaluate + * @returns {ASTNode} node that is the function expression of the given IIFE, or null if none exist + */ + function getFunctionNodeFromIIFE(node) { + const callee = node.callee; + + if (callee.type === "FunctionExpression") { + return callee; + } + + if (includeFunctionPrototypeMethods && + callee.type === "MemberExpression" && + callee.object.type === "FunctionExpression" && + (astUtils.getStaticPropertyName(callee) === "call" || astUtils.getStaticPropertyName(callee) === "apply") + ) { + return callee.object; + } + + return null; + } + + + return { + CallExpression(node) { + const innerNode = getFunctionNodeFromIIFE(node); + + if (!innerNode) { + return; + } + + const isCallExpressionWrapped = isWrappedInAnyParens(node), + isFunctionExpressionWrapped = isWrappedInAnyParens(innerNode); + + if (!isCallExpressionWrapped && !isFunctionExpressionWrapped) { + context.report({ + node, + messageId: "wrapInvocation", + fix(fixer) { + const nodeToSurround = style === "inside" ? innerNode : node; + + return fixer.replaceText(nodeToSurround, `(${sourceCode.getText(nodeToSurround)})`); + } + }); + } else if (style === "inside" && !isFunctionExpressionWrapped) { + context.report({ + node, + messageId: "wrapExpression", + fix(fixer) { + + // The outer call expression will always be wrapped at this point. + + if (isWrappedInGroupingParens(node) && !isCalleeOfNewExpression(node)) { + + /* + * Parenthesize the function expression and remove unnecessary grouping parens around the call expression. + * Replace the range between the end of the function expression and the end of the call expression. + * for example, in `(function(foo) {}(bar))`, the range `(bar))` should get replaced with `)(bar)`. + */ + + const parenAfter = sourceCode.getTokenAfter(node); + + return fixer.replaceTextRange( + [innerNode.range[1], parenAfter.range[1]], + `)${sourceCode.getText().slice(innerNode.range[1], parenAfter.range[0])}` + ); + } + + /* + * Call expression is wrapped in mandatory parens such as if(), or in necessary grouping parens. + * These parens cannot be removed, so just parenthesize the function expression. + */ + + return fixer.replaceText(innerNode, `(${sourceCode.getText(innerNode)})`); + } + }); + } else if (style === "outside" && !isCallExpressionWrapped) { + context.report({ + node, + messageId: "moveInvocation", + fix(fixer) { + + /* + * The inner function expression will always be wrapped at this point. + * It's only necessary to replace the range between the end of the function expression + * and the call expression. For example, in `(function(foo) {})(bar)`, the range `)(bar)` + * should get replaced with `(bar))`. + */ + const parenAfter = sourceCode.getTokenAfter(innerNode); + + return fixer.replaceTextRange( + [parenAfter.range[0], node.range[1]], + `${sourceCode.getText().slice(parenAfter.range[1], node.range[1])})` + ); + } + }); + } + } + }; + + } +}; diff --git a/eslint/lib/rules/wrap-regex.js b/eslint/lib/rules/wrap-regex.js new file mode 100644 index 0000000..4ecbcec --- /dev/null +++ b/eslint/lib/rules/wrap-regex.js @@ -0,0 +1,59 @@ +/** + * @fileoverview Rule to flag when regex literals are not wrapped in parens + * @author Matt DuVall + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "require parenthesis around regex literals", + category: "Stylistic Issues", + recommended: false, + url: "https://eslint.org/docs/rules/wrap-regex" + }, + + schema: [], + fixable: "code", + + messages: { + requireParens: "Wrap the regexp literal in parens to disambiguate the slash." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + return { + + Literal(node) { + const token = sourceCode.getFirstToken(node), + nodeType = token.type; + + if (nodeType === "RegularExpression") { + const beforeToken = sourceCode.getTokenBefore(node); + const afterToken = sourceCode.getTokenAfter(node); + const ancestors = context.getAncestors(); + const grandparent = ancestors[ancestors.length - 1]; + + if (grandparent.type === "MemberExpression" && grandparent.object === node && + !(beforeToken && beforeToken.value === "(" && afterToken && afterToken.value === ")")) { + context.report({ + node, + messageId: "requireParens", + fix: fixer => fixer.replaceText(node, `(${sourceCode.getText(node)})`) + }); + } + } + } + }; + + } +}; diff --git a/eslint/lib/rules/yield-star-spacing.js b/eslint/lib/rules/yield-star-spacing.js new file mode 100644 index 0000000..20b8e9e --- /dev/null +++ b/eslint/lib/rules/yield-star-spacing.js @@ -0,0 +1,127 @@ +/** + * @fileoverview Rule to check the spacing around the * in yield* expressions. + * @author Bryan Smith + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "layout", + + docs: { + description: "require or disallow spacing around the `*` in `yield*` expressions", + category: "ECMAScript 6", + recommended: false, + url: "https://eslint.org/docs/rules/yield-star-spacing" + }, + + fixable: "whitespace", + + schema: [ + { + oneOf: [ + { + enum: ["before", "after", "both", "neither"] + }, + { + type: "object", + properties: { + before: { type: "boolean" }, + after: { type: "boolean" } + }, + additionalProperties: false + } + ] + } + ], + messages: { + missingBefore: "Missing space before *.", + missingAfter: "Missing space after *.", + unexpectedBefore: "Unexpected space before *.", + unexpectedAfter: "Unexpected space after *." + } + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + const mode = (function(option) { + if (!option || typeof option === "string") { + return { + before: { before: true, after: false }, + after: { before: false, after: true }, + both: { before: true, after: true }, + neither: { before: false, after: false } + }[option || "after"]; + } + return option; + }(context.options[0])); + + /** + * Checks the spacing between two tokens before or after the star token. + * @param {string} side Either "before" or "after". + * @param {Token} leftToken `function` keyword token if side is "before", or + * star token if side is "after". + * @param {Token} rightToken Star token if side is "before", or identifier + * token if side is "after". + * @returns {void} + */ + function checkSpacing(side, leftToken, rightToken) { + if (sourceCode.isSpaceBetweenTokens(leftToken, rightToken) !== mode[side]) { + const after = leftToken.value === "*"; + const spaceRequired = mode[side]; + const node = after ? leftToken : rightToken; + let messageId = ""; + + if (spaceRequired) { + messageId = side === "before" ? "missingBefore" : "missingAfter"; + } else { + messageId = side === "before" ? "unexpectedBefore" : "unexpectedAfter"; + } + + context.report({ + node, + messageId, + fix(fixer) { + if (spaceRequired) { + if (after) { + return fixer.insertTextAfter(node, " "); + } + return fixer.insertTextBefore(node, " "); + } + return fixer.removeRange([leftToken.range[1], rightToken.range[0]]); + } + }); + } + } + + /** + * Enforces the spacing around the star if node is a yield* expression. + * @param {ASTNode} node A yield expression node. + * @returns {void} + */ + function checkExpression(node) { + if (!node.delegate) { + return; + } + + const tokens = sourceCode.getFirstTokens(node, 3); + const yieldToken = tokens[0]; + const starToken = tokens[1]; + const nextToken = tokens[2]; + + checkSpacing("before", yieldToken, starToken); + checkSpacing("after", starToken, nextToken); + } + + return { + YieldExpression: checkExpression + }; + + } +}; diff --git a/eslint/lib/rules/yoda.js b/eslint/lib/rules/yoda.js new file mode 100644 index 0000000..c4ff3f8 --- /dev/null +++ b/eslint/lib/rules/yoda.js @@ -0,0 +1,355 @@ +/** + * @fileoverview Rule to require or disallow yoda comparisons + * @author Nicholas C. Zakas + */ +"use strict"; + +//-------------------------------------------------------------------------- +// Requirements +//-------------------------------------------------------------------------- + +const astUtils = require("./utils/ast-utils"); + +//-------------------------------------------------------------------------- +// Helpers +//-------------------------------------------------------------------------- + +/** + * Determines whether an operator is a comparison operator. + * @param {string} operator The operator to check. + * @returns {boolean} Whether or not it is a comparison operator. + */ +function isComparisonOperator(operator) { + return (/^(==|===|!=|!==|<|>|<=|>=)$/u).test(operator); +} + +/** + * Determines whether an operator is an equality operator. + * @param {string} operator The operator to check. + * @returns {boolean} Whether or not it is an equality operator. + */ +function isEqualityOperator(operator) { + return (/^(==|===)$/u).test(operator); +} + +/** + * Determines whether an operator is one used in a range test. + * Allowed operators are `<` and `<=`. + * @param {string} operator The operator to check. + * @returns {boolean} Whether the operator is used in range tests. + */ +function isRangeTestOperator(operator) { + return ["<", "<="].indexOf(operator) >= 0; +} + +/** + * Determines whether a non-Literal node is a negative number that should be + * treated as if it were a single Literal node. + * @param {ASTNode} node Node to test. + * @returns {boolean} True if the node is a negative number that looks like a + * real literal and should be treated as such. + */ +function isNegativeNumericLiteral(node) { + return (node.type === "UnaryExpression" && + node.operator === "-" && + node.prefix && + astUtils.isNumericLiteral(node.argument)); +} + +/** + * Determines whether a node is a Template Literal which can be determined statically. + * @param {ASTNode} node Node to test + * @returns {boolean} True if the node is a Template Literal without expression. + */ +function isStaticTemplateLiteral(node) { + return node.type === "TemplateLiteral" && node.expressions.length === 0; +} + +/** + * Determines whether a non-Literal node should be treated as a single Literal node. + * @param {ASTNode} node Node to test + * @returns {boolean} True if the node should be treated as a single Literal node. + */ +function looksLikeLiteral(node) { + return isNegativeNumericLiteral(node) || + isStaticTemplateLiteral(node); +} + +/** + * Attempts to derive a Literal node from nodes that are treated like literals. + * @param {ASTNode} node Node to normalize. + * @param {number} [defaultValue] The default value to be returned if the node + * is not a Literal. + * @returns {ASTNode} One of the following options. + * 1. The original node if the node is already a Literal + * 2. A normalized Literal node with the negative number as the value if the + * node represents a negative number literal. + * 3. A normalized Literal node with the string as the value if the node is + * a Template Literal without expression. + * 4. The Literal node which has the `defaultValue` argument if it exists. + * 5. Otherwise `null`. + */ +function getNormalizedLiteral(node, defaultValue) { + if (node.type === "Literal") { + return node; + } + + if (isNegativeNumericLiteral(node)) { + return { + type: "Literal", + value: -node.argument.value, + raw: `-${node.argument.value}` + }; + } + + if (isStaticTemplateLiteral(node)) { + return { + type: "Literal", + value: node.quasis[0].value.cooked, + raw: node.quasis[0].value.raw + }; + } + + if (defaultValue) { + return { + type: "Literal", + value: defaultValue, + raw: String(defaultValue) + }; + } + + return null; +} + +/** + * Checks whether two expressions reference the same value. For example: + * a = a + * a.b = a.b + * a[0] = a[0] + * a['b'] = a['b'] + * @param {ASTNode} a Left side of the comparison. + * @param {ASTNode} b Right side of the comparison. + * @returns {boolean} True if both sides match and reference the same value. + */ +function same(a, b) { + if (a.type !== b.type) { + return false; + } + + switch (a.type) { + case "Identifier": + return a.name === b.name; + + case "Literal": + return a.value === b.value; + + case "MemberExpression": { + const nameA = astUtils.getStaticPropertyName(a); + + // x.y = x["y"] + if (nameA !== null) { + return ( + same(a.object, b.object) && + nameA === astUtils.getStaticPropertyName(b) + ); + } + + /* + * x[0] = x[0] + * x[y] = x[y] + * x.y = x.y + */ + return ( + a.computed === b.computed && + same(a.object, b.object) && + same(a.property, b.property) + ); + } + + case "ThisExpression": + return true; + + default: + return false; + } +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "require or disallow \"Yoda\" conditions", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/yoda" + }, + + schema: [ + { + enum: ["always", "never"] + }, + { + type: "object", + properties: { + exceptRange: { + type: "boolean", + default: false + }, + onlyEquality: { + type: "boolean", + default: false + } + }, + additionalProperties: false + } + ], + + fixable: "code", + messages: { + expected: "Expected literal to be on the {{expectedSide}} side of {{operator}}." + } + }, + + create(context) { + + // Default to "never" (!always) if no option + const always = (context.options[0] === "always"); + const exceptRange = (context.options[1] && context.options[1].exceptRange); + const onlyEquality = (context.options[1] && context.options[1].onlyEquality); + + const sourceCode = context.getSourceCode(); + + /** + * Determines whether node represents a range test. + * A range test is a "between" test like `(0 <= x && x < 1)` or an "outside" + * test like `(x < 0 || 1 <= x)`. It must be wrapped in parentheses, and + * both operators must be `<` or `<=`. Finally, the literal on the left side + * must be less than or equal to the literal on the right side so that the + * test makes any sense. + * @param {ASTNode} node LogicalExpression node to test. + * @returns {boolean} Whether node is a range test. + */ + function isRangeTest(node) { + const left = node.left, + right = node.right; + + /** + * Determines whether node is of the form `0 <= x && x < 1`. + * @returns {boolean} Whether node is a "between" range test. + */ + function isBetweenTest() { + let leftLiteral, rightLiteral; + + return (node.operator === "&&" && + (leftLiteral = getNormalizedLiteral(left.left)) && + (rightLiteral = getNormalizedLiteral(right.right, Number.POSITIVE_INFINITY)) && + leftLiteral.value <= rightLiteral.value && + same(left.right, right.left)); + } + + /** + * Determines whether node is of the form `x < 0 || 1 <= x`. + * @returns {boolean} Whether node is an "outside" range test. + */ + function isOutsideTest() { + let leftLiteral, rightLiteral; + + return (node.operator === "||" && + (leftLiteral = getNormalizedLiteral(left.right, Number.NEGATIVE_INFINITY)) && + (rightLiteral = getNormalizedLiteral(right.left)) && + leftLiteral.value <= rightLiteral.value && + same(left.left, right.right)); + } + + /** + * Determines whether node is wrapped in parentheses. + * @returns {boolean} Whether node is preceded immediately by an open + * paren token and followed immediately by a close + * paren token. + */ + function isParenWrapped() { + return astUtils.isParenthesised(sourceCode, node); + } + + return (node.type === "LogicalExpression" && + left.type === "BinaryExpression" && + right.type === "BinaryExpression" && + isRangeTestOperator(left.operator) && + isRangeTestOperator(right.operator) && + (isBetweenTest() || isOutsideTest()) && + isParenWrapped()); + } + + const OPERATOR_FLIP_MAP = { + "===": "===", + "!==": "!==", + "==": "==", + "!=": "!=", + "<": ">", + ">": "<", + "<=": ">=", + ">=": "<=" + }; + + /** + * Returns a string representation of a BinaryExpression node with its sides/operator flipped around. + * @param {ASTNode} node The BinaryExpression node + * @returns {string} A string representation of the node with the sides and operator flipped + */ + function getFlippedString(node) { + const tokenBefore = sourceCode.getTokenBefore(node); + const operatorToken = sourceCode.getFirstTokenBetween(node.left, node.right, token => token.value === node.operator); + const textBeforeOperator = sourceCode.getText().slice(sourceCode.getTokenBefore(operatorToken).range[1], operatorToken.range[0]); + const textAfterOperator = sourceCode.getText().slice(operatorToken.range[1], sourceCode.getTokenAfter(operatorToken).range[0]); + const leftText = sourceCode.getText().slice(node.range[0], sourceCode.getTokenBefore(operatorToken).range[1]); + const firstRightToken = sourceCode.getTokenAfter(operatorToken); + const rightText = sourceCode.getText().slice(firstRightToken.range[0], node.range[1]); + + let prefix = ""; + + if (tokenBefore && tokenBefore.range[1] === node.range[0] && + !astUtils.canTokensBeAdjacent(tokenBefore, firstRightToken)) { + prefix = " "; + } + + return prefix + rightText + textBeforeOperator + OPERATOR_FLIP_MAP[operatorToken.value] + textAfterOperator + leftText; + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + BinaryExpression(node) { + const expectedLiteral = always ? node.left : node.right; + const expectedNonLiteral = always ? node.right : node.left; + + // If `expectedLiteral` is not a literal, and `expectedNonLiteral` is a literal, raise an error. + if ( + (expectedNonLiteral.type === "Literal" || looksLikeLiteral(expectedNonLiteral)) && + !(expectedLiteral.type === "Literal" || looksLikeLiteral(expectedLiteral)) && + !(!isEqualityOperator(node.operator) && onlyEquality) && + isComparisonOperator(node.operator) && + !(exceptRange && isRangeTest(context.getAncestors().pop())) + ) { + context.report({ + node, + messageId: "expected", + data: { + operator: node.operator, + expectedSide: always ? "left" : "right" + }, + fix: fixer => fixer.replaceText(node, getFlippedString(node)) + }); + } + + } + }; + + } +}; diff --git a/eslint/lib/shared/ajv.js b/eslint/lib/shared/ajv.js new file mode 100644 index 0000000..3fb0fbd --- /dev/null +++ b/eslint/lib/shared/ajv.js @@ -0,0 +1,34 @@ +/** + * @fileoverview The instance of Ajv validator. + * @author Evgeny Poberezkin + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const Ajv = require("ajv"), + metaSchema = require("ajv/lib/refs/json-schema-draft-04.json"); + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = (additionalOptions = {}) => { + const ajv = new Ajv({ + meta: false, + useDefaults: true, + validateSchema: false, + missingRefs: "ignore", + verbose: true, + schemaId: "auto", + ...additionalOptions + }); + + ajv.addMetaSchema(metaSchema); + // eslint-disable-next-line no-underscore-dangle + ajv._opts.defaultMeta = metaSchema.id; + + return ajv; +}; diff --git a/eslint/lib/shared/ast-utils.js b/eslint/lib/shared/ast-utils.js new file mode 100644 index 0000000..4ebd49c --- /dev/null +++ b/eslint/lib/shared/ast-utils.js @@ -0,0 +1,29 @@ +/** + * @fileoverview Common utils for AST. + * + * This file contains only shared items for core and rules. + * If you make a utility for rules, please see `../rules/utils/ast-utils.js`. + * + * @author Toru Nagashima + */ +"use strict"; + +const breakableTypePattern = /^(?:(?:Do)?While|For(?:In|Of)?|Switch)Statement$/u; +const lineBreakPattern = /\r\n|[\r\n\u2028\u2029]/u; +const shebangPattern = /^#!([^\r\n]+)/u; + +/** + * Creates a version of the `lineBreakPattern` regex with the global flag. + * Global regexes are mutable, so this needs to be a function instead of a constant. + * @returns {RegExp} A global regular expression that matches line terminators + */ +function createGlobalLinebreakMatcher() { + return new RegExp(lineBreakPattern.source, "gu"); +} + +module.exports = { + breakableTypePattern, + lineBreakPattern, + createGlobalLinebreakMatcher, + shebangPattern +}; diff --git a/eslint/lib/shared/config-ops.js b/eslint/lib/shared/config-ops.js new file mode 100644 index 0000000..3b4d569 --- /dev/null +++ b/eslint/lib/shared/config-ops.js @@ -0,0 +1,130 @@ +/** + * @fileoverview Config file operations. This file must be usable in the browser, + * so no Node-specific code can be here. + * @author Nicholas C. Zakas + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Private +//------------------------------------------------------------------------------ + +const RULE_SEVERITY_STRINGS = ["off", "warn", "error"], + RULE_SEVERITY = RULE_SEVERITY_STRINGS.reduce((map, value, index) => { + map[value] = index; + return map; + }, {}), + VALID_SEVERITIES = [0, 1, 2, "off", "warn", "error"]; + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = { + + /** + * Normalizes the severity value of a rule's configuration to a number + * @param {(number|string|[number, ...*]|[string, ...*])} ruleConfig A rule's configuration value, generally + * received from the user. A valid config value is either 0, 1, 2, the string "off" (treated the same as 0), + * the string "warn" (treated the same as 1), the string "error" (treated the same as 2), or an array + * whose first element is one of the above values. Strings are matched case-insensitively. + * @returns {(0|1|2)} The numeric severity value if the config value was valid, otherwise 0. + */ + getRuleSeverity(ruleConfig) { + const severityValue = Array.isArray(ruleConfig) ? ruleConfig[0] : ruleConfig; + + if (severityValue === 0 || severityValue === 1 || severityValue === 2) { + return severityValue; + } + + if (typeof severityValue === "string") { + return RULE_SEVERITY[severityValue.toLowerCase()] || 0; + } + + return 0; + }, + + /** + * Converts old-style severity settings (0, 1, 2) into new-style + * severity settings (off, warn, error) for all rules. Assumption is that severity + * values have already been validated as correct. + * @param {Object} config The config object to normalize. + * @returns {void} + */ + normalizeToStrings(config) { + + if (config.rules) { + Object.keys(config.rules).forEach(ruleId => { + const ruleConfig = config.rules[ruleId]; + + if (typeof ruleConfig === "number") { + config.rules[ruleId] = RULE_SEVERITY_STRINGS[ruleConfig] || RULE_SEVERITY_STRINGS[0]; + } else if (Array.isArray(ruleConfig) && typeof ruleConfig[0] === "number") { + ruleConfig[0] = RULE_SEVERITY_STRINGS[ruleConfig[0]] || RULE_SEVERITY_STRINGS[0]; + } + }); + } + }, + + /** + * Determines if the severity for the given rule configuration represents an error. + * @param {int|string|Array} ruleConfig The configuration for an individual rule. + * @returns {boolean} True if the rule represents an error, false if not. + */ + isErrorSeverity(ruleConfig) { + return module.exports.getRuleSeverity(ruleConfig) === 2; + }, + + /** + * Checks whether a given config has valid severity or not. + * @param {number|string|Array} ruleConfig The configuration for an individual rule. + * @returns {boolean} `true` if the configuration has valid severity. + */ + isValidSeverity(ruleConfig) { + let severity = Array.isArray(ruleConfig) ? ruleConfig[0] : ruleConfig; + + if (typeof severity === "string") { + severity = severity.toLowerCase(); + } + return VALID_SEVERITIES.indexOf(severity) !== -1; + }, + + /** + * Checks whether every rule of a given config has valid severity or not. + * @param {Object} config The configuration for rules. + * @returns {boolean} `true` if the configuration has valid severity. + */ + isEverySeverityValid(config) { + return Object.keys(config).every(ruleId => this.isValidSeverity(config[ruleId])); + }, + + /** + * Normalizes a value for a global in a config + * @param {(boolean|string|null)} configuredValue The value given for a global in configuration or in + * a global directive comment + * @returns {("readable"|"writeable"|"off")} The value normalized as a string + * @throws Error if global value is invalid + */ + normalizeConfigGlobal(configuredValue) { + switch (configuredValue) { + case "off": + return "off"; + + case true: + case "true": + case "writeable": + case "writable": + return "writable"; + + case null: + case false: + case "false": + case "readable": + case "readonly": + return "readonly"; + + default: + throw new Error(`'${configuredValue}' is not a valid configuration for a global (use 'readonly', 'writable', or 'off')`); + } + } +}; diff --git a/eslint/lib/shared/config-validator.js b/eslint/lib/shared/config-validator.js new file mode 100644 index 0000000..458bd2a --- /dev/null +++ b/eslint/lib/shared/config-validator.js @@ -0,0 +1,326 @@ +/** + * @fileoverview Validates configs. + * @author Brandon Mills + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const + util = require("util"), + configSchema = require("../../conf/config-schema"), + BuiltInEnvironments = require("../../conf/environments"), + BuiltInRules = require("../rules"), + ConfigOps = require("./config-ops"), + { emitDeprecationWarning } = require("./deprecation-warnings"); + +const ajv = require("./ajv")(); +const ruleValidators = new WeakMap(); +const noop = Function.prototype; + +//------------------------------------------------------------------------------ +// Private +//------------------------------------------------------------------------------ +let validateSchema; +const severityMap = { + error: 2, + warn: 1, + off: 0 +}; + +/** + * Gets a complete options schema for a rule. + * @param {{create: Function, schema: (Array|null)}} rule A new-style rule object + * @returns {Object} JSON Schema for the rule's options. + */ +function getRuleOptionsSchema(rule) { + if (!rule) { + return null; + } + + const schema = rule.schema || rule.meta && rule.meta.schema; + + // Given a tuple of schemas, insert warning level at the beginning + if (Array.isArray(schema)) { + if (schema.length) { + return { + type: "array", + items: schema, + minItems: 0, + maxItems: schema.length + }; + } + return { + type: "array", + minItems: 0, + maxItems: 0 + }; + + } + + // Given a full schema, leave it alone + return schema || null; +} + +/** + * Validates a rule's severity and returns the severity value. Throws an error if the severity is invalid. + * @param {options} options The given options for the rule. + * @returns {number|string} The rule's severity value + */ +function validateRuleSeverity(options) { + const severity = Array.isArray(options) ? options[0] : options; + const normSeverity = typeof severity === "string" ? severityMap[severity.toLowerCase()] : severity; + + if (normSeverity === 0 || normSeverity === 1 || normSeverity === 2) { + return normSeverity; + } + + throw new Error(`\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed '${util.inspect(severity).replace(/'/gu, "\"").replace(/\n/gu, "")}').\n`); + +} + +/** + * Validates the non-severity options passed to a rule, based on its schema. + * @param {{create: Function}} rule The rule to validate + * @param {Array} localOptions The options for the rule, excluding severity + * @returns {void} + */ +function validateRuleSchema(rule, localOptions) { + if (!ruleValidators.has(rule)) { + const schema = getRuleOptionsSchema(rule); + + if (schema) { + ruleValidators.set(rule, ajv.compile(schema)); + } + } + + const validateRule = ruleValidators.get(rule); + + if (validateRule) { + validateRule(localOptions); + if (validateRule.errors) { + throw new Error(validateRule.errors.map( + error => `\tValue ${JSON.stringify(error.data)} ${error.message}.\n` + ).join("")); + } + } +} + +/** + * Validates a rule's options against its schema. + * @param {{create: Function}|null} rule The rule that the config is being validated for + * @param {string} ruleId The rule's unique name. + * @param {Array|number} options The given options for the rule. + * @param {string|null} source The name of the configuration source to report in any errors. If null or undefined, + * no source is prepended to the message. + * @returns {void} + */ +function validateRuleOptions(rule, ruleId, options, source = null) { + try { + const severity = validateRuleSeverity(options); + + if (severity !== 0) { + validateRuleSchema(rule, Array.isArray(options) ? options.slice(1) : []); + } + } catch (err) { + const enhancedMessage = `Configuration for rule "${ruleId}" is invalid:\n${err.message}`; + + if (typeof source === "string") { + throw new Error(`${source}:\n\t${enhancedMessage}`); + } else { + throw new Error(enhancedMessage); + } + } +} + +/** + * Validates an environment object + * @param {Object} environment The environment config object to validate. + * @param {string} source The name of the configuration source to report in any errors. + * @param {function(envId:string): Object} [getAdditionalEnv] A map from strings to loaded environments. + * @returns {void} + */ +function validateEnvironment( + environment, + source, + getAdditionalEnv = noop +) { + + // not having an environment is ok + if (!environment) { + return; + } + + Object.keys(environment).forEach(id => { + const env = getAdditionalEnv(id) || BuiltInEnvironments.get(id) || null; + + if (!env) { + const message = `${source}:\n\tEnvironment key "${id}" is unknown\n`; + + throw new Error(message); + } + }); +} + +/** + * Validates a rules config object + * @param {Object} rulesConfig The rules config object to validate. + * @param {string} source The name of the configuration source to report in any errors. + * @param {function(ruleId:string): Object} getAdditionalRule A map from strings to loaded rules + * @returns {void} + */ +function validateRules( + rulesConfig, + source, + getAdditionalRule = noop +) { + if (!rulesConfig) { + return; + } + + Object.keys(rulesConfig).forEach(id => { + const rule = getAdditionalRule(id) || BuiltInRules.get(id) || null; + + validateRuleOptions(rule, id, rulesConfig[id], source); + }); +} + +/** + * Validates a `globals` section of a config file + * @param {Object} globalsConfig The `globals` section + * @param {string|null} source The name of the configuration source to report in the event of an error. + * @returns {void} + */ +function validateGlobals(globalsConfig, source = null) { + if (!globalsConfig) { + return; + } + + Object.entries(globalsConfig) + .forEach(([configuredGlobal, configuredValue]) => { + try { + ConfigOps.normalizeConfigGlobal(configuredValue); + } catch (err) { + throw new Error(`ESLint configuration of global '${configuredGlobal}' in ${source} is invalid:\n${err.message}`); + } + }); +} + +/** + * Validate `processor` configuration. + * @param {string|undefined} processorName The processor name. + * @param {string} source The name of config file. + * @param {function(id:string): Processor} getProcessor The getter of defined processors. + * @returns {void} + */ +function validateProcessor(processorName, source, getProcessor) { + if (processorName && !getProcessor(processorName)) { + throw new Error(`ESLint configuration of processor in '${source}' is invalid: '${processorName}' was not found.`); + } +} + +/** + * Formats an array of schema validation errors. + * @param {Array} errors An array of error messages to format. + * @returns {string} Formatted error message + */ +function formatErrors(errors) { + return errors.map(error => { + if (error.keyword === "additionalProperties") { + const formattedPropertyPath = error.dataPath.length ? `${error.dataPath.slice(1)}.${error.params.additionalProperty}` : error.params.additionalProperty; + + return `Unexpected top-level property "${formattedPropertyPath}"`; + } + if (error.keyword === "type") { + const formattedField = error.dataPath.slice(1); + const formattedExpectedType = Array.isArray(error.schema) ? error.schema.join("/") : error.schema; + const formattedValue = JSON.stringify(error.data); + + return `Property "${formattedField}" is the wrong type (expected ${formattedExpectedType} but got \`${formattedValue}\`)`; + } + + const field = error.dataPath[0] === "." ? error.dataPath.slice(1) : error.dataPath; + + return `"${field}" ${error.message}. Value: ${JSON.stringify(error.data)}`; + }).map(message => `\t- ${message}.\n`).join(""); +} + +/** + * Validates the top level properties of the config object. + * @param {Object} config The config object to validate. + * @param {string} source The name of the configuration source to report in any errors. + * @returns {void} + */ +function validateConfigSchema(config, source = null) { + validateSchema = validateSchema || ajv.compile(configSchema); + + if (!validateSchema(config)) { + throw new Error(`ESLint configuration in ${source} is invalid:\n${formatErrors(validateSchema.errors)}`); + } + + if (Object.hasOwnProperty.call(config, "ecmaFeatures")) { + emitDeprecationWarning(source, "ESLINT_LEGACY_ECMAFEATURES"); + } +} + +/** + * Validates an entire config object. + * @param {Object} config The config object to validate. + * @param {string} source The name of the configuration source to report in any errors. + * @param {function(ruleId:string): Object} [getAdditionalRule] A map from strings to loaded rules. + * @param {function(envId:string): Object} [getAdditionalEnv] A map from strings to loaded envs. + * @returns {void} + */ +function validate(config, source, getAdditionalRule, getAdditionalEnv) { + validateConfigSchema(config, source); + validateRules(config.rules, source, getAdditionalRule); + validateEnvironment(config.env, source, getAdditionalEnv); + validateGlobals(config.globals, source); + + for (const override of config.overrides || []) { + validateRules(override.rules, source, getAdditionalRule); + validateEnvironment(override.env, source, getAdditionalEnv); + validateGlobals(config.globals, source); + } +} + +const validated = new WeakSet(); + +/** + * Validate config array object. + * @param {ConfigArray} configArray The config array to validate. + * @returns {void} + */ +function validateConfigArray(configArray) { + const getPluginEnv = Map.prototype.get.bind(configArray.pluginEnvironments); + const getPluginProcessor = Map.prototype.get.bind(configArray.pluginProcessors); + const getPluginRule = Map.prototype.get.bind(configArray.pluginRules); + + // Validate. + for (const element of configArray) { + if (validated.has(element)) { + continue; + } + validated.add(element); + + validateEnvironment(element.env, element.name, getPluginEnv); + validateGlobals(element.globals, element.name); + validateProcessor(element.processor, element.name, getPluginProcessor); + validateRules(element.rules, element.name, getPluginRule); + } +} + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = { + getRuleOptionsSchema, + validate, + validateConfigArray, + validateConfigSchema, + validateRuleOptions +}; diff --git a/eslint/lib/shared/deprecation-warnings.js b/eslint/lib/shared/deprecation-warnings.js new file mode 100644 index 0000000..e1481a2 --- /dev/null +++ b/eslint/lib/shared/deprecation-warnings.js @@ -0,0 +1,56 @@ +/** + * @fileoverview Provide the function that emits deprecation warnings. + * @author Toru Nagashima + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const path = require("path"); +const lodash = require("lodash"); + +//------------------------------------------------------------------------------ +// Private +//------------------------------------------------------------------------------ + +// Defitions for deprecation warnings. +const deprecationWarningMessages = { + ESLINT_LEGACY_ECMAFEATURES: + "The 'ecmaFeatures' config file property is deprecated and has no effect.", + ESLINT_PERSONAL_CONFIG_LOAD: + "'~/.eslintrc.*' config files have been deprecated. " + + "Please use a config file per project or the '--config' option.", + ESLINT_PERSONAL_CONFIG_SUPPRESS: + "'~/.eslintrc.*' config files have been deprecated. " + + "Please remove it or add 'root:true' to the config files in your " + + "projects in order to avoid loading '~/.eslintrc.*' accidentally." +}; + +/** + * Emits a deprecation warning containing a given filepath. A new deprecation warning is emitted + * for each unique file path, but repeated invocations with the same file path have no effect. + * No warnings are emitted if the `--no-deprecation` or `--no-warnings` Node runtime flags are active. + * @param {string} source The name of the configuration source to report the warning for. + * @param {string} errorCode The warning message to show. + * @returns {void} + */ +const emitDeprecationWarning = lodash.memoize((source, errorCode) => { + const rel = path.relative(process.cwd(), source); + const message = deprecationWarningMessages[errorCode]; + + process.emitWarning( + `${message} (found in "${rel}")`, + "DeprecationWarning", + errorCode + ); +}, (...args) => JSON.stringify(args)); + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = { + emitDeprecationWarning +}; diff --git a/eslint/lib/shared/logging.js b/eslint/lib/shared/logging.js new file mode 100644 index 0000000..6aa1f5a --- /dev/null +++ b/eslint/lib/shared/logging.js @@ -0,0 +1,30 @@ +/** + * @fileoverview Handle logging for ESLint + * @author Gyandeep Singh + */ + +"use strict"; + +/* eslint no-console: "off" */ + +/* istanbul ignore next */ +module.exports = { + + /** + * Cover for console.log + * @param {...any} args The elements to log. + * @returns {void} + */ + info(...args) { + console.log(...args); + }, + + /** + * Cover for console.error + * @param {...any} args The elements to log. + * @returns {void} + */ + error(...args) { + console.error(...args); + } +}; diff --git a/eslint/lib/shared/naming.js b/eslint/lib/shared/naming.js new file mode 100644 index 0000000..32cff94 --- /dev/null +++ b/eslint/lib/shared/naming.js @@ -0,0 +1,97 @@ +/** + * @fileoverview Common helpers for naming of plugins, formatters and configs + */ +"use strict"; + +const NAMESPACE_REGEX = /^@.*\//iu; + +/** + * Brings package name to correct format based on prefix + * @param {string} name The name of the package. + * @param {string} prefix Can be either "eslint-plugin", "eslint-config" or "eslint-formatter" + * @returns {string} Normalized name of the package + * @private + */ +function normalizePackageName(name, prefix) { + let normalizedName = name; + + /** + * On Windows, name can come in with Windows slashes instead of Unix slashes. + * Normalize to Unix first to avoid errors later on. + * https://github.com/eslint/eslint/issues/5644 + */ + if (normalizedName.includes("\\")) { + normalizedName = normalizedName.replace(/\\/gu, "/"); + } + + if (normalizedName.charAt(0) === "@") { + + /** + * it's a scoped package + * package name is the prefix, or just a username + */ + const scopedPackageShortcutRegex = new RegExp(`^(@[^/]+)(?:/(?:${prefix})?)?$`, "u"), + scopedPackageNameRegex = new RegExp(`^${prefix}(-|$)`, "u"); + + if (scopedPackageShortcutRegex.test(normalizedName)) { + normalizedName = normalizedName.replace(scopedPackageShortcutRegex, `$1/${prefix}`); + } else if (!scopedPackageNameRegex.test(normalizedName.split("/")[1])) { + + /** + * for scoped packages, insert the prefix after the first / unless + * the path is already @scope/eslint or @scope/eslint-xxx-yyy + */ + normalizedName = normalizedName.replace(/^@([^/]+)\/(.*)$/u, `@$1/${prefix}-$2`); + } + } else if (!normalizedName.startsWith(`${prefix}-`)) { + normalizedName = `${prefix}-${normalizedName}`; + } + + return normalizedName; +} + +/** + * Removes the prefix from a fullname. + * @param {string} fullname The term which may have the prefix. + * @param {string} prefix The prefix to remove. + * @returns {string} The term without prefix. + */ +function getShorthandName(fullname, prefix) { + if (fullname[0] === "@") { + let matchResult = new RegExp(`^(@[^/]+)/${prefix}$`, "u").exec(fullname); + + if (matchResult) { + return matchResult[1]; + } + + matchResult = new RegExp(`^(@[^/]+)/${prefix}-(.+)$`, "u").exec(fullname); + if (matchResult) { + return `${matchResult[1]}/${matchResult[2]}`; + } + } else if (fullname.startsWith(`${prefix}-`)) { + return fullname.slice(prefix.length + 1); + } + + return fullname; +} + +/** + * Gets the scope (namespace) of a term. + * @param {string} term The term which may have the namespace. + * @returns {string} The namespace of the term if it has one. + */ +function getNamespaceFromTerm(term) { + const match = term.match(NAMESPACE_REGEX); + + return match ? match[0] : ""; +} + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = { + normalizePackageName, + getShorthandName, + getNamespaceFromTerm +}; diff --git a/eslint/lib/shared/relative-module-resolver.js b/eslint/lib/shared/relative-module-resolver.js new file mode 100644 index 0000000..fa6cca7 --- /dev/null +++ b/eslint/lib/shared/relative-module-resolver.js @@ -0,0 +1,43 @@ +/** + * Utility for resolving a module relative to another module + * @author Teddy Katz + */ + +"use strict"; + +const Module = require("module"); + +/* + * `Module.createRequire` is added in v12.2.0. It supports URL as well. + * We only support the case where the argument is a filepath, not a URL. + */ +const createRequire = Module.createRequire || Module.createRequireFromPath; + +module.exports = { + + /** + * Resolves a Node module relative to another module + * @param {string} moduleName The name of a Node module, or a path to a Node module. + * @param {string} relativeToPath An absolute path indicating the module that `moduleName` should be resolved relative to. This must be + * a file rather than a directory, but the file need not actually exist. + * @returns {string} The absolute path that would result from calling `require.resolve(moduleName)` in a file located at `relativeToPath` + */ + resolve(moduleName, relativeToPath) { + try { + return createRequire(relativeToPath).resolve(moduleName); + } catch (error) { + + // This `if` block is for older Node.js than 12.0.0. We can remove this block in the future. + if ( + typeof error === "object" && + error !== null && + error.code === "MODULE_NOT_FOUND" && + !error.requireStack && + error.message.includes(moduleName) + ) { + error.message += `\nRequire stack:\n- ${relativeToPath}`; + } + throw error; + } + } +}; diff --git a/eslint/lib/shared/runtime-info.js b/eslint/lib/shared/runtime-info.js new file mode 100644 index 0000000..feed005 --- /dev/null +++ b/eslint/lib/shared/runtime-info.js @@ -0,0 +1,163 @@ +/** + * @fileoverview Utility to get information about the execution environment. + * @author Kai Cataldo + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const path = require("path"); +const spawn = require("cross-spawn"); +const { isEmpty } = require("lodash"); +const log = require("../shared/logging"); +const packageJson = require("../../package.json"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Generates and returns execution environment information. + * @returns {string} A string that contains execution environment information. + */ +function environment() { + const cache = new Map(); + + /** + * Checks if a path is a child of a directory. + * @param {string} parentPath The parent path to check. + * @param {string} childPath The path to check. + * @returns {boolean} Whether or not the given path is a child of a directory. + */ + function isChildOfDirectory(parentPath, childPath) { + return !path.relative(parentPath, childPath).startsWith(".."); + } + + /** + * Synchronously executes a shell command and formats the result. + * @param {string} cmd The command to execute. + * @param {Array} args The arguments to be executed with the command. + * @returns {string} The version returned by the command. + */ + function execCommand(cmd, args) { + const key = [cmd, ...args].join(" "); + + if (cache.has(key)) { + return cache.get(key); + } + + const process = spawn.sync(cmd, args, { encoding: "utf8" }); + + if (process.error) { + throw process.error; + } + + const result = process.stdout.trim(); + + cache.set(key, result); + return result; + } + + /** + * Normalizes a version number. + * @param {string} versionStr The string to normalize. + * @returns {string} The normalized version number. + */ + function normalizeVersionStr(versionStr) { + return versionStr.startsWith("v") ? versionStr : `v${versionStr}`; + } + + /** + * Gets bin version. + * @param {string} bin The bin to check. + * @returns {string} The normalized version returned by the command. + */ + function getBinVersion(bin) { + const binArgs = ["--version"]; + + try { + return normalizeVersionStr(execCommand(bin, binArgs)); + } catch (e) { + log.error(`Error finding ${bin} version running the command \`${bin} ${binArgs.join(" ")}\``); + throw e; + } + } + + /** + * Gets installed npm package version. + * @param {string} pkg The package to check. + * @param {boolean} global Whether to check globally or not. + * @returns {string} The normalized version returned by the command. + */ + function getNpmPackageVersion(pkg, { global = false } = {}) { + const npmBinArgs = ["bin", "-g"]; + const npmLsArgs = ["ls", "--depth=0", "--json", "eslint"]; + + if (global) { + npmLsArgs.push("-g"); + } + + try { + const parsedStdout = JSON.parse(execCommand("npm", npmLsArgs)); + + /* + * Checking globally returns an empty JSON object, while local checks + * include the name and version of the local project. + */ + if (isEmpty(parsedStdout) || !(parsedStdout.dependencies && parsedStdout.dependencies.eslint)) { + return "Not found"; + } + + const [, processBinPath] = process.argv; + let npmBinPath; + + try { + npmBinPath = execCommand("npm", npmBinArgs); + } catch (e) { + log.error(`Error finding npm binary path when running command \`npm ${npmBinArgs.join(" ")}\``); + throw e; + } + + const isGlobal = isChildOfDirectory(npmBinPath, processBinPath); + let pkgVersion = parsedStdout.dependencies.eslint.version; + + if ((global && isGlobal) || (!global && !isGlobal)) { + pkgVersion += " (Currently used)"; + } + + return normalizeVersionStr(pkgVersion); + } catch (e) { + log.error(`Error finding ${pkg} version running the command \`npm ${npmLsArgs.join(" ")}\``); + throw e; + } + } + + return [ + "Environment Info:", + "", + `Node version: ${getBinVersion("node")}`, + `npm version: ${getBinVersion("npm")}`, + `Local ESLint version: ${getNpmPackageVersion("eslint", { global: false })}`, + `Global ESLint version: ${getNpmPackageVersion("eslint", { global: true })}` + ].join("\n"); +} + +/** + * Returns version of currently executing ESLint. + * @returns {string} The version from the currently executing ESLint's package.json. + */ +function version() { + return `v${packageJson.version}`; +} + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = { + environment, + version +}; diff --git a/eslint/lib/shared/traverser.js b/eslint/lib/shared/traverser.js new file mode 100644 index 0000000..32f7677 --- /dev/null +++ b/eslint/lib/shared/traverser.js @@ -0,0 +1,195 @@ +/** + * @fileoverview Traverser to traverse AST trees. + * @author Nicholas C. Zakas + * @author Toru Nagashima + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const vk = require("eslint-visitor-keys"); +const debug = require("debug")("eslint:traverser"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Do nothing. + * @returns {void} + */ +function noop() { + + // do nothing. +} + +/** + * Check whether the given value is an ASTNode or not. + * @param {any} x The value to check. + * @returns {boolean} `true` if the value is an ASTNode. + */ +function isNode(x) { + return x !== null && typeof x === "object" && typeof x.type === "string"; +} + +/** + * Get the visitor keys of a given node. + * @param {Object} visitorKeys The map of visitor keys. + * @param {ASTNode} node The node to get their visitor keys. + * @returns {string[]} The visitor keys of the node. + */ +function getVisitorKeys(visitorKeys, node) { + let keys = visitorKeys[node.type]; + + if (!keys) { + keys = vk.getKeys(node); + debug("Unknown node type \"%s\": Estimated visitor keys %j", node.type, keys); + } + + return keys; +} + +/** + * The traverser class to traverse AST trees. + */ +class Traverser { + constructor() { + this._current = null; + this._parents = []; + this._skipped = false; + this._broken = false; + this._visitorKeys = null; + this._enter = null; + this._leave = null; + } + + // eslint-disable-next-line jsdoc/require-description + /** + * @returns {ASTNode} The current node. + */ + current() { + return this._current; + } + + // eslint-disable-next-line jsdoc/require-description + /** + * @returns {ASTNode[]} The ancestor nodes. + */ + parents() { + return this._parents.slice(0); + } + + /** + * Break the current traversal. + * @returns {void} + */ + break() { + this._broken = true; + } + + /** + * Skip child nodes for the current traversal. + * @returns {void} + */ + skip() { + this._skipped = true; + } + + /** + * Traverse the given AST tree. + * @param {ASTNode} node The root node to traverse. + * @param {Object} options The option object. + * @param {Object} [options.visitorKeys=DEFAULT_VISITOR_KEYS] The keys of each node types to traverse child nodes. Default is `./default-visitor-keys.json`. + * @param {Function} [options.enter=noop] The callback function which is called on entering each node. + * @param {Function} [options.leave=noop] The callback function which is called on leaving each node. + * @returns {void} + */ + traverse(node, options) { + this._current = null; + this._parents = []; + this._skipped = false; + this._broken = false; + this._visitorKeys = options.visitorKeys || vk.KEYS; + this._enter = options.enter || noop; + this._leave = options.leave || noop; + this._traverse(node, null); + } + + /** + * Traverse the given AST tree recursively. + * @param {ASTNode} node The current node. + * @param {ASTNode|null} parent The parent node. + * @returns {void} + * @private + */ + _traverse(node, parent) { + if (!isNode(node)) { + return; + } + + this._current = node; + this._skipped = false; + this._enter(node, parent); + + if (!this._skipped && !this._broken) { + const keys = getVisitorKeys(this._visitorKeys, node); + + if (keys.length >= 1) { + this._parents.push(node); + for (let i = 0; i < keys.length && !this._broken; ++i) { + const child = node[keys[i]]; + + if (Array.isArray(child)) { + for (let j = 0; j < child.length && !this._broken; ++j) { + this._traverse(child[j], node); + } + } else { + this._traverse(child, node); + } + } + this._parents.pop(); + } + } + + if (!this._broken) { + this._leave(node, parent); + } + + this._current = parent; + } + + /** + * Calculates the keys to use for traversal. + * @param {ASTNode} node The node to read keys from. + * @returns {string[]} An array of keys to visit on the node. + * @private + */ + static getKeys(node) { + return vk.getKeys(node); + } + + /** + * Traverse the given AST tree. + * @param {ASTNode} node The root node to traverse. + * @param {Object} options The option object. + * @param {Object} [options.visitorKeys=DEFAULT_VISITOR_KEYS] The keys of each node types to traverse child nodes. Default is `./default-visitor-keys.json`. + * @param {Function} [options.enter=noop] The callback function which is called on entering each node. + * @param {Function} [options.leave=noop] The callback function which is called on leaving each node. + * @returns {void} + */ + static traverse(node, options) { + new Traverser().traverse(node, options); + } + + /** + * The default visitor keys. + * @type {Object} + */ + static get DEFAULT_VISITOR_KEYS() { + return vk.KEYS; + } +} + +module.exports = Traverser; diff --git a/eslint/lib/shared/types.js b/eslint/lib/shared/types.js new file mode 100644 index 0000000..bf37327 --- /dev/null +++ b/eslint/lib/shared/types.js @@ -0,0 +1,143 @@ +/** + * @fileoverview Define common types for input completion. + * @author Toru Nagashima + */ +"use strict"; + +/** @type {any} */ +module.exports = {}; + +/** @typedef {boolean | "off" | "readable" | "readonly" | "writable" | "writeable"} GlobalConf */ +/** @typedef {0 | 1 | 2 | "off" | "warn" | "error"} SeverityConf */ +/** @typedef {SeverityConf | [SeverityConf, ...any[]]} RuleConf */ + +/** + * @typedef {Object} EcmaFeatures + * @property {boolean} [globalReturn] Enabling `return` statements at the top-level. + * @property {boolean} [jsx] Enabling JSX syntax. + * @property {boolean} [impliedStrict] Enabling strict mode always. + */ + +/** + * @typedef {Object} ParserOptions + * @property {EcmaFeatures} [ecmaFeatures] The optional features. + * @property {3|5|6|7|8|9|10|11|2015|2016|2017|2018|2019|2020} [ecmaVersion] The ECMAScript version (or revision number). + * @property {"script"|"module"} [sourceType] The source code type. + */ + +/** + * @typedef {Object} ConfigData + * @property {Record} [env] The environment settings. + * @property {string | string[]} [extends] The path to other config files or the package name of shareable configs. + * @property {Record} [globals] The global variable settings. + * @property {string | string[]} [ignorePatterns] The glob patterns that ignore to lint. + * @property {boolean} [noInlineConfig] The flag that disables directive comments. + * @property {OverrideConfigData[]} [overrides] The override settings per kind of files. + * @property {string} [parser] The path to a parser or the package name of a parser. + * @property {ParserOptions} [parserOptions] The parser options. + * @property {string[]} [plugins] The plugin specifiers. + * @property {string} [processor] The processor specifier. + * @property {boolean} [reportUnusedDisableDirectives] The flag to report unused `eslint-disable` comments. + * @property {boolean} [root] The root flag. + * @property {Record} [rules] The rule settings. + * @property {Object} [settings] The shared settings. + */ + +/** + * @typedef {Object} OverrideConfigData + * @property {Record} [env] The environment settings. + * @property {string | string[]} [excludedFiles] The glob pattarns for excluded files. + * @property {string | string[]} [extends] The path to other config files or the package name of shareable configs. + * @property {string | string[]} files The glob pattarns for target files. + * @property {Record} [globals] The global variable settings. + * @property {boolean} [noInlineConfig] The flag that disables directive comments. + * @property {OverrideConfigData[]} [overrides] The override settings per kind of files. + * @property {string} [parser] The path to a parser or the package name of a parser. + * @property {ParserOptions} [parserOptions] The parser options. + * @property {string[]} [plugins] The plugin specifiers. + * @property {string} [processor] The processor specifier. + * @property {boolean} [reportUnusedDisableDirectives] The flag to report unused `eslint-disable` comments. + * @property {Record} [rules] The rule settings. + * @property {Object} [settings] The shared settings. + */ + +/** + * @typedef {Object} ParseResult + * @property {Object} ast The AST. + * @property {ScopeManager} [scopeManager] The scope manager of the AST. + * @property {Record} [services] The services that the parser provides. + * @property {Record} [visitorKeys] The visitor keys of the AST. + */ + +/** + * @typedef {Object} Parser + * @property {(text:string, options:ParserOptions) => Object} parse The definition of global variables. + * @property {(text:string, options:ParserOptions) => ParseResult} [parseForESLint] The parser options that will be enabled under this environment. + */ + +/** + * @typedef {Object} Environment + * @property {Record} [globals] The definition of global variables. + * @property {ParserOptions} [parserOptions] The parser options that will be enabled under this environment. + */ + +/** + * @typedef {Object} LintMessage + * @property {number} column The 1-based column number. + * @property {number} [endColumn] The 1-based column number of the end location. + * @property {number} [endLine] The 1-based line number of the end location. + * @property {boolean} fatal If `true` then this is a fatal error. + * @property {{range:[number,number], text:string}} [fix] Information for autofix. + * @property {number} line The 1-based line number. + * @property {string} message The error message. + * @property {string|null} ruleId The ID of the rule which makes this message. + * @property {0|1|2} severity The severity of this message. + * @property {Array<{desc?: string, messageId?: string, fix: {range: [number, number], text: string}}>} [suggestions] Information for suggestions. + */ + +/** + * @typedef {Object} SuggestionResult + * @property {string} desc A short description. + * @property {string} [messageId] Id referencing a message for the description. + * @property {{ text: string, range: number[] }} fix fix result info + */ + +/** + * @typedef {Object} Processor + * @property {(text:string, filename:string) => Array} [preprocess] The function to extract code blocks. + * @property {(messagesList:LintMessage[][], filename:string) => LintMessage[]} [postprocess] The function to merge messages. + * @property {boolean} [supportsAutofix] If `true` then it means the processor supports autofix. + */ + +/** + * @typedef {Object} RuleMetaDocs + * @property {string} category The category of the rule. + * @property {string} description The description of the rule. + * @property {boolean} recommended If `true` then the rule is included in `eslint:recommended` preset. + * @property {string} url The URL of the rule documentation. + */ + +/** + * @typedef {Object} RuleMeta + * @property {boolean} [deprecated] If `true` then the rule has been deprecated. + * @property {RuleMetaDocs} docs The document information of the rule. + * @property {"code"|"whitespace"} [fixable] The autofix type. + * @property {Record} [messages] The messages the rule reports. + * @property {string[]} [replacedBy] The IDs of the alternative rules. + * @property {Array|Object} schema The option schema of the rule. + * @property {"problem"|"suggestion"|"layout"} type The rule type. + */ + +/** + * @typedef {Object} Rule + * @property {Function} create The factory of the rule. + * @property {RuleMeta} meta The meta data of the rule. + */ + +/** + * @typedef {Object} Plugin + * @property {Record} [configs] The definition of plugin configs. + * @property {Record} [environments] The definition of plugin environments. + * @property {Record} [processors] The definition of plugin processors. + * @property {Record} [rules] The definition of plugin rules. + */ diff --git a/eslint/lib/source-code/index.js b/eslint/lib/source-code/index.js new file mode 100644 index 0000000..76e2786 --- /dev/null +++ b/eslint/lib/source-code/index.js @@ -0,0 +1,5 @@ +"use strict"; + +module.exports = { + SourceCode: require("./source-code") +}; diff --git a/eslint/lib/source-code/source-code.js b/eslint/lib/source-code/source-code.js new file mode 100644 index 0000000..591d5a7 --- /dev/null +++ b/eslint/lib/source-code/source-code.js @@ -0,0 +1,585 @@ +/** + * @fileoverview Abstraction of JavaScript source code. + * @author Nicholas C. Zakas + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const + { isCommentToken } = require("eslint-utils"), + TokenStore = require("./token-store"), + astUtils = require("../shared/ast-utils"), + Traverser = require("../shared/traverser"), + lodash = require("lodash"); + +//------------------------------------------------------------------------------ +// Private +//------------------------------------------------------------------------------ + +/** + * Validates that the given AST has the required information. + * @param {ASTNode} ast The Program node of the AST to check. + * @throws {Error} If the AST doesn't contain the correct information. + * @returns {void} + * @private + */ +function validate(ast) { + if (!ast.tokens) { + throw new Error("AST is missing the tokens array."); + } + + if (!ast.comments) { + throw new Error("AST is missing the comments array."); + } + + if (!ast.loc) { + throw new Error("AST is missing location information."); + } + + if (!ast.range) { + throw new Error("AST is missing range information"); + } +} + +/** + * Check to see if its a ES6 export declaration. + * @param {ASTNode} astNode An AST node. + * @returns {boolean} whether the given node represents an export declaration. + * @private + */ +function looksLikeExport(astNode) { + return astNode.type === "ExportDefaultDeclaration" || astNode.type === "ExportNamedDeclaration" || + astNode.type === "ExportAllDeclaration" || astNode.type === "ExportSpecifier"; +} + +/** + * Merges two sorted lists into a larger sorted list in O(n) time. + * @param {Token[]} tokens The list of tokens. + * @param {Token[]} comments The list of comments. + * @returns {Token[]} A sorted list of tokens and comments. + * @private + */ +function sortedMerge(tokens, comments) { + const result = []; + let tokenIndex = 0; + let commentIndex = 0; + + while (tokenIndex < tokens.length || commentIndex < comments.length) { + if (commentIndex >= comments.length || tokenIndex < tokens.length && tokens[tokenIndex].range[0] < comments[commentIndex].range[0]) { + result.push(tokens[tokenIndex++]); + } else { + result.push(comments[commentIndex++]); + } + } + + return result; +} + +/** + * Determines if two nodes or tokens overlap. + * @param {ASTNode|Token} first The first node or token to check. + * @param {ASTNode|Token} second The second node or token to check. + * @returns {boolean} True if the two nodes or tokens overlap. + * @private + */ +function nodesOrTokensOverlap(first, second) { + return (first.range[0] <= second.range[0] && first.range[1] >= second.range[0]) || + (second.range[0] <= first.range[0] && second.range[1] >= first.range[0]); +} + +/** + * Determines if two nodes or tokens have at least one whitespace character + * between them. Order does not matter. Returns false if the given nodes or + * tokens overlap. + * @param {SourceCode} sourceCode The source code object. + * @param {ASTNode|Token} first The first node or token to check between. + * @param {ASTNode|Token} second The second node or token to check between. + * @param {boolean} checkInsideOfJSXText If `true` is present, check inside of JSXText tokens for backward compatibility. + * @returns {boolean} True if there is a whitespace character between + * any of the tokens found between the two given nodes or tokens. + * @public + */ +function isSpaceBetween(sourceCode, first, second, checkInsideOfJSXText) { + if (nodesOrTokensOverlap(first, second)) { + return false; + } + + const [startingNodeOrToken, endingNodeOrToken] = first.range[1] <= second.range[0] + ? [first, second] + : [second, first]; + const firstToken = sourceCode.getLastToken(startingNodeOrToken) || startingNodeOrToken; + const finalToken = sourceCode.getFirstToken(endingNodeOrToken) || endingNodeOrToken; + let currentToken = firstToken; + + while (currentToken !== finalToken) { + const nextToken = sourceCode.getTokenAfter(currentToken, { includeComments: true }); + + if ( + currentToken.range[1] !== nextToken.range[0] || + + /* + * For backward compatibility, check spaces in JSXText. + * https://github.com/eslint/eslint/issues/12614 + */ + ( + checkInsideOfJSXText && + nextToken !== finalToken && + nextToken.type === "JSXText" && + /\s/u.test(nextToken.value) + ) + ) { + return true; + } + + currentToken = nextToken; + } + + return false; +} + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +class SourceCode extends TokenStore { + + /** + * Represents parsed source code. + * @param {string|Object} textOrConfig The source code text or config object. + * @param {string} textOrConfig.text The source code text. + * @param {ASTNode} textOrConfig.ast The Program node of the AST representing the code. This AST should be created from the text that BOM was stripped. + * @param {Object|null} textOrConfig.parserServices The parser services. + * @param {ScopeManager|null} textOrConfig.scopeManager The scope of this source code. + * @param {Object|null} textOrConfig.visitorKeys The visitor keys to traverse AST. + * @param {ASTNode} [astIfNoConfig] The Program node of the AST representing the code. This AST should be created from the text that BOM was stripped. + */ + constructor(textOrConfig, astIfNoConfig) { + let text, ast, parserServices, scopeManager, visitorKeys; + + // Process overloading. + if (typeof textOrConfig === "string") { + text = textOrConfig; + ast = astIfNoConfig; + } else if (typeof textOrConfig === "object" && textOrConfig !== null) { + text = textOrConfig.text; + ast = textOrConfig.ast; + parserServices = textOrConfig.parserServices; + scopeManager = textOrConfig.scopeManager; + visitorKeys = textOrConfig.visitorKeys; + } + + validate(ast); + super(ast.tokens, ast.comments); + + /** + * The flag to indicate that the source code has Unicode BOM. + * @type boolean + */ + this.hasBOM = (text.charCodeAt(0) === 0xFEFF); + + /** + * The original text source code. + * BOM was stripped from this text. + * @type string + */ + this.text = (this.hasBOM ? text.slice(1) : text); + + /** + * The parsed AST for the source code. + * @type ASTNode + */ + this.ast = ast; + + /** + * The parser services of this source code. + * @type {Object} + */ + this.parserServices = parserServices || {}; + + /** + * The scope of this source code. + * @type {ScopeManager|null} + */ + this.scopeManager = scopeManager || null; + + /** + * The visitor keys to traverse AST. + * @type {Object} + */ + this.visitorKeys = visitorKeys || Traverser.DEFAULT_VISITOR_KEYS; + + // Check the source text for the presence of a shebang since it is parsed as a standard line comment. + const shebangMatched = this.text.match(astUtils.shebangPattern); + const hasShebang = shebangMatched && ast.comments.length && ast.comments[0].value === shebangMatched[1]; + + if (hasShebang) { + ast.comments[0].type = "Shebang"; + } + + this.tokensAndComments = sortedMerge(ast.tokens, ast.comments); + + /** + * The source code split into lines according to ECMA-262 specification. + * This is done to avoid each rule needing to do so separately. + * @type string[] + */ + this.lines = []; + this.lineStartIndices = [0]; + + const lineEndingPattern = astUtils.createGlobalLinebreakMatcher(); + let match; + + /* + * Previously, this was implemented using a regex that + * matched a sequence of non-linebreak characters followed by a + * linebreak, then adding the lengths of the matches. However, + * this caused a catastrophic backtracking issue when the end + * of a file contained a large number of non-newline characters. + * To avoid this, the current implementation just matches newlines + * and uses match.index to get the correct line start indices. + */ + while ((match = lineEndingPattern.exec(this.text))) { + this.lines.push(this.text.slice(this.lineStartIndices[this.lineStartIndices.length - 1], match.index)); + this.lineStartIndices.push(match.index + match[0].length); + } + this.lines.push(this.text.slice(this.lineStartIndices[this.lineStartIndices.length - 1])); + + // Cache for comments found using getComments(). + this._commentCache = new WeakMap(); + + // don't allow modification of this object + Object.freeze(this); + Object.freeze(this.lines); + } + + /** + * Split the source code into multiple lines based on the line delimiters. + * @param {string} text Source code as a string. + * @returns {string[]} Array of source code lines. + * @public + */ + static splitLines(text) { + return text.split(astUtils.createGlobalLinebreakMatcher()); + } + + /** + * Gets the source code for the given node. + * @param {ASTNode} [node] The AST node to get the text for. + * @param {int} [beforeCount] The number of characters before the node to retrieve. + * @param {int} [afterCount] The number of characters after the node to retrieve. + * @returns {string} The text representing the AST node. + * @public + */ + getText(node, beforeCount, afterCount) { + if (node) { + return this.text.slice(Math.max(node.range[0] - (beforeCount || 0), 0), + node.range[1] + (afterCount || 0)); + } + return this.text; + } + + /** + * Gets the entire source text split into an array of lines. + * @returns {Array} The source text as an array of lines. + * @public + */ + getLines() { + return this.lines; + } + + /** + * Retrieves an array containing all comments in the source code. + * @returns {ASTNode[]} An array of comment nodes. + * @public + */ + getAllComments() { + return this.ast.comments; + } + + /** + * Gets all comments for the given node. + * @param {ASTNode} node The AST node to get the comments for. + * @returns {Object} An object containing a leading and trailing array + * of comments indexed by their position. + * @public + */ + getComments(node) { + if (this._commentCache.has(node)) { + return this._commentCache.get(node); + } + + const comments = { + leading: [], + trailing: [] + }; + + /* + * Return all comments as leading comments of the Program node when + * there is no executable code. + */ + if (node.type === "Program") { + if (node.body.length === 0) { + comments.leading = node.comments; + } + } else { + + /* + * Return comments as trailing comments of nodes that only contain + * comments (to mimic the comment attachment behavior present in Espree). + */ + if ((node.type === "BlockStatement" || node.type === "ClassBody") && node.body.length === 0 || + node.type === "ObjectExpression" && node.properties.length === 0 || + node.type === "ArrayExpression" && node.elements.length === 0 || + node.type === "SwitchStatement" && node.cases.length === 0 + ) { + comments.trailing = this.getTokens(node, { + includeComments: true, + filter: isCommentToken + }); + } + + /* + * Iterate over tokens before and after node and collect comment tokens. + * Do not include comments that exist outside of the parent node + * to avoid duplication. + */ + let currentToken = this.getTokenBefore(node, { includeComments: true }); + + while (currentToken && isCommentToken(currentToken)) { + if (node.parent && (currentToken.start < node.parent.start)) { + break; + } + comments.leading.push(currentToken); + currentToken = this.getTokenBefore(currentToken, { includeComments: true }); + } + + comments.leading.reverse(); + + currentToken = this.getTokenAfter(node, { includeComments: true }); + + while (currentToken && isCommentToken(currentToken)) { + if (node.parent && (currentToken.end > node.parent.end)) { + break; + } + comments.trailing.push(currentToken); + currentToken = this.getTokenAfter(currentToken, { includeComments: true }); + } + } + + this._commentCache.set(node, comments); + return comments; + } + + /** + * Retrieves the JSDoc comment for a given node. + * @param {ASTNode} node The AST node to get the comment for. + * @returns {Token|null} The Block comment token containing the JSDoc comment + * for the given node or null if not found. + * @public + * @deprecated + */ + getJSDocComment(node) { + + /** + * Checks for the presence of a JSDoc comment for the given node and returns it. + * @param {ASTNode} astNode The AST node to get the comment for. + * @returns {Token|null} The Block comment token containing the JSDoc comment + * for the given node or null if not found. + * @private + */ + const findJSDocComment = astNode => { + const tokenBefore = this.getTokenBefore(astNode, { includeComments: true }); + + if ( + tokenBefore && + isCommentToken(tokenBefore) && + tokenBefore.type === "Block" && + tokenBefore.value.charAt(0) === "*" && + astNode.loc.start.line - tokenBefore.loc.end.line <= 1 + ) { + return tokenBefore; + } + + return null; + }; + let parent = node.parent; + + switch (node.type) { + case "ClassDeclaration": + case "FunctionDeclaration": + return findJSDocComment(looksLikeExport(parent) ? parent : node); + + case "ClassExpression": + return findJSDocComment(parent.parent); + + case "ArrowFunctionExpression": + case "FunctionExpression": + if (parent.type !== "CallExpression" && parent.type !== "NewExpression") { + while ( + !this.getCommentsBefore(parent).length && + !/Function/u.test(parent.type) && + parent.type !== "MethodDefinition" && + parent.type !== "Property" + ) { + parent = parent.parent; + + if (!parent) { + break; + } + } + + if (parent && parent.type !== "FunctionDeclaration" && parent.type !== "Program") { + return findJSDocComment(parent); + } + } + + return findJSDocComment(node); + + // falls through + default: + return null; + } + } + + /** + * Gets the deepest node containing a range index. + * @param {int} index Range index of the desired node. + * @returns {ASTNode} The node if found or null if not found. + * @public + */ + getNodeByRangeIndex(index) { + let result = null; + + Traverser.traverse(this.ast, { + visitorKeys: this.visitorKeys, + enter(node) { + if (node.range[0] <= index && index < node.range[1]) { + result = node; + } else { + this.skip(); + } + }, + leave(node) { + if (node === result) { + this.break(); + } + } + }); + + return result; + } + + /** + * Determines if two nodes or tokens have at least one whitespace character + * between them. Order does not matter. Returns false if the given nodes or + * tokens overlap. + * @param {ASTNode|Token} first The first node or token to check between. + * @param {ASTNode|Token} second The second node or token to check between. + * @returns {boolean} True if there is a whitespace character between + * any of the tokens found between the two given nodes or tokens. + * @public + */ + isSpaceBetween(first, second) { + return isSpaceBetween(this, first, second, false); + } + + /** + * Determines if two nodes or tokens have at least one whitespace character + * between them. Order does not matter. Returns false if the given nodes or + * tokens overlap. + * For backward compatibility, this method returns true if there are + * `JSXText` tokens that contain whitespaces between the two. + * @param {ASTNode|Token} first The first node or token to check between. + * @param {ASTNode|Token} second The second node or token to check between. + * @returns {boolean} True if there is a whitespace character between + * any of the tokens found between the two given nodes or tokens. + * @deprecated in favor of isSpaceBetween(). + * @public + */ + isSpaceBetweenTokens(first, second) { + return isSpaceBetween(this, first, second, true); + } + + /** + * Converts a source text index into a (line, column) pair. + * @param {number} index The index of a character in a file + * @returns {Object} A {line, column} location object with a 0-indexed column + * @public + */ + getLocFromIndex(index) { + if (typeof index !== "number") { + throw new TypeError("Expected `index` to be a number."); + } + + if (index < 0 || index > this.text.length) { + throw new RangeError(`Index out of range (requested index ${index}, but source text has length ${this.text.length}).`); + } + + /* + * For an argument of this.text.length, return the location one "spot" past the last character + * of the file. If the last character is a linebreak, the location will be column 0 of the next + * line; otherwise, the location will be in the next column on the same line. + * + * See getIndexFromLoc for the motivation for this special case. + */ + if (index === this.text.length) { + return { line: this.lines.length, column: this.lines[this.lines.length - 1].length }; + } + + /* + * To figure out which line rangeIndex is on, determine the last index at which rangeIndex could + * be inserted into lineIndices to keep the list sorted. + */ + const lineNumber = lodash.sortedLastIndex(this.lineStartIndices, index); + + return { line: lineNumber, column: index - this.lineStartIndices[lineNumber - 1] }; + } + + /** + * Converts a (line, column) pair into a range index. + * @param {Object} loc A line/column location + * @param {number} loc.line The line number of the location (1-indexed) + * @param {number} loc.column The column number of the location (0-indexed) + * @returns {number} The range index of the location in the file. + * @public + */ + getIndexFromLoc(loc) { + if (typeof loc !== "object" || typeof loc.line !== "number" || typeof loc.column !== "number") { + throw new TypeError("Expected `loc` to be an object with numeric `line` and `column` properties."); + } + + if (loc.line <= 0) { + throw new RangeError(`Line number out of range (line ${loc.line} requested). Line numbers should be 1-based.`); + } + + if (loc.line > this.lineStartIndices.length) { + throw new RangeError(`Line number out of range (line ${loc.line} requested, but only ${this.lineStartIndices.length} lines present).`); + } + + const lineStartIndex = this.lineStartIndices[loc.line - 1]; + const lineEndIndex = loc.line === this.lineStartIndices.length ? this.text.length : this.lineStartIndices[loc.line]; + const positionIndex = lineStartIndex + loc.column; + + /* + * By design, getIndexFromLoc({ line: lineNum, column: 0 }) should return the start index of + * the given line, provided that the line number is valid element of this.lines. Since the + * last element of this.lines is an empty string for files with trailing newlines, add a + * special case where getting the index for the first location after the end of the file + * will return the length of the file, rather than throwing an error. This allows rules to + * use getIndexFromLoc consistently without worrying about edge cases at the end of a file. + */ + if ( + loc.line === this.lineStartIndices.length && positionIndex > lineEndIndex || + loc.line < this.lineStartIndices.length && positionIndex >= lineEndIndex + ) { + throw new RangeError(`Column number out of range (column ${loc.column} requested, but the length of line ${loc.line} is ${lineEndIndex - lineStartIndex}).`); + } + + return positionIndex; + } +} + +module.exports = SourceCode; diff --git a/eslint/lib/source-code/token-store/backward-token-comment-cursor.js b/eslint/lib/source-code/token-store/backward-token-comment-cursor.js new file mode 100644 index 0000000..7255a62 --- /dev/null +++ b/eslint/lib/source-code/token-store/backward-token-comment-cursor.js @@ -0,0 +1,57 @@ +/** + * @fileoverview Define the cursor which iterates tokens and comments in reverse. + * @author Toru Nagashima + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const Cursor = require("./cursor"); +const utils = require("./utils"); + +//------------------------------------------------------------------------------ +// Exports +//------------------------------------------------------------------------------ + +/** + * The cursor which iterates tokens and comments in reverse. + */ +module.exports = class BackwardTokenCommentCursor extends Cursor { + + /** + * Initializes this cursor. + * @param {Token[]} tokens The array of tokens. + * @param {Comment[]} comments The array of comments. + * @param {Object} indexMap The map from locations to indices in `tokens`. + * @param {number} startLoc The start location of the iteration range. + * @param {number} endLoc The end location of the iteration range. + */ + constructor(tokens, comments, indexMap, startLoc, endLoc) { + super(); + this.tokens = tokens; + this.comments = comments; + this.tokenIndex = utils.getLastIndex(tokens, indexMap, endLoc); + this.commentIndex = utils.search(comments, endLoc) - 1; + this.border = startLoc; + } + + /** @inheritdoc */ + moveNext() { + const token = (this.tokenIndex >= 0) ? this.tokens[this.tokenIndex] : null; + const comment = (this.commentIndex >= 0) ? this.comments[this.commentIndex] : null; + + if (token && (!comment || token.range[1] > comment.range[1])) { + this.current = token; + this.tokenIndex -= 1; + } else if (comment) { + this.current = comment; + this.commentIndex -= 1; + } else { + this.current = null; + } + + return Boolean(this.current) && (this.border === -1 || this.current.range[0] >= this.border); + } +}; diff --git a/eslint/lib/source-code/token-store/backward-token-cursor.js b/eslint/lib/source-code/token-store/backward-token-cursor.js new file mode 100644 index 0000000..454a244 --- /dev/null +++ b/eslint/lib/source-code/token-store/backward-token-cursor.js @@ -0,0 +1,58 @@ +/** + * @fileoverview Define the cursor which iterates tokens only in reverse. + * @author Toru Nagashima + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const Cursor = require("./cursor"); +const utils = require("./utils"); + +//------------------------------------------------------------------------------ +// Exports +//------------------------------------------------------------------------------ + +/** + * The cursor which iterates tokens only in reverse. + */ +module.exports = class BackwardTokenCursor extends Cursor { + + /** + * Initializes this cursor. + * @param {Token[]} tokens The array of tokens. + * @param {Comment[]} comments The array of comments. + * @param {Object} indexMap The map from locations to indices in `tokens`. + * @param {number} startLoc The start location of the iteration range. + * @param {number} endLoc The end location of the iteration range. + */ + constructor(tokens, comments, indexMap, startLoc, endLoc) { + super(); + this.tokens = tokens; + this.index = utils.getLastIndex(tokens, indexMap, endLoc); + this.indexEnd = utils.getFirstIndex(tokens, indexMap, startLoc); + } + + /** @inheritdoc */ + moveNext() { + if (this.index >= this.indexEnd) { + this.current = this.tokens[this.index]; + this.index -= 1; + return true; + } + return false; + } + + /* + * + * Shorthand for performance. + * + */ + + /** @inheritdoc */ + getOneToken() { + return (this.index >= this.indexEnd) ? this.tokens[this.index] : null; + } +}; diff --git a/eslint/lib/source-code/token-store/cursor.js b/eslint/lib/source-code/token-store/cursor.js new file mode 100644 index 0000000..4e1595c --- /dev/null +++ b/eslint/lib/source-code/token-store/cursor.js @@ -0,0 +1,76 @@ +/** + * @fileoverview Define the abstract class about cursors which iterate tokens. + * @author Toru Nagashima + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Exports +//------------------------------------------------------------------------------ + +/** + * The abstract class about cursors which iterate tokens. + * + * This class has 2 abstract methods. + * + * - `current: Token | Comment | null` ... The current token. + * - `moveNext(): boolean` ... Moves this cursor to the next token. If the next token didn't exist, it returns `false`. + * + * This is similar to ES2015 Iterators. + * However, Iterators were slow (at 2017-01), so I created this class as similar to C# IEnumerable. + * + * There are the following known sub classes. + * + * - ForwardTokenCursor .......... The cursor which iterates tokens only. + * - BackwardTokenCursor ......... The cursor which iterates tokens only in reverse. + * - ForwardTokenCommentCursor ... The cursor which iterates tokens and comments. + * - BackwardTokenCommentCursor .. The cursor which iterates tokens and comments in reverse. + * - DecorativeCursor + * - FilterCursor ............ The cursor which ignores the specified tokens. + * - SkipCursor .............. The cursor which ignores the first few tokens. + * - LimitCursor ............. The cursor which limits the count of tokens. + * + */ +module.exports = class Cursor { + + /** + * Initializes this cursor. + */ + constructor() { + this.current = null; + } + + /** + * Gets the first token. + * This consumes this cursor. + * @returns {Token|Comment} The first token or null. + */ + getOneToken() { + return this.moveNext() ? this.current : null; + } + + /** + * Gets the first tokens. + * This consumes this cursor. + * @returns {(Token|Comment)[]} All tokens. + */ + getAllTokens() { + const tokens = []; + + while (this.moveNext()) { + tokens.push(this.current); + } + + return tokens; + } + + /** + * Moves this cursor to the next token. + * @returns {boolean} `true` if the next token exists. + * @abstract + */ + /* istanbul ignore next */ + moveNext() { // eslint-disable-line class-methods-use-this + throw new Error("Not implemented."); + } +}; diff --git a/eslint/lib/source-code/token-store/cursors.js b/eslint/lib/source-code/token-store/cursors.js new file mode 100644 index 0000000..30c72b6 --- /dev/null +++ b/eslint/lib/source-code/token-store/cursors.js @@ -0,0 +1,90 @@ +/** + * @fileoverview Define 2 token factories; forward and backward. + * @author Toru Nagashima + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const BackwardTokenCommentCursor = require("./backward-token-comment-cursor"); +const BackwardTokenCursor = require("./backward-token-cursor"); +const FilterCursor = require("./filter-cursor"); +const ForwardTokenCommentCursor = require("./forward-token-comment-cursor"); +const ForwardTokenCursor = require("./forward-token-cursor"); +const LimitCursor = require("./limit-cursor"); +const SkipCursor = require("./skip-cursor"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * The cursor factory. + * @private + */ +class CursorFactory { + + /** + * Initializes this cursor. + * @param {Function} TokenCursor The class of the cursor which iterates tokens only. + * @param {Function} TokenCommentCursor The class of the cursor which iterates the mix of tokens and comments. + */ + constructor(TokenCursor, TokenCommentCursor) { + this.TokenCursor = TokenCursor; + this.TokenCommentCursor = TokenCommentCursor; + } + + /** + * Creates a base cursor instance that can be decorated by createCursor. + * @param {Token[]} tokens The array of tokens. + * @param {Comment[]} comments The array of comments. + * @param {Object} indexMap The map from locations to indices in `tokens`. + * @param {number} startLoc The start location of the iteration range. + * @param {number} endLoc The end location of the iteration range. + * @param {boolean} includeComments The flag to iterate comments as well. + * @returns {Cursor} The created base cursor. + */ + createBaseCursor(tokens, comments, indexMap, startLoc, endLoc, includeComments) { + const Cursor = includeComments ? this.TokenCommentCursor : this.TokenCursor; + + return new Cursor(tokens, comments, indexMap, startLoc, endLoc); + } + + /** + * Creates a cursor that iterates tokens with normalized options. + * @param {Token[]} tokens The array of tokens. + * @param {Comment[]} comments The array of comments. + * @param {Object} indexMap The map from locations to indices in `tokens`. + * @param {number} startLoc The start location of the iteration range. + * @param {number} endLoc The end location of the iteration range. + * @param {boolean} includeComments The flag to iterate comments as well. + * @param {Function|null} filter The predicate function to choose tokens. + * @param {number} skip The count of tokens the cursor skips. + * @param {number} count The maximum count of tokens the cursor iterates. Zero is no iteration for backward compatibility. + * @returns {Cursor} The created cursor. + */ + createCursor(tokens, comments, indexMap, startLoc, endLoc, includeComments, filter, skip, count) { + let cursor = this.createBaseCursor(tokens, comments, indexMap, startLoc, endLoc, includeComments); + + if (filter) { + cursor = new FilterCursor(cursor, filter); + } + if (skip >= 1) { + cursor = new SkipCursor(cursor, skip); + } + if (count >= 0) { + cursor = new LimitCursor(cursor, count); + } + + return cursor; + } +} + +//------------------------------------------------------------------------------ +// Exports +//------------------------------------------------------------------------------ + +exports.forward = new CursorFactory(ForwardTokenCursor, ForwardTokenCommentCursor); +exports.backward = new CursorFactory(BackwardTokenCursor, BackwardTokenCommentCursor); diff --git a/eslint/lib/source-code/token-store/decorative-cursor.js b/eslint/lib/source-code/token-store/decorative-cursor.js new file mode 100644 index 0000000..3ee7b0b --- /dev/null +++ b/eslint/lib/source-code/token-store/decorative-cursor.js @@ -0,0 +1,39 @@ +/** + * @fileoverview Define the abstract class about cursors which manipulate another cursor. + * @author Toru Nagashima + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const Cursor = require("./cursor"); + +//------------------------------------------------------------------------------ +// Exports +//------------------------------------------------------------------------------ + +/** + * The abstract class about cursors which manipulate another cursor. + */ +module.exports = class DecorativeCursor extends Cursor { + + /** + * Initializes this cursor. + * @param {Cursor} cursor The cursor to be decorated. + */ + constructor(cursor) { + super(); + this.cursor = cursor; + } + + /** @inheritdoc */ + moveNext() { + const retv = this.cursor.moveNext(); + + this.current = this.cursor.current; + + return retv; + } +}; diff --git a/eslint/lib/source-code/token-store/filter-cursor.js b/eslint/lib/source-code/token-store/filter-cursor.js new file mode 100644 index 0000000..08c4f22 --- /dev/null +++ b/eslint/lib/source-code/token-store/filter-cursor.js @@ -0,0 +1,43 @@ +/** + * @fileoverview Define the cursor which ignores specified tokens. + * @author Toru Nagashima + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const DecorativeCursor = require("./decorative-cursor"); + +//------------------------------------------------------------------------------ +// Exports +//------------------------------------------------------------------------------ + +/** + * The decorative cursor which ignores specified tokens. + */ +module.exports = class FilterCursor extends DecorativeCursor { + + /** + * Initializes this cursor. + * @param {Cursor} cursor The cursor to be decorated. + * @param {Function} predicate The predicate function to decide tokens this cursor iterates. + */ + constructor(cursor, predicate) { + super(cursor); + this.predicate = predicate; + } + + /** @inheritdoc */ + moveNext() { + const predicate = this.predicate; + + while (super.moveNext()) { + if (predicate(this.current)) { + return true; + } + } + return false; + } +}; diff --git a/eslint/lib/source-code/token-store/forward-token-comment-cursor.js b/eslint/lib/source-code/token-store/forward-token-comment-cursor.js new file mode 100644 index 0000000..50c7a39 --- /dev/null +++ b/eslint/lib/source-code/token-store/forward-token-comment-cursor.js @@ -0,0 +1,57 @@ +/** + * @fileoverview Define the cursor which iterates tokens and comments. + * @author Toru Nagashima + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const Cursor = require("./cursor"); +const utils = require("./utils"); + +//------------------------------------------------------------------------------ +// Exports +//------------------------------------------------------------------------------ + +/** + * The cursor which iterates tokens and comments. + */ +module.exports = class ForwardTokenCommentCursor extends Cursor { + + /** + * Initializes this cursor. + * @param {Token[]} tokens The array of tokens. + * @param {Comment[]} comments The array of comments. + * @param {Object} indexMap The map from locations to indices in `tokens`. + * @param {number} startLoc The start location of the iteration range. + * @param {number} endLoc The end location of the iteration range. + */ + constructor(tokens, comments, indexMap, startLoc, endLoc) { + super(); + this.tokens = tokens; + this.comments = comments; + this.tokenIndex = utils.getFirstIndex(tokens, indexMap, startLoc); + this.commentIndex = utils.search(comments, startLoc); + this.border = endLoc; + } + + /** @inheritdoc */ + moveNext() { + const token = (this.tokenIndex < this.tokens.length) ? this.tokens[this.tokenIndex] : null; + const comment = (this.commentIndex < this.comments.length) ? this.comments[this.commentIndex] : null; + + if (token && (!comment || token.range[0] < comment.range[0])) { + this.current = token; + this.tokenIndex += 1; + } else if (comment) { + this.current = comment; + this.commentIndex += 1; + } else { + this.current = null; + } + + return Boolean(this.current) && (this.border === -1 || this.current.range[1] <= this.border); + } +}; diff --git a/eslint/lib/source-code/token-store/forward-token-cursor.js b/eslint/lib/source-code/token-store/forward-token-cursor.js new file mode 100644 index 0000000..e8c1860 --- /dev/null +++ b/eslint/lib/source-code/token-store/forward-token-cursor.js @@ -0,0 +1,63 @@ +/** + * @fileoverview Define the cursor which iterates tokens only. + * @author Toru Nagashima + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const Cursor = require("./cursor"); +const utils = require("./utils"); + +//------------------------------------------------------------------------------ +// Exports +//------------------------------------------------------------------------------ + +/** + * The cursor which iterates tokens only. + */ +module.exports = class ForwardTokenCursor extends Cursor { + + /** + * Initializes this cursor. + * @param {Token[]} tokens The array of tokens. + * @param {Comment[]} comments The array of comments. + * @param {Object} indexMap The map from locations to indices in `tokens`. + * @param {number} startLoc The start location of the iteration range. + * @param {number} endLoc The end location of the iteration range. + */ + constructor(tokens, comments, indexMap, startLoc, endLoc) { + super(); + this.tokens = tokens; + this.index = utils.getFirstIndex(tokens, indexMap, startLoc); + this.indexEnd = utils.getLastIndex(tokens, indexMap, endLoc); + } + + /** @inheritdoc */ + moveNext() { + if (this.index <= this.indexEnd) { + this.current = this.tokens[this.index]; + this.index += 1; + return true; + } + return false; + } + + /* + * + * Shorthand for performance. + * + */ + + /** @inheritdoc */ + getOneToken() { + return (this.index <= this.indexEnd) ? this.tokens[this.index] : null; + } + + /** @inheritdoc */ + getAllTokens() { + return this.tokens.slice(this.index, this.indexEnd + 1); + } +}; diff --git a/eslint/lib/source-code/token-store/index.js b/eslint/lib/source-code/token-store/index.js new file mode 100644 index 0000000..25db8a4 --- /dev/null +++ b/eslint/lib/source-code/token-store/index.js @@ -0,0 +1,627 @@ +/** + * @fileoverview Object to handle access and retrieval of tokens. + * @author Brandon Mills + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const assert = require("assert"); +const { isCommentToken } = require("eslint-utils"); +const cursors = require("./cursors"); +const ForwardTokenCursor = require("./forward-token-cursor"); +const PaddedTokenCursor = require("./padded-token-cursor"); +const utils = require("./utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const TOKENS = Symbol("tokens"); +const COMMENTS = Symbol("comments"); +const INDEX_MAP = Symbol("indexMap"); + +/** + * Creates the map from locations to indices in `tokens`. + * + * The first/last location of tokens is mapped to the index of the token. + * The first/last location of comments is mapped to the index of the next token of each comment. + * @param {Token[]} tokens The array of tokens. + * @param {Comment[]} comments The array of comments. + * @returns {Object} The map from locations to indices in `tokens`. + * @private + */ +function createIndexMap(tokens, comments) { + const map = Object.create(null); + let tokenIndex = 0; + let commentIndex = 0; + let nextStart = 0; + let range = null; + + while (tokenIndex < tokens.length || commentIndex < comments.length) { + nextStart = (commentIndex < comments.length) ? comments[commentIndex].range[0] : Number.MAX_SAFE_INTEGER; + while (tokenIndex < tokens.length && (range = tokens[tokenIndex].range)[0] < nextStart) { + map[range[0]] = tokenIndex; + map[range[1] - 1] = tokenIndex; + tokenIndex += 1; + } + + nextStart = (tokenIndex < tokens.length) ? tokens[tokenIndex].range[0] : Number.MAX_SAFE_INTEGER; + while (commentIndex < comments.length && (range = comments[commentIndex].range)[0] < nextStart) { + map[range[0]] = tokenIndex; + map[range[1] - 1] = tokenIndex; + commentIndex += 1; + } + } + + return map; +} + +/** + * Creates the cursor iterates tokens with options. + * @param {CursorFactory} factory The cursor factory to initialize cursor. + * @param {Token[]} tokens The array of tokens. + * @param {Comment[]} comments The array of comments. + * @param {Object} indexMap The map from locations to indices in `tokens`. + * @param {number} startLoc The start location of the iteration range. + * @param {number} endLoc The end location of the iteration range. + * @param {number|Function|Object} [opts=0] The option object. If this is a number then it's `opts.skip`. If this is a function then it's `opts.filter`. + * @param {boolean} [opts.includeComments=false] The flag to iterate comments as well. + * @param {Function|null} [opts.filter=null] The predicate function to choose tokens. + * @param {number} [opts.skip=0] The count of tokens the cursor skips. + * @returns {Cursor} The created cursor. + * @private + */ +function createCursorWithSkip(factory, tokens, comments, indexMap, startLoc, endLoc, opts) { + let includeComments = false; + let skip = 0; + let filter = null; + + if (typeof opts === "number") { + skip = opts | 0; + } else if (typeof opts === "function") { + filter = opts; + } else if (opts) { + includeComments = !!opts.includeComments; + skip = opts.skip | 0; + filter = opts.filter || null; + } + assert(skip >= 0, "options.skip should be zero or a positive integer."); + assert(!filter || typeof filter === "function", "options.filter should be a function."); + + return factory.createCursor(tokens, comments, indexMap, startLoc, endLoc, includeComments, filter, skip, -1); +} + +/** + * Creates the cursor iterates tokens with options. + * @param {CursorFactory} factory The cursor factory to initialize cursor. + * @param {Token[]} tokens The array of tokens. + * @param {Comment[]} comments The array of comments. + * @param {Object} indexMap The map from locations to indices in `tokens`. + * @param {number} startLoc The start location of the iteration range. + * @param {number} endLoc The end location of the iteration range. + * @param {number|Function|Object} [opts=0] The option object. If this is a number then it's `opts.count`. If this is a function then it's `opts.filter`. + * @param {boolean} [opts.includeComments] The flag to iterate comments as well. + * @param {Function|null} [opts.filter=null] The predicate function to choose tokens. + * @param {number} [opts.count=0] The maximum count of tokens the cursor iterates. Zero is no iteration for backward compatibility. + * @returns {Cursor} The created cursor. + * @private + */ +function createCursorWithCount(factory, tokens, comments, indexMap, startLoc, endLoc, opts) { + let includeComments = false; + let count = 0; + let countExists = false; + let filter = null; + + if (typeof opts === "number") { + count = opts | 0; + countExists = true; + } else if (typeof opts === "function") { + filter = opts; + } else if (opts) { + includeComments = !!opts.includeComments; + count = opts.count | 0; + countExists = typeof opts.count === "number"; + filter = opts.filter || null; + } + assert(count >= 0, "options.count should be zero or a positive integer."); + assert(!filter || typeof filter === "function", "options.filter should be a function."); + + return factory.createCursor(tokens, comments, indexMap, startLoc, endLoc, includeComments, filter, 0, countExists ? count : -1); +} + +/** + * Creates the cursor iterates tokens with options. + * This is overload function of the below. + * @param {Token[]} tokens The array of tokens. + * @param {Comment[]} comments The array of comments. + * @param {Object} indexMap The map from locations to indices in `tokens`. + * @param {number} startLoc The start location of the iteration range. + * @param {number} endLoc The end location of the iteration range. + * @param {Function|Object} opts The option object. If this is a function then it's `opts.filter`. + * @param {boolean} [opts.includeComments] The flag to iterate comments as well. + * @param {Function|null} [opts.filter=null] The predicate function to choose tokens. + * @param {number} [opts.count=0] The maximum count of tokens the cursor iterates. Zero is no iteration for backward compatibility. + * @returns {Cursor} The created cursor. + * @private + */ +/** + * Creates the cursor iterates tokens with options. + * @param {Token[]} tokens The array of tokens. + * @param {Comment[]} comments The array of comments. + * @param {Object} indexMap The map from locations to indices in `tokens`. + * @param {number} startLoc The start location of the iteration range. + * @param {number} endLoc The end location of the iteration range. + * @param {number} [beforeCount=0] The number of tokens before the node to retrieve. + * @param {boolean} [afterCount=0] The number of tokens after the node to retrieve. + * @returns {Cursor} The created cursor. + * @private + */ +function createCursorWithPadding(tokens, comments, indexMap, startLoc, endLoc, beforeCount, afterCount) { + if (typeof beforeCount === "undefined" && typeof afterCount === "undefined") { + return new ForwardTokenCursor(tokens, comments, indexMap, startLoc, endLoc); + } + if (typeof beforeCount === "number" || typeof beforeCount === "undefined") { + return new PaddedTokenCursor(tokens, comments, indexMap, startLoc, endLoc, beforeCount | 0, afterCount | 0); + } + return createCursorWithCount(cursors.forward, tokens, comments, indexMap, startLoc, endLoc, beforeCount); +} + +/** + * Gets comment tokens that are adjacent to the current cursor position. + * @param {Cursor} cursor A cursor instance. + * @returns {Array} An array of comment tokens adjacent to the current cursor position. + * @private + */ +function getAdjacentCommentTokensFromCursor(cursor) { + const tokens = []; + let currentToken = cursor.getOneToken(); + + while (currentToken && isCommentToken(currentToken)) { + tokens.push(currentToken); + currentToken = cursor.getOneToken(); + } + + return tokens; +} + +//------------------------------------------------------------------------------ +// Exports +//------------------------------------------------------------------------------ + +/** + * The token store. + * + * This class provides methods to get tokens by locations as fast as possible. + * The methods are a part of public API, so we should be careful if it changes this class. + * + * People can get tokens in O(1) by the hash map which is mapping from the location of tokens/comments to tokens. + * Also people can get a mix of tokens and comments in O(log k), the k is the number of comments. + * Assuming that comments to be much fewer than tokens, this does not make hash map from token's locations to comments to reduce memory cost. + * This uses binary-searching instead for comments. + */ +module.exports = class TokenStore { + + /** + * Initializes this token store. + * @param {Token[]} tokens The array of tokens. + * @param {Comment[]} comments The array of comments. + */ + constructor(tokens, comments) { + this[TOKENS] = tokens; + this[COMMENTS] = comments; + this[INDEX_MAP] = createIndexMap(tokens, comments); + } + + //-------------------------------------------------------------------------- + // Gets single token. + //-------------------------------------------------------------------------- + + /** + * Gets the token starting at the specified index. + * @param {number} offset Index of the start of the token's range. + * @param {Object} [options=0] The option object. + * @param {boolean} [options.includeComments=false] The flag to iterate comments as well. + * @returns {Token|null} The token starting at index, or null if no such token. + */ + getTokenByRangeStart(offset, options) { + const includeComments = options && options.includeComments; + const token = cursors.forward.createBaseCursor( + this[TOKENS], + this[COMMENTS], + this[INDEX_MAP], + offset, + -1, + includeComments + ).getOneToken(); + + if (token && token.range[0] === offset) { + return token; + } + return null; + } + + /** + * Gets the first token of the given node. + * @param {ASTNode} node The AST node. + * @param {number|Function|Object} [options=0] The option object. If this is a number then it's `options.skip`. If this is a function then it's `options.filter`. + * @param {boolean} [options.includeComments=false] The flag to iterate comments as well. + * @param {Function|null} [options.filter=null] The predicate function to choose tokens. + * @param {number} [options.skip=0] The count of tokens the cursor skips. + * @returns {Token|null} An object representing the token. + */ + getFirstToken(node, options) { + return createCursorWithSkip( + cursors.forward, + this[TOKENS], + this[COMMENTS], + this[INDEX_MAP], + node.range[0], + node.range[1], + options + ).getOneToken(); + } + + /** + * Gets the last token of the given node. + * @param {ASTNode} node The AST node. + * @param {number|Function|Object} [options=0] The option object. Same options as getFirstToken() + * @returns {Token|null} An object representing the token. + */ + getLastToken(node, options) { + return createCursorWithSkip( + cursors.backward, + this[TOKENS], + this[COMMENTS], + this[INDEX_MAP], + node.range[0], + node.range[1], + options + ).getOneToken(); + } + + /** + * Gets the token that precedes a given node or token. + * @param {ASTNode|Token|Comment} node The AST node or token. + * @param {number|Function|Object} [options=0] The option object. Same options as getFirstToken() + * @returns {Token|null} An object representing the token. + */ + getTokenBefore(node, options) { + return createCursorWithSkip( + cursors.backward, + this[TOKENS], + this[COMMENTS], + this[INDEX_MAP], + -1, + node.range[0], + options + ).getOneToken(); + } + + /** + * Gets the token that follows a given node or token. + * @param {ASTNode|Token|Comment} node The AST node or token. + * @param {number|Function|Object} [options=0] The option object. Same options as getFirstToken() + * @returns {Token|null} An object representing the token. + */ + getTokenAfter(node, options) { + return createCursorWithSkip( + cursors.forward, + this[TOKENS], + this[COMMENTS], + this[INDEX_MAP], + node.range[1], + -1, + options + ).getOneToken(); + } + + /** + * Gets the first token between two non-overlapping nodes. + * @param {ASTNode|Token|Comment} left Node before the desired token range. + * @param {ASTNode|Token|Comment} right Node after the desired token range. + * @param {number|Function|Object} [options=0] The option object. Same options as getFirstToken() + * @returns {Token|null} An object representing the token. + */ + getFirstTokenBetween(left, right, options) { + return createCursorWithSkip( + cursors.forward, + this[TOKENS], + this[COMMENTS], + this[INDEX_MAP], + left.range[1], + right.range[0], + options + ).getOneToken(); + } + + /** + * Gets the last token between two non-overlapping nodes. + * @param {ASTNode|Token|Comment} left Node before the desired token range. + * @param {ASTNode|Token|Comment} right Node after the desired token range. + * @param {number|Function|Object} [options=0] The option object. Same options as getFirstToken() + * @returns {Token|null} An object representing the token. + */ + getLastTokenBetween(left, right, options) { + return createCursorWithSkip( + cursors.backward, + this[TOKENS], + this[COMMENTS], + this[INDEX_MAP], + left.range[1], + right.range[0], + options + ).getOneToken(); + } + + /** + * Gets the token that precedes a given node or token in the token stream. + * This is defined for backward compatibility. Use `includeComments` option instead. + * TODO: We have a plan to remove this in a future major version. + * @param {ASTNode|Token|Comment} node The AST node or token. + * @param {number} [skip=0] A number of tokens to skip. + * @returns {Token|null} An object representing the token. + * @deprecated + */ + getTokenOrCommentBefore(node, skip) { + return this.getTokenBefore(node, { includeComments: true, skip }); + } + + /** + * Gets the token that follows a given node or token in the token stream. + * This is defined for backward compatibility. Use `includeComments` option instead. + * TODO: We have a plan to remove this in a future major version. + * @param {ASTNode|Token|Comment} node The AST node or token. + * @param {number} [skip=0] A number of tokens to skip. + * @returns {Token|null} An object representing the token. + * @deprecated + */ + getTokenOrCommentAfter(node, skip) { + return this.getTokenAfter(node, { includeComments: true, skip }); + } + + //-------------------------------------------------------------------------- + // Gets multiple tokens. + //-------------------------------------------------------------------------- + + /** + * Gets the first `count` tokens of the given node. + * @param {ASTNode} node The AST node. + * @param {number|Function|Object} [options=0] The option object. If this is a number then it's `options.count`. If this is a function then it's `options.filter`. + * @param {boolean} [options.includeComments=false] The flag to iterate comments as well. + * @param {Function|null} [options.filter=null] The predicate function to choose tokens. + * @param {number} [options.count=0] The maximum count of tokens the cursor iterates. + * @returns {Token[]} Tokens. + */ + getFirstTokens(node, options) { + return createCursorWithCount( + cursors.forward, + this[TOKENS], + this[COMMENTS], + this[INDEX_MAP], + node.range[0], + node.range[1], + options + ).getAllTokens(); + } + + /** + * Gets the last `count` tokens of the given node. + * @param {ASTNode} node The AST node. + * @param {number|Function|Object} [options=0] The option object. Same options as getFirstTokens() + * @returns {Token[]} Tokens. + */ + getLastTokens(node, options) { + return createCursorWithCount( + cursors.backward, + this[TOKENS], + this[COMMENTS], + this[INDEX_MAP], + node.range[0], + node.range[1], + options + ).getAllTokens().reverse(); + } + + /** + * Gets the `count` tokens that precedes a given node or token. + * @param {ASTNode|Token|Comment} node The AST node or token. + * @param {number|Function|Object} [options=0] The option object. Same options as getFirstTokens() + * @returns {Token[]} Tokens. + */ + getTokensBefore(node, options) { + return createCursorWithCount( + cursors.backward, + this[TOKENS], + this[COMMENTS], + this[INDEX_MAP], + -1, + node.range[0], + options + ).getAllTokens().reverse(); + } + + /** + * Gets the `count` tokens that follows a given node or token. + * @param {ASTNode|Token|Comment} node The AST node or token. + * @param {number|Function|Object} [options=0] The option object. Same options as getFirstTokens() + * @returns {Token[]} Tokens. + */ + getTokensAfter(node, options) { + return createCursorWithCount( + cursors.forward, + this[TOKENS], + this[COMMENTS], + this[INDEX_MAP], + node.range[1], + -1, + options + ).getAllTokens(); + } + + /** + * Gets the first `count` tokens between two non-overlapping nodes. + * @param {ASTNode|Token|Comment} left Node before the desired token range. + * @param {ASTNode|Token|Comment} right Node after the desired token range. + * @param {number|Function|Object} [options=0] The option object. Same options as getFirstTokens() + * @returns {Token[]} Tokens between left and right. + */ + getFirstTokensBetween(left, right, options) { + return createCursorWithCount( + cursors.forward, + this[TOKENS], + this[COMMENTS], + this[INDEX_MAP], + left.range[1], + right.range[0], + options + ).getAllTokens(); + } + + /** + * Gets the last `count` tokens between two non-overlapping nodes. + * @param {ASTNode|Token|Comment} left Node before the desired token range. + * @param {ASTNode|Token|Comment} right Node after the desired token range. + * @param {number|Function|Object} [options=0] The option object. Same options as getFirstTokens() + * @returns {Token[]} Tokens between left and right. + */ + getLastTokensBetween(left, right, options) { + return createCursorWithCount( + cursors.backward, + this[TOKENS], + this[COMMENTS], + this[INDEX_MAP], + left.range[1], + right.range[0], + options + ).getAllTokens().reverse(); + } + + /** + * Gets all tokens that are related to the given node. + * @param {ASTNode} node The AST node. + * @param {Function|Object} options The option object. If this is a function then it's `options.filter`. + * @param {boolean} [options.includeComments=false] The flag to iterate comments as well. + * @param {Function|null} [options.filter=null] The predicate function to choose tokens. + * @param {number} [options.count=0] The maximum count of tokens the cursor iterates. + * @returns {Token[]} Array of objects representing tokens. + */ + /** + * Gets all tokens that are related to the given node. + * @param {ASTNode} node The AST node. + * @param {int} [beforeCount=0] The number of tokens before the node to retrieve. + * @param {int} [afterCount=0] The number of tokens after the node to retrieve. + * @returns {Token[]} Array of objects representing tokens. + */ + getTokens(node, beforeCount, afterCount) { + return createCursorWithPadding( + this[TOKENS], + this[COMMENTS], + this[INDEX_MAP], + node.range[0], + node.range[1], + beforeCount, + afterCount + ).getAllTokens(); + } + + /** + * Gets all of the tokens between two non-overlapping nodes. + * @param {ASTNode|Token|Comment} left Node before the desired token range. + * @param {ASTNode|Token|Comment} right Node after the desired token range. + * @param {Function|Object} options The option object. If this is a function then it's `options.filter`. + * @param {boolean} [options.includeComments=false] The flag to iterate comments as well. + * @param {Function|null} [options.filter=null] The predicate function to choose tokens. + * @param {number} [options.count=0] The maximum count of tokens the cursor iterates. + * @returns {Token[]} Tokens between left and right. + */ + /** + * Gets all of the tokens between two non-overlapping nodes. + * @param {ASTNode|Token|Comment} left Node before the desired token range. + * @param {ASTNode|Token|Comment} right Node after the desired token range. + * @param {int} [padding=0] Number of extra tokens on either side of center. + * @returns {Token[]} Tokens between left and right. + */ + getTokensBetween(left, right, padding) { + return createCursorWithPadding( + this[TOKENS], + this[COMMENTS], + this[INDEX_MAP], + left.range[1], + right.range[0], + padding, + padding + ).getAllTokens(); + } + + //-------------------------------------------------------------------------- + // Others. + //-------------------------------------------------------------------------- + + /** + * Checks whether any comments exist or not between the given 2 nodes. + * @param {ASTNode} left The node to check. + * @param {ASTNode} right The node to check. + * @returns {boolean} `true` if one or more comments exist. + */ + commentsExistBetween(left, right) { + const index = utils.search(this[COMMENTS], left.range[1]); + + return ( + index < this[COMMENTS].length && + this[COMMENTS][index].range[1] <= right.range[0] + ); + } + + /** + * Gets all comment tokens directly before the given node or token. + * @param {ASTNode|token} nodeOrToken The AST node or token to check for adjacent comment tokens. + * @returns {Array} An array of comments in occurrence order. + */ + getCommentsBefore(nodeOrToken) { + const cursor = createCursorWithCount( + cursors.backward, + this[TOKENS], + this[COMMENTS], + this[INDEX_MAP], + -1, + nodeOrToken.range[0], + { includeComments: true } + ); + + return getAdjacentCommentTokensFromCursor(cursor).reverse(); + } + + /** + * Gets all comment tokens directly after the given node or token. + * @param {ASTNode|token} nodeOrToken The AST node or token to check for adjacent comment tokens. + * @returns {Array} An array of comments in occurrence order. + */ + getCommentsAfter(nodeOrToken) { + const cursor = createCursorWithCount( + cursors.forward, + this[TOKENS], + this[COMMENTS], + this[INDEX_MAP], + nodeOrToken.range[1], + -1, + { includeComments: true } + ); + + return getAdjacentCommentTokensFromCursor(cursor); + } + + /** + * Gets all comment tokens inside the given node. + * @param {ASTNode} node The AST node to get the comments for. + * @returns {Array} An array of comments in occurrence order. + */ + getCommentsInside(node) { + return this.getTokens(node, { + includeComments: true, + filter: isCommentToken + }); + } +}; diff --git a/eslint/lib/source-code/token-store/limit-cursor.js b/eslint/lib/source-code/token-store/limit-cursor.js new file mode 100644 index 0000000..0fd92a7 --- /dev/null +++ b/eslint/lib/source-code/token-store/limit-cursor.js @@ -0,0 +1,40 @@ +/** + * @fileoverview Define the cursor which limits the number of tokens. + * @author Toru Nagashima + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const DecorativeCursor = require("./decorative-cursor"); + +//------------------------------------------------------------------------------ +// Exports +//------------------------------------------------------------------------------ + +/** + * The decorative cursor which limits the number of tokens. + */ +module.exports = class LimitCursor extends DecorativeCursor { + + /** + * Initializes this cursor. + * @param {Cursor} cursor The cursor to be decorated. + * @param {number} count The count of tokens this cursor iterates. + */ + constructor(cursor, count) { + super(cursor); + this.count = count; + } + + /** @inheritdoc */ + moveNext() { + if (this.count > 0) { + this.count -= 1; + return super.moveNext(); + } + return false; + } +}; diff --git a/eslint/lib/source-code/token-store/padded-token-cursor.js b/eslint/lib/source-code/token-store/padded-token-cursor.js new file mode 100644 index 0000000..89349fa --- /dev/null +++ b/eslint/lib/source-code/token-store/padded-token-cursor.js @@ -0,0 +1,38 @@ +/** + * @fileoverview Define the cursor which iterates tokens only, with inflated range. + * @author Toru Nagashima + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const ForwardTokenCursor = require("./forward-token-cursor"); + +//------------------------------------------------------------------------------ +// Exports +//------------------------------------------------------------------------------ + +/** + * The cursor which iterates tokens only, with inflated range. + * This is for the backward compatibility of padding options. + */ +module.exports = class PaddedTokenCursor extends ForwardTokenCursor { + + /** + * Initializes this cursor. + * @param {Token[]} tokens The array of tokens. + * @param {Comment[]} comments The array of comments. + * @param {Object} indexMap The map from locations to indices in `tokens`. + * @param {number} startLoc The start location of the iteration range. + * @param {number} endLoc The end location of the iteration range. + * @param {number} beforeCount The number of tokens this cursor iterates before start. + * @param {number} afterCount The number of tokens this cursor iterates after end. + */ + constructor(tokens, comments, indexMap, startLoc, endLoc, beforeCount, afterCount) { + super(tokens, comments, indexMap, startLoc, endLoc); + this.index = Math.max(0, this.index - beforeCount); + this.indexEnd = Math.min(tokens.length - 1, this.indexEnd + afterCount); + } +}; diff --git a/eslint/lib/source-code/token-store/skip-cursor.js b/eslint/lib/source-code/token-store/skip-cursor.js new file mode 100644 index 0000000..f068f53 --- /dev/null +++ b/eslint/lib/source-code/token-store/skip-cursor.js @@ -0,0 +1,42 @@ +/** + * @fileoverview Define the cursor which ignores the first few tokens. + * @author Toru Nagashima + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const DecorativeCursor = require("./decorative-cursor"); + +//------------------------------------------------------------------------------ +// Exports +//------------------------------------------------------------------------------ + +/** + * The decorative cursor which ignores the first few tokens. + */ +module.exports = class SkipCursor extends DecorativeCursor { + + /** + * Initializes this cursor. + * @param {Cursor} cursor The cursor to be decorated. + * @param {number} count The count of tokens this cursor skips. + */ + constructor(cursor, count) { + super(cursor); + this.count = count; + } + + /** @inheritdoc */ + moveNext() { + while (this.count > 0) { + this.count -= 1; + if (!super.moveNext()) { + return false; + } + } + return super.moveNext(); + } +}; diff --git a/eslint/lib/source-code/token-store/utils.js b/eslint/lib/source-code/token-store/utils.js new file mode 100644 index 0000000..21e1d6f --- /dev/null +++ b/eslint/lib/source-code/token-store/utils.js @@ -0,0 +1,100 @@ +/** + * @fileoverview Define utility functions for token store. + * @author Toru Nagashima + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const lodash = require("lodash"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Gets `token.range[0]` from the given token. + * @param {Node|Token|Comment} token The token to get. + * @returns {number} The start location. + * @private + */ +function getStartLocation(token) { + return token.range[0]; +} + +//------------------------------------------------------------------------------ +// Exports +//------------------------------------------------------------------------------ + +/** + * Binary-searches the index of the first token which is after the given location. + * If it was not found, this returns `tokens.length`. + * @param {(Token|Comment)[]} tokens It searches the token in this list. + * @param {number} location The location to search. + * @returns {number} The found index or `tokens.length`. + */ +exports.search = function search(tokens, location) { + return lodash.sortedIndexBy( + tokens, + { range: [location] }, + getStartLocation + ); +}; + +/** + * Gets the index of the `startLoc` in `tokens`. + * `startLoc` can be the value of `node.range[1]`, so this checks about `startLoc - 1` as well. + * @param {(Token|Comment)[]} tokens The tokens to find an index. + * @param {Object} indexMap The map from locations to indices. + * @param {number} startLoc The location to get an index. + * @returns {number} The index. + */ +exports.getFirstIndex = function getFirstIndex(tokens, indexMap, startLoc) { + if (startLoc in indexMap) { + return indexMap[startLoc]; + } + if ((startLoc - 1) in indexMap) { + const index = indexMap[startLoc - 1]; + const token = (index >= 0 && index < tokens.length) ? tokens[index] : null; + + /* + * For the map of "comment's location -> token's index", it points the next token of a comment. + * In that case, +1 is unnecessary. + */ + if (token && token.range[0] >= startLoc) { + return index; + } + return index + 1; + } + return 0; +}; + +/** + * Gets the index of the `endLoc` in `tokens`. + * The information of end locations are recorded at `endLoc - 1` in `indexMap`, so this checks about `endLoc - 1` as well. + * @param {(Token|Comment)[]} tokens The tokens to find an index. + * @param {Object} indexMap The map from locations to indices. + * @param {number} endLoc The location to get an index. + * @returns {number} The index. + */ +exports.getLastIndex = function getLastIndex(tokens, indexMap, endLoc) { + if (endLoc in indexMap) { + return indexMap[endLoc] - 1; + } + if ((endLoc - 1) in indexMap) { + const index = indexMap[endLoc - 1]; + const token = (index >= 0 && index < tokens.length) ? tokens[index] : null; + + /* + * For the map of "comment's location -> token's index", it points the next token of a comment. + * In that case, -1 is necessary. + */ + if (token && token.range[1] > endLoc) { + return index - 1; + } + return index; + } + return tokens.length - 1; +}; diff --git a/eslint/messages/all-files-ignored.txt b/eslint/messages/all-files-ignored.txt new file mode 100644 index 0000000..3f4c8ce --- /dev/null +++ b/eslint/messages/all-files-ignored.txt @@ -0,0 +1,8 @@ +You are linting "<%= pattern %>", but all of the files matching the glob pattern "<%= pattern %>" are ignored. + +If you don't want to lint these files, remove the pattern "<%= pattern %>" from the list of arguments passed to ESLint. + +If you do want to lint these files, try the following solutions: + +* Check your .eslintignore file, or the eslintIgnore property in package.json, to ensure that the files are not configured to be ignored. +* Explicitly list the files from this glob that you'd like to lint on the command-line, rather than providing a glob as an argument. diff --git a/eslint/messages/extend-config-missing.txt b/eslint/messages/extend-config-missing.txt new file mode 100644 index 0000000..0411819 --- /dev/null +++ b/eslint/messages/extend-config-missing.txt @@ -0,0 +1,5 @@ +ESLint couldn't find the config "<%- configName %>" to extend from. Please check that the name of the config is correct. + +The config "<%- configName %>" was referenced from the config file in "<%- importerName %>". + +If you still have problems, please stop by https://gitter.im/eslint/eslint to chat with the team. diff --git a/eslint/messages/failed-to-read-json.txt b/eslint/messages/failed-to-read-json.txt new file mode 100644 index 0000000..b5e2b86 --- /dev/null +++ b/eslint/messages/failed-to-read-json.txt @@ -0,0 +1,3 @@ +Failed to read JSON file at <%= path %>: + +<%= message %> diff --git a/eslint/messages/file-not-found.txt b/eslint/messages/file-not-found.txt new file mode 100644 index 0000000..639498e --- /dev/null +++ b/eslint/messages/file-not-found.txt @@ -0,0 +1,2 @@ +No files matching the pattern "<%= pattern %>"<% if (globDisabled) { %> (with disabling globs)<% } %> were found. +Please check for typing mistakes in the pattern. diff --git a/eslint/messages/no-config-found.txt b/eslint/messages/no-config-found.txt new file mode 100644 index 0000000..348f6dc --- /dev/null +++ b/eslint/messages/no-config-found.txt @@ -0,0 +1,7 @@ +ESLint couldn't find a configuration file. To set up a configuration file for this project, please run: + + eslint --init + +ESLint looked for configuration files in <%= directoryPath %> and its ancestors. If it found none, it then looked in your home directory. + +If you think you already have a configuration file or if you need more help, please stop by the ESLint chat room: https://gitter.im/eslint/eslint diff --git a/eslint/messages/plugin-conflict.txt b/eslint/messages/plugin-conflict.txt new file mode 100644 index 0000000..6fcf7c8 --- /dev/null +++ b/eslint/messages/plugin-conflict.txt @@ -0,0 +1,7 @@ +ESLint couldn't determine the plugin "<%- pluginId %>" uniquely. +<% for (const { filePath, importerName } of plugins) { %> +- <%= filePath %> (loaded in "<%= importerName %>")<% } %> + +Please remove the "plugins" setting from either config or remove either plugin installation. + +If you still can't figure out the problem, please stop by https://gitter.im/eslint/eslint to chat with the team. diff --git a/eslint/messages/plugin-missing.txt b/eslint/messages/plugin-missing.txt new file mode 100644 index 0000000..32e9f0a --- /dev/null +++ b/eslint/messages/plugin-missing.txt @@ -0,0 +1,11 @@ +ESLint couldn't find the plugin "<%- pluginName %>". + +(The package "<%- pluginName %>" was not found when loaded as a Node module from the directory "<%- resolvePluginsRelativeTo %>".) + +It's likely that the plugin isn't installed correctly. Try reinstalling by running the following: + + npm install <%- pluginName %>@latest --save-dev + +The plugin "<%- pluginName %>" was referenced from the config file in "<%- importerName %>". + +If you still can't figure out the problem, please stop by https://gitter.im/eslint/eslint to chat with the team. diff --git a/eslint/messages/print-config-with-directory-path.txt b/eslint/messages/print-config-with-directory-path.txt new file mode 100644 index 0000000..1afc9b1 --- /dev/null +++ b/eslint/messages/print-config-with-directory-path.txt @@ -0,0 +1,2 @@ +The '--print-config' CLI option requires a path to a source code file rather than a directory. +See also: https://eslint.org/docs/user-guide/command-line-interface#--print-config diff --git a/eslint/messages/whitespace-found.txt b/eslint/messages/whitespace-found.txt new file mode 100644 index 0000000..eea4efc --- /dev/null +++ b/eslint/messages/whitespace-found.txt @@ -0,0 +1,3 @@ +ESLint couldn't find the plugin "<%- pluginName %>". because there is whitespace in the name. Please check your configuration and remove all whitespace from the plugin name. + +If you still can't figure out the problem, please stop by https://gitter.im/eslint/eslint to chat with the team. diff --git a/eslint/package.json b/eslint/package.json new file mode 100644 index 0000000..ed15f61 --- /dev/null +++ b/eslint/package.json @@ -0,0 +1,145 @@ +{ + "name": "eslint", + "version": "7.0.0-alpha.3", + "author": "Nicholas C. Zakas ", + "description": "An AST-based pattern checker for JavaScript.", + "bin": { + "eslint": "./bin/eslint.js" + }, + "main": "./lib/api.js", + "scripts": { + "test": "node Makefile.js test", + "test:cli": "mocha", + "lint": "node Makefile.js lint", + "fix": "node Makefile.js lint -- fix", + "fuzz": "node Makefile.js fuzz", + "generate-release": "node Makefile.js generateRelease", + "generate-alpharelease": "node Makefile.js generatePrerelease -- alpha", + "generate-betarelease": "node Makefile.js generatePrerelease -- beta", + "generate-rcrelease": "node Makefile.js generatePrerelease -- rc", + "publish-release": "node Makefile.js publishRelease", + "docs": "node Makefile.js docs", + "gensite": "node Makefile.js gensite", + "webpack": "node Makefile.js webpack", + "perf": "node Makefile.js perf" + }, + "gitHooks": { + "pre-commit": "lint-staged" + }, + "lint-staged": { + "*.js": [ + "eslint --fix", + "git add" + ], + "*.md": "markdownlint" + }, + "files": [ + "LICENSE", + "README.md", + "bin", + "conf", + "lib", + "messages" + ], + "repository": "eslint/eslint", + "funding": "https://opencollective.com/eslint", + "homepage": "https://eslint.org", + "bugs": "https://github.com/eslint/eslint/issues/", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", + "chalk": "^3.0.0", + "cross-spawn": "^7.0.1", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.2.1", + "esquery": "^1.2.0", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^7.0.0", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.14", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.8.3", + "progress": "^2.0.0", + "regexpp": "^3.0.0", + "semver": "^7.1.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "devDependencies": { + "@babel/core": "^7.4.3", + "@babel/preset-env": "^7.4.3", + "acorn": "^7.1.1", + "babel-loader": "^8.0.5", + "chai": "^4.0.1", + "cheerio": "^0.22.0", + "common-tags": "^1.8.0", + "core-js": "^3.1.3", + "dateformat": "^3.0.3", + "ejs": "^2.6.1", + "eslint": "file:.", + "eslint-config-eslint": "file:packages/eslint-config-eslint", + "eslint-plugin-eslint-plugin": "^2.2.1", + "eslint-plugin-internal-rules": "file:tools/internal-rules", + "eslint-plugin-jsdoc": "^15.9.5", + "eslint-plugin-node": "^9.0.0", + "eslint-release": "^1.2.0", + "eslump": "^2.0.0", + "esprima": "^4.0.1", + "glob": "^7.1.3", + "jsdoc": "^3.5.5", + "karma": "^4.0.1", + "karma-chrome-launcher": "^2.2.0", + "karma-mocha": "^1.3.0", + "karma-mocha-reporter": "^2.2.3", + "karma-webpack": "^4.0.0-rc.6", + "leche": "^2.2.3", + "lint-staged": "^8.1.5", + "load-perf": "^0.2.0", + "markdownlint": "^0.15.0", + "markdownlint-cli": "^0.17.0", + "memfs": "^3.0.1", + "mocha": "^6.1.2", + "mocha-junit-reporter": "^1.23.0", + "npm-license": "^0.3.3", + "nyc": "^14.1.1", + "proxyquire": "^2.0.1", + "puppeteer": "^1.18.0", + "recast": "^0.18.1", + "regenerator-runtime": "^0.13.2", + "shelljs": "^0.8.2", + "sinon": "^7.3.2", + "temp": "^0.9.0", + "webpack": "^4.35.0", + "webpack-cli": "^3.3.5", + "yorkie": "^2.0.0" + }, + "keywords": [ + "ast", + "lint", + "javascript", + "ecmascript", + "espree" + ], + "license": "MIT", + "engines": { + "node": "^10.12.0 || >=12.0.0" + } +} diff --git a/eslint/templates/blogpost.md.ejs b/eslint/templates/blogpost.md.ejs new file mode 100644 index 0000000..a812a79 --- /dev/null +++ b/eslint/templates/blogpost.md.ejs @@ -0,0 +1,108 @@ +--- +layout: post +title: ESLint v<%- version %> released +tags: + - release + - <%- type %> +--- +# ESLint v<%= version %> released + +We just pushed ESLint v<%- version %>, which is a <%- type %> release upgrade of ESLint. This release <% if (type !== "patch") { %>adds some new features and <% } %>fixes several bugs found in the previous release.<% if (type === "major") { %> This release also has some breaking changes, so please read the following closely.<% } %> + +<% +const RULE_REGEX = new RegExp(`\`?\\b(${ruleList.join("|")})\\b\`?`, "g"); + +function linkify(line) { + return line + .replace(/([^\s/]+[/][^\s/]+)?#(\d+)/g, (_, repo, num) => `[${repo || ""}#${num}](https://github.com/${repo || "eslint/eslint"}/issues/${num})`) + .replace(RULE_REGEX, "[$&](/docs/rules/$1)"); +} + +function outputList(items) { + items.forEach(function(line) {%> +<%- linkify(line) %><% + }); +} + +%> + +<% if (prereleaseMajorVersion) { %> +## Highlights + +This is a summary of the major changes you need to know about for this version of ESLint. + +### Installing + +Since this is a pre-release version, you will not automatically be upgraded by npm. You must specify the `next` tag when installing: + +``` +npm i eslint@next --save-dev +``` + +You can also specify the version directly: + +``` +npm i eslint@<%- version %> --save-dev +``` + +### Migration Guide + +As there are a lot of changes, we've created a [migration guide](/docs/<%- prereleaseMajorVersion %>/user-guide/migrating-to-<%- prereleaseMajorVersion %>) describing the changes in great detail along with the steps you should take to address them. We expect that most users should be able to upgrade without any build changes, but the migration guide should be a useful resource if you encounter problems. + +<% } %> + +<% if (typeof changelog.breaking !== "undefined") { %> +## Breaking Changes + +<% outputList(changelog.breaking); %> + +<% } %> + +<% if (typeof changelog.new !== "undefined") { %> +## Features + +<% outputList(changelog.new); %> + +<% } %> + +<% if (typeof changelog.update !== "undefined") { %> +## Enhancements + +<% outputList(changelog.update); %> + +<% } %> + +<% if (typeof changelog.fix !== "undefined") { %> +## Bug Fixes + +<% outputList(changelog.fix); %> + +<% } %> + +<% if (typeof changelog.docs !== "undefined") { %> +## Documentation + +<% outputList(changelog.docs); %> + +<% } %> + +<% if (typeof changelog.upgrade !== "undefined") { %> +## Dependency Upgrades + +<% outputList(changelog.upgrade); %> + +<% } %> + +<% if (typeof changelog.build !== "undefined") { %> +## Build Related + +<% outputList(changelog.build); %> + +<% } %> + +<% if (typeof changelog.chore !== "undefined") { %> +## Chores + +<% outputList(changelog.chore); %> + +<% } %> diff --git a/eslint/templates/bug-report.md b/eslint/templates/bug-report.md new file mode 100644 index 0000000..d31fcde --- /dev/null +++ b/eslint/templates/bug-report.md @@ -0,0 +1,26 @@ +**Tell us about your environment** + +* **ESLint Version:** +* **Node Version:** +* **npm Version:** + +**What parser (default, Babel-ESLint, etc.) are you using?** + +**Please show your full configuration:** + +
+Configuration + + +```js + +``` + +
+ +**What did you do? Please include the actual source code causing the issue.** + +**What did you expect to happen?** + +**What actually happened? Please include the actual, raw output from ESLint.** + diff --git a/eslint/templates/formatter-examples.md.ejs b/eslint/templates/formatter-examples.md.ejs new file mode 100644 index 0000000..3231ea4 --- /dev/null +++ b/eslint/templates/formatter-examples.md.ejs @@ -0,0 +1,59 @@ +--- +title: Documentation +layout: doc +--- +# ESLint Formatters + +ESLint comes with several built-in formatters to control the appearance of the linting results, and supports third-party formatters as well. + +You can specify a formatter using the `--format` or `-f` flag on the command line. For example, `--format codeframe` uses the `codeframe` formatter. + +The built-in formatter options are: + +<% Object.keys(formatterResults).forEach(function(formatterName) { -%> +* [<%= formatterName %>](#<%= formatterName %>) +<% }) -%> + +## Example Source + +Examples of each formatter were created from linting `fullOfProblems.js` using the `.eslintrc` configuration shown below. + +### `fullOfProblems.js` + +```js +function addOne(i) { + if (i != NaN) { + return i ++ + } else { + return + } +}; +``` + +### `.eslintrc`: + +```json +{ + "extends": "eslint:recommended", + "rules": { + "consistent-return": 2, + "indent" : [1, 4], + "no-else-return" : 1, + "semi" : [1, "always"], + "space-unary-ops" : 2 + } +} +``` + +## Output Examples +<% Object.keys(formatterResults).forEach(function(formatterName) { -%> + +### <%= formatterName %> +<% if (formatterName !== "html") { -%> +``` +<%- formatterResults[formatterName].result %> +``` +<% } else {-%> + +<% } -%> +<% }) -%> diff --git a/eslint/templates/rule-change-proposal.md b/eslint/templates/rule-change-proposal.md new file mode 100644 index 0000000..ee10001 --- /dev/null +++ b/eslint/templates/rule-change-proposal.md @@ -0,0 +1,20 @@ +**What rule do you want to change?** + + +**Does this change cause the rule to produce more or fewer warnings?** + + +**How will the change be implemented? (New option, new default behavior, etc.)?** + + +**Please provide some example code that this change will affect:** + +```js + +``` + +**What does the rule currently do for this code?** + + +**What will the rule do after it's changed?** + diff --git a/eslint/templates/rule-proposal.md b/eslint/templates/rule-proposal.md new file mode 100644 index 0000000..383b1b4 --- /dev/null +++ b/eslint/templates/rule-proposal.md @@ -0,0 +1,18 @@ +**Please describe what the rule should do:** + + +**What category of rule is this? (place an "X" next to just one item)** + +[ ] Enforces code style +[ ] Warns about a potential error +[ ] Suggests an alternate way of doing something +[ ] Other (please specify:) + +**Provide 2-3 code examples that this rule will warn about:** + +```js + +``` + +**Why should this rule be included in ESLint (instead of a plugin)?** + diff --git a/eslint/tests/bench/bench.js b/eslint/tests/bench/bench.js new file mode 100644 index 0000000..ad97af7 --- /dev/null +++ b/eslint/tests/bench/bench.js @@ -0,0 +1,38 @@ +var Linter = require("../../lib/linter").Linter, + fs = require("fs"); + +var config = require("../../conf/eslint-recommended"); + +var large = fs.readFileSync(__dirname + "/large.js", "utf8"), + medium = fs.readFileSync(__dirname + "/medium.js", "utf8"), + small = fs.readFileSync(__dirname + "/small.js", "utf8"); + +var runs = { + large: large, + medium: medium, + small: small +}; +var linter = new Linter(); + +benchmark.runs = runs; +benchmark(Boolean, 1); + +function benchmark(grep, times) { + console.profile("all"); + for(var key in runs) { + if(grep(key)) { + console.time(key); + console.profile(key); + run(runs[key], times); + console.profileEnd(key); + console.timeEnd(key); + } + } + console.profileEnd("all"); +} + +function run(content, times) { + while(times--) { + linter.verify(content, config); + } +} diff --git a/eslint/tests/bench/large.js b/eslint/tests/bench/large.js new file mode 100644 index 0000000..24ba727 --- /dev/null +++ b/eslint/tests/bench/large.js @@ -0,0 +1,60571 @@ +// 2.4.3 +var JSHINT; +if (typeof window === 'undefined') window = {}; +(function () { +var require; +require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= 65 && i <= 90 || // A-Z + i === 95 || // _ + i >= 97 && i <= 122; // a-z +} + +var identifierPartTable = []; + +for (var i = 0; i < 128; i++) { + identifierPartTable[i] = + identifierStartTable[i] || // $, _, A-Z, a-z + i >= 48 && i <= 57; // 0-9 +} + +module.exports = { + asciiIdentifierStartTable: identifierStartTable, + asciiIdentifierPartTable: identifierPartTable +}; + +},{}],2:[function(require,module,exports){ +module.exports = [ + 768, + 769, + 770, + 771, + 772, + 773, + 774, + 775, + 776, + 777, + 778, + 779, + 780, + 781, + 782, + 783, + 784, + 785, + 786, + 787, + 788, + 789, + 790, + 791, + 792, + 793, + 794, + 795, + 796, + 797, + 798, + 799, + 800, + 801, + 802, + 803, + 804, + 805, + 806, + 807, + 808, + 809, + 810, + 811, + 812, + 813, + 814, + 815, + 816, + 817, + 818, + 819, + 820, + 821, + 822, + 823, + 824, + 825, + 826, + 827, + 828, + 829, + 830, + 831, + 832, + 833, + 834, + 835, + 836, + 837, + 838, + 839, + 840, + 841, + 842, + 843, + 844, + 845, + 846, + 847, + 848, + 849, + 850, + 851, + 852, + 853, + 854, + 855, + 856, + 857, + 858, + 859, + 860, + 861, + 862, + 863, + 864, + 865, + 866, + 867, + 868, + 869, + 870, + 871, + 872, + 873, + 874, + 875, + 876, + 877, + 878, + 879, + 1155, + 1156, + 1157, + 1158, + 1159, + 1425, + 1426, + 1427, + 1428, + 1429, + 1430, + 1431, + 1432, + 1433, + 1434, + 1435, + 1436, + 1437, + 1438, + 1439, + 1440, + 1441, + 1442, + 1443, + 1444, + 1445, + 1446, + 1447, + 1448, + 1449, + 1450, + 1451, + 1452, + 1453, + 1454, + 1455, + 1456, + 1457, + 1458, + 1459, + 1460, + 1461, + 1462, + 1463, + 1464, + 1465, + 1466, + 1467, + 1468, + 1469, + 1471, + 1473, + 1474, + 1476, + 1477, + 1479, + 1552, + 1553, + 1554, + 1555, + 1556, + 1557, + 1558, + 1559, + 1560, + 1561, + 1562, + 1611, + 1612, + 1613, + 1614, + 1615, + 1616, + 1617, + 1618, + 1619, + 1620, + 1621, + 1622, + 1623, + 1624, + 1625, + 1626, + 1627, + 1628, + 1629, + 1630, + 1631, + 1632, + 1633, + 1634, + 1635, + 1636, + 1637, + 1638, + 1639, + 1640, + 1641, + 1648, + 1750, + 1751, + 1752, + 1753, + 1754, + 1755, + 1756, + 1759, + 1760, + 1761, + 1762, + 1763, + 1764, + 1767, + 1768, + 1770, + 1771, + 1772, + 1773, + 1776, + 1777, + 1778, + 1779, + 1780, + 1781, + 1782, + 1783, + 1784, + 1785, + 1809, + 1840, + 1841, + 1842, + 1843, + 1844, + 1845, + 1846, + 1847, + 1848, + 1849, + 1850, + 1851, + 1852, + 1853, + 1854, + 1855, + 1856, + 1857, + 1858, + 1859, + 1860, + 1861, + 1862, + 1863, + 1864, + 1865, + 1866, + 1958, + 1959, + 1960, + 1961, + 1962, + 1963, + 1964, + 1965, + 1966, + 1967, + 1968, + 1984, + 1985, + 1986, + 1987, + 1988, + 1989, + 1990, + 1991, + 1992, + 1993, + 2027, + 2028, + 2029, + 2030, + 2031, + 2032, + 2033, + 2034, + 2035, + 2070, + 2071, + 2072, + 2073, + 2075, + 2076, + 2077, + 2078, + 2079, + 2080, + 2081, + 2082, + 2083, + 2085, + 2086, + 2087, + 2089, + 2090, + 2091, + 2092, + 2093, + 2137, + 2138, + 2139, + 2276, + 2277, + 2278, + 2279, + 2280, + 2281, + 2282, + 2283, + 2284, + 2285, + 2286, + 2287, + 2288, + 2289, + 2290, + 2291, + 2292, + 2293, + 2294, + 2295, + 2296, + 2297, + 2298, + 2299, + 2300, + 2301, + 2302, + 2304, + 2305, + 2306, + 2307, + 2362, + 2363, + 2364, + 2366, + 2367, + 2368, + 2369, + 2370, + 2371, + 2372, + 2373, + 2374, + 2375, + 2376, + 2377, + 2378, + 2379, + 2380, + 2381, + 2382, + 2383, + 2385, + 2386, + 2387, + 2388, + 2389, + 2390, + 2391, + 2402, + 2403, + 2406, + 2407, + 2408, + 2409, + 2410, + 2411, + 2412, + 2413, + 2414, + 2415, + 2433, + 2434, + 2435, + 2492, + 2494, + 2495, + 2496, + 2497, + 2498, + 2499, + 2500, + 2503, + 2504, + 2507, + 2508, + 2509, + 2519, + 2530, + 2531, + 2534, + 2535, + 2536, + 2537, + 2538, + 2539, + 2540, + 2541, + 2542, + 2543, + 2561, + 2562, + 2563, + 2620, + 2622, + 2623, + 2624, + 2625, + 2626, + 2631, + 2632, + 2635, + 2636, + 2637, + 2641, + 2662, + 2663, + 2664, + 2665, + 2666, + 2667, + 2668, + 2669, + 2670, + 2671, + 2672, + 2673, + 2677, + 2689, + 2690, + 2691, + 2748, + 2750, + 2751, + 2752, + 2753, + 2754, + 2755, + 2756, + 2757, + 2759, + 2760, + 2761, + 2763, + 2764, + 2765, + 2786, + 2787, + 2790, + 2791, + 2792, + 2793, + 2794, + 2795, + 2796, + 2797, + 2798, + 2799, + 2817, + 2818, + 2819, + 2876, + 2878, + 2879, + 2880, + 2881, + 2882, + 2883, + 2884, + 2887, + 2888, + 2891, + 2892, + 2893, + 2902, + 2903, + 2914, + 2915, + 2918, + 2919, + 2920, + 2921, + 2922, + 2923, + 2924, + 2925, + 2926, + 2927, + 2946, + 3006, + 3007, + 3008, + 3009, + 3010, + 3014, + 3015, + 3016, + 3018, + 3019, + 3020, + 3021, + 3031, + 3046, + 3047, + 3048, + 3049, + 3050, + 3051, + 3052, + 3053, + 3054, + 3055, + 3073, + 3074, + 3075, + 3134, + 3135, + 3136, + 3137, + 3138, + 3139, + 3140, + 3142, + 3143, + 3144, + 3146, + 3147, + 3148, + 3149, + 3157, + 3158, + 3170, + 3171, + 3174, + 3175, + 3176, + 3177, + 3178, + 3179, + 3180, + 3181, + 3182, + 3183, + 3202, + 3203, + 3260, + 3262, + 3263, + 3264, + 3265, + 3266, + 3267, + 3268, + 3270, + 3271, + 3272, + 3274, + 3275, + 3276, + 3277, + 3285, + 3286, + 3298, + 3299, + 3302, + 3303, + 3304, + 3305, + 3306, + 3307, + 3308, + 3309, + 3310, + 3311, + 3330, + 3331, + 3390, + 3391, + 3392, + 3393, + 3394, + 3395, + 3396, + 3398, + 3399, + 3400, + 3402, + 3403, + 3404, + 3405, + 3415, + 3426, + 3427, + 3430, + 3431, + 3432, + 3433, + 3434, + 3435, + 3436, + 3437, + 3438, + 3439, + 3458, + 3459, + 3530, + 3535, + 3536, + 3537, + 3538, + 3539, + 3540, + 3542, + 3544, + 3545, + 3546, + 3547, + 3548, + 3549, + 3550, + 3551, + 3570, + 3571, + 3633, + 3636, + 3637, + 3638, + 3639, + 3640, + 3641, + 3642, + 3655, + 3656, + 3657, + 3658, + 3659, + 3660, + 3661, + 3662, + 3664, + 3665, + 3666, + 3667, + 3668, + 3669, + 3670, + 3671, + 3672, + 3673, + 3761, + 3764, + 3765, + 3766, + 3767, + 3768, + 3769, + 3771, + 3772, + 3784, + 3785, + 3786, + 3787, + 3788, + 3789, + 3792, + 3793, + 3794, + 3795, + 3796, + 3797, + 3798, + 3799, + 3800, + 3801, + 3864, + 3865, + 3872, + 3873, + 3874, + 3875, + 3876, + 3877, + 3878, + 3879, + 3880, + 3881, + 3893, + 3895, + 3897, + 3902, + 3903, + 3953, + 3954, + 3955, + 3956, + 3957, + 3958, + 3959, + 3960, + 3961, + 3962, + 3963, + 3964, + 3965, + 3966, + 3967, + 3968, + 3969, + 3970, + 3971, + 3972, + 3974, + 3975, + 3981, + 3982, + 3983, + 3984, + 3985, + 3986, + 3987, + 3988, + 3989, + 3990, + 3991, + 3993, + 3994, + 3995, + 3996, + 3997, + 3998, + 3999, + 4000, + 4001, + 4002, + 4003, + 4004, + 4005, + 4006, + 4007, + 4008, + 4009, + 4010, + 4011, + 4012, + 4013, + 4014, + 4015, + 4016, + 4017, + 4018, + 4019, + 4020, + 4021, + 4022, + 4023, + 4024, + 4025, + 4026, + 4027, + 4028, + 4038, + 4139, + 4140, + 4141, + 4142, + 4143, + 4144, + 4145, + 4146, + 4147, + 4148, + 4149, + 4150, + 4151, + 4152, + 4153, + 4154, + 4155, + 4156, + 4157, + 4158, + 4160, + 4161, + 4162, + 4163, + 4164, + 4165, + 4166, + 4167, + 4168, + 4169, + 4182, + 4183, + 4184, + 4185, + 4190, + 4191, + 4192, + 4194, + 4195, + 4196, + 4199, + 4200, + 4201, + 4202, + 4203, + 4204, + 4205, + 4209, + 4210, + 4211, + 4212, + 4226, + 4227, + 4228, + 4229, + 4230, + 4231, + 4232, + 4233, + 4234, + 4235, + 4236, + 4237, + 4239, + 4240, + 4241, + 4242, + 4243, + 4244, + 4245, + 4246, + 4247, + 4248, + 4249, + 4250, + 4251, + 4252, + 4253, + 4957, + 4958, + 4959, + 5906, + 5907, + 5908, + 5938, + 5939, + 5940, + 5970, + 5971, + 6002, + 6003, + 6068, + 6069, + 6070, + 6071, + 6072, + 6073, + 6074, + 6075, + 6076, + 6077, + 6078, + 6079, + 6080, + 6081, + 6082, + 6083, + 6084, + 6085, + 6086, + 6087, + 6088, + 6089, + 6090, + 6091, + 6092, + 6093, + 6094, + 6095, + 6096, + 6097, + 6098, + 6099, + 6109, + 6112, + 6113, + 6114, + 6115, + 6116, + 6117, + 6118, + 6119, + 6120, + 6121, + 6155, + 6156, + 6157, + 6160, + 6161, + 6162, + 6163, + 6164, + 6165, + 6166, + 6167, + 6168, + 6169, + 6313, + 6432, + 6433, + 6434, + 6435, + 6436, + 6437, + 6438, + 6439, + 6440, + 6441, + 6442, + 6443, + 6448, + 6449, + 6450, + 6451, + 6452, + 6453, + 6454, + 6455, + 6456, + 6457, + 6458, + 6459, + 6470, + 6471, + 6472, + 6473, + 6474, + 6475, + 6476, + 6477, + 6478, + 6479, + 6576, + 6577, + 6578, + 6579, + 6580, + 6581, + 6582, + 6583, + 6584, + 6585, + 6586, + 6587, + 6588, + 6589, + 6590, + 6591, + 6592, + 6600, + 6601, + 6608, + 6609, + 6610, + 6611, + 6612, + 6613, + 6614, + 6615, + 6616, + 6617, + 6679, + 6680, + 6681, + 6682, + 6683, + 6741, + 6742, + 6743, + 6744, + 6745, + 6746, + 6747, + 6748, + 6749, + 6750, + 6752, + 6753, + 6754, + 6755, + 6756, + 6757, + 6758, + 6759, + 6760, + 6761, + 6762, + 6763, + 6764, + 6765, + 6766, + 6767, + 6768, + 6769, + 6770, + 6771, + 6772, + 6773, + 6774, + 6775, + 6776, + 6777, + 6778, + 6779, + 6780, + 6783, + 6784, + 6785, + 6786, + 6787, + 6788, + 6789, + 6790, + 6791, + 6792, + 6793, + 6800, + 6801, + 6802, + 6803, + 6804, + 6805, + 6806, + 6807, + 6808, + 6809, + 6912, + 6913, + 6914, + 6915, + 6916, + 6964, + 6965, + 6966, + 6967, + 6968, + 6969, + 6970, + 6971, + 6972, + 6973, + 6974, + 6975, + 6976, + 6977, + 6978, + 6979, + 6980, + 6992, + 6993, + 6994, + 6995, + 6996, + 6997, + 6998, + 6999, + 7000, + 7001, + 7019, + 7020, + 7021, + 7022, + 7023, + 7024, + 7025, + 7026, + 7027, + 7040, + 7041, + 7042, + 7073, + 7074, + 7075, + 7076, + 7077, + 7078, + 7079, + 7080, + 7081, + 7082, + 7083, + 7084, + 7085, + 7088, + 7089, + 7090, + 7091, + 7092, + 7093, + 7094, + 7095, + 7096, + 7097, + 7142, + 7143, + 7144, + 7145, + 7146, + 7147, + 7148, + 7149, + 7150, + 7151, + 7152, + 7153, + 7154, + 7155, + 7204, + 7205, + 7206, + 7207, + 7208, + 7209, + 7210, + 7211, + 7212, + 7213, + 7214, + 7215, + 7216, + 7217, + 7218, + 7219, + 7220, + 7221, + 7222, + 7223, + 7232, + 7233, + 7234, + 7235, + 7236, + 7237, + 7238, + 7239, + 7240, + 7241, + 7248, + 7249, + 7250, + 7251, + 7252, + 7253, + 7254, + 7255, + 7256, + 7257, + 7376, + 7377, + 7378, + 7380, + 7381, + 7382, + 7383, + 7384, + 7385, + 7386, + 7387, + 7388, + 7389, + 7390, + 7391, + 7392, + 7393, + 7394, + 7395, + 7396, + 7397, + 7398, + 7399, + 7400, + 7405, + 7410, + 7411, + 7412, + 7616, + 7617, + 7618, + 7619, + 7620, + 7621, + 7622, + 7623, + 7624, + 7625, + 7626, + 7627, + 7628, + 7629, + 7630, + 7631, + 7632, + 7633, + 7634, + 7635, + 7636, + 7637, + 7638, + 7639, + 7640, + 7641, + 7642, + 7643, + 7644, + 7645, + 7646, + 7647, + 7648, + 7649, + 7650, + 7651, + 7652, + 7653, + 7654, + 7676, + 7677, + 7678, + 7679, + 8204, + 8205, + 8255, + 8256, + 8276, + 8400, + 8401, + 8402, + 8403, + 8404, + 8405, + 8406, + 8407, + 8408, + 8409, + 8410, + 8411, + 8412, + 8417, + 8421, + 8422, + 8423, + 8424, + 8425, + 8426, + 8427, + 8428, + 8429, + 8430, + 8431, + 8432, + 11503, + 11504, + 11505, + 11647, + 11744, + 11745, + 11746, + 11747, + 11748, + 11749, + 11750, + 11751, + 11752, + 11753, + 11754, + 11755, + 11756, + 11757, + 11758, + 11759, + 11760, + 11761, + 11762, + 11763, + 11764, + 11765, + 11766, + 11767, + 11768, + 11769, + 11770, + 11771, + 11772, + 11773, + 11774, + 11775, + 12330, + 12331, + 12332, + 12333, + 12334, + 12335, + 12441, + 12442, + 42528, + 42529, + 42530, + 42531, + 42532, + 42533, + 42534, + 42535, + 42536, + 42537, + 42607, + 42612, + 42613, + 42614, + 42615, + 42616, + 42617, + 42618, + 42619, + 42620, + 42621, + 42655, + 42736, + 42737, + 43010, + 43014, + 43019, + 43043, + 43044, + 43045, + 43046, + 43047, + 43136, + 43137, + 43188, + 43189, + 43190, + 43191, + 43192, + 43193, + 43194, + 43195, + 43196, + 43197, + 43198, + 43199, + 43200, + 43201, + 43202, + 43203, + 43204, + 43216, + 43217, + 43218, + 43219, + 43220, + 43221, + 43222, + 43223, + 43224, + 43225, + 43232, + 43233, + 43234, + 43235, + 43236, + 43237, + 43238, + 43239, + 43240, + 43241, + 43242, + 43243, + 43244, + 43245, + 43246, + 43247, + 43248, + 43249, + 43264, + 43265, + 43266, + 43267, + 43268, + 43269, + 43270, + 43271, + 43272, + 43273, + 43302, + 43303, + 43304, + 43305, + 43306, + 43307, + 43308, + 43309, + 43335, + 43336, + 43337, + 43338, + 43339, + 43340, + 43341, + 43342, + 43343, + 43344, + 43345, + 43346, + 43347, + 43392, + 43393, + 43394, + 43395, + 43443, + 43444, + 43445, + 43446, + 43447, + 43448, + 43449, + 43450, + 43451, + 43452, + 43453, + 43454, + 43455, + 43456, + 43472, + 43473, + 43474, + 43475, + 43476, + 43477, + 43478, + 43479, + 43480, + 43481, + 43561, + 43562, + 43563, + 43564, + 43565, + 43566, + 43567, + 43568, + 43569, + 43570, + 43571, + 43572, + 43573, + 43574, + 43587, + 43596, + 43597, + 43600, + 43601, + 43602, + 43603, + 43604, + 43605, + 43606, + 43607, + 43608, + 43609, + 43643, + 43696, + 43698, + 43699, + 43700, + 43703, + 43704, + 43710, + 43711, + 43713, + 43755, + 43756, + 43757, + 43758, + 43759, + 43765, + 43766, + 44003, + 44004, + 44005, + 44006, + 44007, + 44008, + 44009, + 44010, + 44012, + 44013, + 44016, + 44017, + 44018, + 44019, + 44020, + 44021, + 44022, + 44023, + 44024, + 44025, + 64286, + 65024, + 65025, + 65026, + 65027, + 65028, + 65029, + 65030, + 65031, + 65032, + 65033, + 65034, + 65035, + 65036, + 65037, + 65038, + 65039, + 65056, + 65057, + 65058, + 65059, + 65060, + 65061, + 65062, + 65075, + 65076, + 65101, + 65102, + 65103, + 65296, + 65297, + 65298, + 65299, + 65300, + 65301, + 65302, + 65303, + 65304, + 65305, + 65343 +]; + +},{}],3:[function(require,module,exports){ +module.exports = [ + 170, + 181, + 186, + 192, + 193, + 194, + 195, + 196, + 197, + 198, + 199, + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 209, + 210, + 211, + 212, + 213, + 214, + 216, + 217, + 218, + 219, + 220, + 221, + 222, + 223, + 224, + 225, + 226, + 227, + 228, + 229, + 230, + 231, + 232, + 233, + 234, + 235, + 236, + 237, + 238, + 239, + 240, + 241, + 242, + 243, + 244, + 245, + 246, + 248, + 249, + 250, + 251, + 252, + 253, + 254, + 255, + 256, + 257, + 258, + 259, + 260, + 261, + 262, + 263, + 264, + 265, + 266, + 267, + 268, + 269, + 270, + 271, + 272, + 273, + 274, + 275, + 276, + 277, + 278, + 279, + 280, + 281, + 282, + 283, + 284, + 285, + 286, + 287, + 288, + 289, + 290, + 291, + 292, + 293, + 294, + 295, + 296, + 297, + 298, + 299, + 300, + 301, + 302, + 303, + 304, + 305, + 306, + 307, + 308, + 309, + 310, + 311, + 312, + 313, + 314, + 315, + 316, + 317, + 318, + 319, + 320, + 321, + 322, + 323, + 324, + 325, + 326, + 327, + 328, + 329, + 330, + 331, + 332, + 333, + 334, + 335, + 336, + 337, + 338, + 339, + 340, + 341, + 342, + 343, + 344, + 345, + 346, + 347, + 348, + 349, + 350, + 351, + 352, + 353, + 354, + 355, + 356, + 357, + 358, + 359, + 360, + 361, + 362, + 363, + 364, + 365, + 366, + 367, + 368, + 369, + 370, + 371, + 372, + 373, + 374, + 375, + 376, + 377, + 378, + 379, + 380, + 381, + 382, + 383, + 384, + 385, + 386, + 387, + 388, + 389, + 390, + 391, + 392, + 393, + 394, + 395, + 396, + 397, + 398, + 399, + 400, + 401, + 402, + 403, + 404, + 405, + 406, + 407, + 408, + 409, + 410, + 411, + 412, + 413, + 414, + 415, + 416, + 417, + 418, + 419, + 420, + 421, + 422, + 423, + 424, + 425, + 426, + 427, + 428, + 429, + 430, + 431, + 432, + 433, + 434, + 435, + 436, + 437, + 438, + 439, + 440, + 441, + 442, + 443, + 444, + 445, + 446, + 447, + 448, + 449, + 450, + 451, + 452, + 453, + 454, + 455, + 456, + 457, + 458, + 459, + 460, + 461, + 462, + 463, + 464, + 465, + 466, + 467, + 468, + 469, + 470, + 471, + 472, + 473, + 474, + 475, + 476, + 477, + 478, + 479, + 480, + 481, + 482, + 483, + 484, + 485, + 486, + 487, + 488, + 489, + 490, + 491, + 492, + 493, + 494, + 495, + 496, + 497, + 498, + 499, + 500, + 501, + 502, + 503, + 504, + 505, + 506, + 507, + 508, + 509, + 510, + 511, + 512, + 513, + 514, + 515, + 516, + 517, + 518, + 519, + 520, + 521, + 522, + 523, + 524, + 525, + 526, + 527, + 528, + 529, + 530, + 531, + 532, + 533, + 534, + 535, + 536, + 537, + 538, + 539, + 540, + 541, + 542, + 543, + 544, + 545, + 546, + 547, + 548, + 549, + 550, + 551, + 552, + 553, + 554, + 555, + 556, + 557, + 558, + 559, + 560, + 561, + 562, + 563, + 564, + 565, + 566, + 567, + 568, + 569, + 570, + 571, + 572, + 573, + 574, + 575, + 576, + 577, + 578, + 579, + 580, + 581, + 582, + 583, + 584, + 585, + 586, + 587, + 588, + 589, + 590, + 591, + 592, + 593, + 594, + 595, + 596, + 597, + 598, + 599, + 600, + 601, + 602, + 603, + 604, + 605, + 606, + 607, + 608, + 609, + 610, + 611, + 612, + 613, + 614, + 615, + 616, + 617, + 618, + 619, + 620, + 621, + 622, + 623, + 624, + 625, + 626, + 627, + 628, + 629, + 630, + 631, + 632, + 633, + 634, + 635, + 636, + 637, + 638, + 639, + 640, + 641, + 642, + 643, + 644, + 645, + 646, + 647, + 648, + 649, + 650, + 651, + 652, + 653, + 654, + 655, + 656, + 657, + 658, + 659, + 660, + 661, + 662, + 663, + 664, + 665, + 666, + 667, + 668, + 669, + 670, + 671, + 672, + 673, + 674, + 675, + 676, + 677, + 678, + 679, + 680, + 681, + 682, + 683, + 684, + 685, + 686, + 687, + 688, + 689, + 690, + 691, + 692, + 693, + 694, + 695, + 696, + 697, + 698, + 699, + 700, + 701, + 702, + 703, + 704, + 705, + 710, + 711, + 712, + 713, + 714, + 715, + 716, + 717, + 718, + 719, + 720, + 721, + 736, + 737, + 738, + 739, + 740, + 748, + 750, + 880, + 881, + 882, + 883, + 884, + 886, + 887, + 890, + 891, + 892, + 893, + 902, + 904, + 905, + 906, + 908, + 910, + 911, + 912, + 913, + 914, + 915, + 916, + 917, + 918, + 919, + 920, + 921, + 922, + 923, + 924, + 925, + 926, + 927, + 928, + 929, + 931, + 932, + 933, + 934, + 935, + 936, + 937, + 938, + 939, + 940, + 941, + 942, + 943, + 944, + 945, + 946, + 947, + 948, + 949, + 950, + 951, + 952, + 953, + 954, + 955, + 956, + 957, + 958, + 959, + 960, + 961, + 962, + 963, + 964, + 965, + 966, + 967, + 968, + 969, + 970, + 971, + 972, + 973, + 974, + 975, + 976, + 977, + 978, + 979, + 980, + 981, + 982, + 983, + 984, + 985, + 986, + 987, + 988, + 989, + 990, + 991, + 992, + 993, + 994, + 995, + 996, + 997, + 998, + 999, + 1000, + 1001, + 1002, + 1003, + 1004, + 1005, + 1006, + 1007, + 1008, + 1009, + 1010, + 1011, + 1012, + 1013, + 1015, + 1016, + 1017, + 1018, + 1019, + 1020, + 1021, + 1022, + 1023, + 1024, + 1025, + 1026, + 1027, + 1028, + 1029, + 1030, + 1031, + 1032, + 1033, + 1034, + 1035, + 1036, + 1037, + 1038, + 1039, + 1040, + 1041, + 1042, + 1043, + 1044, + 1045, + 1046, + 1047, + 1048, + 1049, + 1050, + 1051, + 1052, + 1053, + 1054, + 1055, + 1056, + 1057, + 1058, + 1059, + 1060, + 1061, + 1062, + 1063, + 1064, + 1065, + 1066, + 1067, + 1068, + 1069, + 1070, + 1071, + 1072, + 1073, + 1074, + 1075, + 1076, + 1077, + 1078, + 1079, + 1080, + 1081, + 1082, + 1083, + 1084, + 1085, + 1086, + 1087, + 1088, + 1089, + 1090, + 1091, + 1092, + 1093, + 1094, + 1095, + 1096, + 1097, + 1098, + 1099, + 1100, + 1101, + 1102, + 1103, + 1104, + 1105, + 1106, + 1107, + 1108, + 1109, + 1110, + 1111, + 1112, + 1113, + 1114, + 1115, + 1116, + 1117, + 1118, + 1119, + 1120, + 1121, + 1122, + 1123, + 1124, + 1125, + 1126, + 1127, + 1128, + 1129, + 1130, + 1131, + 1132, + 1133, + 1134, + 1135, + 1136, + 1137, + 1138, + 1139, + 1140, + 1141, + 1142, + 1143, + 1144, + 1145, + 1146, + 1147, + 1148, + 1149, + 1150, + 1151, + 1152, + 1153, + 1162, + 1163, + 1164, + 1165, + 1166, + 1167, + 1168, + 1169, + 1170, + 1171, + 1172, + 1173, + 1174, + 1175, + 1176, + 1177, + 1178, + 1179, + 1180, + 1181, + 1182, + 1183, + 1184, + 1185, + 1186, + 1187, + 1188, + 1189, + 1190, + 1191, + 1192, + 1193, + 1194, + 1195, + 1196, + 1197, + 1198, + 1199, + 1200, + 1201, + 1202, + 1203, + 1204, + 1205, + 1206, + 1207, + 1208, + 1209, + 1210, + 1211, + 1212, + 1213, + 1214, + 1215, + 1216, + 1217, + 1218, + 1219, + 1220, + 1221, + 1222, + 1223, + 1224, + 1225, + 1226, + 1227, + 1228, + 1229, + 1230, + 1231, + 1232, + 1233, + 1234, + 1235, + 1236, + 1237, + 1238, + 1239, + 1240, + 1241, + 1242, + 1243, + 1244, + 1245, + 1246, + 1247, + 1248, + 1249, + 1250, + 1251, + 1252, + 1253, + 1254, + 1255, + 1256, + 1257, + 1258, + 1259, + 1260, + 1261, + 1262, + 1263, + 1264, + 1265, + 1266, + 1267, + 1268, + 1269, + 1270, + 1271, + 1272, + 1273, + 1274, + 1275, + 1276, + 1277, + 1278, + 1279, + 1280, + 1281, + 1282, + 1283, + 1284, + 1285, + 1286, + 1287, + 1288, + 1289, + 1290, + 1291, + 1292, + 1293, + 1294, + 1295, + 1296, + 1297, + 1298, + 1299, + 1300, + 1301, + 1302, + 1303, + 1304, + 1305, + 1306, + 1307, + 1308, + 1309, + 1310, + 1311, + 1312, + 1313, + 1314, + 1315, + 1316, + 1317, + 1318, + 1319, + 1329, + 1330, + 1331, + 1332, + 1333, + 1334, + 1335, + 1336, + 1337, + 1338, + 1339, + 1340, + 1341, + 1342, + 1343, + 1344, + 1345, + 1346, + 1347, + 1348, + 1349, + 1350, + 1351, + 1352, + 1353, + 1354, + 1355, + 1356, + 1357, + 1358, + 1359, + 1360, + 1361, + 1362, + 1363, + 1364, + 1365, + 1366, + 1369, + 1377, + 1378, + 1379, + 1380, + 1381, + 1382, + 1383, + 1384, + 1385, + 1386, + 1387, + 1388, + 1389, + 1390, + 1391, + 1392, + 1393, + 1394, + 1395, + 1396, + 1397, + 1398, + 1399, + 1400, + 1401, + 1402, + 1403, + 1404, + 1405, + 1406, + 1407, + 1408, + 1409, + 1410, + 1411, + 1412, + 1413, + 1414, + 1415, + 1488, + 1489, + 1490, + 1491, + 1492, + 1493, + 1494, + 1495, + 1496, + 1497, + 1498, + 1499, + 1500, + 1501, + 1502, + 1503, + 1504, + 1505, + 1506, + 1507, + 1508, + 1509, + 1510, + 1511, + 1512, + 1513, + 1514, + 1520, + 1521, + 1522, + 1568, + 1569, + 1570, + 1571, + 1572, + 1573, + 1574, + 1575, + 1576, + 1577, + 1578, + 1579, + 1580, + 1581, + 1582, + 1583, + 1584, + 1585, + 1586, + 1587, + 1588, + 1589, + 1590, + 1591, + 1592, + 1593, + 1594, + 1595, + 1596, + 1597, + 1598, + 1599, + 1600, + 1601, + 1602, + 1603, + 1604, + 1605, + 1606, + 1607, + 1608, + 1609, + 1610, + 1646, + 1647, + 1649, + 1650, + 1651, + 1652, + 1653, + 1654, + 1655, + 1656, + 1657, + 1658, + 1659, + 1660, + 1661, + 1662, + 1663, + 1664, + 1665, + 1666, + 1667, + 1668, + 1669, + 1670, + 1671, + 1672, + 1673, + 1674, + 1675, + 1676, + 1677, + 1678, + 1679, + 1680, + 1681, + 1682, + 1683, + 1684, + 1685, + 1686, + 1687, + 1688, + 1689, + 1690, + 1691, + 1692, + 1693, + 1694, + 1695, + 1696, + 1697, + 1698, + 1699, + 1700, + 1701, + 1702, + 1703, + 1704, + 1705, + 1706, + 1707, + 1708, + 1709, + 1710, + 1711, + 1712, + 1713, + 1714, + 1715, + 1716, + 1717, + 1718, + 1719, + 1720, + 1721, + 1722, + 1723, + 1724, + 1725, + 1726, + 1727, + 1728, + 1729, + 1730, + 1731, + 1732, + 1733, + 1734, + 1735, + 1736, + 1737, + 1738, + 1739, + 1740, + 1741, + 1742, + 1743, + 1744, + 1745, + 1746, + 1747, + 1749, + 1765, + 1766, + 1774, + 1775, + 1786, + 1787, + 1788, + 1791, + 1808, + 1810, + 1811, + 1812, + 1813, + 1814, + 1815, + 1816, + 1817, + 1818, + 1819, + 1820, + 1821, + 1822, + 1823, + 1824, + 1825, + 1826, + 1827, + 1828, + 1829, + 1830, + 1831, + 1832, + 1833, + 1834, + 1835, + 1836, + 1837, + 1838, + 1839, + 1869, + 1870, + 1871, + 1872, + 1873, + 1874, + 1875, + 1876, + 1877, + 1878, + 1879, + 1880, + 1881, + 1882, + 1883, + 1884, + 1885, + 1886, + 1887, + 1888, + 1889, + 1890, + 1891, + 1892, + 1893, + 1894, + 1895, + 1896, + 1897, + 1898, + 1899, + 1900, + 1901, + 1902, + 1903, + 1904, + 1905, + 1906, + 1907, + 1908, + 1909, + 1910, + 1911, + 1912, + 1913, + 1914, + 1915, + 1916, + 1917, + 1918, + 1919, + 1920, + 1921, + 1922, + 1923, + 1924, + 1925, + 1926, + 1927, + 1928, + 1929, + 1930, + 1931, + 1932, + 1933, + 1934, + 1935, + 1936, + 1937, + 1938, + 1939, + 1940, + 1941, + 1942, + 1943, + 1944, + 1945, + 1946, + 1947, + 1948, + 1949, + 1950, + 1951, + 1952, + 1953, + 1954, + 1955, + 1956, + 1957, + 1969, + 1994, + 1995, + 1996, + 1997, + 1998, + 1999, + 2000, + 2001, + 2002, + 2003, + 2004, + 2005, + 2006, + 2007, + 2008, + 2009, + 2010, + 2011, + 2012, + 2013, + 2014, + 2015, + 2016, + 2017, + 2018, + 2019, + 2020, + 2021, + 2022, + 2023, + 2024, + 2025, + 2026, + 2036, + 2037, + 2042, + 2048, + 2049, + 2050, + 2051, + 2052, + 2053, + 2054, + 2055, + 2056, + 2057, + 2058, + 2059, + 2060, + 2061, + 2062, + 2063, + 2064, + 2065, + 2066, + 2067, + 2068, + 2069, + 2074, + 2084, + 2088, + 2112, + 2113, + 2114, + 2115, + 2116, + 2117, + 2118, + 2119, + 2120, + 2121, + 2122, + 2123, + 2124, + 2125, + 2126, + 2127, + 2128, + 2129, + 2130, + 2131, + 2132, + 2133, + 2134, + 2135, + 2136, + 2208, + 2210, + 2211, + 2212, + 2213, + 2214, + 2215, + 2216, + 2217, + 2218, + 2219, + 2220, + 2308, + 2309, + 2310, + 2311, + 2312, + 2313, + 2314, + 2315, + 2316, + 2317, + 2318, + 2319, + 2320, + 2321, + 2322, + 2323, + 2324, + 2325, + 2326, + 2327, + 2328, + 2329, + 2330, + 2331, + 2332, + 2333, + 2334, + 2335, + 2336, + 2337, + 2338, + 2339, + 2340, + 2341, + 2342, + 2343, + 2344, + 2345, + 2346, + 2347, + 2348, + 2349, + 2350, + 2351, + 2352, + 2353, + 2354, + 2355, + 2356, + 2357, + 2358, + 2359, + 2360, + 2361, + 2365, + 2384, + 2392, + 2393, + 2394, + 2395, + 2396, + 2397, + 2398, + 2399, + 2400, + 2401, + 2417, + 2418, + 2419, + 2420, + 2421, + 2422, + 2423, + 2425, + 2426, + 2427, + 2428, + 2429, + 2430, + 2431, + 2437, + 2438, + 2439, + 2440, + 2441, + 2442, + 2443, + 2444, + 2447, + 2448, + 2451, + 2452, + 2453, + 2454, + 2455, + 2456, + 2457, + 2458, + 2459, + 2460, + 2461, + 2462, + 2463, + 2464, + 2465, + 2466, + 2467, + 2468, + 2469, + 2470, + 2471, + 2472, + 2474, + 2475, + 2476, + 2477, + 2478, + 2479, + 2480, + 2482, + 2486, + 2487, + 2488, + 2489, + 2493, + 2510, + 2524, + 2525, + 2527, + 2528, + 2529, + 2544, + 2545, + 2565, + 2566, + 2567, + 2568, + 2569, + 2570, + 2575, + 2576, + 2579, + 2580, + 2581, + 2582, + 2583, + 2584, + 2585, + 2586, + 2587, + 2588, + 2589, + 2590, + 2591, + 2592, + 2593, + 2594, + 2595, + 2596, + 2597, + 2598, + 2599, + 2600, + 2602, + 2603, + 2604, + 2605, + 2606, + 2607, + 2608, + 2610, + 2611, + 2613, + 2614, + 2616, + 2617, + 2649, + 2650, + 2651, + 2652, + 2654, + 2674, + 2675, + 2676, + 2693, + 2694, + 2695, + 2696, + 2697, + 2698, + 2699, + 2700, + 2701, + 2703, + 2704, + 2705, + 2707, + 2708, + 2709, + 2710, + 2711, + 2712, + 2713, + 2714, + 2715, + 2716, + 2717, + 2718, + 2719, + 2720, + 2721, + 2722, + 2723, + 2724, + 2725, + 2726, + 2727, + 2728, + 2730, + 2731, + 2732, + 2733, + 2734, + 2735, + 2736, + 2738, + 2739, + 2741, + 2742, + 2743, + 2744, + 2745, + 2749, + 2768, + 2784, + 2785, + 2821, + 2822, + 2823, + 2824, + 2825, + 2826, + 2827, + 2828, + 2831, + 2832, + 2835, + 2836, + 2837, + 2838, + 2839, + 2840, + 2841, + 2842, + 2843, + 2844, + 2845, + 2846, + 2847, + 2848, + 2849, + 2850, + 2851, + 2852, + 2853, + 2854, + 2855, + 2856, + 2858, + 2859, + 2860, + 2861, + 2862, + 2863, + 2864, + 2866, + 2867, + 2869, + 2870, + 2871, + 2872, + 2873, + 2877, + 2908, + 2909, + 2911, + 2912, + 2913, + 2929, + 2947, + 2949, + 2950, + 2951, + 2952, + 2953, + 2954, + 2958, + 2959, + 2960, + 2962, + 2963, + 2964, + 2965, + 2969, + 2970, + 2972, + 2974, + 2975, + 2979, + 2980, + 2984, + 2985, + 2986, + 2990, + 2991, + 2992, + 2993, + 2994, + 2995, + 2996, + 2997, + 2998, + 2999, + 3000, + 3001, + 3024, + 3077, + 3078, + 3079, + 3080, + 3081, + 3082, + 3083, + 3084, + 3086, + 3087, + 3088, + 3090, + 3091, + 3092, + 3093, + 3094, + 3095, + 3096, + 3097, + 3098, + 3099, + 3100, + 3101, + 3102, + 3103, + 3104, + 3105, + 3106, + 3107, + 3108, + 3109, + 3110, + 3111, + 3112, + 3114, + 3115, + 3116, + 3117, + 3118, + 3119, + 3120, + 3121, + 3122, + 3123, + 3125, + 3126, + 3127, + 3128, + 3129, + 3133, + 3160, + 3161, + 3168, + 3169, + 3205, + 3206, + 3207, + 3208, + 3209, + 3210, + 3211, + 3212, + 3214, + 3215, + 3216, + 3218, + 3219, + 3220, + 3221, + 3222, + 3223, + 3224, + 3225, + 3226, + 3227, + 3228, + 3229, + 3230, + 3231, + 3232, + 3233, + 3234, + 3235, + 3236, + 3237, + 3238, + 3239, + 3240, + 3242, + 3243, + 3244, + 3245, + 3246, + 3247, + 3248, + 3249, + 3250, + 3251, + 3253, + 3254, + 3255, + 3256, + 3257, + 3261, + 3294, + 3296, + 3297, + 3313, + 3314, + 3333, + 3334, + 3335, + 3336, + 3337, + 3338, + 3339, + 3340, + 3342, + 3343, + 3344, + 3346, + 3347, + 3348, + 3349, + 3350, + 3351, + 3352, + 3353, + 3354, + 3355, + 3356, + 3357, + 3358, + 3359, + 3360, + 3361, + 3362, + 3363, + 3364, + 3365, + 3366, + 3367, + 3368, + 3369, + 3370, + 3371, + 3372, + 3373, + 3374, + 3375, + 3376, + 3377, + 3378, + 3379, + 3380, + 3381, + 3382, + 3383, + 3384, + 3385, + 3386, + 3389, + 3406, + 3424, + 3425, + 3450, + 3451, + 3452, + 3453, + 3454, + 3455, + 3461, + 3462, + 3463, + 3464, + 3465, + 3466, + 3467, + 3468, + 3469, + 3470, + 3471, + 3472, + 3473, + 3474, + 3475, + 3476, + 3477, + 3478, + 3482, + 3483, + 3484, + 3485, + 3486, + 3487, + 3488, + 3489, + 3490, + 3491, + 3492, + 3493, + 3494, + 3495, + 3496, + 3497, + 3498, + 3499, + 3500, + 3501, + 3502, + 3503, + 3504, + 3505, + 3507, + 3508, + 3509, + 3510, + 3511, + 3512, + 3513, + 3514, + 3515, + 3517, + 3520, + 3521, + 3522, + 3523, + 3524, + 3525, + 3526, + 3585, + 3586, + 3587, + 3588, + 3589, + 3590, + 3591, + 3592, + 3593, + 3594, + 3595, + 3596, + 3597, + 3598, + 3599, + 3600, + 3601, + 3602, + 3603, + 3604, + 3605, + 3606, + 3607, + 3608, + 3609, + 3610, + 3611, + 3612, + 3613, + 3614, + 3615, + 3616, + 3617, + 3618, + 3619, + 3620, + 3621, + 3622, + 3623, + 3624, + 3625, + 3626, + 3627, + 3628, + 3629, + 3630, + 3631, + 3632, + 3634, + 3635, + 3648, + 3649, + 3650, + 3651, + 3652, + 3653, + 3654, + 3713, + 3714, + 3716, + 3719, + 3720, + 3722, + 3725, + 3732, + 3733, + 3734, + 3735, + 3737, + 3738, + 3739, + 3740, + 3741, + 3742, + 3743, + 3745, + 3746, + 3747, + 3749, + 3751, + 3754, + 3755, + 3757, + 3758, + 3759, + 3760, + 3762, + 3763, + 3773, + 3776, + 3777, + 3778, + 3779, + 3780, + 3782, + 3804, + 3805, + 3806, + 3807, + 3840, + 3904, + 3905, + 3906, + 3907, + 3908, + 3909, + 3910, + 3911, + 3913, + 3914, + 3915, + 3916, + 3917, + 3918, + 3919, + 3920, + 3921, + 3922, + 3923, + 3924, + 3925, + 3926, + 3927, + 3928, + 3929, + 3930, + 3931, + 3932, + 3933, + 3934, + 3935, + 3936, + 3937, + 3938, + 3939, + 3940, + 3941, + 3942, + 3943, + 3944, + 3945, + 3946, + 3947, + 3948, + 3976, + 3977, + 3978, + 3979, + 3980, + 4096, + 4097, + 4098, + 4099, + 4100, + 4101, + 4102, + 4103, + 4104, + 4105, + 4106, + 4107, + 4108, + 4109, + 4110, + 4111, + 4112, + 4113, + 4114, + 4115, + 4116, + 4117, + 4118, + 4119, + 4120, + 4121, + 4122, + 4123, + 4124, + 4125, + 4126, + 4127, + 4128, + 4129, + 4130, + 4131, + 4132, + 4133, + 4134, + 4135, + 4136, + 4137, + 4138, + 4159, + 4176, + 4177, + 4178, + 4179, + 4180, + 4181, + 4186, + 4187, + 4188, + 4189, + 4193, + 4197, + 4198, + 4206, + 4207, + 4208, + 4213, + 4214, + 4215, + 4216, + 4217, + 4218, + 4219, + 4220, + 4221, + 4222, + 4223, + 4224, + 4225, + 4238, + 4256, + 4257, + 4258, + 4259, + 4260, + 4261, + 4262, + 4263, + 4264, + 4265, + 4266, + 4267, + 4268, + 4269, + 4270, + 4271, + 4272, + 4273, + 4274, + 4275, + 4276, + 4277, + 4278, + 4279, + 4280, + 4281, + 4282, + 4283, + 4284, + 4285, + 4286, + 4287, + 4288, + 4289, + 4290, + 4291, + 4292, + 4293, + 4295, + 4301, + 4304, + 4305, + 4306, + 4307, + 4308, + 4309, + 4310, + 4311, + 4312, + 4313, + 4314, + 4315, + 4316, + 4317, + 4318, + 4319, + 4320, + 4321, + 4322, + 4323, + 4324, + 4325, + 4326, + 4327, + 4328, + 4329, + 4330, + 4331, + 4332, + 4333, + 4334, + 4335, + 4336, + 4337, + 4338, + 4339, + 4340, + 4341, + 4342, + 4343, + 4344, + 4345, + 4346, + 4348, + 4349, + 4350, + 4351, + 4352, + 4353, + 4354, + 4355, + 4356, + 4357, + 4358, + 4359, + 4360, + 4361, + 4362, + 4363, + 4364, + 4365, + 4366, + 4367, + 4368, + 4369, + 4370, + 4371, + 4372, + 4373, + 4374, + 4375, + 4376, + 4377, + 4378, + 4379, + 4380, + 4381, + 4382, + 4383, + 4384, + 4385, + 4386, + 4387, + 4388, + 4389, + 4390, + 4391, + 4392, + 4393, + 4394, + 4395, + 4396, + 4397, + 4398, + 4399, + 4400, + 4401, + 4402, + 4403, + 4404, + 4405, + 4406, + 4407, + 4408, + 4409, + 4410, + 4411, + 4412, + 4413, + 4414, + 4415, + 4416, + 4417, + 4418, + 4419, + 4420, + 4421, + 4422, + 4423, + 4424, + 4425, + 4426, + 4427, + 4428, + 4429, + 4430, + 4431, + 4432, + 4433, + 4434, + 4435, + 4436, + 4437, + 4438, + 4439, + 4440, + 4441, + 4442, + 4443, + 4444, + 4445, + 4446, + 4447, + 4448, + 4449, + 4450, + 4451, + 4452, + 4453, + 4454, + 4455, + 4456, + 4457, + 4458, + 4459, + 4460, + 4461, + 4462, + 4463, + 4464, + 4465, + 4466, + 4467, + 4468, + 4469, + 4470, + 4471, + 4472, + 4473, + 4474, + 4475, + 4476, + 4477, + 4478, + 4479, + 4480, + 4481, + 4482, + 4483, + 4484, + 4485, + 4486, + 4487, + 4488, + 4489, + 4490, + 4491, + 4492, + 4493, + 4494, + 4495, + 4496, + 4497, + 4498, + 4499, + 4500, + 4501, + 4502, + 4503, + 4504, + 4505, + 4506, + 4507, + 4508, + 4509, + 4510, + 4511, + 4512, + 4513, + 4514, + 4515, + 4516, + 4517, + 4518, + 4519, + 4520, + 4521, + 4522, + 4523, + 4524, + 4525, + 4526, + 4527, + 4528, + 4529, + 4530, + 4531, + 4532, + 4533, + 4534, + 4535, + 4536, + 4537, + 4538, + 4539, + 4540, + 4541, + 4542, + 4543, + 4544, + 4545, + 4546, + 4547, + 4548, + 4549, + 4550, + 4551, + 4552, + 4553, + 4554, + 4555, + 4556, + 4557, + 4558, + 4559, + 4560, + 4561, + 4562, + 4563, + 4564, + 4565, + 4566, + 4567, + 4568, + 4569, + 4570, + 4571, + 4572, + 4573, + 4574, + 4575, + 4576, + 4577, + 4578, + 4579, + 4580, + 4581, + 4582, + 4583, + 4584, + 4585, + 4586, + 4587, + 4588, + 4589, + 4590, + 4591, + 4592, + 4593, + 4594, + 4595, + 4596, + 4597, + 4598, + 4599, + 4600, + 4601, + 4602, + 4603, + 4604, + 4605, + 4606, + 4607, + 4608, + 4609, + 4610, + 4611, + 4612, + 4613, + 4614, + 4615, + 4616, + 4617, + 4618, + 4619, + 4620, + 4621, + 4622, + 4623, + 4624, + 4625, + 4626, + 4627, + 4628, + 4629, + 4630, + 4631, + 4632, + 4633, + 4634, + 4635, + 4636, + 4637, + 4638, + 4639, + 4640, + 4641, + 4642, + 4643, + 4644, + 4645, + 4646, + 4647, + 4648, + 4649, + 4650, + 4651, + 4652, + 4653, + 4654, + 4655, + 4656, + 4657, + 4658, + 4659, + 4660, + 4661, + 4662, + 4663, + 4664, + 4665, + 4666, + 4667, + 4668, + 4669, + 4670, + 4671, + 4672, + 4673, + 4674, + 4675, + 4676, + 4677, + 4678, + 4679, + 4680, + 4682, + 4683, + 4684, + 4685, + 4688, + 4689, + 4690, + 4691, + 4692, + 4693, + 4694, + 4696, + 4698, + 4699, + 4700, + 4701, + 4704, + 4705, + 4706, + 4707, + 4708, + 4709, + 4710, + 4711, + 4712, + 4713, + 4714, + 4715, + 4716, + 4717, + 4718, + 4719, + 4720, + 4721, + 4722, + 4723, + 4724, + 4725, + 4726, + 4727, + 4728, + 4729, + 4730, + 4731, + 4732, + 4733, + 4734, + 4735, + 4736, + 4737, + 4738, + 4739, + 4740, + 4741, + 4742, + 4743, + 4744, + 4746, + 4747, + 4748, + 4749, + 4752, + 4753, + 4754, + 4755, + 4756, + 4757, + 4758, + 4759, + 4760, + 4761, + 4762, + 4763, + 4764, + 4765, + 4766, + 4767, + 4768, + 4769, + 4770, + 4771, + 4772, + 4773, + 4774, + 4775, + 4776, + 4777, + 4778, + 4779, + 4780, + 4781, + 4782, + 4783, + 4784, + 4786, + 4787, + 4788, + 4789, + 4792, + 4793, + 4794, + 4795, + 4796, + 4797, + 4798, + 4800, + 4802, + 4803, + 4804, + 4805, + 4808, + 4809, + 4810, + 4811, + 4812, + 4813, + 4814, + 4815, + 4816, + 4817, + 4818, + 4819, + 4820, + 4821, + 4822, + 4824, + 4825, + 4826, + 4827, + 4828, + 4829, + 4830, + 4831, + 4832, + 4833, + 4834, + 4835, + 4836, + 4837, + 4838, + 4839, + 4840, + 4841, + 4842, + 4843, + 4844, + 4845, + 4846, + 4847, + 4848, + 4849, + 4850, + 4851, + 4852, + 4853, + 4854, + 4855, + 4856, + 4857, + 4858, + 4859, + 4860, + 4861, + 4862, + 4863, + 4864, + 4865, + 4866, + 4867, + 4868, + 4869, + 4870, + 4871, + 4872, + 4873, + 4874, + 4875, + 4876, + 4877, + 4878, + 4879, + 4880, + 4882, + 4883, + 4884, + 4885, + 4888, + 4889, + 4890, + 4891, + 4892, + 4893, + 4894, + 4895, + 4896, + 4897, + 4898, + 4899, + 4900, + 4901, + 4902, + 4903, + 4904, + 4905, + 4906, + 4907, + 4908, + 4909, + 4910, + 4911, + 4912, + 4913, + 4914, + 4915, + 4916, + 4917, + 4918, + 4919, + 4920, + 4921, + 4922, + 4923, + 4924, + 4925, + 4926, + 4927, + 4928, + 4929, + 4930, + 4931, + 4932, + 4933, + 4934, + 4935, + 4936, + 4937, + 4938, + 4939, + 4940, + 4941, + 4942, + 4943, + 4944, + 4945, + 4946, + 4947, + 4948, + 4949, + 4950, + 4951, + 4952, + 4953, + 4954, + 4992, + 4993, + 4994, + 4995, + 4996, + 4997, + 4998, + 4999, + 5000, + 5001, + 5002, + 5003, + 5004, + 5005, + 5006, + 5007, + 5024, + 5025, + 5026, + 5027, + 5028, + 5029, + 5030, + 5031, + 5032, + 5033, + 5034, + 5035, + 5036, + 5037, + 5038, + 5039, + 5040, + 5041, + 5042, + 5043, + 5044, + 5045, + 5046, + 5047, + 5048, + 5049, + 5050, + 5051, + 5052, + 5053, + 5054, + 5055, + 5056, + 5057, + 5058, + 5059, + 5060, + 5061, + 5062, + 5063, + 5064, + 5065, + 5066, + 5067, + 5068, + 5069, + 5070, + 5071, + 5072, + 5073, + 5074, + 5075, + 5076, + 5077, + 5078, + 5079, + 5080, + 5081, + 5082, + 5083, + 5084, + 5085, + 5086, + 5087, + 5088, + 5089, + 5090, + 5091, + 5092, + 5093, + 5094, + 5095, + 5096, + 5097, + 5098, + 5099, + 5100, + 5101, + 5102, + 5103, + 5104, + 5105, + 5106, + 5107, + 5108, + 5121, + 5122, + 5123, + 5124, + 5125, + 5126, + 5127, + 5128, + 5129, + 5130, + 5131, + 5132, + 5133, + 5134, + 5135, + 5136, + 5137, + 5138, + 5139, + 5140, + 5141, + 5142, + 5143, + 5144, + 5145, + 5146, + 5147, + 5148, + 5149, + 5150, + 5151, + 5152, + 5153, + 5154, + 5155, + 5156, + 5157, + 5158, + 5159, + 5160, + 5161, + 5162, + 5163, + 5164, + 5165, + 5166, + 5167, + 5168, + 5169, + 5170, + 5171, + 5172, + 5173, + 5174, + 5175, + 5176, + 5177, + 5178, + 5179, + 5180, + 5181, + 5182, + 5183, + 5184, + 5185, + 5186, + 5187, + 5188, + 5189, + 5190, + 5191, + 5192, + 5193, + 5194, + 5195, + 5196, + 5197, + 5198, + 5199, + 5200, + 5201, + 5202, + 5203, + 5204, + 5205, + 5206, + 5207, + 5208, + 5209, + 5210, + 5211, + 5212, + 5213, + 5214, + 5215, + 5216, + 5217, + 5218, + 5219, + 5220, + 5221, + 5222, + 5223, + 5224, + 5225, + 5226, + 5227, + 5228, + 5229, + 5230, + 5231, + 5232, + 5233, + 5234, + 5235, + 5236, + 5237, + 5238, + 5239, + 5240, + 5241, + 5242, + 5243, + 5244, + 5245, + 5246, + 5247, + 5248, + 5249, + 5250, + 5251, + 5252, + 5253, + 5254, + 5255, + 5256, + 5257, + 5258, + 5259, + 5260, + 5261, + 5262, + 5263, + 5264, + 5265, + 5266, + 5267, + 5268, + 5269, + 5270, + 5271, + 5272, + 5273, + 5274, + 5275, + 5276, + 5277, + 5278, + 5279, + 5280, + 5281, + 5282, + 5283, + 5284, + 5285, + 5286, + 5287, + 5288, + 5289, + 5290, + 5291, + 5292, + 5293, + 5294, + 5295, + 5296, + 5297, + 5298, + 5299, + 5300, + 5301, + 5302, + 5303, + 5304, + 5305, + 5306, + 5307, + 5308, + 5309, + 5310, + 5311, + 5312, + 5313, + 5314, + 5315, + 5316, + 5317, + 5318, + 5319, + 5320, + 5321, + 5322, + 5323, + 5324, + 5325, + 5326, + 5327, + 5328, + 5329, + 5330, + 5331, + 5332, + 5333, + 5334, + 5335, + 5336, + 5337, + 5338, + 5339, + 5340, + 5341, + 5342, + 5343, + 5344, + 5345, + 5346, + 5347, + 5348, + 5349, + 5350, + 5351, + 5352, + 5353, + 5354, + 5355, + 5356, + 5357, + 5358, + 5359, + 5360, + 5361, + 5362, + 5363, + 5364, + 5365, + 5366, + 5367, + 5368, + 5369, + 5370, + 5371, + 5372, + 5373, + 5374, + 5375, + 5376, + 5377, + 5378, + 5379, + 5380, + 5381, + 5382, + 5383, + 5384, + 5385, + 5386, + 5387, + 5388, + 5389, + 5390, + 5391, + 5392, + 5393, + 5394, + 5395, + 5396, + 5397, + 5398, + 5399, + 5400, + 5401, + 5402, + 5403, + 5404, + 5405, + 5406, + 5407, + 5408, + 5409, + 5410, + 5411, + 5412, + 5413, + 5414, + 5415, + 5416, + 5417, + 5418, + 5419, + 5420, + 5421, + 5422, + 5423, + 5424, + 5425, + 5426, + 5427, + 5428, + 5429, + 5430, + 5431, + 5432, + 5433, + 5434, + 5435, + 5436, + 5437, + 5438, + 5439, + 5440, + 5441, + 5442, + 5443, + 5444, + 5445, + 5446, + 5447, + 5448, + 5449, + 5450, + 5451, + 5452, + 5453, + 5454, + 5455, + 5456, + 5457, + 5458, + 5459, + 5460, + 5461, + 5462, + 5463, + 5464, + 5465, + 5466, + 5467, + 5468, + 5469, + 5470, + 5471, + 5472, + 5473, + 5474, + 5475, + 5476, + 5477, + 5478, + 5479, + 5480, + 5481, + 5482, + 5483, + 5484, + 5485, + 5486, + 5487, + 5488, + 5489, + 5490, + 5491, + 5492, + 5493, + 5494, + 5495, + 5496, + 5497, + 5498, + 5499, + 5500, + 5501, + 5502, + 5503, + 5504, + 5505, + 5506, + 5507, + 5508, + 5509, + 5510, + 5511, + 5512, + 5513, + 5514, + 5515, + 5516, + 5517, + 5518, + 5519, + 5520, + 5521, + 5522, + 5523, + 5524, + 5525, + 5526, + 5527, + 5528, + 5529, + 5530, + 5531, + 5532, + 5533, + 5534, + 5535, + 5536, + 5537, + 5538, + 5539, + 5540, + 5541, + 5542, + 5543, + 5544, + 5545, + 5546, + 5547, + 5548, + 5549, + 5550, + 5551, + 5552, + 5553, + 5554, + 5555, + 5556, + 5557, + 5558, + 5559, + 5560, + 5561, + 5562, + 5563, + 5564, + 5565, + 5566, + 5567, + 5568, + 5569, + 5570, + 5571, + 5572, + 5573, + 5574, + 5575, + 5576, + 5577, + 5578, + 5579, + 5580, + 5581, + 5582, + 5583, + 5584, + 5585, + 5586, + 5587, + 5588, + 5589, + 5590, + 5591, + 5592, + 5593, + 5594, + 5595, + 5596, + 5597, + 5598, + 5599, + 5600, + 5601, + 5602, + 5603, + 5604, + 5605, + 5606, + 5607, + 5608, + 5609, + 5610, + 5611, + 5612, + 5613, + 5614, + 5615, + 5616, + 5617, + 5618, + 5619, + 5620, + 5621, + 5622, + 5623, + 5624, + 5625, + 5626, + 5627, + 5628, + 5629, + 5630, + 5631, + 5632, + 5633, + 5634, + 5635, + 5636, + 5637, + 5638, + 5639, + 5640, + 5641, + 5642, + 5643, + 5644, + 5645, + 5646, + 5647, + 5648, + 5649, + 5650, + 5651, + 5652, + 5653, + 5654, + 5655, + 5656, + 5657, + 5658, + 5659, + 5660, + 5661, + 5662, + 5663, + 5664, + 5665, + 5666, + 5667, + 5668, + 5669, + 5670, + 5671, + 5672, + 5673, + 5674, + 5675, + 5676, + 5677, + 5678, + 5679, + 5680, + 5681, + 5682, + 5683, + 5684, + 5685, + 5686, + 5687, + 5688, + 5689, + 5690, + 5691, + 5692, + 5693, + 5694, + 5695, + 5696, + 5697, + 5698, + 5699, + 5700, + 5701, + 5702, + 5703, + 5704, + 5705, + 5706, + 5707, + 5708, + 5709, + 5710, + 5711, + 5712, + 5713, + 5714, + 5715, + 5716, + 5717, + 5718, + 5719, + 5720, + 5721, + 5722, + 5723, + 5724, + 5725, + 5726, + 5727, + 5728, + 5729, + 5730, + 5731, + 5732, + 5733, + 5734, + 5735, + 5736, + 5737, + 5738, + 5739, + 5740, + 5743, + 5744, + 5745, + 5746, + 5747, + 5748, + 5749, + 5750, + 5751, + 5752, + 5753, + 5754, + 5755, + 5756, + 5757, + 5758, + 5759, + 5761, + 5762, + 5763, + 5764, + 5765, + 5766, + 5767, + 5768, + 5769, + 5770, + 5771, + 5772, + 5773, + 5774, + 5775, + 5776, + 5777, + 5778, + 5779, + 5780, + 5781, + 5782, + 5783, + 5784, + 5785, + 5786, + 5792, + 5793, + 5794, + 5795, + 5796, + 5797, + 5798, + 5799, + 5800, + 5801, + 5802, + 5803, + 5804, + 5805, + 5806, + 5807, + 5808, + 5809, + 5810, + 5811, + 5812, + 5813, + 5814, + 5815, + 5816, + 5817, + 5818, + 5819, + 5820, + 5821, + 5822, + 5823, + 5824, + 5825, + 5826, + 5827, + 5828, + 5829, + 5830, + 5831, + 5832, + 5833, + 5834, + 5835, + 5836, + 5837, + 5838, + 5839, + 5840, + 5841, + 5842, + 5843, + 5844, + 5845, + 5846, + 5847, + 5848, + 5849, + 5850, + 5851, + 5852, + 5853, + 5854, + 5855, + 5856, + 5857, + 5858, + 5859, + 5860, + 5861, + 5862, + 5863, + 5864, + 5865, + 5866, + 5870, + 5871, + 5872, + 5888, + 5889, + 5890, + 5891, + 5892, + 5893, + 5894, + 5895, + 5896, + 5897, + 5898, + 5899, + 5900, + 5902, + 5903, + 5904, + 5905, + 5920, + 5921, + 5922, + 5923, + 5924, + 5925, + 5926, + 5927, + 5928, + 5929, + 5930, + 5931, + 5932, + 5933, + 5934, + 5935, + 5936, + 5937, + 5952, + 5953, + 5954, + 5955, + 5956, + 5957, + 5958, + 5959, + 5960, + 5961, + 5962, + 5963, + 5964, + 5965, + 5966, + 5967, + 5968, + 5969, + 5984, + 5985, + 5986, + 5987, + 5988, + 5989, + 5990, + 5991, + 5992, + 5993, + 5994, + 5995, + 5996, + 5998, + 5999, + 6000, + 6016, + 6017, + 6018, + 6019, + 6020, + 6021, + 6022, + 6023, + 6024, + 6025, + 6026, + 6027, + 6028, + 6029, + 6030, + 6031, + 6032, + 6033, + 6034, + 6035, + 6036, + 6037, + 6038, + 6039, + 6040, + 6041, + 6042, + 6043, + 6044, + 6045, + 6046, + 6047, + 6048, + 6049, + 6050, + 6051, + 6052, + 6053, + 6054, + 6055, + 6056, + 6057, + 6058, + 6059, + 6060, + 6061, + 6062, + 6063, + 6064, + 6065, + 6066, + 6067, + 6103, + 6108, + 6176, + 6177, + 6178, + 6179, + 6180, + 6181, + 6182, + 6183, + 6184, + 6185, + 6186, + 6187, + 6188, + 6189, + 6190, + 6191, + 6192, + 6193, + 6194, + 6195, + 6196, + 6197, + 6198, + 6199, + 6200, + 6201, + 6202, + 6203, + 6204, + 6205, + 6206, + 6207, + 6208, + 6209, + 6210, + 6211, + 6212, + 6213, + 6214, + 6215, + 6216, + 6217, + 6218, + 6219, + 6220, + 6221, + 6222, + 6223, + 6224, + 6225, + 6226, + 6227, + 6228, + 6229, + 6230, + 6231, + 6232, + 6233, + 6234, + 6235, + 6236, + 6237, + 6238, + 6239, + 6240, + 6241, + 6242, + 6243, + 6244, + 6245, + 6246, + 6247, + 6248, + 6249, + 6250, + 6251, + 6252, + 6253, + 6254, + 6255, + 6256, + 6257, + 6258, + 6259, + 6260, + 6261, + 6262, + 6263, + 6272, + 6273, + 6274, + 6275, + 6276, + 6277, + 6278, + 6279, + 6280, + 6281, + 6282, + 6283, + 6284, + 6285, + 6286, + 6287, + 6288, + 6289, + 6290, + 6291, + 6292, + 6293, + 6294, + 6295, + 6296, + 6297, + 6298, + 6299, + 6300, + 6301, + 6302, + 6303, + 6304, + 6305, + 6306, + 6307, + 6308, + 6309, + 6310, + 6311, + 6312, + 6314, + 6320, + 6321, + 6322, + 6323, + 6324, + 6325, + 6326, + 6327, + 6328, + 6329, + 6330, + 6331, + 6332, + 6333, + 6334, + 6335, + 6336, + 6337, + 6338, + 6339, + 6340, + 6341, + 6342, + 6343, + 6344, + 6345, + 6346, + 6347, + 6348, + 6349, + 6350, + 6351, + 6352, + 6353, + 6354, + 6355, + 6356, + 6357, + 6358, + 6359, + 6360, + 6361, + 6362, + 6363, + 6364, + 6365, + 6366, + 6367, + 6368, + 6369, + 6370, + 6371, + 6372, + 6373, + 6374, + 6375, + 6376, + 6377, + 6378, + 6379, + 6380, + 6381, + 6382, + 6383, + 6384, + 6385, + 6386, + 6387, + 6388, + 6389, + 6400, + 6401, + 6402, + 6403, + 6404, + 6405, + 6406, + 6407, + 6408, + 6409, + 6410, + 6411, + 6412, + 6413, + 6414, + 6415, + 6416, + 6417, + 6418, + 6419, + 6420, + 6421, + 6422, + 6423, + 6424, + 6425, + 6426, + 6427, + 6428, + 6480, + 6481, + 6482, + 6483, + 6484, + 6485, + 6486, + 6487, + 6488, + 6489, + 6490, + 6491, + 6492, + 6493, + 6494, + 6495, + 6496, + 6497, + 6498, + 6499, + 6500, + 6501, + 6502, + 6503, + 6504, + 6505, + 6506, + 6507, + 6508, + 6509, + 6512, + 6513, + 6514, + 6515, + 6516, + 6528, + 6529, + 6530, + 6531, + 6532, + 6533, + 6534, + 6535, + 6536, + 6537, + 6538, + 6539, + 6540, + 6541, + 6542, + 6543, + 6544, + 6545, + 6546, + 6547, + 6548, + 6549, + 6550, + 6551, + 6552, + 6553, + 6554, + 6555, + 6556, + 6557, + 6558, + 6559, + 6560, + 6561, + 6562, + 6563, + 6564, + 6565, + 6566, + 6567, + 6568, + 6569, + 6570, + 6571, + 6593, + 6594, + 6595, + 6596, + 6597, + 6598, + 6599, + 6656, + 6657, + 6658, + 6659, + 6660, + 6661, + 6662, + 6663, + 6664, + 6665, + 6666, + 6667, + 6668, + 6669, + 6670, + 6671, + 6672, + 6673, + 6674, + 6675, + 6676, + 6677, + 6678, + 6688, + 6689, + 6690, + 6691, + 6692, + 6693, + 6694, + 6695, + 6696, + 6697, + 6698, + 6699, + 6700, + 6701, + 6702, + 6703, + 6704, + 6705, + 6706, + 6707, + 6708, + 6709, + 6710, + 6711, + 6712, + 6713, + 6714, + 6715, + 6716, + 6717, + 6718, + 6719, + 6720, + 6721, + 6722, + 6723, + 6724, + 6725, + 6726, + 6727, + 6728, + 6729, + 6730, + 6731, + 6732, + 6733, + 6734, + 6735, + 6736, + 6737, + 6738, + 6739, + 6740, + 6823, + 6917, + 6918, + 6919, + 6920, + 6921, + 6922, + 6923, + 6924, + 6925, + 6926, + 6927, + 6928, + 6929, + 6930, + 6931, + 6932, + 6933, + 6934, + 6935, + 6936, + 6937, + 6938, + 6939, + 6940, + 6941, + 6942, + 6943, + 6944, + 6945, + 6946, + 6947, + 6948, + 6949, + 6950, + 6951, + 6952, + 6953, + 6954, + 6955, + 6956, + 6957, + 6958, + 6959, + 6960, + 6961, + 6962, + 6963, + 6981, + 6982, + 6983, + 6984, + 6985, + 6986, + 6987, + 7043, + 7044, + 7045, + 7046, + 7047, + 7048, + 7049, + 7050, + 7051, + 7052, + 7053, + 7054, + 7055, + 7056, + 7057, + 7058, + 7059, + 7060, + 7061, + 7062, + 7063, + 7064, + 7065, + 7066, + 7067, + 7068, + 7069, + 7070, + 7071, + 7072, + 7086, + 7087, + 7098, + 7099, + 7100, + 7101, + 7102, + 7103, + 7104, + 7105, + 7106, + 7107, + 7108, + 7109, + 7110, + 7111, + 7112, + 7113, + 7114, + 7115, + 7116, + 7117, + 7118, + 7119, + 7120, + 7121, + 7122, + 7123, + 7124, + 7125, + 7126, + 7127, + 7128, + 7129, + 7130, + 7131, + 7132, + 7133, + 7134, + 7135, + 7136, + 7137, + 7138, + 7139, + 7140, + 7141, + 7168, + 7169, + 7170, + 7171, + 7172, + 7173, + 7174, + 7175, + 7176, + 7177, + 7178, + 7179, + 7180, + 7181, + 7182, + 7183, + 7184, + 7185, + 7186, + 7187, + 7188, + 7189, + 7190, + 7191, + 7192, + 7193, + 7194, + 7195, + 7196, + 7197, + 7198, + 7199, + 7200, + 7201, + 7202, + 7203, + 7245, + 7246, + 7247, + 7258, + 7259, + 7260, + 7261, + 7262, + 7263, + 7264, + 7265, + 7266, + 7267, + 7268, + 7269, + 7270, + 7271, + 7272, + 7273, + 7274, + 7275, + 7276, + 7277, + 7278, + 7279, + 7280, + 7281, + 7282, + 7283, + 7284, + 7285, + 7286, + 7287, + 7288, + 7289, + 7290, + 7291, + 7292, + 7293, + 7401, + 7402, + 7403, + 7404, + 7406, + 7407, + 7408, + 7409, + 7413, + 7414, + 7424, + 7425, + 7426, + 7427, + 7428, + 7429, + 7430, + 7431, + 7432, + 7433, + 7434, + 7435, + 7436, + 7437, + 7438, + 7439, + 7440, + 7441, + 7442, + 7443, + 7444, + 7445, + 7446, + 7447, + 7448, + 7449, + 7450, + 7451, + 7452, + 7453, + 7454, + 7455, + 7456, + 7457, + 7458, + 7459, + 7460, + 7461, + 7462, + 7463, + 7464, + 7465, + 7466, + 7467, + 7468, + 7469, + 7470, + 7471, + 7472, + 7473, + 7474, + 7475, + 7476, + 7477, + 7478, + 7479, + 7480, + 7481, + 7482, + 7483, + 7484, + 7485, + 7486, + 7487, + 7488, + 7489, + 7490, + 7491, + 7492, + 7493, + 7494, + 7495, + 7496, + 7497, + 7498, + 7499, + 7500, + 7501, + 7502, + 7503, + 7504, + 7505, + 7506, + 7507, + 7508, + 7509, + 7510, + 7511, + 7512, + 7513, + 7514, + 7515, + 7516, + 7517, + 7518, + 7519, + 7520, + 7521, + 7522, + 7523, + 7524, + 7525, + 7526, + 7527, + 7528, + 7529, + 7530, + 7531, + 7532, + 7533, + 7534, + 7535, + 7536, + 7537, + 7538, + 7539, + 7540, + 7541, + 7542, + 7543, + 7544, + 7545, + 7546, + 7547, + 7548, + 7549, + 7550, + 7551, + 7552, + 7553, + 7554, + 7555, + 7556, + 7557, + 7558, + 7559, + 7560, + 7561, + 7562, + 7563, + 7564, + 7565, + 7566, + 7567, + 7568, + 7569, + 7570, + 7571, + 7572, + 7573, + 7574, + 7575, + 7576, + 7577, + 7578, + 7579, + 7580, + 7581, + 7582, + 7583, + 7584, + 7585, + 7586, + 7587, + 7588, + 7589, + 7590, + 7591, + 7592, + 7593, + 7594, + 7595, + 7596, + 7597, + 7598, + 7599, + 7600, + 7601, + 7602, + 7603, + 7604, + 7605, + 7606, + 7607, + 7608, + 7609, + 7610, + 7611, + 7612, + 7613, + 7614, + 7615, + 7680, + 7681, + 7682, + 7683, + 7684, + 7685, + 7686, + 7687, + 7688, + 7689, + 7690, + 7691, + 7692, + 7693, + 7694, + 7695, + 7696, + 7697, + 7698, + 7699, + 7700, + 7701, + 7702, + 7703, + 7704, + 7705, + 7706, + 7707, + 7708, + 7709, + 7710, + 7711, + 7712, + 7713, + 7714, + 7715, + 7716, + 7717, + 7718, + 7719, + 7720, + 7721, + 7722, + 7723, + 7724, + 7725, + 7726, + 7727, + 7728, + 7729, + 7730, + 7731, + 7732, + 7733, + 7734, + 7735, + 7736, + 7737, + 7738, + 7739, + 7740, + 7741, + 7742, + 7743, + 7744, + 7745, + 7746, + 7747, + 7748, + 7749, + 7750, + 7751, + 7752, + 7753, + 7754, + 7755, + 7756, + 7757, + 7758, + 7759, + 7760, + 7761, + 7762, + 7763, + 7764, + 7765, + 7766, + 7767, + 7768, + 7769, + 7770, + 7771, + 7772, + 7773, + 7774, + 7775, + 7776, + 7777, + 7778, + 7779, + 7780, + 7781, + 7782, + 7783, + 7784, + 7785, + 7786, + 7787, + 7788, + 7789, + 7790, + 7791, + 7792, + 7793, + 7794, + 7795, + 7796, + 7797, + 7798, + 7799, + 7800, + 7801, + 7802, + 7803, + 7804, + 7805, + 7806, + 7807, + 7808, + 7809, + 7810, + 7811, + 7812, + 7813, + 7814, + 7815, + 7816, + 7817, + 7818, + 7819, + 7820, + 7821, + 7822, + 7823, + 7824, + 7825, + 7826, + 7827, + 7828, + 7829, + 7830, + 7831, + 7832, + 7833, + 7834, + 7835, + 7836, + 7837, + 7838, + 7839, + 7840, + 7841, + 7842, + 7843, + 7844, + 7845, + 7846, + 7847, + 7848, + 7849, + 7850, + 7851, + 7852, + 7853, + 7854, + 7855, + 7856, + 7857, + 7858, + 7859, + 7860, + 7861, + 7862, + 7863, + 7864, + 7865, + 7866, + 7867, + 7868, + 7869, + 7870, + 7871, + 7872, + 7873, + 7874, + 7875, + 7876, + 7877, + 7878, + 7879, + 7880, + 7881, + 7882, + 7883, + 7884, + 7885, + 7886, + 7887, + 7888, + 7889, + 7890, + 7891, + 7892, + 7893, + 7894, + 7895, + 7896, + 7897, + 7898, + 7899, + 7900, + 7901, + 7902, + 7903, + 7904, + 7905, + 7906, + 7907, + 7908, + 7909, + 7910, + 7911, + 7912, + 7913, + 7914, + 7915, + 7916, + 7917, + 7918, + 7919, + 7920, + 7921, + 7922, + 7923, + 7924, + 7925, + 7926, + 7927, + 7928, + 7929, + 7930, + 7931, + 7932, + 7933, + 7934, + 7935, + 7936, + 7937, + 7938, + 7939, + 7940, + 7941, + 7942, + 7943, + 7944, + 7945, + 7946, + 7947, + 7948, + 7949, + 7950, + 7951, + 7952, + 7953, + 7954, + 7955, + 7956, + 7957, + 7960, + 7961, + 7962, + 7963, + 7964, + 7965, + 7968, + 7969, + 7970, + 7971, + 7972, + 7973, + 7974, + 7975, + 7976, + 7977, + 7978, + 7979, + 7980, + 7981, + 7982, + 7983, + 7984, + 7985, + 7986, + 7987, + 7988, + 7989, + 7990, + 7991, + 7992, + 7993, + 7994, + 7995, + 7996, + 7997, + 7998, + 7999, + 8000, + 8001, + 8002, + 8003, + 8004, + 8005, + 8008, + 8009, + 8010, + 8011, + 8012, + 8013, + 8016, + 8017, + 8018, + 8019, + 8020, + 8021, + 8022, + 8023, + 8025, + 8027, + 8029, + 8031, + 8032, + 8033, + 8034, + 8035, + 8036, + 8037, + 8038, + 8039, + 8040, + 8041, + 8042, + 8043, + 8044, + 8045, + 8046, + 8047, + 8048, + 8049, + 8050, + 8051, + 8052, + 8053, + 8054, + 8055, + 8056, + 8057, + 8058, + 8059, + 8060, + 8061, + 8064, + 8065, + 8066, + 8067, + 8068, + 8069, + 8070, + 8071, + 8072, + 8073, + 8074, + 8075, + 8076, + 8077, + 8078, + 8079, + 8080, + 8081, + 8082, + 8083, + 8084, + 8085, + 8086, + 8087, + 8088, + 8089, + 8090, + 8091, + 8092, + 8093, + 8094, + 8095, + 8096, + 8097, + 8098, + 8099, + 8100, + 8101, + 8102, + 8103, + 8104, + 8105, + 8106, + 8107, + 8108, + 8109, + 8110, + 8111, + 8112, + 8113, + 8114, + 8115, + 8116, + 8118, + 8119, + 8120, + 8121, + 8122, + 8123, + 8124, + 8126, + 8130, + 8131, + 8132, + 8134, + 8135, + 8136, + 8137, + 8138, + 8139, + 8140, + 8144, + 8145, + 8146, + 8147, + 8150, + 8151, + 8152, + 8153, + 8154, + 8155, + 8160, + 8161, + 8162, + 8163, + 8164, + 8165, + 8166, + 8167, + 8168, + 8169, + 8170, + 8171, + 8172, + 8178, + 8179, + 8180, + 8182, + 8183, + 8184, + 8185, + 8186, + 8187, + 8188, + 8305, + 8319, + 8336, + 8337, + 8338, + 8339, + 8340, + 8341, + 8342, + 8343, + 8344, + 8345, + 8346, + 8347, + 8348, + 8450, + 8455, + 8458, + 8459, + 8460, + 8461, + 8462, + 8463, + 8464, + 8465, + 8466, + 8467, + 8469, + 8473, + 8474, + 8475, + 8476, + 8477, + 8484, + 8486, + 8488, + 8490, + 8491, + 8492, + 8493, + 8495, + 8496, + 8497, + 8498, + 8499, + 8500, + 8501, + 8502, + 8503, + 8504, + 8505, + 8508, + 8509, + 8510, + 8511, + 8517, + 8518, + 8519, + 8520, + 8521, + 8526, + 8544, + 8545, + 8546, + 8547, + 8548, + 8549, + 8550, + 8551, + 8552, + 8553, + 8554, + 8555, + 8556, + 8557, + 8558, + 8559, + 8560, + 8561, + 8562, + 8563, + 8564, + 8565, + 8566, + 8567, + 8568, + 8569, + 8570, + 8571, + 8572, + 8573, + 8574, + 8575, + 8576, + 8577, + 8578, + 8579, + 8580, + 8581, + 8582, + 8583, + 8584, + 11264, + 11265, + 11266, + 11267, + 11268, + 11269, + 11270, + 11271, + 11272, + 11273, + 11274, + 11275, + 11276, + 11277, + 11278, + 11279, + 11280, + 11281, + 11282, + 11283, + 11284, + 11285, + 11286, + 11287, + 11288, + 11289, + 11290, + 11291, + 11292, + 11293, + 11294, + 11295, + 11296, + 11297, + 11298, + 11299, + 11300, + 11301, + 11302, + 11303, + 11304, + 11305, + 11306, + 11307, + 11308, + 11309, + 11310, + 11312, + 11313, + 11314, + 11315, + 11316, + 11317, + 11318, + 11319, + 11320, + 11321, + 11322, + 11323, + 11324, + 11325, + 11326, + 11327, + 11328, + 11329, + 11330, + 11331, + 11332, + 11333, + 11334, + 11335, + 11336, + 11337, + 11338, + 11339, + 11340, + 11341, + 11342, + 11343, + 11344, + 11345, + 11346, + 11347, + 11348, + 11349, + 11350, + 11351, + 11352, + 11353, + 11354, + 11355, + 11356, + 11357, + 11358, + 11360, + 11361, + 11362, + 11363, + 11364, + 11365, + 11366, + 11367, + 11368, + 11369, + 11370, + 11371, + 11372, + 11373, + 11374, + 11375, + 11376, + 11377, + 11378, + 11379, + 11380, + 11381, + 11382, + 11383, + 11384, + 11385, + 11386, + 11387, + 11388, + 11389, + 11390, + 11391, + 11392, + 11393, + 11394, + 11395, + 11396, + 11397, + 11398, + 11399, + 11400, + 11401, + 11402, + 11403, + 11404, + 11405, + 11406, + 11407, + 11408, + 11409, + 11410, + 11411, + 11412, + 11413, + 11414, + 11415, + 11416, + 11417, + 11418, + 11419, + 11420, + 11421, + 11422, + 11423, + 11424, + 11425, + 11426, + 11427, + 11428, + 11429, + 11430, + 11431, + 11432, + 11433, + 11434, + 11435, + 11436, + 11437, + 11438, + 11439, + 11440, + 11441, + 11442, + 11443, + 11444, + 11445, + 11446, + 11447, + 11448, + 11449, + 11450, + 11451, + 11452, + 11453, + 11454, + 11455, + 11456, + 11457, + 11458, + 11459, + 11460, + 11461, + 11462, + 11463, + 11464, + 11465, + 11466, + 11467, + 11468, + 11469, + 11470, + 11471, + 11472, + 11473, + 11474, + 11475, + 11476, + 11477, + 11478, + 11479, + 11480, + 11481, + 11482, + 11483, + 11484, + 11485, + 11486, + 11487, + 11488, + 11489, + 11490, + 11491, + 11492, + 11499, + 11500, + 11501, + 11502, + 11506, + 11507, + 11520, + 11521, + 11522, + 11523, + 11524, + 11525, + 11526, + 11527, + 11528, + 11529, + 11530, + 11531, + 11532, + 11533, + 11534, + 11535, + 11536, + 11537, + 11538, + 11539, + 11540, + 11541, + 11542, + 11543, + 11544, + 11545, + 11546, + 11547, + 11548, + 11549, + 11550, + 11551, + 11552, + 11553, + 11554, + 11555, + 11556, + 11557, + 11559, + 11565, + 11568, + 11569, + 11570, + 11571, + 11572, + 11573, + 11574, + 11575, + 11576, + 11577, + 11578, + 11579, + 11580, + 11581, + 11582, + 11583, + 11584, + 11585, + 11586, + 11587, + 11588, + 11589, + 11590, + 11591, + 11592, + 11593, + 11594, + 11595, + 11596, + 11597, + 11598, + 11599, + 11600, + 11601, + 11602, + 11603, + 11604, + 11605, + 11606, + 11607, + 11608, + 11609, + 11610, + 11611, + 11612, + 11613, + 11614, + 11615, + 11616, + 11617, + 11618, + 11619, + 11620, + 11621, + 11622, + 11623, + 11631, + 11648, + 11649, + 11650, + 11651, + 11652, + 11653, + 11654, + 11655, + 11656, + 11657, + 11658, + 11659, + 11660, + 11661, + 11662, + 11663, + 11664, + 11665, + 11666, + 11667, + 11668, + 11669, + 11670, + 11680, + 11681, + 11682, + 11683, + 11684, + 11685, + 11686, + 11688, + 11689, + 11690, + 11691, + 11692, + 11693, + 11694, + 11696, + 11697, + 11698, + 11699, + 11700, + 11701, + 11702, + 11704, + 11705, + 11706, + 11707, + 11708, + 11709, + 11710, + 11712, + 11713, + 11714, + 11715, + 11716, + 11717, + 11718, + 11720, + 11721, + 11722, + 11723, + 11724, + 11725, + 11726, + 11728, + 11729, + 11730, + 11731, + 11732, + 11733, + 11734, + 11736, + 11737, + 11738, + 11739, + 11740, + 11741, + 11742, + 11823, + 12293, + 12294, + 12295, + 12321, + 12322, + 12323, + 12324, + 12325, + 12326, + 12327, + 12328, + 12329, + 12337, + 12338, + 12339, + 12340, + 12341, + 12344, + 12345, + 12346, + 12347, + 12348, + 12353, + 12354, + 12355, + 12356, + 12357, + 12358, + 12359, + 12360, + 12361, + 12362, + 12363, + 12364, + 12365, + 12366, + 12367, + 12368, + 12369, + 12370, + 12371, + 12372, + 12373, + 12374, + 12375, + 12376, + 12377, + 12378, + 12379, + 12380, + 12381, + 12382, + 12383, + 12384, + 12385, + 12386, + 12387, + 12388, + 12389, + 12390, + 12391, + 12392, + 12393, + 12394, + 12395, + 12396, + 12397, + 12398, + 12399, + 12400, + 12401, + 12402, + 12403, + 12404, + 12405, + 12406, + 12407, + 12408, + 12409, + 12410, + 12411, + 12412, + 12413, + 12414, + 12415, + 12416, + 12417, + 12418, + 12419, + 12420, + 12421, + 12422, + 12423, + 12424, + 12425, + 12426, + 12427, + 12428, + 12429, + 12430, + 12431, + 12432, + 12433, + 12434, + 12435, + 12436, + 12437, + 12438, + 12445, + 12446, + 12447, + 12449, + 12450, + 12451, + 12452, + 12453, + 12454, + 12455, + 12456, + 12457, + 12458, + 12459, + 12460, + 12461, + 12462, + 12463, + 12464, + 12465, + 12466, + 12467, + 12468, + 12469, + 12470, + 12471, + 12472, + 12473, + 12474, + 12475, + 12476, + 12477, + 12478, + 12479, + 12480, + 12481, + 12482, + 12483, + 12484, + 12485, + 12486, + 12487, + 12488, + 12489, + 12490, + 12491, + 12492, + 12493, + 12494, + 12495, + 12496, + 12497, + 12498, + 12499, + 12500, + 12501, + 12502, + 12503, + 12504, + 12505, + 12506, + 12507, + 12508, + 12509, + 12510, + 12511, + 12512, + 12513, + 12514, + 12515, + 12516, + 12517, + 12518, + 12519, + 12520, + 12521, + 12522, + 12523, + 12524, + 12525, + 12526, + 12527, + 12528, + 12529, + 12530, + 12531, + 12532, + 12533, + 12534, + 12535, + 12536, + 12537, + 12538, + 12540, + 12541, + 12542, + 12543, + 12549, + 12550, + 12551, + 12552, + 12553, + 12554, + 12555, + 12556, + 12557, + 12558, + 12559, + 12560, + 12561, + 12562, + 12563, + 12564, + 12565, + 12566, + 12567, + 12568, + 12569, + 12570, + 12571, + 12572, + 12573, + 12574, + 12575, + 12576, + 12577, + 12578, + 12579, + 12580, + 12581, + 12582, + 12583, + 12584, + 12585, + 12586, + 12587, + 12588, + 12589, + 12593, + 12594, + 12595, + 12596, + 12597, + 12598, + 12599, + 12600, + 12601, + 12602, + 12603, + 12604, + 12605, + 12606, + 12607, + 12608, + 12609, + 12610, + 12611, + 12612, + 12613, + 12614, + 12615, + 12616, + 12617, + 12618, + 12619, + 12620, + 12621, + 12622, + 12623, + 12624, + 12625, + 12626, + 12627, + 12628, + 12629, + 12630, + 12631, + 12632, + 12633, + 12634, + 12635, + 12636, + 12637, + 12638, + 12639, + 12640, + 12641, + 12642, + 12643, + 12644, + 12645, + 12646, + 12647, + 12648, + 12649, + 12650, + 12651, + 12652, + 12653, + 12654, + 12655, + 12656, + 12657, + 12658, + 12659, + 12660, + 12661, + 12662, + 12663, + 12664, + 12665, + 12666, + 12667, + 12668, + 12669, + 12670, + 12671, + 12672, + 12673, + 12674, + 12675, + 12676, + 12677, + 12678, + 12679, + 12680, + 12681, + 12682, + 12683, + 12684, + 12685, + 12686, + 12704, + 12705, + 12706, + 12707, + 12708, + 12709, + 12710, + 12711, + 12712, + 12713, + 12714, + 12715, + 12716, + 12717, + 12718, + 12719, + 12720, + 12721, + 12722, + 12723, + 12724, + 12725, + 12726, + 12727, + 12728, + 12729, + 12730, + 12784, + 12785, + 12786, + 12787, + 12788, + 12789, + 12790, + 12791, + 12792, + 12793, + 12794, + 12795, + 12796, + 12797, + 12798, + 12799, + 13312, + 13313, + 13314, + 13315, + 13316, + 13317, + 13318, + 13319, + 13320, + 13321, + 13322, + 13323, + 13324, + 13325, + 13326, + 13327, + 13328, + 13329, + 13330, + 13331, + 13332, + 13333, + 13334, + 13335, + 13336, + 13337, + 13338, + 13339, + 13340, + 13341, + 13342, + 13343, + 13344, + 13345, + 13346, + 13347, + 13348, + 13349, + 13350, + 13351, + 13352, + 13353, + 13354, + 13355, + 13356, + 13357, + 13358, + 13359, + 13360, + 13361, + 13362, + 13363, + 13364, + 13365, + 13366, + 13367, + 13368, + 13369, + 13370, + 13371, + 13372, + 13373, + 13374, + 13375, + 13376, + 13377, + 13378, + 13379, + 13380, + 13381, + 13382, + 13383, + 13384, + 13385, + 13386, + 13387, + 13388, + 13389, + 13390, + 13391, + 13392, + 13393, + 13394, + 13395, + 13396, + 13397, + 13398, + 13399, + 13400, + 13401, + 13402, + 13403, + 13404, + 13405, + 13406, + 13407, + 13408, + 13409, + 13410, + 13411, + 13412, + 13413, + 13414, + 13415, + 13416, + 13417, + 13418, + 13419, + 13420, + 13421, + 13422, + 13423, + 13424, + 13425, + 13426, + 13427, + 13428, + 13429, + 13430, + 13431, + 13432, + 13433, + 13434, + 13435, + 13436, + 13437, + 13438, + 13439, + 13440, + 13441, + 13442, + 13443, + 13444, + 13445, + 13446, + 13447, + 13448, + 13449, + 13450, + 13451, + 13452, + 13453, + 13454, + 13455, + 13456, + 13457, + 13458, + 13459, + 13460, + 13461, + 13462, + 13463, + 13464, + 13465, + 13466, + 13467, + 13468, + 13469, + 13470, + 13471, + 13472, + 13473, + 13474, + 13475, + 13476, + 13477, + 13478, + 13479, + 13480, + 13481, + 13482, + 13483, + 13484, + 13485, + 13486, + 13487, + 13488, + 13489, + 13490, + 13491, + 13492, + 13493, + 13494, + 13495, + 13496, + 13497, + 13498, + 13499, + 13500, + 13501, + 13502, + 13503, + 13504, + 13505, + 13506, + 13507, + 13508, + 13509, + 13510, + 13511, + 13512, + 13513, + 13514, + 13515, + 13516, + 13517, + 13518, + 13519, + 13520, + 13521, + 13522, + 13523, + 13524, + 13525, + 13526, + 13527, + 13528, + 13529, + 13530, + 13531, + 13532, + 13533, + 13534, + 13535, + 13536, + 13537, + 13538, + 13539, + 13540, + 13541, + 13542, + 13543, + 13544, + 13545, + 13546, + 13547, + 13548, + 13549, + 13550, + 13551, + 13552, + 13553, + 13554, + 13555, + 13556, + 13557, + 13558, + 13559, + 13560, + 13561, + 13562, + 13563, + 13564, + 13565, + 13566, + 13567, + 13568, + 13569, + 13570, + 13571, + 13572, + 13573, + 13574, + 13575, + 13576, + 13577, + 13578, + 13579, + 13580, + 13581, + 13582, + 13583, + 13584, + 13585, + 13586, + 13587, + 13588, + 13589, + 13590, + 13591, + 13592, + 13593, + 13594, + 13595, + 13596, + 13597, + 13598, + 13599, + 13600, + 13601, + 13602, + 13603, + 13604, + 13605, + 13606, + 13607, + 13608, + 13609, + 13610, + 13611, + 13612, + 13613, + 13614, + 13615, + 13616, + 13617, + 13618, + 13619, + 13620, + 13621, + 13622, + 13623, + 13624, + 13625, + 13626, + 13627, + 13628, + 13629, + 13630, + 13631, + 13632, + 13633, + 13634, + 13635, + 13636, + 13637, + 13638, + 13639, + 13640, + 13641, + 13642, + 13643, + 13644, + 13645, + 13646, + 13647, + 13648, + 13649, + 13650, + 13651, + 13652, + 13653, + 13654, + 13655, + 13656, + 13657, + 13658, + 13659, + 13660, + 13661, + 13662, + 13663, + 13664, + 13665, + 13666, + 13667, + 13668, + 13669, + 13670, + 13671, + 13672, + 13673, + 13674, + 13675, + 13676, + 13677, + 13678, + 13679, + 13680, + 13681, + 13682, + 13683, + 13684, + 13685, + 13686, + 13687, + 13688, + 13689, + 13690, + 13691, + 13692, + 13693, + 13694, + 13695, + 13696, + 13697, + 13698, + 13699, + 13700, + 13701, + 13702, + 13703, + 13704, + 13705, + 13706, + 13707, + 13708, + 13709, + 13710, + 13711, + 13712, + 13713, + 13714, + 13715, + 13716, + 13717, + 13718, + 13719, + 13720, + 13721, + 13722, + 13723, + 13724, + 13725, + 13726, + 13727, + 13728, + 13729, + 13730, + 13731, + 13732, + 13733, + 13734, + 13735, + 13736, + 13737, + 13738, + 13739, + 13740, + 13741, + 13742, + 13743, + 13744, + 13745, + 13746, + 13747, + 13748, + 13749, + 13750, + 13751, + 13752, + 13753, + 13754, + 13755, + 13756, + 13757, + 13758, + 13759, + 13760, + 13761, + 13762, + 13763, + 13764, + 13765, + 13766, + 13767, + 13768, + 13769, + 13770, + 13771, + 13772, + 13773, + 13774, + 13775, + 13776, + 13777, + 13778, + 13779, + 13780, + 13781, + 13782, + 13783, + 13784, + 13785, + 13786, + 13787, + 13788, + 13789, + 13790, + 13791, + 13792, + 13793, + 13794, + 13795, + 13796, + 13797, + 13798, + 13799, + 13800, + 13801, + 13802, + 13803, + 13804, + 13805, + 13806, + 13807, + 13808, + 13809, + 13810, + 13811, + 13812, + 13813, + 13814, + 13815, + 13816, + 13817, + 13818, + 13819, + 13820, + 13821, + 13822, + 13823, + 13824, + 13825, + 13826, + 13827, + 13828, + 13829, + 13830, + 13831, + 13832, + 13833, + 13834, + 13835, + 13836, + 13837, + 13838, + 13839, + 13840, + 13841, + 13842, + 13843, + 13844, + 13845, + 13846, + 13847, + 13848, + 13849, + 13850, + 13851, + 13852, + 13853, + 13854, + 13855, + 13856, + 13857, + 13858, + 13859, + 13860, + 13861, + 13862, + 13863, + 13864, + 13865, + 13866, + 13867, + 13868, + 13869, + 13870, + 13871, + 13872, + 13873, + 13874, + 13875, + 13876, + 13877, + 13878, + 13879, + 13880, + 13881, + 13882, + 13883, + 13884, + 13885, + 13886, + 13887, + 13888, + 13889, + 13890, + 13891, + 13892, + 13893, + 13894, + 13895, + 13896, + 13897, + 13898, + 13899, + 13900, + 13901, + 13902, + 13903, + 13904, + 13905, + 13906, + 13907, + 13908, + 13909, + 13910, + 13911, + 13912, + 13913, + 13914, + 13915, + 13916, + 13917, + 13918, + 13919, + 13920, + 13921, + 13922, + 13923, + 13924, + 13925, + 13926, + 13927, + 13928, + 13929, + 13930, + 13931, + 13932, + 13933, + 13934, + 13935, + 13936, + 13937, + 13938, + 13939, + 13940, + 13941, + 13942, + 13943, + 13944, + 13945, + 13946, + 13947, + 13948, + 13949, + 13950, + 13951, + 13952, + 13953, + 13954, + 13955, + 13956, + 13957, + 13958, + 13959, + 13960, + 13961, + 13962, + 13963, + 13964, + 13965, + 13966, + 13967, + 13968, + 13969, + 13970, + 13971, + 13972, + 13973, + 13974, + 13975, + 13976, + 13977, + 13978, + 13979, + 13980, + 13981, + 13982, + 13983, + 13984, + 13985, + 13986, + 13987, + 13988, + 13989, + 13990, + 13991, + 13992, + 13993, + 13994, + 13995, + 13996, + 13997, + 13998, + 13999, + 14000, + 14001, + 14002, + 14003, + 14004, + 14005, + 14006, + 14007, + 14008, + 14009, + 14010, + 14011, + 14012, + 14013, + 14014, + 14015, + 14016, + 14017, + 14018, + 14019, + 14020, + 14021, + 14022, + 14023, + 14024, + 14025, + 14026, + 14027, + 14028, + 14029, + 14030, + 14031, + 14032, + 14033, + 14034, + 14035, + 14036, + 14037, + 14038, + 14039, + 14040, + 14041, + 14042, + 14043, + 14044, + 14045, + 14046, + 14047, + 14048, + 14049, + 14050, + 14051, + 14052, + 14053, + 14054, + 14055, + 14056, + 14057, + 14058, + 14059, + 14060, + 14061, + 14062, + 14063, + 14064, + 14065, + 14066, + 14067, + 14068, + 14069, + 14070, + 14071, + 14072, + 14073, + 14074, + 14075, + 14076, + 14077, + 14078, + 14079, + 14080, + 14081, + 14082, + 14083, + 14084, + 14085, + 14086, + 14087, + 14088, + 14089, + 14090, + 14091, + 14092, + 14093, + 14094, + 14095, + 14096, + 14097, + 14098, + 14099, + 14100, + 14101, + 14102, + 14103, + 14104, + 14105, + 14106, + 14107, + 14108, + 14109, + 14110, + 14111, + 14112, + 14113, + 14114, + 14115, + 14116, + 14117, + 14118, + 14119, + 14120, + 14121, + 14122, + 14123, + 14124, + 14125, + 14126, + 14127, + 14128, + 14129, + 14130, + 14131, + 14132, + 14133, + 14134, + 14135, + 14136, + 14137, + 14138, + 14139, + 14140, + 14141, + 14142, + 14143, + 14144, + 14145, + 14146, + 14147, + 14148, + 14149, + 14150, + 14151, + 14152, + 14153, + 14154, + 14155, + 14156, + 14157, + 14158, + 14159, + 14160, + 14161, + 14162, + 14163, + 14164, + 14165, + 14166, + 14167, + 14168, + 14169, + 14170, + 14171, + 14172, + 14173, + 14174, + 14175, + 14176, + 14177, + 14178, + 14179, + 14180, + 14181, + 14182, + 14183, + 14184, + 14185, + 14186, + 14187, + 14188, + 14189, + 14190, + 14191, + 14192, + 14193, + 14194, + 14195, + 14196, + 14197, + 14198, + 14199, + 14200, + 14201, + 14202, + 14203, + 14204, + 14205, + 14206, + 14207, + 14208, + 14209, + 14210, + 14211, + 14212, + 14213, + 14214, + 14215, + 14216, + 14217, + 14218, + 14219, + 14220, + 14221, + 14222, + 14223, + 14224, + 14225, + 14226, + 14227, + 14228, + 14229, + 14230, + 14231, + 14232, + 14233, + 14234, + 14235, + 14236, + 14237, + 14238, + 14239, + 14240, + 14241, + 14242, + 14243, + 14244, + 14245, + 14246, + 14247, + 14248, + 14249, + 14250, + 14251, + 14252, + 14253, + 14254, + 14255, + 14256, + 14257, + 14258, + 14259, + 14260, + 14261, + 14262, + 14263, + 14264, + 14265, + 14266, + 14267, + 14268, + 14269, + 14270, + 14271, + 14272, + 14273, + 14274, + 14275, + 14276, + 14277, + 14278, + 14279, + 14280, + 14281, + 14282, + 14283, + 14284, + 14285, + 14286, + 14287, + 14288, + 14289, + 14290, + 14291, + 14292, + 14293, + 14294, + 14295, + 14296, + 14297, + 14298, + 14299, + 14300, + 14301, + 14302, + 14303, + 14304, + 14305, + 14306, + 14307, + 14308, + 14309, + 14310, + 14311, + 14312, + 14313, + 14314, + 14315, + 14316, + 14317, + 14318, + 14319, + 14320, + 14321, + 14322, + 14323, + 14324, + 14325, + 14326, + 14327, + 14328, + 14329, + 14330, + 14331, + 14332, + 14333, + 14334, + 14335, + 14336, + 14337, + 14338, + 14339, + 14340, + 14341, + 14342, + 14343, + 14344, + 14345, + 14346, + 14347, + 14348, + 14349, + 14350, + 14351, + 14352, + 14353, + 14354, + 14355, + 14356, + 14357, + 14358, + 14359, + 14360, + 14361, + 14362, + 14363, + 14364, + 14365, + 14366, + 14367, + 14368, + 14369, + 14370, + 14371, + 14372, + 14373, + 14374, + 14375, + 14376, + 14377, + 14378, + 14379, + 14380, + 14381, + 14382, + 14383, + 14384, + 14385, + 14386, + 14387, + 14388, + 14389, + 14390, + 14391, + 14392, + 14393, + 14394, + 14395, + 14396, + 14397, + 14398, + 14399, + 14400, + 14401, + 14402, + 14403, + 14404, + 14405, + 14406, + 14407, + 14408, + 14409, + 14410, + 14411, + 14412, + 14413, + 14414, + 14415, + 14416, + 14417, + 14418, + 14419, + 14420, + 14421, + 14422, + 14423, + 14424, + 14425, + 14426, + 14427, + 14428, + 14429, + 14430, + 14431, + 14432, + 14433, + 14434, + 14435, + 14436, + 14437, + 14438, + 14439, + 14440, + 14441, + 14442, + 14443, + 14444, + 14445, + 14446, + 14447, + 14448, + 14449, + 14450, + 14451, + 14452, + 14453, + 14454, + 14455, + 14456, + 14457, + 14458, + 14459, + 14460, + 14461, + 14462, + 14463, + 14464, + 14465, + 14466, + 14467, + 14468, + 14469, + 14470, + 14471, + 14472, + 14473, + 14474, + 14475, + 14476, + 14477, + 14478, + 14479, + 14480, + 14481, + 14482, + 14483, + 14484, + 14485, + 14486, + 14487, + 14488, + 14489, + 14490, + 14491, + 14492, + 14493, + 14494, + 14495, + 14496, + 14497, + 14498, + 14499, + 14500, + 14501, + 14502, + 14503, + 14504, + 14505, + 14506, + 14507, + 14508, + 14509, + 14510, + 14511, + 14512, + 14513, + 14514, + 14515, + 14516, + 14517, + 14518, + 14519, + 14520, + 14521, + 14522, + 14523, + 14524, + 14525, + 14526, + 14527, + 14528, + 14529, + 14530, + 14531, + 14532, + 14533, + 14534, + 14535, + 14536, + 14537, + 14538, + 14539, + 14540, + 14541, + 14542, + 14543, + 14544, + 14545, + 14546, + 14547, + 14548, + 14549, + 14550, + 14551, + 14552, + 14553, + 14554, + 14555, + 14556, + 14557, + 14558, + 14559, + 14560, + 14561, + 14562, + 14563, + 14564, + 14565, + 14566, + 14567, + 14568, + 14569, + 14570, + 14571, + 14572, + 14573, + 14574, + 14575, + 14576, + 14577, + 14578, + 14579, + 14580, + 14581, + 14582, + 14583, + 14584, + 14585, + 14586, + 14587, + 14588, + 14589, + 14590, + 14591, + 14592, + 14593, + 14594, + 14595, + 14596, + 14597, + 14598, + 14599, + 14600, + 14601, + 14602, + 14603, + 14604, + 14605, + 14606, + 14607, + 14608, + 14609, + 14610, + 14611, + 14612, + 14613, + 14614, + 14615, + 14616, + 14617, + 14618, + 14619, + 14620, + 14621, + 14622, + 14623, + 14624, + 14625, + 14626, + 14627, + 14628, + 14629, + 14630, + 14631, + 14632, + 14633, + 14634, + 14635, + 14636, + 14637, + 14638, + 14639, + 14640, + 14641, + 14642, + 14643, + 14644, + 14645, + 14646, + 14647, + 14648, + 14649, + 14650, + 14651, + 14652, + 14653, + 14654, + 14655, + 14656, + 14657, + 14658, + 14659, + 14660, + 14661, + 14662, + 14663, + 14664, + 14665, + 14666, + 14667, + 14668, + 14669, + 14670, + 14671, + 14672, + 14673, + 14674, + 14675, + 14676, + 14677, + 14678, + 14679, + 14680, + 14681, + 14682, + 14683, + 14684, + 14685, + 14686, + 14687, + 14688, + 14689, + 14690, + 14691, + 14692, + 14693, + 14694, + 14695, + 14696, + 14697, + 14698, + 14699, + 14700, + 14701, + 14702, + 14703, + 14704, + 14705, + 14706, + 14707, + 14708, + 14709, + 14710, + 14711, + 14712, + 14713, + 14714, + 14715, + 14716, + 14717, + 14718, + 14719, + 14720, + 14721, + 14722, + 14723, + 14724, + 14725, + 14726, + 14727, + 14728, + 14729, + 14730, + 14731, + 14732, + 14733, + 14734, + 14735, + 14736, + 14737, + 14738, + 14739, + 14740, + 14741, + 14742, + 14743, + 14744, + 14745, + 14746, + 14747, + 14748, + 14749, + 14750, + 14751, + 14752, + 14753, + 14754, + 14755, + 14756, + 14757, + 14758, + 14759, + 14760, + 14761, + 14762, + 14763, + 14764, + 14765, + 14766, + 14767, + 14768, + 14769, + 14770, + 14771, + 14772, + 14773, + 14774, + 14775, + 14776, + 14777, + 14778, + 14779, + 14780, + 14781, + 14782, + 14783, + 14784, + 14785, + 14786, + 14787, + 14788, + 14789, + 14790, + 14791, + 14792, + 14793, + 14794, + 14795, + 14796, + 14797, + 14798, + 14799, + 14800, + 14801, + 14802, + 14803, + 14804, + 14805, + 14806, + 14807, + 14808, + 14809, + 14810, + 14811, + 14812, + 14813, + 14814, + 14815, + 14816, + 14817, + 14818, + 14819, + 14820, + 14821, + 14822, + 14823, + 14824, + 14825, + 14826, + 14827, + 14828, + 14829, + 14830, + 14831, + 14832, + 14833, + 14834, + 14835, + 14836, + 14837, + 14838, + 14839, + 14840, + 14841, + 14842, + 14843, + 14844, + 14845, + 14846, + 14847, + 14848, + 14849, + 14850, + 14851, + 14852, + 14853, + 14854, + 14855, + 14856, + 14857, + 14858, + 14859, + 14860, + 14861, + 14862, + 14863, + 14864, + 14865, + 14866, + 14867, + 14868, + 14869, + 14870, + 14871, + 14872, + 14873, + 14874, + 14875, + 14876, + 14877, + 14878, + 14879, + 14880, + 14881, + 14882, + 14883, + 14884, + 14885, + 14886, + 14887, + 14888, + 14889, + 14890, + 14891, + 14892, + 14893, + 14894, + 14895, + 14896, + 14897, + 14898, + 14899, + 14900, + 14901, + 14902, + 14903, + 14904, + 14905, + 14906, + 14907, + 14908, + 14909, + 14910, + 14911, + 14912, + 14913, + 14914, + 14915, + 14916, + 14917, + 14918, + 14919, + 14920, + 14921, + 14922, + 14923, + 14924, + 14925, + 14926, + 14927, + 14928, + 14929, + 14930, + 14931, + 14932, + 14933, + 14934, + 14935, + 14936, + 14937, + 14938, + 14939, + 14940, + 14941, + 14942, + 14943, + 14944, + 14945, + 14946, + 14947, + 14948, + 14949, + 14950, + 14951, + 14952, + 14953, + 14954, + 14955, + 14956, + 14957, + 14958, + 14959, + 14960, + 14961, + 14962, + 14963, + 14964, + 14965, + 14966, + 14967, + 14968, + 14969, + 14970, + 14971, + 14972, + 14973, + 14974, + 14975, + 14976, + 14977, + 14978, + 14979, + 14980, + 14981, + 14982, + 14983, + 14984, + 14985, + 14986, + 14987, + 14988, + 14989, + 14990, + 14991, + 14992, + 14993, + 14994, + 14995, + 14996, + 14997, + 14998, + 14999, + 15000, + 15001, + 15002, + 15003, + 15004, + 15005, + 15006, + 15007, + 15008, + 15009, + 15010, + 15011, + 15012, + 15013, + 15014, + 15015, + 15016, + 15017, + 15018, + 15019, + 15020, + 15021, + 15022, + 15023, + 15024, + 15025, + 15026, + 15027, + 15028, + 15029, + 15030, + 15031, + 15032, + 15033, + 15034, + 15035, + 15036, + 15037, + 15038, + 15039, + 15040, + 15041, + 15042, + 15043, + 15044, + 15045, + 15046, + 15047, + 15048, + 15049, + 15050, + 15051, + 15052, + 15053, + 15054, + 15055, + 15056, + 15057, + 15058, + 15059, + 15060, + 15061, + 15062, + 15063, + 15064, + 15065, + 15066, + 15067, + 15068, + 15069, + 15070, + 15071, + 15072, + 15073, + 15074, + 15075, + 15076, + 15077, + 15078, + 15079, + 15080, + 15081, + 15082, + 15083, + 15084, + 15085, + 15086, + 15087, + 15088, + 15089, + 15090, + 15091, + 15092, + 15093, + 15094, + 15095, + 15096, + 15097, + 15098, + 15099, + 15100, + 15101, + 15102, + 15103, + 15104, + 15105, + 15106, + 15107, + 15108, + 15109, + 15110, + 15111, + 15112, + 15113, + 15114, + 15115, + 15116, + 15117, + 15118, + 15119, + 15120, + 15121, + 15122, + 15123, + 15124, + 15125, + 15126, + 15127, + 15128, + 15129, + 15130, + 15131, + 15132, + 15133, + 15134, + 15135, + 15136, + 15137, + 15138, + 15139, + 15140, + 15141, + 15142, + 15143, + 15144, + 15145, + 15146, + 15147, + 15148, + 15149, + 15150, + 15151, + 15152, + 15153, + 15154, + 15155, + 15156, + 15157, + 15158, + 15159, + 15160, + 15161, + 15162, + 15163, + 15164, + 15165, + 15166, + 15167, + 15168, + 15169, + 15170, + 15171, + 15172, + 15173, + 15174, + 15175, + 15176, + 15177, + 15178, + 15179, + 15180, + 15181, + 15182, + 15183, + 15184, + 15185, + 15186, + 15187, + 15188, + 15189, + 15190, + 15191, + 15192, + 15193, + 15194, + 15195, + 15196, + 15197, + 15198, + 15199, + 15200, + 15201, + 15202, + 15203, + 15204, + 15205, + 15206, + 15207, + 15208, + 15209, + 15210, + 15211, + 15212, + 15213, + 15214, + 15215, + 15216, + 15217, + 15218, + 15219, + 15220, + 15221, + 15222, + 15223, + 15224, + 15225, + 15226, + 15227, + 15228, + 15229, + 15230, + 15231, + 15232, + 15233, + 15234, + 15235, + 15236, + 15237, + 15238, + 15239, + 15240, + 15241, + 15242, + 15243, + 15244, + 15245, + 15246, + 15247, + 15248, + 15249, + 15250, + 15251, + 15252, + 15253, + 15254, + 15255, + 15256, + 15257, + 15258, + 15259, + 15260, + 15261, + 15262, + 15263, + 15264, + 15265, + 15266, + 15267, + 15268, + 15269, + 15270, + 15271, + 15272, + 15273, + 15274, + 15275, + 15276, + 15277, + 15278, + 15279, + 15280, + 15281, + 15282, + 15283, + 15284, + 15285, + 15286, + 15287, + 15288, + 15289, + 15290, + 15291, + 15292, + 15293, + 15294, + 15295, + 15296, + 15297, + 15298, + 15299, + 15300, + 15301, + 15302, + 15303, + 15304, + 15305, + 15306, + 15307, + 15308, + 15309, + 15310, + 15311, + 15312, + 15313, + 15314, + 15315, + 15316, + 15317, + 15318, + 15319, + 15320, + 15321, + 15322, + 15323, + 15324, + 15325, + 15326, + 15327, + 15328, + 15329, + 15330, + 15331, + 15332, + 15333, + 15334, + 15335, + 15336, + 15337, + 15338, + 15339, + 15340, + 15341, + 15342, + 15343, + 15344, + 15345, + 15346, + 15347, + 15348, + 15349, + 15350, + 15351, + 15352, + 15353, + 15354, + 15355, + 15356, + 15357, + 15358, + 15359, + 15360, + 15361, + 15362, + 15363, + 15364, + 15365, + 15366, + 15367, + 15368, + 15369, + 15370, + 15371, + 15372, + 15373, + 15374, + 15375, + 15376, + 15377, + 15378, + 15379, + 15380, + 15381, + 15382, + 15383, + 15384, + 15385, + 15386, + 15387, + 15388, + 15389, + 15390, + 15391, + 15392, + 15393, + 15394, + 15395, + 15396, + 15397, + 15398, + 15399, + 15400, + 15401, + 15402, + 15403, + 15404, + 15405, + 15406, + 15407, + 15408, + 15409, + 15410, + 15411, + 15412, + 15413, + 15414, + 15415, + 15416, + 15417, + 15418, + 15419, + 15420, + 15421, + 15422, + 15423, + 15424, + 15425, + 15426, + 15427, + 15428, + 15429, + 15430, + 15431, + 15432, + 15433, + 15434, + 15435, + 15436, + 15437, + 15438, + 15439, + 15440, + 15441, + 15442, + 15443, + 15444, + 15445, + 15446, + 15447, + 15448, + 15449, + 15450, + 15451, + 15452, + 15453, + 15454, + 15455, + 15456, + 15457, + 15458, + 15459, + 15460, + 15461, + 15462, + 15463, + 15464, + 15465, + 15466, + 15467, + 15468, + 15469, + 15470, + 15471, + 15472, + 15473, + 15474, + 15475, + 15476, + 15477, + 15478, + 15479, + 15480, + 15481, + 15482, + 15483, + 15484, + 15485, + 15486, + 15487, + 15488, + 15489, + 15490, + 15491, + 15492, + 15493, + 15494, + 15495, + 15496, + 15497, + 15498, + 15499, + 15500, + 15501, + 15502, + 15503, + 15504, + 15505, + 15506, + 15507, + 15508, + 15509, + 15510, + 15511, + 15512, + 15513, + 15514, + 15515, + 15516, + 15517, + 15518, + 15519, + 15520, + 15521, + 15522, + 15523, + 15524, + 15525, + 15526, + 15527, + 15528, + 15529, + 15530, + 15531, + 15532, + 15533, + 15534, + 15535, + 15536, + 15537, + 15538, + 15539, + 15540, + 15541, + 15542, + 15543, + 15544, + 15545, + 15546, + 15547, + 15548, + 15549, + 15550, + 15551, + 15552, + 15553, + 15554, + 15555, + 15556, + 15557, + 15558, + 15559, + 15560, + 15561, + 15562, + 15563, + 15564, + 15565, + 15566, + 15567, + 15568, + 15569, + 15570, + 15571, + 15572, + 15573, + 15574, + 15575, + 15576, + 15577, + 15578, + 15579, + 15580, + 15581, + 15582, + 15583, + 15584, + 15585, + 15586, + 15587, + 15588, + 15589, + 15590, + 15591, + 15592, + 15593, + 15594, + 15595, + 15596, + 15597, + 15598, + 15599, + 15600, + 15601, + 15602, + 15603, + 15604, + 15605, + 15606, + 15607, + 15608, + 15609, + 15610, + 15611, + 15612, + 15613, + 15614, + 15615, + 15616, + 15617, + 15618, + 15619, + 15620, + 15621, + 15622, + 15623, + 15624, + 15625, + 15626, + 15627, + 15628, + 15629, + 15630, + 15631, + 15632, + 15633, + 15634, + 15635, + 15636, + 15637, + 15638, + 15639, + 15640, + 15641, + 15642, + 15643, + 15644, + 15645, + 15646, + 15647, + 15648, + 15649, + 15650, + 15651, + 15652, + 15653, + 15654, + 15655, + 15656, + 15657, + 15658, + 15659, + 15660, + 15661, + 15662, + 15663, + 15664, + 15665, + 15666, + 15667, + 15668, + 15669, + 15670, + 15671, + 15672, + 15673, + 15674, + 15675, + 15676, + 15677, + 15678, + 15679, + 15680, + 15681, + 15682, + 15683, + 15684, + 15685, + 15686, + 15687, + 15688, + 15689, + 15690, + 15691, + 15692, + 15693, + 15694, + 15695, + 15696, + 15697, + 15698, + 15699, + 15700, + 15701, + 15702, + 15703, + 15704, + 15705, + 15706, + 15707, + 15708, + 15709, + 15710, + 15711, + 15712, + 15713, + 15714, + 15715, + 15716, + 15717, + 15718, + 15719, + 15720, + 15721, + 15722, + 15723, + 15724, + 15725, + 15726, + 15727, + 15728, + 15729, + 15730, + 15731, + 15732, + 15733, + 15734, + 15735, + 15736, + 15737, + 15738, + 15739, + 15740, + 15741, + 15742, + 15743, + 15744, + 15745, + 15746, + 15747, + 15748, + 15749, + 15750, + 15751, + 15752, + 15753, + 15754, + 15755, + 15756, + 15757, + 15758, + 15759, + 15760, + 15761, + 15762, + 15763, + 15764, + 15765, + 15766, + 15767, + 15768, + 15769, + 15770, + 15771, + 15772, + 15773, + 15774, + 15775, + 15776, + 15777, + 15778, + 15779, + 15780, + 15781, + 15782, + 15783, + 15784, + 15785, + 15786, + 15787, + 15788, + 15789, + 15790, + 15791, + 15792, + 15793, + 15794, + 15795, + 15796, + 15797, + 15798, + 15799, + 15800, + 15801, + 15802, + 15803, + 15804, + 15805, + 15806, + 15807, + 15808, + 15809, + 15810, + 15811, + 15812, + 15813, + 15814, + 15815, + 15816, + 15817, + 15818, + 15819, + 15820, + 15821, + 15822, + 15823, + 15824, + 15825, + 15826, + 15827, + 15828, + 15829, + 15830, + 15831, + 15832, + 15833, + 15834, + 15835, + 15836, + 15837, + 15838, + 15839, + 15840, + 15841, + 15842, + 15843, + 15844, + 15845, + 15846, + 15847, + 15848, + 15849, + 15850, + 15851, + 15852, + 15853, + 15854, + 15855, + 15856, + 15857, + 15858, + 15859, + 15860, + 15861, + 15862, + 15863, + 15864, + 15865, + 15866, + 15867, + 15868, + 15869, + 15870, + 15871, + 15872, + 15873, + 15874, + 15875, + 15876, + 15877, + 15878, + 15879, + 15880, + 15881, + 15882, + 15883, + 15884, + 15885, + 15886, + 15887, + 15888, + 15889, + 15890, + 15891, + 15892, + 15893, + 15894, + 15895, + 15896, + 15897, + 15898, + 15899, + 15900, + 15901, + 15902, + 15903, + 15904, + 15905, + 15906, + 15907, + 15908, + 15909, + 15910, + 15911, + 15912, + 15913, + 15914, + 15915, + 15916, + 15917, + 15918, + 15919, + 15920, + 15921, + 15922, + 15923, + 15924, + 15925, + 15926, + 15927, + 15928, + 15929, + 15930, + 15931, + 15932, + 15933, + 15934, + 15935, + 15936, + 15937, + 15938, + 15939, + 15940, + 15941, + 15942, + 15943, + 15944, + 15945, + 15946, + 15947, + 15948, + 15949, + 15950, + 15951, + 15952, + 15953, + 15954, + 15955, + 15956, + 15957, + 15958, + 15959, + 15960, + 15961, + 15962, + 15963, + 15964, + 15965, + 15966, + 15967, + 15968, + 15969, + 15970, + 15971, + 15972, + 15973, + 15974, + 15975, + 15976, + 15977, + 15978, + 15979, + 15980, + 15981, + 15982, + 15983, + 15984, + 15985, + 15986, + 15987, + 15988, + 15989, + 15990, + 15991, + 15992, + 15993, + 15994, + 15995, + 15996, + 15997, + 15998, + 15999, + 16000, + 16001, + 16002, + 16003, + 16004, + 16005, + 16006, + 16007, + 16008, + 16009, + 16010, + 16011, + 16012, + 16013, + 16014, + 16015, + 16016, + 16017, + 16018, + 16019, + 16020, + 16021, + 16022, + 16023, + 16024, + 16025, + 16026, + 16027, + 16028, + 16029, + 16030, + 16031, + 16032, + 16033, + 16034, + 16035, + 16036, + 16037, + 16038, + 16039, + 16040, + 16041, + 16042, + 16043, + 16044, + 16045, + 16046, + 16047, + 16048, + 16049, + 16050, + 16051, + 16052, + 16053, + 16054, + 16055, + 16056, + 16057, + 16058, + 16059, + 16060, + 16061, + 16062, + 16063, + 16064, + 16065, + 16066, + 16067, + 16068, + 16069, + 16070, + 16071, + 16072, + 16073, + 16074, + 16075, + 16076, + 16077, + 16078, + 16079, + 16080, + 16081, + 16082, + 16083, + 16084, + 16085, + 16086, + 16087, + 16088, + 16089, + 16090, + 16091, + 16092, + 16093, + 16094, + 16095, + 16096, + 16097, + 16098, + 16099, + 16100, + 16101, + 16102, + 16103, + 16104, + 16105, + 16106, + 16107, + 16108, + 16109, + 16110, + 16111, + 16112, + 16113, + 16114, + 16115, + 16116, + 16117, + 16118, + 16119, + 16120, + 16121, + 16122, + 16123, + 16124, + 16125, + 16126, + 16127, + 16128, + 16129, + 16130, + 16131, + 16132, + 16133, + 16134, + 16135, + 16136, + 16137, + 16138, + 16139, + 16140, + 16141, + 16142, + 16143, + 16144, + 16145, + 16146, + 16147, + 16148, + 16149, + 16150, + 16151, + 16152, + 16153, + 16154, + 16155, + 16156, + 16157, + 16158, + 16159, + 16160, + 16161, + 16162, + 16163, + 16164, + 16165, + 16166, + 16167, + 16168, + 16169, + 16170, + 16171, + 16172, + 16173, + 16174, + 16175, + 16176, + 16177, + 16178, + 16179, + 16180, + 16181, + 16182, + 16183, + 16184, + 16185, + 16186, + 16187, + 16188, + 16189, + 16190, + 16191, + 16192, + 16193, + 16194, + 16195, + 16196, + 16197, + 16198, + 16199, + 16200, + 16201, + 16202, + 16203, + 16204, + 16205, + 16206, + 16207, + 16208, + 16209, + 16210, + 16211, + 16212, + 16213, + 16214, + 16215, + 16216, + 16217, + 16218, + 16219, + 16220, + 16221, + 16222, + 16223, + 16224, + 16225, + 16226, + 16227, + 16228, + 16229, + 16230, + 16231, + 16232, + 16233, + 16234, + 16235, + 16236, + 16237, + 16238, + 16239, + 16240, + 16241, + 16242, + 16243, + 16244, + 16245, + 16246, + 16247, + 16248, + 16249, + 16250, + 16251, + 16252, + 16253, + 16254, + 16255, + 16256, + 16257, + 16258, + 16259, + 16260, + 16261, + 16262, + 16263, + 16264, + 16265, + 16266, + 16267, + 16268, + 16269, + 16270, + 16271, + 16272, + 16273, + 16274, + 16275, + 16276, + 16277, + 16278, + 16279, + 16280, + 16281, + 16282, + 16283, + 16284, + 16285, + 16286, + 16287, + 16288, + 16289, + 16290, + 16291, + 16292, + 16293, + 16294, + 16295, + 16296, + 16297, + 16298, + 16299, + 16300, + 16301, + 16302, + 16303, + 16304, + 16305, + 16306, + 16307, + 16308, + 16309, + 16310, + 16311, + 16312, + 16313, + 16314, + 16315, + 16316, + 16317, + 16318, + 16319, + 16320, + 16321, + 16322, + 16323, + 16324, + 16325, + 16326, + 16327, + 16328, + 16329, + 16330, + 16331, + 16332, + 16333, + 16334, + 16335, + 16336, + 16337, + 16338, + 16339, + 16340, + 16341, + 16342, + 16343, + 16344, + 16345, + 16346, + 16347, + 16348, + 16349, + 16350, + 16351, + 16352, + 16353, + 16354, + 16355, + 16356, + 16357, + 16358, + 16359, + 16360, + 16361, + 16362, + 16363, + 16364, + 16365, + 16366, + 16367, + 16368, + 16369, + 16370, + 16371, + 16372, + 16373, + 16374, + 16375, + 16376, + 16377, + 16378, + 16379, + 16380, + 16381, + 16382, + 16383, + 16384, + 16385, + 16386, + 16387, + 16388, + 16389, + 16390, + 16391, + 16392, + 16393, + 16394, + 16395, + 16396, + 16397, + 16398, + 16399, + 16400, + 16401, + 16402, + 16403, + 16404, + 16405, + 16406, + 16407, + 16408, + 16409, + 16410, + 16411, + 16412, + 16413, + 16414, + 16415, + 16416, + 16417, + 16418, + 16419, + 16420, + 16421, + 16422, + 16423, + 16424, + 16425, + 16426, + 16427, + 16428, + 16429, + 16430, + 16431, + 16432, + 16433, + 16434, + 16435, + 16436, + 16437, + 16438, + 16439, + 16440, + 16441, + 16442, + 16443, + 16444, + 16445, + 16446, + 16447, + 16448, + 16449, + 16450, + 16451, + 16452, + 16453, + 16454, + 16455, + 16456, + 16457, + 16458, + 16459, + 16460, + 16461, + 16462, + 16463, + 16464, + 16465, + 16466, + 16467, + 16468, + 16469, + 16470, + 16471, + 16472, + 16473, + 16474, + 16475, + 16476, + 16477, + 16478, + 16479, + 16480, + 16481, + 16482, + 16483, + 16484, + 16485, + 16486, + 16487, + 16488, + 16489, + 16490, + 16491, + 16492, + 16493, + 16494, + 16495, + 16496, + 16497, + 16498, + 16499, + 16500, + 16501, + 16502, + 16503, + 16504, + 16505, + 16506, + 16507, + 16508, + 16509, + 16510, + 16511, + 16512, + 16513, + 16514, + 16515, + 16516, + 16517, + 16518, + 16519, + 16520, + 16521, + 16522, + 16523, + 16524, + 16525, + 16526, + 16527, + 16528, + 16529, + 16530, + 16531, + 16532, + 16533, + 16534, + 16535, + 16536, + 16537, + 16538, + 16539, + 16540, + 16541, + 16542, + 16543, + 16544, + 16545, + 16546, + 16547, + 16548, + 16549, + 16550, + 16551, + 16552, + 16553, + 16554, + 16555, + 16556, + 16557, + 16558, + 16559, + 16560, + 16561, + 16562, + 16563, + 16564, + 16565, + 16566, + 16567, + 16568, + 16569, + 16570, + 16571, + 16572, + 16573, + 16574, + 16575, + 16576, + 16577, + 16578, + 16579, + 16580, + 16581, + 16582, + 16583, + 16584, + 16585, + 16586, + 16587, + 16588, + 16589, + 16590, + 16591, + 16592, + 16593, + 16594, + 16595, + 16596, + 16597, + 16598, + 16599, + 16600, + 16601, + 16602, + 16603, + 16604, + 16605, + 16606, + 16607, + 16608, + 16609, + 16610, + 16611, + 16612, + 16613, + 16614, + 16615, + 16616, + 16617, + 16618, + 16619, + 16620, + 16621, + 16622, + 16623, + 16624, + 16625, + 16626, + 16627, + 16628, + 16629, + 16630, + 16631, + 16632, + 16633, + 16634, + 16635, + 16636, + 16637, + 16638, + 16639, + 16640, + 16641, + 16642, + 16643, + 16644, + 16645, + 16646, + 16647, + 16648, + 16649, + 16650, + 16651, + 16652, + 16653, + 16654, + 16655, + 16656, + 16657, + 16658, + 16659, + 16660, + 16661, + 16662, + 16663, + 16664, + 16665, + 16666, + 16667, + 16668, + 16669, + 16670, + 16671, + 16672, + 16673, + 16674, + 16675, + 16676, + 16677, + 16678, + 16679, + 16680, + 16681, + 16682, + 16683, + 16684, + 16685, + 16686, + 16687, + 16688, + 16689, + 16690, + 16691, + 16692, + 16693, + 16694, + 16695, + 16696, + 16697, + 16698, + 16699, + 16700, + 16701, + 16702, + 16703, + 16704, + 16705, + 16706, + 16707, + 16708, + 16709, + 16710, + 16711, + 16712, + 16713, + 16714, + 16715, + 16716, + 16717, + 16718, + 16719, + 16720, + 16721, + 16722, + 16723, + 16724, + 16725, + 16726, + 16727, + 16728, + 16729, + 16730, + 16731, + 16732, + 16733, + 16734, + 16735, + 16736, + 16737, + 16738, + 16739, + 16740, + 16741, + 16742, + 16743, + 16744, + 16745, + 16746, + 16747, + 16748, + 16749, + 16750, + 16751, + 16752, + 16753, + 16754, + 16755, + 16756, + 16757, + 16758, + 16759, + 16760, + 16761, + 16762, + 16763, + 16764, + 16765, + 16766, + 16767, + 16768, + 16769, + 16770, + 16771, + 16772, + 16773, + 16774, + 16775, + 16776, + 16777, + 16778, + 16779, + 16780, + 16781, + 16782, + 16783, + 16784, + 16785, + 16786, + 16787, + 16788, + 16789, + 16790, + 16791, + 16792, + 16793, + 16794, + 16795, + 16796, + 16797, + 16798, + 16799, + 16800, + 16801, + 16802, + 16803, + 16804, + 16805, + 16806, + 16807, + 16808, + 16809, + 16810, + 16811, + 16812, + 16813, + 16814, + 16815, + 16816, + 16817, + 16818, + 16819, + 16820, + 16821, + 16822, + 16823, + 16824, + 16825, + 16826, + 16827, + 16828, + 16829, + 16830, + 16831, + 16832, + 16833, + 16834, + 16835, + 16836, + 16837, + 16838, + 16839, + 16840, + 16841, + 16842, + 16843, + 16844, + 16845, + 16846, + 16847, + 16848, + 16849, + 16850, + 16851, + 16852, + 16853, + 16854, + 16855, + 16856, + 16857, + 16858, + 16859, + 16860, + 16861, + 16862, + 16863, + 16864, + 16865, + 16866, + 16867, + 16868, + 16869, + 16870, + 16871, + 16872, + 16873, + 16874, + 16875, + 16876, + 16877, + 16878, + 16879, + 16880, + 16881, + 16882, + 16883, + 16884, + 16885, + 16886, + 16887, + 16888, + 16889, + 16890, + 16891, + 16892, + 16893, + 16894, + 16895, + 16896, + 16897, + 16898, + 16899, + 16900, + 16901, + 16902, + 16903, + 16904, + 16905, + 16906, + 16907, + 16908, + 16909, + 16910, + 16911, + 16912, + 16913, + 16914, + 16915, + 16916, + 16917, + 16918, + 16919, + 16920, + 16921, + 16922, + 16923, + 16924, + 16925, + 16926, + 16927, + 16928, + 16929, + 16930, + 16931, + 16932, + 16933, + 16934, + 16935, + 16936, + 16937, + 16938, + 16939, + 16940, + 16941, + 16942, + 16943, + 16944, + 16945, + 16946, + 16947, + 16948, + 16949, + 16950, + 16951, + 16952, + 16953, + 16954, + 16955, + 16956, + 16957, + 16958, + 16959, + 16960, + 16961, + 16962, + 16963, + 16964, + 16965, + 16966, + 16967, + 16968, + 16969, + 16970, + 16971, + 16972, + 16973, + 16974, + 16975, + 16976, + 16977, + 16978, + 16979, + 16980, + 16981, + 16982, + 16983, + 16984, + 16985, + 16986, + 16987, + 16988, + 16989, + 16990, + 16991, + 16992, + 16993, + 16994, + 16995, + 16996, + 16997, + 16998, + 16999, + 17000, + 17001, + 17002, + 17003, + 17004, + 17005, + 17006, + 17007, + 17008, + 17009, + 17010, + 17011, + 17012, + 17013, + 17014, + 17015, + 17016, + 17017, + 17018, + 17019, + 17020, + 17021, + 17022, + 17023, + 17024, + 17025, + 17026, + 17027, + 17028, + 17029, + 17030, + 17031, + 17032, + 17033, + 17034, + 17035, + 17036, + 17037, + 17038, + 17039, + 17040, + 17041, + 17042, + 17043, + 17044, + 17045, + 17046, + 17047, + 17048, + 17049, + 17050, + 17051, + 17052, + 17053, + 17054, + 17055, + 17056, + 17057, + 17058, + 17059, + 17060, + 17061, + 17062, + 17063, + 17064, + 17065, + 17066, + 17067, + 17068, + 17069, + 17070, + 17071, + 17072, + 17073, + 17074, + 17075, + 17076, + 17077, + 17078, + 17079, + 17080, + 17081, + 17082, + 17083, + 17084, + 17085, + 17086, + 17087, + 17088, + 17089, + 17090, + 17091, + 17092, + 17093, + 17094, + 17095, + 17096, + 17097, + 17098, + 17099, + 17100, + 17101, + 17102, + 17103, + 17104, + 17105, + 17106, + 17107, + 17108, + 17109, + 17110, + 17111, + 17112, + 17113, + 17114, + 17115, + 17116, + 17117, + 17118, + 17119, + 17120, + 17121, + 17122, + 17123, + 17124, + 17125, + 17126, + 17127, + 17128, + 17129, + 17130, + 17131, + 17132, + 17133, + 17134, + 17135, + 17136, + 17137, + 17138, + 17139, + 17140, + 17141, + 17142, + 17143, + 17144, + 17145, + 17146, + 17147, + 17148, + 17149, + 17150, + 17151, + 17152, + 17153, + 17154, + 17155, + 17156, + 17157, + 17158, + 17159, + 17160, + 17161, + 17162, + 17163, + 17164, + 17165, + 17166, + 17167, + 17168, + 17169, + 17170, + 17171, + 17172, + 17173, + 17174, + 17175, + 17176, + 17177, + 17178, + 17179, + 17180, + 17181, + 17182, + 17183, + 17184, + 17185, + 17186, + 17187, + 17188, + 17189, + 17190, + 17191, + 17192, + 17193, + 17194, + 17195, + 17196, + 17197, + 17198, + 17199, + 17200, + 17201, + 17202, + 17203, + 17204, + 17205, + 17206, + 17207, + 17208, + 17209, + 17210, + 17211, + 17212, + 17213, + 17214, + 17215, + 17216, + 17217, + 17218, + 17219, + 17220, + 17221, + 17222, + 17223, + 17224, + 17225, + 17226, + 17227, + 17228, + 17229, + 17230, + 17231, + 17232, + 17233, + 17234, + 17235, + 17236, + 17237, + 17238, + 17239, + 17240, + 17241, + 17242, + 17243, + 17244, + 17245, + 17246, + 17247, + 17248, + 17249, + 17250, + 17251, + 17252, + 17253, + 17254, + 17255, + 17256, + 17257, + 17258, + 17259, + 17260, + 17261, + 17262, + 17263, + 17264, + 17265, + 17266, + 17267, + 17268, + 17269, + 17270, + 17271, + 17272, + 17273, + 17274, + 17275, + 17276, + 17277, + 17278, + 17279, + 17280, + 17281, + 17282, + 17283, + 17284, + 17285, + 17286, + 17287, + 17288, + 17289, + 17290, + 17291, + 17292, + 17293, + 17294, + 17295, + 17296, + 17297, + 17298, + 17299, + 17300, + 17301, + 17302, + 17303, + 17304, + 17305, + 17306, + 17307, + 17308, + 17309, + 17310, + 17311, + 17312, + 17313, + 17314, + 17315, + 17316, + 17317, + 17318, + 17319, + 17320, + 17321, + 17322, + 17323, + 17324, + 17325, + 17326, + 17327, + 17328, + 17329, + 17330, + 17331, + 17332, + 17333, + 17334, + 17335, + 17336, + 17337, + 17338, + 17339, + 17340, + 17341, + 17342, + 17343, + 17344, + 17345, + 17346, + 17347, + 17348, + 17349, + 17350, + 17351, + 17352, + 17353, + 17354, + 17355, + 17356, + 17357, + 17358, + 17359, + 17360, + 17361, + 17362, + 17363, + 17364, + 17365, + 17366, + 17367, + 17368, + 17369, + 17370, + 17371, + 17372, + 17373, + 17374, + 17375, + 17376, + 17377, + 17378, + 17379, + 17380, + 17381, + 17382, + 17383, + 17384, + 17385, + 17386, + 17387, + 17388, + 17389, + 17390, + 17391, + 17392, + 17393, + 17394, + 17395, + 17396, + 17397, + 17398, + 17399, + 17400, + 17401, + 17402, + 17403, + 17404, + 17405, + 17406, + 17407, + 17408, + 17409, + 17410, + 17411, + 17412, + 17413, + 17414, + 17415, + 17416, + 17417, + 17418, + 17419, + 17420, + 17421, + 17422, + 17423, + 17424, + 17425, + 17426, + 17427, + 17428, + 17429, + 17430, + 17431, + 17432, + 17433, + 17434, + 17435, + 17436, + 17437, + 17438, + 17439, + 17440, + 17441, + 17442, + 17443, + 17444, + 17445, + 17446, + 17447, + 17448, + 17449, + 17450, + 17451, + 17452, + 17453, + 17454, + 17455, + 17456, + 17457, + 17458, + 17459, + 17460, + 17461, + 17462, + 17463, + 17464, + 17465, + 17466, + 17467, + 17468, + 17469, + 17470, + 17471, + 17472, + 17473, + 17474, + 17475, + 17476, + 17477, + 17478, + 17479, + 17480, + 17481, + 17482, + 17483, + 17484, + 17485, + 17486, + 17487, + 17488, + 17489, + 17490, + 17491, + 17492, + 17493, + 17494, + 17495, + 17496, + 17497, + 17498, + 17499, + 17500, + 17501, + 17502, + 17503, + 17504, + 17505, + 17506, + 17507, + 17508, + 17509, + 17510, + 17511, + 17512, + 17513, + 17514, + 17515, + 17516, + 17517, + 17518, + 17519, + 17520, + 17521, + 17522, + 17523, + 17524, + 17525, + 17526, + 17527, + 17528, + 17529, + 17530, + 17531, + 17532, + 17533, + 17534, + 17535, + 17536, + 17537, + 17538, + 17539, + 17540, + 17541, + 17542, + 17543, + 17544, + 17545, + 17546, + 17547, + 17548, + 17549, + 17550, + 17551, + 17552, + 17553, + 17554, + 17555, + 17556, + 17557, + 17558, + 17559, + 17560, + 17561, + 17562, + 17563, + 17564, + 17565, + 17566, + 17567, + 17568, + 17569, + 17570, + 17571, + 17572, + 17573, + 17574, + 17575, + 17576, + 17577, + 17578, + 17579, + 17580, + 17581, + 17582, + 17583, + 17584, + 17585, + 17586, + 17587, + 17588, + 17589, + 17590, + 17591, + 17592, + 17593, + 17594, + 17595, + 17596, + 17597, + 17598, + 17599, + 17600, + 17601, + 17602, + 17603, + 17604, + 17605, + 17606, + 17607, + 17608, + 17609, + 17610, + 17611, + 17612, + 17613, + 17614, + 17615, + 17616, + 17617, + 17618, + 17619, + 17620, + 17621, + 17622, + 17623, + 17624, + 17625, + 17626, + 17627, + 17628, + 17629, + 17630, + 17631, + 17632, + 17633, + 17634, + 17635, + 17636, + 17637, + 17638, + 17639, + 17640, + 17641, + 17642, + 17643, + 17644, + 17645, + 17646, + 17647, + 17648, + 17649, + 17650, + 17651, + 17652, + 17653, + 17654, + 17655, + 17656, + 17657, + 17658, + 17659, + 17660, + 17661, + 17662, + 17663, + 17664, + 17665, + 17666, + 17667, + 17668, + 17669, + 17670, + 17671, + 17672, + 17673, + 17674, + 17675, + 17676, + 17677, + 17678, + 17679, + 17680, + 17681, + 17682, + 17683, + 17684, + 17685, + 17686, + 17687, + 17688, + 17689, + 17690, + 17691, + 17692, + 17693, + 17694, + 17695, + 17696, + 17697, + 17698, + 17699, + 17700, + 17701, + 17702, + 17703, + 17704, + 17705, + 17706, + 17707, + 17708, + 17709, + 17710, + 17711, + 17712, + 17713, + 17714, + 17715, + 17716, + 17717, + 17718, + 17719, + 17720, + 17721, + 17722, + 17723, + 17724, + 17725, + 17726, + 17727, + 17728, + 17729, + 17730, + 17731, + 17732, + 17733, + 17734, + 17735, + 17736, + 17737, + 17738, + 17739, + 17740, + 17741, + 17742, + 17743, + 17744, + 17745, + 17746, + 17747, + 17748, + 17749, + 17750, + 17751, + 17752, + 17753, + 17754, + 17755, + 17756, + 17757, + 17758, + 17759, + 17760, + 17761, + 17762, + 17763, + 17764, + 17765, + 17766, + 17767, + 17768, + 17769, + 17770, + 17771, + 17772, + 17773, + 17774, + 17775, + 17776, + 17777, + 17778, + 17779, + 17780, + 17781, + 17782, + 17783, + 17784, + 17785, + 17786, + 17787, + 17788, + 17789, + 17790, + 17791, + 17792, + 17793, + 17794, + 17795, + 17796, + 17797, + 17798, + 17799, + 17800, + 17801, + 17802, + 17803, + 17804, + 17805, + 17806, + 17807, + 17808, + 17809, + 17810, + 17811, + 17812, + 17813, + 17814, + 17815, + 17816, + 17817, + 17818, + 17819, + 17820, + 17821, + 17822, + 17823, + 17824, + 17825, + 17826, + 17827, + 17828, + 17829, + 17830, + 17831, + 17832, + 17833, + 17834, + 17835, + 17836, + 17837, + 17838, + 17839, + 17840, + 17841, + 17842, + 17843, + 17844, + 17845, + 17846, + 17847, + 17848, + 17849, + 17850, + 17851, + 17852, + 17853, + 17854, + 17855, + 17856, + 17857, + 17858, + 17859, + 17860, + 17861, + 17862, + 17863, + 17864, + 17865, + 17866, + 17867, + 17868, + 17869, + 17870, + 17871, + 17872, + 17873, + 17874, + 17875, + 17876, + 17877, + 17878, + 17879, + 17880, + 17881, + 17882, + 17883, + 17884, + 17885, + 17886, + 17887, + 17888, + 17889, + 17890, + 17891, + 17892, + 17893, + 17894, + 17895, + 17896, + 17897, + 17898, + 17899, + 17900, + 17901, + 17902, + 17903, + 17904, + 17905, + 17906, + 17907, + 17908, + 17909, + 17910, + 17911, + 17912, + 17913, + 17914, + 17915, + 17916, + 17917, + 17918, + 17919, + 17920, + 17921, + 17922, + 17923, + 17924, + 17925, + 17926, + 17927, + 17928, + 17929, + 17930, + 17931, + 17932, + 17933, + 17934, + 17935, + 17936, + 17937, + 17938, + 17939, + 17940, + 17941, + 17942, + 17943, + 17944, + 17945, + 17946, + 17947, + 17948, + 17949, + 17950, + 17951, + 17952, + 17953, + 17954, + 17955, + 17956, + 17957, + 17958, + 17959, + 17960, + 17961, + 17962, + 17963, + 17964, + 17965, + 17966, + 17967, + 17968, + 17969, + 17970, + 17971, + 17972, + 17973, + 17974, + 17975, + 17976, + 17977, + 17978, + 17979, + 17980, + 17981, + 17982, + 17983, + 17984, + 17985, + 17986, + 17987, + 17988, + 17989, + 17990, + 17991, + 17992, + 17993, + 17994, + 17995, + 17996, + 17997, + 17998, + 17999, + 18000, + 18001, + 18002, + 18003, + 18004, + 18005, + 18006, + 18007, + 18008, + 18009, + 18010, + 18011, + 18012, + 18013, + 18014, + 18015, + 18016, + 18017, + 18018, + 18019, + 18020, + 18021, + 18022, + 18023, + 18024, + 18025, + 18026, + 18027, + 18028, + 18029, + 18030, + 18031, + 18032, + 18033, + 18034, + 18035, + 18036, + 18037, + 18038, + 18039, + 18040, + 18041, + 18042, + 18043, + 18044, + 18045, + 18046, + 18047, + 18048, + 18049, + 18050, + 18051, + 18052, + 18053, + 18054, + 18055, + 18056, + 18057, + 18058, + 18059, + 18060, + 18061, + 18062, + 18063, + 18064, + 18065, + 18066, + 18067, + 18068, + 18069, + 18070, + 18071, + 18072, + 18073, + 18074, + 18075, + 18076, + 18077, + 18078, + 18079, + 18080, + 18081, + 18082, + 18083, + 18084, + 18085, + 18086, + 18087, + 18088, + 18089, + 18090, + 18091, + 18092, + 18093, + 18094, + 18095, + 18096, + 18097, + 18098, + 18099, + 18100, + 18101, + 18102, + 18103, + 18104, + 18105, + 18106, + 18107, + 18108, + 18109, + 18110, + 18111, + 18112, + 18113, + 18114, + 18115, + 18116, + 18117, + 18118, + 18119, + 18120, + 18121, + 18122, + 18123, + 18124, + 18125, + 18126, + 18127, + 18128, + 18129, + 18130, + 18131, + 18132, + 18133, + 18134, + 18135, + 18136, + 18137, + 18138, + 18139, + 18140, + 18141, + 18142, + 18143, + 18144, + 18145, + 18146, + 18147, + 18148, + 18149, + 18150, + 18151, + 18152, + 18153, + 18154, + 18155, + 18156, + 18157, + 18158, + 18159, + 18160, + 18161, + 18162, + 18163, + 18164, + 18165, + 18166, + 18167, + 18168, + 18169, + 18170, + 18171, + 18172, + 18173, + 18174, + 18175, + 18176, + 18177, + 18178, + 18179, + 18180, + 18181, + 18182, + 18183, + 18184, + 18185, + 18186, + 18187, + 18188, + 18189, + 18190, + 18191, + 18192, + 18193, + 18194, + 18195, + 18196, + 18197, + 18198, + 18199, + 18200, + 18201, + 18202, + 18203, + 18204, + 18205, + 18206, + 18207, + 18208, + 18209, + 18210, + 18211, + 18212, + 18213, + 18214, + 18215, + 18216, + 18217, + 18218, + 18219, + 18220, + 18221, + 18222, + 18223, + 18224, + 18225, + 18226, + 18227, + 18228, + 18229, + 18230, + 18231, + 18232, + 18233, + 18234, + 18235, + 18236, + 18237, + 18238, + 18239, + 18240, + 18241, + 18242, + 18243, + 18244, + 18245, + 18246, + 18247, + 18248, + 18249, + 18250, + 18251, + 18252, + 18253, + 18254, + 18255, + 18256, + 18257, + 18258, + 18259, + 18260, + 18261, + 18262, + 18263, + 18264, + 18265, + 18266, + 18267, + 18268, + 18269, + 18270, + 18271, + 18272, + 18273, + 18274, + 18275, + 18276, + 18277, + 18278, + 18279, + 18280, + 18281, + 18282, + 18283, + 18284, + 18285, + 18286, + 18287, + 18288, + 18289, + 18290, + 18291, + 18292, + 18293, + 18294, + 18295, + 18296, + 18297, + 18298, + 18299, + 18300, + 18301, + 18302, + 18303, + 18304, + 18305, + 18306, + 18307, + 18308, + 18309, + 18310, + 18311, + 18312, + 18313, + 18314, + 18315, + 18316, + 18317, + 18318, + 18319, + 18320, + 18321, + 18322, + 18323, + 18324, + 18325, + 18326, + 18327, + 18328, + 18329, + 18330, + 18331, + 18332, + 18333, + 18334, + 18335, + 18336, + 18337, + 18338, + 18339, + 18340, + 18341, + 18342, + 18343, + 18344, + 18345, + 18346, + 18347, + 18348, + 18349, + 18350, + 18351, + 18352, + 18353, + 18354, + 18355, + 18356, + 18357, + 18358, + 18359, + 18360, + 18361, + 18362, + 18363, + 18364, + 18365, + 18366, + 18367, + 18368, + 18369, + 18370, + 18371, + 18372, + 18373, + 18374, + 18375, + 18376, + 18377, + 18378, + 18379, + 18380, + 18381, + 18382, + 18383, + 18384, + 18385, + 18386, + 18387, + 18388, + 18389, + 18390, + 18391, + 18392, + 18393, + 18394, + 18395, + 18396, + 18397, + 18398, + 18399, + 18400, + 18401, + 18402, + 18403, + 18404, + 18405, + 18406, + 18407, + 18408, + 18409, + 18410, + 18411, + 18412, + 18413, + 18414, + 18415, + 18416, + 18417, + 18418, + 18419, + 18420, + 18421, + 18422, + 18423, + 18424, + 18425, + 18426, + 18427, + 18428, + 18429, + 18430, + 18431, + 18432, + 18433, + 18434, + 18435, + 18436, + 18437, + 18438, + 18439, + 18440, + 18441, + 18442, + 18443, + 18444, + 18445, + 18446, + 18447, + 18448, + 18449, + 18450, + 18451, + 18452, + 18453, + 18454, + 18455, + 18456, + 18457, + 18458, + 18459, + 18460, + 18461, + 18462, + 18463, + 18464, + 18465, + 18466, + 18467, + 18468, + 18469, + 18470, + 18471, + 18472, + 18473, + 18474, + 18475, + 18476, + 18477, + 18478, + 18479, + 18480, + 18481, + 18482, + 18483, + 18484, + 18485, + 18486, + 18487, + 18488, + 18489, + 18490, + 18491, + 18492, + 18493, + 18494, + 18495, + 18496, + 18497, + 18498, + 18499, + 18500, + 18501, + 18502, + 18503, + 18504, + 18505, + 18506, + 18507, + 18508, + 18509, + 18510, + 18511, + 18512, + 18513, + 18514, + 18515, + 18516, + 18517, + 18518, + 18519, + 18520, + 18521, + 18522, + 18523, + 18524, + 18525, + 18526, + 18527, + 18528, + 18529, + 18530, + 18531, + 18532, + 18533, + 18534, + 18535, + 18536, + 18537, + 18538, + 18539, + 18540, + 18541, + 18542, + 18543, + 18544, + 18545, + 18546, + 18547, + 18548, + 18549, + 18550, + 18551, + 18552, + 18553, + 18554, + 18555, + 18556, + 18557, + 18558, + 18559, + 18560, + 18561, + 18562, + 18563, + 18564, + 18565, + 18566, + 18567, + 18568, + 18569, + 18570, + 18571, + 18572, + 18573, + 18574, + 18575, + 18576, + 18577, + 18578, + 18579, + 18580, + 18581, + 18582, + 18583, + 18584, + 18585, + 18586, + 18587, + 18588, + 18589, + 18590, + 18591, + 18592, + 18593, + 18594, + 18595, + 18596, + 18597, + 18598, + 18599, + 18600, + 18601, + 18602, + 18603, + 18604, + 18605, + 18606, + 18607, + 18608, + 18609, + 18610, + 18611, + 18612, + 18613, + 18614, + 18615, + 18616, + 18617, + 18618, + 18619, + 18620, + 18621, + 18622, + 18623, + 18624, + 18625, + 18626, + 18627, + 18628, + 18629, + 18630, + 18631, + 18632, + 18633, + 18634, + 18635, + 18636, + 18637, + 18638, + 18639, + 18640, + 18641, + 18642, + 18643, + 18644, + 18645, + 18646, + 18647, + 18648, + 18649, + 18650, + 18651, + 18652, + 18653, + 18654, + 18655, + 18656, + 18657, + 18658, + 18659, + 18660, + 18661, + 18662, + 18663, + 18664, + 18665, + 18666, + 18667, + 18668, + 18669, + 18670, + 18671, + 18672, + 18673, + 18674, + 18675, + 18676, + 18677, + 18678, + 18679, + 18680, + 18681, + 18682, + 18683, + 18684, + 18685, + 18686, + 18687, + 18688, + 18689, + 18690, + 18691, + 18692, + 18693, + 18694, + 18695, + 18696, + 18697, + 18698, + 18699, + 18700, + 18701, + 18702, + 18703, + 18704, + 18705, + 18706, + 18707, + 18708, + 18709, + 18710, + 18711, + 18712, + 18713, + 18714, + 18715, + 18716, + 18717, + 18718, + 18719, + 18720, + 18721, + 18722, + 18723, + 18724, + 18725, + 18726, + 18727, + 18728, + 18729, + 18730, + 18731, + 18732, + 18733, + 18734, + 18735, + 18736, + 18737, + 18738, + 18739, + 18740, + 18741, + 18742, + 18743, + 18744, + 18745, + 18746, + 18747, + 18748, + 18749, + 18750, + 18751, + 18752, + 18753, + 18754, + 18755, + 18756, + 18757, + 18758, + 18759, + 18760, + 18761, + 18762, + 18763, + 18764, + 18765, + 18766, + 18767, + 18768, + 18769, + 18770, + 18771, + 18772, + 18773, + 18774, + 18775, + 18776, + 18777, + 18778, + 18779, + 18780, + 18781, + 18782, + 18783, + 18784, + 18785, + 18786, + 18787, + 18788, + 18789, + 18790, + 18791, + 18792, + 18793, + 18794, + 18795, + 18796, + 18797, + 18798, + 18799, + 18800, + 18801, + 18802, + 18803, + 18804, + 18805, + 18806, + 18807, + 18808, + 18809, + 18810, + 18811, + 18812, + 18813, + 18814, + 18815, + 18816, + 18817, + 18818, + 18819, + 18820, + 18821, + 18822, + 18823, + 18824, + 18825, + 18826, + 18827, + 18828, + 18829, + 18830, + 18831, + 18832, + 18833, + 18834, + 18835, + 18836, + 18837, + 18838, + 18839, + 18840, + 18841, + 18842, + 18843, + 18844, + 18845, + 18846, + 18847, + 18848, + 18849, + 18850, + 18851, + 18852, + 18853, + 18854, + 18855, + 18856, + 18857, + 18858, + 18859, + 18860, + 18861, + 18862, + 18863, + 18864, + 18865, + 18866, + 18867, + 18868, + 18869, + 18870, + 18871, + 18872, + 18873, + 18874, + 18875, + 18876, + 18877, + 18878, + 18879, + 18880, + 18881, + 18882, + 18883, + 18884, + 18885, + 18886, + 18887, + 18888, + 18889, + 18890, + 18891, + 18892, + 18893, + 18894, + 18895, + 18896, + 18897, + 18898, + 18899, + 18900, + 18901, + 18902, + 18903, + 18904, + 18905, + 18906, + 18907, + 18908, + 18909, + 18910, + 18911, + 18912, + 18913, + 18914, + 18915, + 18916, + 18917, + 18918, + 18919, + 18920, + 18921, + 18922, + 18923, + 18924, + 18925, + 18926, + 18927, + 18928, + 18929, + 18930, + 18931, + 18932, + 18933, + 18934, + 18935, + 18936, + 18937, + 18938, + 18939, + 18940, + 18941, + 18942, + 18943, + 18944, + 18945, + 18946, + 18947, + 18948, + 18949, + 18950, + 18951, + 18952, + 18953, + 18954, + 18955, + 18956, + 18957, + 18958, + 18959, + 18960, + 18961, + 18962, + 18963, + 18964, + 18965, + 18966, + 18967, + 18968, + 18969, + 18970, + 18971, + 18972, + 18973, + 18974, + 18975, + 18976, + 18977, + 18978, + 18979, + 18980, + 18981, + 18982, + 18983, + 18984, + 18985, + 18986, + 18987, + 18988, + 18989, + 18990, + 18991, + 18992, + 18993, + 18994, + 18995, + 18996, + 18997, + 18998, + 18999, + 19000, + 19001, + 19002, + 19003, + 19004, + 19005, + 19006, + 19007, + 19008, + 19009, + 19010, + 19011, + 19012, + 19013, + 19014, + 19015, + 19016, + 19017, + 19018, + 19019, + 19020, + 19021, + 19022, + 19023, + 19024, + 19025, + 19026, + 19027, + 19028, + 19029, + 19030, + 19031, + 19032, + 19033, + 19034, + 19035, + 19036, + 19037, + 19038, + 19039, + 19040, + 19041, + 19042, + 19043, + 19044, + 19045, + 19046, + 19047, + 19048, + 19049, + 19050, + 19051, + 19052, + 19053, + 19054, + 19055, + 19056, + 19057, + 19058, + 19059, + 19060, + 19061, + 19062, + 19063, + 19064, + 19065, + 19066, + 19067, + 19068, + 19069, + 19070, + 19071, + 19072, + 19073, + 19074, + 19075, + 19076, + 19077, + 19078, + 19079, + 19080, + 19081, + 19082, + 19083, + 19084, + 19085, + 19086, + 19087, + 19088, + 19089, + 19090, + 19091, + 19092, + 19093, + 19094, + 19095, + 19096, + 19097, + 19098, + 19099, + 19100, + 19101, + 19102, + 19103, + 19104, + 19105, + 19106, + 19107, + 19108, + 19109, + 19110, + 19111, + 19112, + 19113, + 19114, + 19115, + 19116, + 19117, + 19118, + 19119, + 19120, + 19121, + 19122, + 19123, + 19124, + 19125, + 19126, + 19127, + 19128, + 19129, + 19130, + 19131, + 19132, + 19133, + 19134, + 19135, + 19136, + 19137, + 19138, + 19139, + 19140, + 19141, + 19142, + 19143, + 19144, + 19145, + 19146, + 19147, + 19148, + 19149, + 19150, + 19151, + 19152, + 19153, + 19154, + 19155, + 19156, + 19157, + 19158, + 19159, + 19160, + 19161, + 19162, + 19163, + 19164, + 19165, + 19166, + 19167, + 19168, + 19169, + 19170, + 19171, + 19172, + 19173, + 19174, + 19175, + 19176, + 19177, + 19178, + 19179, + 19180, + 19181, + 19182, + 19183, + 19184, + 19185, + 19186, + 19187, + 19188, + 19189, + 19190, + 19191, + 19192, + 19193, + 19194, + 19195, + 19196, + 19197, + 19198, + 19199, + 19200, + 19201, + 19202, + 19203, + 19204, + 19205, + 19206, + 19207, + 19208, + 19209, + 19210, + 19211, + 19212, + 19213, + 19214, + 19215, + 19216, + 19217, + 19218, + 19219, + 19220, + 19221, + 19222, + 19223, + 19224, + 19225, + 19226, + 19227, + 19228, + 19229, + 19230, + 19231, + 19232, + 19233, + 19234, + 19235, + 19236, + 19237, + 19238, + 19239, + 19240, + 19241, + 19242, + 19243, + 19244, + 19245, + 19246, + 19247, + 19248, + 19249, + 19250, + 19251, + 19252, + 19253, + 19254, + 19255, + 19256, + 19257, + 19258, + 19259, + 19260, + 19261, + 19262, + 19263, + 19264, + 19265, + 19266, + 19267, + 19268, + 19269, + 19270, + 19271, + 19272, + 19273, + 19274, + 19275, + 19276, + 19277, + 19278, + 19279, + 19280, + 19281, + 19282, + 19283, + 19284, + 19285, + 19286, + 19287, + 19288, + 19289, + 19290, + 19291, + 19292, + 19293, + 19294, + 19295, + 19296, + 19297, + 19298, + 19299, + 19300, + 19301, + 19302, + 19303, + 19304, + 19305, + 19306, + 19307, + 19308, + 19309, + 19310, + 19311, + 19312, + 19313, + 19314, + 19315, + 19316, + 19317, + 19318, + 19319, + 19320, + 19321, + 19322, + 19323, + 19324, + 19325, + 19326, + 19327, + 19328, + 19329, + 19330, + 19331, + 19332, + 19333, + 19334, + 19335, + 19336, + 19337, + 19338, + 19339, + 19340, + 19341, + 19342, + 19343, + 19344, + 19345, + 19346, + 19347, + 19348, + 19349, + 19350, + 19351, + 19352, + 19353, + 19354, + 19355, + 19356, + 19357, + 19358, + 19359, + 19360, + 19361, + 19362, + 19363, + 19364, + 19365, + 19366, + 19367, + 19368, + 19369, + 19370, + 19371, + 19372, + 19373, + 19374, + 19375, + 19376, + 19377, + 19378, + 19379, + 19380, + 19381, + 19382, + 19383, + 19384, + 19385, + 19386, + 19387, + 19388, + 19389, + 19390, + 19391, + 19392, + 19393, + 19394, + 19395, + 19396, + 19397, + 19398, + 19399, + 19400, + 19401, + 19402, + 19403, + 19404, + 19405, + 19406, + 19407, + 19408, + 19409, + 19410, + 19411, + 19412, + 19413, + 19414, + 19415, + 19416, + 19417, + 19418, + 19419, + 19420, + 19421, + 19422, + 19423, + 19424, + 19425, + 19426, + 19427, + 19428, + 19429, + 19430, + 19431, + 19432, + 19433, + 19434, + 19435, + 19436, + 19437, + 19438, + 19439, + 19440, + 19441, + 19442, + 19443, + 19444, + 19445, + 19446, + 19447, + 19448, + 19449, + 19450, + 19451, + 19452, + 19453, + 19454, + 19455, + 19456, + 19457, + 19458, + 19459, + 19460, + 19461, + 19462, + 19463, + 19464, + 19465, + 19466, + 19467, + 19468, + 19469, + 19470, + 19471, + 19472, + 19473, + 19474, + 19475, + 19476, + 19477, + 19478, + 19479, + 19480, + 19481, + 19482, + 19483, + 19484, + 19485, + 19486, + 19487, + 19488, + 19489, + 19490, + 19491, + 19492, + 19493, + 19494, + 19495, + 19496, + 19497, + 19498, + 19499, + 19500, + 19501, + 19502, + 19503, + 19504, + 19505, + 19506, + 19507, + 19508, + 19509, + 19510, + 19511, + 19512, + 19513, + 19514, + 19515, + 19516, + 19517, + 19518, + 19519, + 19520, + 19521, + 19522, + 19523, + 19524, + 19525, + 19526, + 19527, + 19528, + 19529, + 19530, + 19531, + 19532, + 19533, + 19534, + 19535, + 19536, + 19537, + 19538, + 19539, + 19540, + 19541, + 19542, + 19543, + 19544, + 19545, + 19546, + 19547, + 19548, + 19549, + 19550, + 19551, + 19552, + 19553, + 19554, + 19555, + 19556, + 19557, + 19558, + 19559, + 19560, + 19561, + 19562, + 19563, + 19564, + 19565, + 19566, + 19567, + 19568, + 19569, + 19570, + 19571, + 19572, + 19573, + 19574, + 19575, + 19576, + 19577, + 19578, + 19579, + 19580, + 19581, + 19582, + 19583, + 19584, + 19585, + 19586, + 19587, + 19588, + 19589, + 19590, + 19591, + 19592, + 19593, + 19594, + 19595, + 19596, + 19597, + 19598, + 19599, + 19600, + 19601, + 19602, + 19603, + 19604, + 19605, + 19606, + 19607, + 19608, + 19609, + 19610, + 19611, + 19612, + 19613, + 19614, + 19615, + 19616, + 19617, + 19618, + 19619, + 19620, + 19621, + 19622, + 19623, + 19624, + 19625, + 19626, + 19627, + 19628, + 19629, + 19630, + 19631, + 19632, + 19633, + 19634, + 19635, + 19636, + 19637, + 19638, + 19639, + 19640, + 19641, + 19642, + 19643, + 19644, + 19645, + 19646, + 19647, + 19648, + 19649, + 19650, + 19651, + 19652, + 19653, + 19654, + 19655, + 19656, + 19657, + 19658, + 19659, + 19660, + 19661, + 19662, + 19663, + 19664, + 19665, + 19666, + 19667, + 19668, + 19669, + 19670, + 19671, + 19672, + 19673, + 19674, + 19675, + 19676, + 19677, + 19678, + 19679, + 19680, + 19681, + 19682, + 19683, + 19684, + 19685, + 19686, + 19687, + 19688, + 19689, + 19690, + 19691, + 19692, + 19693, + 19694, + 19695, + 19696, + 19697, + 19698, + 19699, + 19700, + 19701, + 19702, + 19703, + 19704, + 19705, + 19706, + 19707, + 19708, + 19709, + 19710, + 19711, + 19712, + 19713, + 19714, + 19715, + 19716, + 19717, + 19718, + 19719, + 19720, + 19721, + 19722, + 19723, + 19724, + 19725, + 19726, + 19727, + 19728, + 19729, + 19730, + 19731, + 19732, + 19733, + 19734, + 19735, + 19736, + 19737, + 19738, + 19739, + 19740, + 19741, + 19742, + 19743, + 19744, + 19745, + 19746, + 19747, + 19748, + 19749, + 19750, + 19751, + 19752, + 19753, + 19754, + 19755, + 19756, + 19757, + 19758, + 19759, + 19760, + 19761, + 19762, + 19763, + 19764, + 19765, + 19766, + 19767, + 19768, + 19769, + 19770, + 19771, + 19772, + 19773, + 19774, + 19775, + 19776, + 19777, + 19778, + 19779, + 19780, + 19781, + 19782, + 19783, + 19784, + 19785, + 19786, + 19787, + 19788, + 19789, + 19790, + 19791, + 19792, + 19793, + 19794, + 19795, + 19796, + 19797, + 19798, + 19799, + 19800, + 19801, + 19802, + 19803, + 19804, + 19805, + 19806, + 19807, + 19808, + 19809, + 19810, + 19811, + 19812, + 19813, + 19814, + 19815, + 19816, + 19817, + 19818, + 19819, + 19820, + 19821, + 19822, + 19823, + 19824, + 19825, + 19826, + 19827, + 19828, + 19829, + 19830, + 19831, + 19832, + 19833, + 19834, + 19835, + 19836, + 19837, + 19838, + 19839, + 19840, + 19841, + 19842, + 19843, + 19844, + 19845, + 19846, + 19847, + 19848, + 19849, + 19850, + 19851, + 19852, + 19853, + 19854, + 19855, + 19856, + 19857, + 19858, + 19859, + 19860, + 19861, + 19862, + 19863, + 19864, + 19865, + 19866, + 19867, + 19868, + 19869, + 19870, + 19871, + 19872, + 19873, + 19874, + 19875, + 19876, + 19877, + 19878, + 19879, + 19880, + 19881, + 19882, + 19883, + 19884, + 19885, + 19886, + 19887, + 19888, + 19889, + 19890, + 19891, + 19892, + 19893, + 19968, + 19969, + 19970, + 19971, + 19972, + 19973, + 19974, + 19975, + 19976, + 19977, + 19978, + 19979, + 19980, + 19981, + 19982, + 19983, + 19984, + 19985, + 19986, + 19987, + 19988, + 19989, + 19990, + 19991, + 19992, + 19993, + 19994, + 19995, + 19996, + 19997, + 19998, + 19999, + 20000, + 20001, + 20002, + 20003, + 20004, + 20005, + 20006, + 20007, + 20008, + 20009, + 20010, + 20011, + 20012, + 20013, + 20014, + 20015, + 20016, + 20017, + 20018, + 20019, + 20020, + 20021, + 20022, + 20023, + 20024, + 20025, + 20026, + 20027, + 20028, + 20029, + 20030, + 20031, + 20032, + 20033, + 20034, + 20035, + 20036, + 20037, + 20038, + 20039, + 20040, + 20041, + 20042, + 20043, + 20044, + 20045, + 20046, + 20047, + 20048, + 20049, + 20050, + 20051, + 20052, + 20053, + 20054, + 20055, + 20056, + 20057, + 20058, + 20059, + 20060, + 20061, + 20062, + 20063, + 20064, + 20065, + 20066, + 20067, + 20068, + 20069, + 20070, + 20071, + 20072, + 20073, + 20074, + 20075, + 20076, + 20077, + 20078, + 20079, + 20080, + 20081, + 20082, + 20083, + 20084, + 20085, + 20086, + 20087, + 20088, + 20089, + 20090, + 20091, + 20092, + 20093, + 20094, + 20095, + 20096, + 20097, + 20098, + 20099, + 20100, + 20101, + 20102, + 20103, + 20104, + 20105, + 20106, + 20107, + 20108, + 20109, + 20110, + 20111, + 20112, + 20113, + 20114, + 20115, + 20116, + 20117, + 20118, + 20119, + 20120, + 20121, + 20122, + 20123, + 20124, + 20125, + 20126, + 20127, + 20128, + 20129, + 20130, + 20131, + 20132, + 20133, + 20134, + 20135, + 20136, + 20137, + 20138, + 20139, + 20140, + 20141, + 20142, + 20143, + 20144, + 20145, + 20146, + 20147, + 20148, + 20149, + 20150, + 20151, + 20152, + 20153, + 20154, + 20155, + 20156, + 20157, + 20158, + 20159, + 20160, + 20161, + 20162, + 20163, + 20164, + 20165, + 20166, + 20167, + 20168, + 20169, + 20170, + 20171, + 20172, + 20173, + 20174, + 20175, + 20176, + 20177, + 20178, + 20179, + 20180, + 20181, + 20182, + 20183, + 20184, + 20185, + 20186, + 20187, + 20188, + 20189, + 20190, + 20191, + 20192, + 20193, + 20194, + 20195, + 20196, + 20197, + 20198, + 20199, + 20200, + 20201, + 20202, + 20203, + 20204, + 20205, + 20206, + 20207, + 20208, + 20209, + 20210, + 20211, + 20212, + 20213, + 20214, + 20215, + 20216, + 20217, + 20218, + 20219, + 20220, + 20221, + 20222, + 20223, + 20224, + 20225, + 20226, + 20227, + 20228, + 20229, + 20230, + 20231, + 20232, + 20233, + 20234, + 20235, + 20236, + 20237, + 20238, + 20239, + 20240, + 20241, + 20242, + 20243, + 20244, + 20245, + 20246, + 20247, + 20248, + 20249, + 20250, + 20251, + 20252, + 20253, + 20254, + 20255, + 20256, + 20257, + 20258, + 20259, + 20260, + 20261, + 20262, + 20263, + 20264, + 20265, + 20266, + 20267, + 20268, + 20269, + 20270, + 20271, + 20272, + 20273, + 20274, + 20275, + 20276, + 20277, + 20278, + 20279, + 20280, + 20281, + 20282, + 20283, + 20284, + 20285, + 20286, + 20287, + 20288, + 20289, + 20290, + 20291, + 20292, + 20293, + 20294, + 20295, + 20296, + 20297, + 20298, + 20299, + 20300, + 20301, + 20302, + 20303, + 20304, + 20305, + 20306, + 20307, + 20308, + 20309, + 20310, + 20311, + 20312, + 20313, + 20314, + 20315, + 20316, + 20317, + 20318, + 20319, + 20320, + 20321, + 20322, + 20323, + 20324, + 20325, + 20326, + 20327, + 20328, + 20329, + 20330, + 20331, + 20332, + 20333, + 20334, + 20335, + 20336, + 20337, + 20338, + 20339, + 20340, + 20341, + 20342, + 20343, + 20344, + 20345, + 20346, + 20347, + 20348, + 20349, + 20350, + 20351, + 20352, + 20353, + 20354, + 20355, + 20356, + 20357, + 20358, + 20359, + 20360, + 20361, + 20362, + 20363, + 20364, + 20365, + 20366, + 20367, + 20368, + 20369, + 20370, + 20371, + 20372, + 20373, + 20374, + 20375, + 20376, + 20377, + 20378, + 20379, + 20380, + 20381, + 20382, + 20383, + 20384, + 20385, + 20386, + 20387, + 20388, + 20389, + 20390, + 20391, + 20392, + 20393, + 20394, + 20395, + 20396, + 20397, + 20398, + 20399, + 20400, + 20401, + 20402, + 20403, + 20404, + 20405, + 20406, + 20407, + 20408, + 20409, + 20410, + 20411, + 20412, + 20413, + 20414, + 20415, + 20416, + 20417, + 20418, + 20419, + 20420, + 20421, + 20422, + 20423, + 20424, + 20425, + 20426, + 20427, + 20428, + 20429, + 20430, + 20431, + 20432, + 20433, + 20434, + 20435, + 20436, + 20437, + 20438, + 20439, + 20440, + 20441, + 20442, + 20443, + 20444, + 20445, + 20446, + 20447, + 20448, + 20449, + 20450, + 20451, + 20452, + 20453, + 20454, + 20455, + 20456, + 20457, + 20458, + 20459, + 20460, + 20461, + 20462, + 20463, + 20464, + 20465, + 20466, + 20467, + 20468, + 20469, + 20470, + 20471, + 20472, + 20473, + 20474, + 20475, + 20476, + 20477, + 20478, + 20479, + 20480, + 20481, + 20482, + 20483, + 20484, + 20485, + 20486, + 20487, + 20488, + 20489, + 20490, + 20491, + 20492, + 20493, + 20494, + 20495, + 20496, + 20497, + 20498, + 20499, + 20500, + 20501, + 20502, + 20503, + 20504, + 20505, + 20506, + 20507, + 20508, + 20509, + 20510, + 20511, + 20512, + 20513, + 20514, + 20515, + 20516, + 20517, + 20518, + 20519, + 20520, + 20521, + 20522, + 20523, + 20524, + 20525, + 20526, + 20527, + 20528, + 20529, + 20530, + 20531, + 20532, + 20533, + 20534, + 20535, + 20536, + 20537, + 20538, + 20539, + 20540, + 20541, + 20542, + 20543, + 20544, + 20545, + 20546, + 20547, + 20548, + 20549, + 20550, + 20551, + 20552, + 20553, + 20554, + 20555, + 20556, + 20557, + 20558, + 20559, + 20560, + 20561, + 20562, + 20563, + 20564, + 20565, + 20566, + 20567, + 20568, + 20569, + 20570, + 20571, + 20572, + 20573, + 20574, + 20575, + 20576, + 20577, + 20578, + 20579, + 20580, + 20581, + 20582, + 20583, + 20584, + 20585, + 20586, + 20587, + 20588, + 20589, + 20590, + 20591, + 20592, + 20593, + 20594, + 20595, + 20596, + 20597, + 20598, + 20599, + 20600, + 20601, + 20602, + 20603, + 20604, + 20605, + 20606, + 20607, + 20608, + 20609, + 20610, + 20611, + 20612, + 20613, + 20614, + 20615, + 20616, + 20617, + 20618, + 20619, + 20620, + 20621, + 20622, + 20623, + 20624, + 20625, + 20626, + 20627, + 20628, + 20629, + 20630, + 20631, + 20632, + 20633, + 20634, + 20635, + 20636, + 20637, + 20638, + 20639, + 20640, + 20641, + 20642, + 20643, + 20644, + 20645, + 20646, + 20647, + 20648, + 20649, + 20650, + 20651, + 20652, + 20653, + 20654, + 20655, + 20656, + 20657, + 20658, + 20659, + 20660, + 20661, + 20662, + 20663, + 20664, + 20665, + 20666, + 20667, + 20668, + 20669, + 20670, + 20671, + 20672, + 20673, + 20674, + 20675, + 20676, + 20677, + 20678, + 20679, + 20680, + 20681, + 20682, + 20683, + 20684, + 20685, + 20686, + 20687, + 20688, + 20689, + 20690, + 20691, + 20692, + 20693, + 20694, + 20695, + 20696, + 20697, + 20698, + 20699, + 20700, + 20701, + 20702, + 20703, + 20704, + 20705, + 20706, + 20707, + 20708, + 20709, + 20710, + 20711, + 20712, + 20713, + 20714, + 20715, + 20716, + 20717, + 20718, + 20719, + 20720, + 20721, + 20722, + 20723, + 20724, + 20725, + 20726, + 20727, + 20728, + 20729, + 20730, + 20731, + 20732, + 20733, + 20734, + 20735, + 20736, + 20737, + 20738, + 20739, + 20740, + 20741, + 20742, + 20743, + 20744, + 20745, + 20746, + 20747, + 20748, + 20749, + 20750, + 20751, + 20752, + 20753, + 20754, + 20755, + 20756, + 20757, + 20758, + 20759, + 20760, + 20761, + 20762, + 20763, + 20764, + 20765, + 20766, + 20767, + 20768, + 20769, + 20770, + 20771, + 20772, + 20773, + 20774, + 20775, + 20776, + 20777, + 20778, + 20779, + 20780, + 20781, + 20782, + 20783, + 20784, + 20785, + 20786, + 20787, + 20788, + 20789, + 20790, + 20791, + 20792, + 20793, + 20794, + 20795, + 20796, + 20797, + 20798, + 20799, + 20800, + 20801, + 20802, + 20803, + 20804, + 20805, + 20806, + 20807, + 20808, + 20809, + 20810, + 20811, + 20812, + 20813, + 20814, + 20815, + 20816, + 20817, + 20818, + 20819, + 20820, + 20821, + 20822, + 20823, + 20824, + 20825, + 20826, + 20827, + 20828, + 20829, + 20830, + 20831, + 20832, + 20833, + 20834, + 20835, + 20836, + 20837, + 20838, + 20839, + 20840, + 20841, + 20842, + 20843, + 20844, + 20845, + 20846, + 20847, + 20848, + 20849, + 20850, + 20851, + 20852, + 20853, + 20854, + 20855, + 20856, + 20857, + 20858, + 20859, + 20860, + 20861, + 20862, + 20863, + 20864, + 20865, + 20866, + 20867, + 20868, + 20869, + 20870, + 20871, + 20872, + 20873, + 20874, + 20875, + 20876, + 20877, + 20878, + 20879, + 20880, + 20881, + 20882, + 20883, + 20884, + 20885, + 20886, + 20887, + 20888, + 20889, + 20890, + 20891, + 20892, + 20893, + 20894, + 20895, + 20896, + 20897, + 20898, + 20899, + 20900, + 20901, + 20902, + 20903, + 20904, + 20905, + 20906, + 20907, + 20908, + 20909, + 20910, + 20911, + 20912, + 20913, + 20914, + 20915, + 20916, + 20917, + 20918, + 20919, + 20920, + 20921, + 20922, + 20923, + 20924, + 20925, + 20926, + 20927, + 20928, + 20929, + 20930, + 20931, + 20932, + 20933, + 20934, + 20935, + 20936, + 20937, + 20938, + 20939, + 20940, + 20941, + 20942, + 20943, + 20944, + 20945, + 20946, + 20947, + 20948, + 20949, + 20950, + 20951, + 20952, + 20953, + 20954, + 20955, + 20956, + 20957, + 20958, + 20959, + 20960, + 20961, + 20962, + 20963, + 20964, + 20965, + 20966, + 20967, + 20968, + 20969, + 20970, + 20971, + 20972, + 20973, + 20974, + 20975, + 20976, + 20977, + 20978, + 20979, + 20980, + 20981, + 20982, + 20983, + 20984, + 20985, + 20986, + 20987, + 20988, + 20989, + 20990, + 20991, + 20992, + 20993, + 20994, + 20995, + 20996, + 20997, + 20998, + 20999, + 21000, + 21001, + 21002, + 21003, + 21004, + 21005, + 21006, + 21007, + 21008, + 21009, + 21010, + 21011, + 21012, + 21013, + 21014, + 21015, + 21016, + 21017, + 21018, + 21019, + 21020, + 21021, + 21022, + 21023, + 21024, + 21025, + 21026, + 21027, + 21028, + 21029, + 21030, + 21031, + 21032, + 21033, + 21034, + 21035, + 21036, + 21037, + 21038, + 21039, + 21040, + 21041, + 21042, + 21043, + 21044, + 21045, + 21046, + 21047, + 21048, + 21049, + 21050, + 21051, + 21052, + 21053, + 21054, + 21055, + 21056, + 21057, + 21058, + 21059, + 21060, + 21061, + 21062, + 21063, + 21064, + 21065, + 21066, + 21067, + 21068, + 21069, + 21070, + 21071, + 21072, + 21073, + 21074, + 21075, + 21076, + 21077, + 21078, + 21079, + 21080, + 21081, + 21082, + 21083, + 21084, + 21085, + 21086, + 21087, + 21088, + 21089, + 21090, + 21091, + 21092, + 21093, + 21094, + 21095, + 21096, + 21097, + 21098, + 21099, + 21100, + 21101, + 21102, + 21103, + 21104, + 21105, + 21106, + 21107, + 21108, + 21109, + 21110, + 21111, + 21112, + 21113, + 21114, + 21115, + 21116, + 21117, + 21118, + 21119, + 21120, + 21121, + 21122, + 21123, + 21124, + 21125, + 21126, + 21127, + 21128, + 21129, + 21130, + 21131, + 21132, + 21133, + 21134, + 21135, + 21136, + 21137, + 21138, + 21139, + 21140, + 21141, + 21142, + 21143, + 21144, + 21145, + 21146, + 21147, + 21148, + 21149, + 21150, + 21151, + 21152, + 21153, + 21154, + 21155, + 21156, + 21157, + 21158, + 21159, + 21160, + 21161, + 21162, + 21163, + 21164, + 21165, + 21166, + 21167, + 21168, + 21169, + 21170, + 21171, + 21172, + 21173, + 21174, + 21175, + 21176, + 21177, + 21178, + 21179, + 21180, + 21181, + 21182, + 21183, + 21184, + 21185, + 21186, + 21187, + 21188, + 21189, + 21190, + 21191, + 21192, + 21193, + 21194, + 21195, + 21196, + 21197, + 21198, + 21199, + 21200, + 21201, + 21202, + 21203, + 21204, + 21205, + 21206, + 21207, + 21208, + 21209, + 21210, + 21211, + 21212, + 21213, + 21214, + 21215, + 21216, + 21217, + 21218, + 21219, + 21220, + 21221, + 21222, + 21223, + 21224, + 21225, + 21226, + 21227, + 21228, + 21229, + 21230, + 21231, + 21232, + 21233, + 21234, + 21235, + 21236, + 21237, + 21238, + 21239, + 21240, + 21241, + 21242, + 21243, + 21244, + 21245, + 21246, + 21247, + 21248, + 21249, + 21250, + 21251, + 21252, + 21253, + 21254, + 21255, + 21256, + 21257, + 21258, + 21259, + 21260, + 21261, + 21262, + 21263, + 21264, + 21265, + 21266, + 21267, + 21268, + 21269, + 21270, + 21271, + 21272, + 21273, + 21274, + 21275, + 21276, + 21277, + 21278, + 21279, + 21280, + 21281, + 21282, + 21283, + 21284, + 21285, + 21286, + 21287, + 21288, + 21289, + 21290, + 21291, + 21292, + 21293, + 21294, + 21295, + 21296, + 21297, + 21298, + 21299, + 21300, + 21301, + 21302, + 21303, + 21304, + 21305, + 21306, + 21307, + 21308, + 21309, + 21310, + 21311, + 21312, + 21313, + 21314, + 21315, + 21316, + 21317, + 21318, + 21319, + 21320, + 21321, + 21322, + 21323, + 21324, + 21325, + 21326, + 21327, + 21328, + 21329, + 21330, + 21331, + 21332, + 21333, + 21334, + 21335, + 21336, + 21337, + 21338, + 21339, + 21340, + 21341, + 21342, + 21343, + 21344, + 21345, + 21346, + 21347, + 21348, + 21349, + 21350, + 21351, + 21352, + 21353, + 21354, + 21355, + 21356, + 21357, + 21358, + 21359, + 21360, + 21361, + 21362, + 21363, + 21364, + 21365, + 21366, + 21367, + 21368, + 21369, + 21370, + 21371, + 21372, + 21373, + 21374, + 21375, + 21376, + 21377, + 21378, + 21379, + 21380, + 21381, + 21382, + 21383, + 21384, + 21385, + 21386, + 21387, + 21388, + 21389, + 21390, + 21391, + 21392, + 21393, + 21394, + 21395, + 21396, + 21397, + 21398, + 21399, + 21400, + 21401, + 21402, + 21403, + 21404, + 21405, + 21406, + 21407, + 21408, + 21409, + 21410, + 21411, + 21412, + 21413, + 21414, + 21415, + 21416, + 21417, + 21418, + 21419, + 21420, + 21421, + 21422, + 21423, + 21424, + 21425, + 21426, + 21427, + 21428, + 21429, + 21430, + 21431, + 21432, + 21433, + 21434, + 21435, + 21436, + 21437, + 21438, + 21439, + 21440, + 21441, + 21442, + 21443, + 21444, + 21445, + 21446, + 21447, + 21448, + 21449, + 21450, + 21451, + 21452, + 21453, + 21454, + 21455, + 21456, + 21457, + 21458, + 21459, + 21460, + 21461, + 21462, + 21463, + 21464, + 21465, + 21466, + 21467, + 21468, + 21469, + 21470, + 21471, + 21472, + 21473, + 21474, + 21475, + 21476, + 21477, + 21478, + 21479, + 21480, + 21481, + 21482, + 21483, + 21484, + 21485, + 21486, + 21487, + 21488, + 21489, + 21490, + 21491, + 21492, + 21493, + 21494, + 21495, + 21496, + 21497, + 21498, + 21499, + 21500, + 21501, + 21502, + 21503, + 21504, + 21505, + 21506, + 21507, + 21508, + 21509, + 21510, + 21511, + 21512, + 21513, + 21514, + 21515, + 21516, + 21517, + 21518, + 21519, + 21520, + 21521, + 21522, + 21523, + 21524, + 21525, + 21526, + 21527, + 21528, + 21529, + 21530, + 21531, + 21532, + 21533, + 21534, + 21535, + 21536, + 21537, + 21538, + 21539, + 21540, + 21541, + 21542, + 21543, + 21544, + 21545, + 21546, + 21547, + 21548, + 21549, + 21550, + 21551, + 21552, + 21553, + 21554, + 21555, + 21556, + 21557, + 21558, + 21559, + 21560, + 21561, + 21562, + 21563, + 21564, + 21565, + 21566, + 21567, + 21568, + 21569, + 21570, + 21571, + 21572, + 21573, + 21574, + 21575, + 21576, + 21577, + 21578, + 21579, + 21580, + 21581, + 21582, + 21583, + 21584, + 21585, + 21586, + 21587, + 21588, + 21589, + 21590, + 21591, + 21592, + 21593, + 21594, + 21595, + 21596, + 21597, + 21598, + 21599, + 21600, + 21601, + 21602, + 21603, + 21604, + 21605, + 21606, + 21607, + 21608, + 21609, + 21610, + 21611, + 21612, + 21613, + 21614, + 21615, + 21616, + 21617, + 21618, + 21619, + 21620, + 21621, + 21622, + 21623, + 21624, + 21625, + 21626, + 21627, + 21628, + 21629, + 21630, + 21631, + 21632, + 21633, + 21634, + 21635, + 21636, + 21637, + 21638, + 21639, + 21640, + 21641, + 21642, + 21643, + 21644, + 21645, + 21646, + 21647, + 21648, + 21649, + 21650, + 21651, + 21652, + 21653, + 21654, + 21655, + 21656, + 21657, + 21658, + 21659, + 21660, + 21661, + 21662, + 21663, + 21664, + 21665, + 21666, + 21667, + 21668, + 21669, + 21670, + 21671, + 21672, + 21673, + 21674, + 21675, + 21676, + 21677, + 21678, + 21679, + 21680, + 21681, + 21682, + 21683, + 21684, + 21685, + 21686, + 21687, + 21688, + 21689, + 21690, + 21691, + 21692, + 21693, + 21694, + 21695, + 21696, + 21697, + 21698, + 21699, + 21700, + 21701, + 21702, + 21703, + 21704, + 21705, + 21706, + 21707, + 21708, + 21709, + 21710, + 21711, + 21712, + 21713, + 21714, + 21715, + 21716, + 21717, + 21718, + 21719, + 21720, + 21721, + 21722, + 21723, + 21724, + 21725, + 21726, + 21727, + 21728, + 21729, + 21730, + 21731, + 21732, + 21733, + 21734, + 21735, + 21736, + 21737, + 21738, + 21739, + 21740, + 21741, + 21742, + 21743, + 21744, + 21745, + 21746, + 21747, + 21748, + 21749, + 21750, + 21751, + 21752, + 21753, + 21754, + 21755, + 21756, + 21757, + 21758, + 21759, + 21760, + 21761, + 21762, + 21763, + 21764, + 21765, + 21766, + 21767, + 21768, + 21769, + 21770, + 21771, + 21772, + 21773, + 21774, + 21775, + 21776, + 21777, + 21778, + 21779, + 21780, + 21781, + 21782, + 21783, + 21784, + 21785, + 21786, + 21787, + 21788, + 21789, + 21790, + 21791, + 21792, + 21793, + 21794, + 21795, + 21796, + 21797, + 21798, + 21799, + 21800, + 21801, + 21802, + 21803, + 21804, + 21805, + 21806, + 21807, + 21808, + 21809, + 21810, + 21811, + 21812, + 21813, + 21814, + 21815, + 21816, + 21817, + 21818, + 21819, + 21820, + 21821, + 21822, + 21823, + 21824, + 21825, + 21826, + 21827, + 21828, + 21829, + 21830, + 21831, + 21832, + 21833, + 21834, + 21835, + 21836, + 21837, + 21838, + 21839, + 21840, + 21841, + 21842, + 21843, + 21844, + 21845, + 21846, + 21847, + 21848, + 21849, + 21850, + 21851, + 21852, + 21853, + 21854, + 21855, + 21856, + 21857, + 21858, + 21859, + 21860, + 21861, + 21862, + 21863, + 21864, + 21865, + 21866, + 21867, + 21868, + 21869, + 21870, + 21871, + 21872, + 21873, + 21874, + 21875, + 21876, + 21877, + 21878, + 21879, + 21880, + 21881, + 21882, + 21883, + 21884, + 21885, + 21886, + 21887, + 21888, + 21889, + 21890, + 21891, + 21892, + 21893, + 21894, + 21895, + 21896, + 21897, + 21898, + 21899, + 21900, + 21901, + 21902, + 21903, + 21904, + 21905, + 21906, + 21907, + 21908, + 21909, + 21910, + 21911, + 21912, + 21913, + 21914, + 21915, + 21916, + 21917, + 21918, + 21919, + 21920, + 21921, + 21922, + 21923, + 21924, + 21925, + 21926, + 21927, + 21928, + 21929, + 21930, + 21931, + 21932, + 21933, + 21934, + 21935, + 21936, + 21937, + 21938, + 21939, + 21940, + 21941, + 21942, + 21943, + 21944, + 21945, + 21946, + 21947, + 21948, + 21949, + 21950, + 21951, + 21952, + 21953, + 21954, + 21955, + 21956, + 21957, + 21958, + 21959, + 21960, + 21961, + 21962, + 21963, + 21964, + 21965, + 21966, + 21967, + 21968, + 21969, + 21970, + 21971, + 21972, + 21973, + 21974, + 21975, + 21976, + 21977, + 21978, + 21979, + 21980, + 21981, + 21982, + 21983, + 21984, + 21985, + 21986, + 21987, + 21988, + 21989, + 21990, + 21991, + 21992, + 21993, + 21994, + 21995, + 21996, + 21997, + 21998, + 21999, + 22000, + 22001, + 22002, + 22003, + 22004, + 22005, + 22006, + 22007, + 22008, + 22009, + 22010, + 22011, + 22012, + 22013, + 22014, + 22015, + 22016, + 22017, + 22018, + 22019, + 22020, + 22021, + 22022, + 22023, + 22024, + 22025, + 22026, + 22027, + 22028, + 22029, + 22030, + 22031, + 22032, + 22033, + 22034, + 22035, + 22036, + 22037, + 22038, + 22039, + 22040, + 22041, + 22042, + 22043, + 22044, + 22045, + 22046, + 22047, + 22048, + 22049, + 22050, + 22051, + 22052, + 22053, + 22054, + 22055, + 22056, + 22057, + 22058, + 22059, + 22060, + 22061, + 22062, + 22063, + 22064, + 22065, + 22066, + 22067, + 22068, + 22069, + 22070, + 22071, + 22072, + 22073, + 22074, + 22075, + 22076, + 22077, + 22078, + 22079, + 22080, + 22081, + 22082, + 22083, + 22084, + 22085, + 22086, + 22087, + 22088, + 22089, + 22090, + 22091, + 22092, + 22093, + 22094, + 22095, + 22096, + 22097, + 22098, + 22099, + 22100, + 22101, + 22102, + 22103, + 22104, + 22105, + 22106, + 22107, + 22108, + 22109, + 22110, + 22111, + 22112, + 22113, + 22114, + 22115, + 22116, + 22117, + 22118, + 22119, + 22120, + 22121, + 22122, + 22123, + 22124, + 22125, + 22126, + 22127, + 22128, + 22129, + 22130, + 22131, + 22132, + 22133, + 22134, + 22135, + 22136, + 22137, + 22138, + 22139, + 22140, + 22141, + 22142, + 22143, + 22144, + 22145, + 22146, + 22147, + 22148, + 22149, + 22150, + 22151, + 22152, + 22153, + 22154, + 22155, + 22156, + 22157, + 22158, + 22159, + 22160, + 22161, + 22162, + 22163, + 22164, + 22165, + 22166, + 22167, + 22168, + 22169, + 22170, + 22171, + 22172, + 22173, + 22174, + 22175, + 22176, + 22177, + 22178, + 22179, + 22180, + 22181, + 22182, + 22183, + 22184, + 22185, + 22186, + 22187, + 22188, + 22189, + 22190, + 22191, + 22192, + 22193, + 22194, + 22195, + 22196, + 22197, + 22198, + 22199, + 22200, + 22201, + 22202, + 22203, + 22204, + 22205, + 22206, + 22207, + 22208, + 22209, + 22210, + 22211, + 22212, + 22213, + 22214, + 22215, + 22216, + 22217, + 22218, + 22219, + 22220, + 22221, + 22222, + 22223, + 22224, + 22225, + 22226, + 22227, + 22228, + 22229, + 22230, + 22231, + 22232, + 22233, + 22234, + 22235, + 22236, + 22237, + 22238, + 22239, + 22240, + 22241, + 22242, + 22243, + 22244, + 22245, + 22246, + 22247, + 22248, + 22249, + 22250, + 22251, + 22252, + 22253, + 22254, + 22255, + 22256, + 22257, + 22258, + 22259, + 22260, + 22261, + 22262, + 22263, + 22264, + 22265, + 22266, + 22267, + 22268, + 22269, + 22270, + 22271, + 22272, + 22273, + 22274, + 22275, + 22276, + 22277, + 22278, + 22279, + 22280, + 22281, + 22282, + 22283, + 22284, + 22285, + 22286, + 22287, + 22288, + 22289, + 22290, + 22291, + 22292, + 22293, + 22294, + 22295, + 22296, + 22297, + 22298, + 22299, + 22300, + 22301, + 22302, + 22303, + 22304, + 22305, + 22306, + 22307, + 22308, + 22309, + 22310, + 22311, + 22312, + 22313, + 22314, + 22315, + 22316, + 22317, + 22318, + 22319, + 22320, + 22321, + 22322, + 22323, + 22324, + 22325, + 22326, + 22327, + 22328, + 22329, + 22330, + 22331, + 22332, + 22333, + 22334, + 22335, + 22336, + 22337, + 22338, + 22339, + 22340, + 22341, + 22342, + 22343, + 22344, + 22345, + 22346, + 22347, + 22348, + 22349, + 22350, + 22351, + 22352, + 22353, + 22354, + 22355, + 22356, + 22357, + 22358, + 22359, + 22360, + 22361, + 22362, + 22363, + 22364, + 22365, + 22366, + 22367, + 22368, + 22369, + 22370, + 22371, + 22372, + 22373, + 22374, + 22375, + 22376, + 22377, + 22378, + 22379, + 22380, + 22381, + 22382, + 22383, + 22384, + 22385, + 22386, + 22387, + 22388, + 22389, + 22390, + 22391, + 22392, + 22393, + 22394, + 22395, + 22396, + 22397, + 22398, + 22399, + 22400, + 22401, + 22402, + 22403, + 22404, + 22405, + 22406, + 22407, + 22408, + 22409, + 22410, + 22411, + 22412, + 22413, + 22414, + 22415, + 22416, + 22417, + 22418, + 22419, + 22420, + 22421, + 22422, + 22423, + 22424, + 22425, + 22426, + 22427, + 22428, + 22429, + 22430, + 22431, + 22432, + 22433, + 22434, + 22435, + 22436, + 22437, + 22438, + 22439, + 22440, + 22441, + 22442, + 22443, + 22444, + 22445, + 22446, + 22447, + 22448, + 22449, + 22450, + 22451, + 22452, + 22453, + 22454, + 22455, + 22456, + 22457, + 22458, + 22459, + 22460, + 22461, + 22462, + 22463, + 22464, + 22465, + 22466, + 22467, + 22468, + 22469, + 22470, + 22471, + 22472, + 22473, + 22474, + 22475, + 22476, + 22477, + 22478, + 22479, + 22480, + 22481, + 22482, + 22483, + 22484, + 22485, + 22486, + 22487, + 22488, + 22489, + 22490, + 22491, + 22492, + 22493, + 22494, + 22495, + 22496, + 22497, + 22498, + 22499, + 22500, + 22501, + 22502, + 22503, + 22504, + 22505, + 22506, + 22507, + 22508, + 22509, + 22510, + 22511, + 22512, + 22513, + 22514, + 22515, + 22516, + 22517, + 22518, + 22519, + 22520, + 22521, + 22522, + 22523, + 22524, + 22525, + 22526, + 22527, + 22528, + 22529, + 22530, + 22531, + 22532, + 22533, + 22534, + 22535, + 22536, + 22537, + 22538, + 22539, + 22540, + 22541, + 22542, + 22543, + 22544, + 22545, + 22546, + 22547, + 22548, + 22549, + 22550, + 22551, + 22552, + 22553, + 22554, + 22555, + 22556, + 22557, + 22558, + 22559, + 22560, + 22561, + 22562, + 22563, + 22564, + 22565, + 22566, + 22567, + 22568, + 22569, + 22570, + 22571, + 22572, + 22573, + 22574, + 22575, + 22576, + 22577, + 22578, + 22579, + 22580, + 22581, + 22582, + 22583, + 22584, + 22585, + 22586, + 22587, + 22588, + 22589, + 22590, + 22591, + 22592, + 22593, + 22594, + 22595, + 22596, + 22597, + 22598, + 22599, + 22600, + 22601, + 22602, + 22603, + 22604, + 22605, + 22606, + 22607, + 22608, + 22609, + 22610, + 22611, + 22612, + 22613, + 22614, + 22615, + 22616, + 22617, + 22618, + 22619, + 22620, + 22621, + 22622, + 22623, + 22624, + 22625, + 22626, + 22627, + 22628, + 22629, + 22630, + 22631, + 22632, + 22633, + 22634, + 22635, + 22636, + 22637, + 22638, + 22639, + 22640, + 22641, + 22642, + 22643, + 22644, + 22645, + 22646, + 22647, + 22648, + 22649, + 22650, + 22651, + 22652, + 22653, + 22654, + 22655, + 22656, + 22657, + 22658, + 22659, + 22660, + 22661, + 22662, + 22663, + 22664, + 22665, + 22666, + 22667, + 22668, + 22669, + 22670, + 22671, + 22672, + 22673, + 22674, + 22675, + 22676, + 22677, + 22678, + 22679, + 22680, + 22681, + 22682, + 22683, + 22684, + 22685, + 22686, + 22687, + 22688, + 22689, + 22690, + 22691, + 22692, + 22693, + 22694, + 22695, + 22696, + 22697, + 22698, + 22699, + 22700, + 22701, + 22702, + 22703, + 22704, + 22705, + 22706, + 22707, + 22708, + 22709, + 22710, + 22711, + 22712, + 22713, + 22714, + 22715, + 22716, + 22717, + 22718, + 22719, + 22720, + 22721, + 22722, + 22723, + 22724, + 22725, + 22726, + 22727, + 22728, + 22729, + 22730, + 22731, + 22732, + 22733, + 22734, + 22735, + 22736, + 22737, + 22738, + 22739, + 22740, + 22741, + 22742, + 22743, + 22744, + 22745, + 22746, + 22747, + 22748, + 22749, + 22750, + 22751, + 22752, + 22753, + 22754, + 22755, + 22756, + 22757, + 22758, + 22759, + 22760, + 22761, + 22762, + 22763, + 22764, + 22765, + 22766, + 22767, + 22768, + 22769, + 22770, + 22771, + 22772, + 22773, + 22774, + 22775, + 22776, + 22777, + 22778, + 22779, + 22780, + 22781, + 22782, + 22783, + 22784, + 22785, + 22786, + 22787, + 22788, + 22789, + 22790, + 22791, + 22792, + 22793, + 22794, + 22795, + 22796, + 22797, + 22798, + 22799, + 22800, + 22801, + 22802, + 22803, + 22804, + 22805, + 22806, + 22807, + 22808, + 22809, + 22810, + 22811, + 22812, + 22813, + 22814, + 22815, + 22816, + 22817, + 22818, + 22819, + 22820, + 22821, + 22822, + 22823, + 22824, + 22825, + 22826, + 22827, + 22828, + 22829, + 22830, + 22831, + 22832, + 22833, + 22834, + 22835, + 22836, + 22837, + 22838, + 22839, + 22840, + 22841, + 22842, + 22843, + 22844, + 22845, + 22846, + 22847, + 22848, + 22849, + 22850, + 22851, + 22852, + 22853, + 22854, + 22855, + 22856, + 22857, + 22858, + 22859, + 22860, + 22861, + 22862, + 22863, + 22864, + 22865, + 22866, + 22867, + 22868, + 22869, + 22870, + 22871, + 22872, + 22873, + 22874, + 22875, + 22876, + 22877, + 22878, + 22879, + 22880, + 22881, + 22882, + 22883, + 22884, + 22885, + 22886, + 22887, + 22888, + 22889, + 22890, + 22891, + 22892, + 22893, + 22894, + 22895, + 22896, + 22897, + 22898, + 22899, + 22900, + 22901, + 22902, + 22903, + 22904, + 22905, + 22906, + 22907, + 22908, + 22909, + 22910, + 22911, + 22912, + 22913, + 22914, + 22915, + 22916, + 22917, + 22918, + 22919, + 22920, + 22921, + 22922, + 22923, + 22924, + 22925, + 22926, + 22927, + 22928, + 22929, + 22930, + 22931, + 22932, + 22933, + 22934, + 22935, + 22936, + 22937, + 22938, + 22939, + 22940, + 22941, + 22942, + 22943, + 22944, + 22945, + 22946, + 22947, + 22948, + 22949, + 22950, + 22951, + 22952, + 22953, + 22954, + 22955, + 22956, + 22957, + 22958, + 22959, + 22960, + 22961, + 22962, + 22963, + 22964, + 22965, + 22966, + 22967, + 22968, + 22969, + 22970, + 22971, + 22972, + 22973, + 22974, + 22975, + 22976, + 22977, + 22978, + 22979, + 22980, + 22981, + 22982, + 22983, + 22984, + 22985, + 22986, + 22987, + 22988, + 22989, + 22990, + 22991, + 22992, + 22993, + 22994, + 22995, + 22996, + 22997, + 22998, + 22999, + 23000, + 23001, + 23002, + 23003, + 23004, + 23005, + 23006, + 23007, + 23008, + 23009, + 23010, + 23011, + 23012, + 23013, + 23014, + 23015, + 23016, + 23017, + 23018, + 23019, + 23020, + 23021, + 23022, + 23023, + 23024, + 23025, + 23026, + 23027, + 23028, + 23029, + 23030, + 23031, + 23032, + 23033, + 23034, + 23035, + 23036, + 23037, + 23038, + 23039, + 23040, + 23041, + 23042, + 23043, + 23044, + 23045, + 23046, + 23047, + 23048, + 23049, + 23050, + 23051, + 23052, + 23053, + 23054, + 23055, + 23056, + 23057, + 23058, + 23059, + 23060, + 23061, + 23062, + 23063, + 23064, + 23065, + 23066, + 23067, + 23068, + 23069, + 23070, + 23071, + 23072, + 23073, + 23074, + 23075, + 23076, + 23077, + 23078, + 23079, + 23080, + 23081, + 23082, + 23083, + 23084, + 23085, + 23086, + 23087, + 23088, + 23089, + 23090, + 23091, + 23092, + 23093, + 23094, + 23095, + 23096, + 23097, + 23098, + 23099, + 23100, + 23101, + 23102, + 23103, + 23104, + 23105, + 23106, + 23107, + 23108, + 23109, + 23110, + 23111, + 23112, + 23113, + 23114, + 23115, + 23116, + 23117, + 23118, + 23119, + 23120, + 23121, + 23122, + 23123, + 23124, + 23125, + 23126, + 23127, + 23128, + 23129, + 23130, + 23131, + 23132, + 23133, + 23134, + 23135, + 23136, + 23137, + 23138, + 23139, + 23140, + 23141, + 23142, + 23143, + 23144, + 23145, + 23146, + 23147, + 23148, + 23149, + 23150, + 23151, + 23152, + 23153, + 23154, + 23155, + 23156, + 23157, + 23158, + 23159, + 23160, + 23161, + 23162, + 23163, + 23164, + 23165, + 23166, + 23167, + 23168, + 23169, + 23170, + 23171, + 23172, + 23173, + 23174, + 23175, + 23176, + 23177, + 23178, + 23179, + 23180, + 23181, + 23182, + 23183, + 23184, + 23185, + 23186, + 23187, + 23188, + 23189, + 23190, + 23191, + 23192, + 23193, + 23194, + 23195, + 23196, + 23197, + 23198, + 23199, + 23200, + 23201, + 23202, + 23203, + 23204, + 23205, + 23206, + 23207, + 23208, + 23209, + 23210, + 23211, + 23212, + 23213, + 23214, + 23215, + 23216, + 23217, + 23218, + 23219, + 23220, + 23221, + 23222, + 23223, + 23224, + 23225, + 23226, + 23227, + 23228, + 23229, + 23230, + 23231, + 23232, + 23233, + 23234, + 23235, + 23236, + 23237, + 23238, + 23239, + 23240, + 23241, + 23242, + 23243, + 23244, + 23245, + 23246, + 23247, + 23248, + 23249, + 23250, + 23251, + 23252, + 23253, + 23254, + 23255, + 23256, + 23257, + 23258, + 23259, + 23260, + 23261, + 23262, + 23263, + 23264, + 23265, + 23266, + 23267, + 23268, + 23269, + 23270, + 23271, + 23272, + 23273, + 23274, + 23275, + 23276, + 23277, + 23278, + 23279, + 23280, + 23281, + 23282, + 23283, + 23284, + 23285, + 23286, + 23287, + 23288, + 23289, + 23290, + 23291, + 23292, + 23293, + 23294, + 23295, + 23296, + 23297, + 23298, + 23299, + 23300, + 23301, + 23302, + 23303, + 23304, + 23305, + 23306, + 23307, + 23308, + 23309, + 23310, + 23311, + 23312, + 23313, + 23314, + 23315, + 23316, + 23317, + 23318, + 23319, + 23320, + 23321, + 23322, + 23323, + 23324, + 23325, + 23326, + 23327, + 23328, + 23329, + 23330, + 23331, + 23332, + 23333, + 23334, + 23335, + 23336, + 23337, + 23338, + 23339, + 23340, + 23341, + 23342, + 23343, + 23344, + 23345, + 23346, + 23347, + 23348, + 23349, + 23350, + 23351, + 23352, + 23353, + 23354, + 23355, + 23356, + 23357, + 23358, + 23359, + 23360, + 23361, + 23362, + 23363, + 23364, + 23365, + 23366, + 23367, + 23368, + 23369, + 23370, + 23371, + 23372, + 23373, + 23374, + 23375, + 23376, + 23377, + 23378, + 23379, + 23380, + 23381, + 23382, + 23383, + 23384, + 23385, + 23386, + 23387, + 23388, + 23389, + 23390, + 23391, + 23392, + 23393, + 23394, + 23395, + 23396, + 23397, + 23398, + 23399, + 23400, + 23401, + 23402, + 23403, + 23404, + 23405, + 23406, + 23407, + 23408, + 23409, + 23410, + 23411, + 23412, + 23413, + 23414, + 23415, + 23416, + 23417, + 23418, + 23419, + 23420, + 23421, + 23422, + 23423, + 23424, + 23425, + 23426, + 23427, + 23428, + 23429, + 23430, + 23431, + 23432, + 23433, + 23434, + 23435, + 23436, + 23437, + 23438, + 23439, + 23440, + 23441, + 23442, + 23443, + 23444, + 23445, + 23446, + 23447, + 23448, + 23449, + 23450, + 23451, + 23452, + 23453, + 23454, + 23455, + 23456, + 23457, + 23458, + 23459, + 23460, + 23461, + 23462, + 23463, + 23464, + 23465, + 23466, + 23467, + 23468, + 23469, + 23470, + 23471, + 23472, + 23473, + 23474, + 23475, + 23476, + 23477, + 23478, + 23479, + 23480, + 23481, + 23482, + 23483, + 23484, + 23485, + 23486, + 23487, + 23488, + 23489, + 23490, + 23491, + 23492, + 23493, + 23494, + 23495, + 23496, + 23497, + 23498, + 23499, + 23500, + 23501, + 23502, + 23503, + 23504, + 23505, + 23506, + 23507, + 23508, + 23509, + 23510, + 23511, + 23512, + 23513, + 23514, + 23515, + 23516, + 23517, + 23518, + 23519, + 23520, + 23521, + 23522, + 23523, + 23524, + 23525, + 23526, + 23527, + 23528, + 23529, + 23530, + 23531, + 23532, + 23533, + 23534, + 23535, + 23536, + 23537, + 23538, + 23539, + 23540, + 23541, + 23542, + 23543, + 23544, + 23545, + 23546, + 23547, + 23548, + 23549, + 23550, + 23551, + 23552, + 23553, + 23554, + 23555, + 23556, + 23557, + 23558, + 23559, + 23560, + 23561, + 23562, + 23563, + 23564, + 23565, + 23566, + 23567, + 23568, + 23569, + 23570, + 23571, + 23572, + 23573, + 23574, + 23575, + 23576, + 23577, + 23578, + 23579, + 23580, + 23581, + 23582, + 23583, + 23584, + 23585, + 23586, + 23587, + 23588, + 23589, + 23590, + 23591, + 23592, + 23593, + 23594, + 23595, + 23596, + 23597, + 23598, + 23599, + 23600, + 23601, + 23602, + 23603, + 23604, + 23605, + 23606, + 23607, + 23608, + 23609, + 23610, + 23611, + 23612, + 23613, + 23614, + 23615, + 23616, + 23617, + 23618, + 23619, + 23620, + 23621, + 23622, + 23623, + 23624, + 23625, + 23626, + 23627, + 23628, + 23629, + 23630, + 23631, + 23632, + 23633, + 23634, + 23635, + 23636, + 23637, + 23638, + 23639, + 23640, + 23641, + 23642, + 23643, + 23644, + 23645, + 23646, + 23647, + 23648, + 23649, + 23650, + 23651, + 23652, + 23653, + 23654, + 23655, + 23656, + 23657, + 23658, + 23659, + 23660, + 23661, + 23662, + 23663, + 23664, + 23665, + 23666, + 23667, + 23668, + 23669, + 23670, + 23671, + 23672, + 23673, + 23674, + 23675, + 23676, + 23677, + 23678, + 23679, + 23680, + 23681, + 23682, + 23683, + 23684, + 23685, + 23686, + 23687, + 23688, + 23689, + 23690, + 23691, + 23692, + 23693, + 23694, + 23695, + 23696, + 23697, + 23698, + 23699, + 23700, + 23701, + 23702, + 23703, + 23704, + 23705, + 23706, + 23707, + 23708, + 23709, + 23710, + 23711, + 23712, + 23713, + 23714, + 23715, + 23716, + 23717, + 23718, + 23719, + 23720, + 23721, + 23722, + 23723, + 23724, + 23725, + 23726, + 23727, + 23728, + 23729, + 23730, + 23731, + 23732, + 23733, + 23734, + 23735, + 23736, + 23737, + 23738, + 23739, + 23740, + 23741, + 23742, + 23743, + 23744, + 23745, + 23746, + 23747, + 23748, + 23749, + 23750, + 23751, + 23752, + 23753, + 23754, + 23755, + 23756, + 23757, + 23758, + 23759, + 23760, + 23761, + 23762, + 23763, + 23764, + 23765, + 23766, + 23767, + 23768, + 23769, + 23770, + 23771, + 23772, + 23773, + 23774, + 23775, + 23776, + 23777, + 23778, + 23779, + 23780, + 23781, + 23782, + 23783, + 23784, + 23785, + 23786, + 23787, + 23788, + 23789, + 23790, + 23791, + 23792, + 23793, + 23794, + 23795, + 23796, + 23797, + 23798, + 23799, + 23800, + 23801, + 23802, + 23803, + 23804, + 23805, + 23806, + 23807, + 23808, + 23809, + 23810, + 23811, + 23812, + 23813, + 23814, + 23815, + 23816, + 23817, + 23818, + 23819, + 23820, + 23821, + 23822, + 23823, + 23824, + 23825, + 23826, + 23827, + 23828, + 23829, + 23830, + 23831, + 23832, + 23833, + 23834, + 23835, + 23836, + 23837, + 23838, + 23839, + 23840, + 23841, + 23842, + 23843, + 23844, + 23845, + 23846, + 23847, + 23848, + 23849, + 23850, + 23851, + 23852, + 23853, + 23854, + 23855, + 23856, + 23857, + 23858, + 23859, + 23860, + 23861, + 23862, + 23863, + 23864, + 23865, + 23866, + 23867, + 23868, + 23869, + 23870, + 23871, + 23872, + 23873, + 23874, + 23875, + 23876, + 23877, + 23878, + 23879, + 23880, + 23881, + 23882, + 23883, + 23884, + 23885, + 23886, + 23887, + 23888, + 23889, + 23890, + 23891, + 23892, + 23893, + 23894, + 23895, + 23896, + 23897, + 23898, + 23899, + 23900, + 23901, + 23902, + 23903, + 23904, + 23905, + 23906, + 23907, + 23908, + 23909, + 23910, + 23911, + 23912, + 23913, + 23914, + 23915, + 23916, + 23917, + 23918, + 23919, + 23920, + 23921, + 23922, + 23923, + 23924, + 23925, + 23926, + 23927, + 23928, + 23929, + 23930, + 23931, + 23932, + 23933, + 23934, + 23935, + 23936, + 23937, + 23938, + 23939, + 23940, + 23941, + 23942, + 23943, + 23944, + 23945, + 23946, + 23947, + 23948, + 23949, + 23950, + 23951, + 23952, + 23953, + 23954, + 23955, + 23956, + 23957, + 23958, + 23959, + 23960, + 23961, + 23962, + 23963, + 23964, + 23965, + 23966, + 23967, + 23968, + 23969, + 23970, + 23971, + 23972, + 23973, + 23974, + 23975, + 23976, + 23977, + 23978, + 23979, + 23980, + 23981, + 23982, + 23983, + 23984, + 23985, + 23986, + 23987, + 23988, + 23989, + 23990, + 23991, + 23992, + 23993, + 23994, + 23995, + 23996, + 23997, + 23998, + 23999, + 24000, + 24001, + 24002, + 24003, + 24004, + 24005, + 24006, + 24007, + 24008, + 24009, + 24010, + 24011, + 24012, + 24013, + 24014, + 24015, + 24016, + 24017, + 24018, + 24019, + 24020, + 24021, + 24022, + 24023, + 24024, + 24025, + 24026, + 24027, + 24028, + 24029, + 24030, + 24031, + 24032, + 24033, + 24034, + 24035, + 24036, + 24037, + 24038, + 24039, + 24040, + 24041, + 24042, + 24043, + 24044, + 24045, + 24046, + 24047, + 24048, + 24049, + 24050, + 24051, + 24052, + 24053, + 24054, + 24055, + 24056, + 24057, + 24058, + 24059, + 24060, + 24061, + 24062, + 24063, + 24064, + 24065, + 24066, + 24067, + 24068, + 24069, + 24070, + 24071, + 24072, + 24073, + 24074, + 24075, + 24076, + 24077, + 24078, + 24079, + 24080, + 24081, + 24082, + 24083, + 24084, + 24085, + 24086, + 24087, + 24088, + 24089, + 24090, + 24091, + 24092, + 24093, + 24094, + 24095, + 24096, + 24097, + 24098, + 24099, + 24100, + 24101, + 24102, + 24103, + 24104, + 24105, + 24106, + 24107, + 24108, + 24109, + 24110, + 24111, + 24112, + 24113, + 24114, + 24115, + 24116, + 24117, + 24118, + 24119, + 24120, + 24121, + 24122, + 24123, + 24124, + 24125, + 24126, + 24127, + 24128, + 24129, + 24130, + 24131, + 24132, + 24133, + 24134, + 24135, + 24136, + 24137, + 24138, + 24139, + 24140, + 24141, + 24142, + 24143, + 24144, + 24145, + 24146, + 24147, + 24148, + 24149, + 24150, + 24151, + 24152, + 24153, + 24154, + 24155, + 24156, + 24157, + 24158, + 24159, + 24160, + 24161, + 24162, + 24163, + 24164, + 24165, + 24166, + 24167, + 24168, + 24169, + 24170, + 24171, + 24172, + 24173, + 24174, + 24175, + 24176, + 24177, + 24178, + 24179, + 24180, + 24181, + 24182, + 24183, + 24184, + 24185, + 24186, + 24187, + 24188, + 24189, + 24190, + 24191, + 24192, + 24193, + 24194, + 24195, + 24196, + 24197, + 24198, + 24199, + 24200, + 24201, + 24202, + 24203, + 24204, + 24205, + 24206, + 24207, + 24208, + 24209, + 24210, + 24211, + 24212, + 24213, + 24214, + 24215, + 24216, + 24217, + 24218, + 24219, + 24220, + 24221, + 24222, + 24223, + 24224, + 24225, + 24226, + 24227, + 24228, + 24229, + 24230, + 24231, + 24232, + 24233, + 24234, + 24235, + 24236, + 24237, + 24238, + 24239, + 24240, + 24241, + 24242, + 24243, + 24244, + 24245, + 24246, + 24247, + 24248, + 24249, + 24250, + 24251, + 24252, + 24253, + 24254, + 24255, + 24256, + 24257, + 24258, + 24259, + 24260, + 24261, + 24262, + 24263, + 24264, + 24265, + 24266, + 24267, + 24268, + 24269, + 24270, + 24271, + 24272, + 24273, + 24274, + 24275, + 24276, + 24277, + 24278, + 24279, + 24280, + 24281, + 24282, + 24283, + 24284, + 24285, + 24286, + 24287, + 24288, + 24289, + 24290, + 24291, + 24292, + 24293, + 24294, + 24295, + 24296, + 24297, + 24298, + 24299, + 24300, + 24301, + 24302, + 24303, + 24304, + 24305, + 24306, + 24307, + 24308, + 24309, + 24310, + 24311, + 24312, + 24313, + 24314, + 24315, + 24316, + 24317, + 24318, + 24319, + 24320, + 24321, + 24322, + 24323, + 24324, + 24325, + 24326, + 24327, + 24328, + 24329, + 24330, + 24331, + 24332, + 24333, + 24334, + 24335, + 24336, + 24337, + 24338, + 24339, + 24340, + 24341, + 24342, + 24343, + 24344, + 24345, + 24346, + 24347, + 24348, + 24349, + 24350, + 24351, + 24352, + 24353, + 24354, + 24355, + 24356, + 24357, + 24358, + 24359, + 24360, + 24361, + 24362, + 24363, + 24364, + 24365, + 24366, + 24367, + 24368, + 24369, + 24370, + 24371, + 24372, + 24373, + 24374, + 24375, + 24376, + 24377, + 24378, + 24379, + 24380, + 24381, + 24382, + 24383, + 24384, + 24385, + 24386, + 24387, + 24388, + 24389, + 24390, + 24391, + 24392, + 24393, + 24394, + 24395, + 24396, + 24397, + 24398, + 24399, + 24400, + 24401, + 24402, + 24403, + 24404, + 24405, + 24406, + 24407, + 24408, + 24409, + 24410, + 24411, + 24412, + 24413, + 24414, + 24415, + 24416, + 24417, + 24418, + 24419, + 24420, + 24421, + 24422, + 24423, + 24424, + 24425, + 24426, + 24427, + 24428, + 24429, + 24430, + 24431, + 24432, + 24433, + 24434, + 24435, + 24436, + 24437, + 24438, + 24439, + 24440, + 24441, + 24442, + 24443, + 24444, + 24445, + 24446, + 24447, + 24448, + 24449, + 24450, + 24451, + 24452, + 24453, + 24454, + 24455, + 24456, + 24457, + 24458, + 24459, + 24460, + 24461, + 24462, + 24463, + 24464, + 24465, + 24466, + 24467, + 24468, + 24469, + 24470, + 24471, + 24472, + 24473, + 24474, + 24475, + 24476, + 24477, + 24478, + 24479, + 24480, + 24481, + 24482, + 24483, + 24484, + 24485, + 24486, + 24487, + 24488, + 24489, + 24490, + 24491, + 24492, + 24493, + 24494, + 24495, + 24496, + 24497, + 24498, + 24499, + 24500, + 24501, + 24502, + 24503, + 24504, + 24505, + 24506, + 24507, + 24508, + 24509, + 24510, + 24511, + 24512, + 24513, + 24514, + 24515, + 24516, + 24517, + 24518, + 24519, + 24520, + 24521, + 24522, + 24523, + 24524, + 24525, + 24526, + 24527, + 24528, + 24529, + 24530, + 24531, + 24532, + 24533, + 24534, + 24535, + 24536, + 24537, + 24538, + 24539, + 24540, + 24541, + 24542, + 24543, + 24544, + 24545, + 24546, + 24547, + 24548, + 24549, + 24550, + 24551, + 24552, + 24553, + 24554, + 24555, + 24556, + 24557, + 24558, + 24559, + 24560, + 24561, + 24562, + 24563, + 24564, + 24565, + 24566, + 24567, + 24568, + 24569, + 24570, + 24571, + 24572, + 24573, + 24574, + 24575, + 24576, + 24577, + 24578, + 24579, + 24580, + 24581, + 24582, + 24583, + 24584, + 24585, + 24586, + 24587, + 24588, + 24589, + 24590, + 24591, + 24592, + 24593, + 24594, + 24595, + 24596, + 24597, + 24598, + 24599, + 24600, + 24601, + 24602, + 24603, + 24604, + 24605, + 24606, + 24607, + 24608, + 24609, + 24610, + 24611, + 24612, + 24613, + 24614, + 24615, + 24616, + 24617, + 24618, + 24619, + 24620, + 24621, + 24622, + 24623, + 24624, + 24625, + 24626, + 24627, + 24628, + 24629, + 24630, + 24631, + 24632, + 24633, + 24634, + 24635, + 24636, + 24637, + 24638, + 24639, + 24640, + 24641, + 24642, + 24643, + 24644, + 24645, + 24646, + 24647, + 24648, + 24649, + 24650, + 24651, + 24652, + 24653, + 24654, + 24655, + 24656, + 24657, + 24658, + 24659, + 24660, + 24661, + 24662, + 24663, + 24664, + 24665, + 24666, + 24667, + 24668, + 24669, + 24670, + 24671, + 24672, + 24673, + 24674, + 24675, + 24676, + 24677, + 24678, + 24679, + 24680, + 24681, + 24682, + 24683, + 24684, + 24685, + 24686, + 24687, + 24688, + 24689, + 24690, + 24691, + 24692, + 24693, + 24694, + 24695, + 24696, + 24697, + 24698, + 24699, + 24700, + 24701, + 24702, + 24703, + 24704, + 24705, + 24706, + 24707, + 24708, + 24709, + 24710, + 24711, + 24712, + 24713, + 24714, + 24715, + 24716, + 24717, + 24718, + 24719, + 24720, + 24721, + 24722, + 24723, + 24724, + 24725, + 24726, + 24727, + 24728, + 24729, + 24730, + 24731, + 24732, + 24733, + 24734, + 24735, + 24736, + 24737, + 24738, + 24739, + 24740, + 24741, + 24742, + 24743, + 24744, + 24745, + 24746, + 24747, + 24748, + 24749, + 24750, + 24751, + 24752, + 24753, + 24754, + 24755, + 24756, + 24757, + 24758, + 24759, + 24760, + 24761, + 24762, + 24763, + 24764, + 24765, + 24766, + 24767, + 24768, + 24769, + 24770, + 24771, + 24772, + 24773, + 24774, + 24775, + 24776, + 24777, + 24778, + 24779, + 24780, + 24781, + 24782, + 24783, + 24784, + 24785, + 24786, + 24787, + 24788, + 24789, + 24790, + 24791, + 24792, + 24793, + 24794, + 24795, + 24796, + 24797, + 24798, + 24799, + 24800, + 24801, + 24802, + 24803, + 24804, + 24805, + 24806, + 24807, + 24808, + 24809, + 24810, + 24811, + 24812, + 24813, + 24814, + 24815, + 24816, + 24817, + 24818, + 24819, + 24820, + 24821, + 24822, + 24823, + 24824, + 24825, + 24826, + 24827, + 24828, + 24829, + 24830, + 24831, + 24832, + 24833, + 24834, + 24835, + 24836, + 24837, + 24838, + 24839, + 24840, + 24841, + 24842, + 24843, + 24844, + 24845, + 24846, + 24847, + 24848, + 24849, + 24850, + 24851, + 24852, + 24853, + 24854, + 24855, + 24856, + 24857, + 24858, + 24859, + 24860, + 24861, + 24862, + 24863, + 24864, + 24865, + 24866, + 24867, + 24868, + 24869, + 24870, + 24871, + 24872, + 24873, + 24874, + 24875, + 24876, + 24877, + 24878, + 24879, + 24880, + 24881, + 24882, + 24883, + 24884, + 24885, + 24886, + 24887, + 24888, + 24889, + 24890, + 24891, + 24892, + 24893, + 24894, + 24895, + 24896, + 24897, + 24898, + 24899, + 24900, + 24901, + 24902, + 24903, + 24904, + 24905, + 24906, + 24907, + 24908, + 24909, + 24910, + 24911, + 24912, + 24913, + 24914, + 24915, + 24916, + 24917, + 24918, + 24919, + 24920, + 24921, + 24922, + 24923, + 24924, + 24925, + 24926, + 24927, + 24928, + 24929, + 24930, + 24931, + 24932, + 24933, + 24934, + 24935, + 24936, + 24937, + 24938, + 24939, + 24940, + 24941, + 24942, + 24943, + 24944, + 24945, + 24946, + 24947, + 24948, + 24949, + 24950, + 24951, + 24952, + 24953, + 24954, + 24955, + 24956, + 24957, + 24958, + 24959, + 24960, + 24961, + 24962, + 24963, + 24964, + 24965, + 24966, + 24967, + 24968, + 24969, + 24970, + 24971, + 24972, + 24973, + 24974, + 24975, + 24976, + 24977, + 24978, + 24979, + 24980, + 24981, + 24982, + 24983, + 24984, + 24985, + 24986, + 24987, + 24988, + 24989, + 24990, + 24991, + 24992, + 24993, + 24994, + 24995, + 24996, + 24997, + 24998, + 24999, + 25000, + 25001, + 25002, + 25003, + 25004, + 25005, + 25006, + 25007, + 25008, + 25009, + 25010, + 25011, + 25012, + 25013, + 25014, + 25015, + 25016, + 25017, + 25018, + 25019, + 25020, + 25021, + 25022, + 25023, + 25024, + 25025, + 25026, + 25027, + 25028, + 25029, + 25030, + 25031, + 25032, + 25033, + 25034, + 25035, + 25036, + 25037, + 25038, + 25039, + 25040, + 25041, + 25042, + 25043, + 25044, + 25045, + 25046, + 25047, + 25048, + 25049, + 25050, + 25051, + 25052, + 25053, + 25054, + 25055, + 25056, + 25057, + 25058, + 25059, + 25060, + 25061, + 25062, + 25063, + 25064, + 25065, + 25066, + 25067, + 25068, + 25069, + 25070, + 25071, + 25072, + 25073, + 25074, + 25075, + 25076, + 25077, + 25078, + 25079, + 25080, + 25081, + 25082, + 25083, + 25084, + 25085, + 25086, + 25087, + 25088, + 25089, + 25090, + 25091, + 25092, + 25093, + 25094, + 25095, + 25096, + 25097, + 25098, + 25099, + 25100, + 25101, + 25102, + 25103, + 25104, + 25105, + 25106, + 25107, + 25108, + 25109, + 25110, + 25111, + 25112, + 25113, + 25114, + 25115, + 25116, + 25117, + 25118, + 25119, + 25120, + 25121, + 25122, + 25123, + 25124, + 25125, + 25126, + 25127, + 25128, + 25129, + 25130, + 25131, + 25132, + 25133, + 25134, + 25135, + 25136, + 25137, + 25138, + 25139, + 25140, + 25141, + 25142, + 25143, + 25144, + 25145, + 25146, + 25147, + 25148, + 25149, + 25150, + 25151, + 25152, + 25153, + 25154, + 25155, + 25156, + 25157, + 25158, + 25159, + 25160, + 25161, + 25162, + 25163, + 25164, + 25165, + 25166, + 25167, + 25168, + 25169, + 25170, + 25171, + 25172, + 25173, + 25174, + 25175, + 25176, + 25177, + 25178, + 25179, + 25180, + 25181, + 25182, + 25183, + 25184, + 25185, + 25186, + 25187, + 25188, + 25189, + 25190, + 25191, + 25192, + 25193, + 25194, + 25195, + 25196, + 25197, + 25198, + 25199, + 25200, + 25201, + 25202, + 25203, + 25204, + 25205, + 25206, + 25207, + 25208, + 25209, + 25210, + 25211, + 25212, + 25213, + 25214, + 25215, + 25216, + 25217, + 25218, + 25219, + 25220, + 25221, + 25222, + 25223, + 25224, + 25225, + 25226, + 25227, + 25228, + 25229, + 25230, + 25231, + 25232, + 25233, + 25234, + 25235, + 25236, + 25237, + 25238, + 25239, + 25240, + 25241, + 25242, + 25243, + 25244, + 25245, + 25246, + 25247, + 25248, + 25249, + 25250, + 25251, + 25252, + 25253, + 25254, + 25255, + 25256, + 25257, + 25258, + 25259, + 25260, + 25261, + 25262, + 25263, + 25264, + 25265, + 25266, + 25267, + 25268, + 25269, + 25270, + 25271, + 25272, + 25273, + 25274, + 25275, + 25276, + 25277, + 25278, + 25279, + 25280, + 25281, + 25282, + 25283, + 25284, + 25285, + 25286, + 25287, + 25288, + 25289, + 25290, + 25291, + 25292, + 25293, + 25294, + 25295, + 25296, + 25297, + 25298, + 25299, + 25300, + 25301, + 25302, + 25303, + 25304, + 25305, + 25306, + 25307, + 25308, + 25309, + 25310, + 25311, + 25312, + 25313, + 25314, + 25315, + 25316, + 25317, + 25318, + 25319, + 25320, + 25321, + 25322, + 25323, + 25324, + 25325, + 25326, + 25327, + 25328, + 25329, + 25330, + 25331, + 25332, + 25333, + 25334, + 25335, + 25336, + 25337, + 25338, + 25339, + 25340, + 25341, + 25342, + 25343, + 25344, + 25345, + 25346, + 25347, + 25348, + 25349, + 25350, + 25351, + 25352, + 25353, + 25354, + 25355, + 25356, + 25357, + 25358, + 25359, + 25360, + 25361, + 25362, + 25363, + 25364, + 25365, + 25366, + 25367, + 25368, + 25369, + 25370, + 25371, + 25372, + 25373, + 25374, + 25375, + 25376, + 25377, + 25378, + 25379, + 25380, + 25381, + 25382, + 25383, + 25384, + 25385, + 25386, + 25387, + 25388, + 25389, + 25390, + 25391, + 25392, + 25393, + 25394, + 25395, + 25396, + 25397, + 25398, + 25399, + 25400, + 25401, + 25402, + 25403, + 25404, + 25405, + 25406, + 25407, + 25408, + 25409, + 25410, + 25411, + 25412, + 25413, + 25414, + 25415, + 25416, + 25417, + 25418, + 25419, + 25420, + 25421, + 25422, + 25423, + 25424, + 25425, + 25426, + 25427, + 25428, + 25429, + 25430, + 25431, + 25432, + 25433, + 25434, + 25435, + 25436, + 25437, + 25438, + 25439, + 25440, + 25441, + 25442, + 25443, + 25444, + 25445, + 25446, + 25447, + 25448, + 25449, + 25450, + 25451, + 25452, + 25453, + 25454, + 25455, + 25456, + 25457, + 25458, + 25459, + 25460, + 25461, + 25462, + 25463, + 25464, + 25465, + 25466, + 25467, + 25468, + 25469, + 25470, + 25471, + 25472, + 25473, + 25474, + 25475, + 25476, + 25477, + 25478, + 25479, + 25480, + 25481, + 25482, + 25483, + 25484, + 25485, + 25486, + 25487, + 25488, + 25489, + 25490, + 25491, + 25492, + 25493, + 25494, + 25495, + 25496, + 25497, + 25498, + 25499, + 25500, + 25501, + 25502, + 25503, + 25504, + 25505, + 25506, + 25507, + 25508, + 25509, + 25510, + 25511, + 25512, + 25513, + 25514, + 25515, + 25516, + 25517, + 25518, + 25519, + 25520, + 25521, + 25522, + 25523, + 25524, + 25525, + 25526, + 25527, + 25528, + 25529, + 25530, + 25531, + 25532, + 25533, + 25534, + 25535, + 25536, + 25537, + 25538, + 25539, + 25540, + 25541, + 25542, + 25543, + 25544, + 25545, + 25546, + 25547, + 25548, + 25549, + 25550, + 25551, + 25552, + 25553, + 25554, + 25555, + 25556, + 25557, + 25558, + 25559, + 25560, + 25561, + 25562, + 25563, + 25564, + 25565, + 25566, + 25567, + 25568, + 25569, + 25570, + 25571, + 25572, + 25573, + 25574, + 25575, + 25576, + 25577, + 25578, + 25579, + 25580, + 25581, + 25582, + 25583, + 25584, + 25585, + 25586, + 25587, + 25588, + 25589, + 25590, + 25591, + 25592, + 25593, + 25594, + 25595, + 25596, + 25597, + 25598, + 25599, + 25600, + 25601, + 25602, + 25603, + 25604, + 25605, + 25606, + 25607, + 25608, + 25609, + 25610, + 25611, + 25612, + 25613, + 25614, + 25615, + 25616, + 25617, + 25618, + 25619, + 25620, + 25621, + 25622, + 25623, + 25624, + 25625, + 25626, + 25627, + 25628, + 25629, + 25630, + 25631, + 25632, + 25633, + 25634, + 25635, + 25636, + 25637, + 25638, + 25639, + 25640, + 25641, + 25642, + 25643, + 25644, + 25645, + 25646, + 25647, + 25648, + 25649, + 25650, + 25651, + 25652, + 25653, + 25654, + 25655, + 25656, + 25657, + 25658, + 25659, + 25660, + 25661, + 25662, + 25663, + 25664, + 25665, + 25666, + 25667, + 25668, + 25669, + 25670, + 25671, + 25672, + 25673, + 25674, + 25675, + 25676, + 25677, + 25678, + 25679, + 25680, + 25681, + 25682, + 25683, + 25684, + 25685, + 25686, + 25687, + 25688, + 25689, + 25690, + 25691, + 25692, + 25693, + 25694, + 25695, + 25696, + 25697, + 25698, + 25699, + 25700, + 25701, + 25702, + 25703, + 25704, + 25705, + 25706, + 25707, + 25708, + 25709, + 25710, + 25711, + 25712, + 25713, + 25714, + 25715, + 25716, + 25717, + 25718, + 25719, + 25720, + 25721, + 25722, + 25723, + 25724, + 25725, + 25726, + 25727, + 25728, + 25729, + 25730, + 25731, + 25732, + 25733, + 25734, + 25735, + 25736, + 25737, + 25738, + 25739, + 25740, + 25741, + 25742, + 25743, + 25744, + 25745, + 25746, + 25747, + 25748, + 25749, + 25750, + 25751, + 25752, + 25753, + 25754, + 25755, + 25756, + 25757, + 25758, + 25759, + 25760, + 25761, + 25762, + 25763, + 25764, + 25765, + 25766, + 25767, + 25768, + 25769, + 25770, + 25771, + 25772, + 25773, + 25774, + 25775, + 25776, + 25777, + 25778, + 25779, + 25780, + 25781, + 25782, + 25783, + 25784, + 25785, + 25786, + 25787, + 25788, + 25789, + 25790, + 25791, + 25792, + 25793, + 25794, + 25795, + 25796, + 25797, + 25798, + 25799, + 25800, + 25801, + 25802, + 25803, + 25804, + 25805, + 25806, + 25807, + 25808, + 25809, + 25810, + 25811, + 25812, + 25813, + 25814, + 25815, + 25816, + 25817, + 25818, + 25819, + 25820, + 25821, + 25822, + 25823, + 25824, + 25825, + 25826, + 25827, + 25828, + 25829, + 25830, + 25831, + 25832, + 25833, + 25834, + 25835, + 25836, + 25837, + 25838, + 25839, + 25840, + 25841, + 25842, + 25843, + 25844, + 25845, + 25846, + 25847, + 25848, + 25849, + 25850, + 25851, + 25852, + 25853, + 25854, + 25855, + 25856, + 25857, + 25858, + 25859, + 25860, + 25861, + 25862, + 25863, + 25864, + 25865, + 25866, + 25867, + 25868, + 25869, + 25870, + 25871, + 25872, + 25873, + 25874, + 25875, + 25876, + 25877, + 25878, + 25879, + 25880, + 25881, + 25882, + 25883, + 25884, + 25885, + 25886, + 25887, + 25888, + 25889, + 25890, + 25891, + 25892, + 25893, + 25894, + 25895, + 25896, + 25897, + 25898, + 25899, + 25900, + 25901, + 25902, + 25903, + 25904, + 25905, + 25906, + 25907, + 25908, + 25909, + 25910, + 25911, + 25912, + 25913, + 25914, + 25915, + 25916, + 25917, + 25918, + 25919, + 25920, + 25921, + 25922, + 25923, + 25924, + 25925, + 25926, + 25927, + 25928, + 25929, + 25930, + 25931, + 25932, + 25933, + 25934, + 25935, + 25936, + 25937, + 25938, + 25939, + 25940, + 25941, + 25942, + 25943, + 25944, + 25945, + 25946, + 25947, + 25948, + 25949, + 25950, + 25951, + 25952, + 25953, + 25954, + 25955, + 25956, + 25957, + 25958, + 25959, + 25960, + 25961, + 25962, + 25963, + 25964, + 25965, + 25966, + 25967, + 25968, + 25969, + 25970, + 25971, + 25972, + 25973, + 25974, + 25975, + 25976, + 25977, + 25978, + 25979, + 25980, + 25981, + 25982, + 25983, + 25984, + 25985, + 25986, + 25987, + 25988, + 25989, + 25990, + 25991, + 25992, + 25993, + 25994, + 25995, + 25996, + 25997, + 25998, + 25999, + 26000, + 26001, + 26002, + 26003, + 26004, + 26005, + 26006, + 26007, + 26008, + 26009, + 26010, + 26011, + 26012, + 26013, + 26014, + 26015, + 26016, + 26017, + 26018, + 26019, + 26020, + 26021, + 26022, + 26023, + 26024, + 26025, + 26026, + 26027, + 26028, + 26029, + 26030, + 26031, + 26032, + 26033, + 26034, + 26035, + 26036, + 26037, + 26038, + 26039, + 26040, + 26041, + 26042, + 26043, + 26044, + 26045, + 26046, + 26047, + 26048, + 26049, + 26050, + 26051, + 26052, + 26053, + 26054, + 26055, + 26056, + 26057, + 26058, + 26059, + 26060, + 26061, + 26062, + 26063, + 26064, + 26065, + 26066, + 26067, + 26068, + 26069, + 26070, + 26071, + 26072, + 26073, + 26074, + 26075, + 26076, + 26077, + 26078, + 26079, + 26080, + 26081, + 26082, + 26083, + 26084, + 26085, + 26086, + 26087, + 26088, + 26089, + 26090, + 26091, + 26092, + 26093, + 26094, + 26095, + 26096, + 26097, + 26098, + 26099, + 26100, + 26101, + 26102, + 26103, + 26104, + 26105, + 26106, + 26107, + 26108, + 26109, + 26110, + 26111, + 26112, + 26113, + 26114, + 26115, + 26116, + 26117, + 26118, + 26119, + 26120, + 26121, + 26122, + 26123, + 26124, + 26125, + 26126, + 26127, + 26128, + 26129, + 26130, + 26131, + 26132, + 26133, + 26134, + 26135, + 26136, + 26137, + 26138, + 26139, + 26140, + 26141, + 26142, + 26143, + 26144, + 26145, + 26146, + 26147, + 26148, + 26149, + 26150, + 26151, + 26152, + 26153, + 26154, + 26155, + 26156, + 26157, + 26158, + 26159, + 26160, + 26161, + 26162, + 26163, + 26164, + 26165, + 26166, + 26167, + 26168, + 26169, + 26170, + 26171, + 26172, + 26173, + 26174, + 26175, + 26176, + 26177, + 26178, + 26179, + 26180, + 26181, + 26182, + 26183, + 26184, + 26185, + 26186, + 26187, + 26188, + 26189, + 26190, + 26191, + 26192, + 26193, + 26194, + 26195, + 26196, + 26197, + 26198, + 26199, + 26200, + 26201, + 26202, + 26203, + 26204, + 26205, + 26206, + 26207, + 26208, + 26209, + 26210, + 26211, + 26212, + 26213, + 26214, + 26215, + 26216, + 26217, + 26218, + 26219, + 26220, + 26221, + 26222, + 26223, + 26224, + 26225, + 26226, + 26227, + 26228, + 26229, + 26230, + 26231, + 26232, + 26233, + 26234, + 26235, + 26236, + 26237, + 26238, + 26239, + 26240, + 26241, + 26242, + 26243, + 26244, + 26245, + 26246, + 26247, + 26248, + 26249, + 26250, + 26251, + 26252, + 26253, + 26254, + 26255, + 26256, + 26257, + 26258, + 26259, + 26260, + 26261, + 26262, + 26263, + 26264, + 26265, + 26266, + 26267, + 26268, + 26269, + 26270, + 26271, + 26272, + 26273, + 26274, + 26275, + 26276, + 26277, + 26278, + 26279, + 26280, + 26281, + 26282, + 26283, + 26284, + 26285, + 26286, + 26287, + 26288, + 26289, + 26290, + 26291, + 26292, + 26293, + 26294, + 26295, + 26296, + 26297, + 26298, + 26299, + 26300, + 26301, + 26302, + 26303, + 26304, + 26305, + 26306, + 26307, + 26308, + 26309, + 26310, + 26311, + 26312, + 26313, + 26314, + 26315, + 26316, + 26317, + 26318, + 26319, + 26320, + 26321, + 26322, + 26323, + 26324, + 26325, + 26326, + 26327, + 26328, + 26329, + 26330, + 26331, + 26332, + 26333, + 26334, + 26335, + 26336, + 26337, + 26338, + 26339, + 26340, + 26341, + 26342, + 26343, + 26344, + 26345, + 26346, + 26347, + 26348, + 26349, + 26350, + 26351, + 26352, + 26353, + 26354, + 26355, + 26356, + 26357, + 26358, + 26359, + 26360, + 26361, + 26362, + 26363, + 26364, + 26365, + 26366, + 26367, + 26368, + 26369, + 26370, + 26371, + 26372, + 26373, + 26374, + 26375, + 26376, + 26377, + 26378, + 26379, + 26380, + 26381, + 26382, + 26383, + 26384, + 26385, + 26386, + 26387, + 26388, + 26389, + 26390, + 26391, + 26392, + 26393, + 26394, + 26395, + 26396, + 26397, + 26398, + 26399, + 26400, + 26401, + 26402, + 26403, + 26404, + 26405, + 26406, + 26407, + 26408, + 26409, + 26410, + 26411, + 26412, + 26413, + 26414, + 26415, + 26416, + 26417, + 26418, + 26419, + 26420, + 26421, + 26422, + 26423, + 26424, + 26425, + 26426, + 26427, + 26428, + 26429, + 26430, + 26431, + 26432, + 26433, + 26434, + 26435, + 26436, + 26437, + 26438, + 26439, + 26440, + 26441, + 26442, + 26443, + 26444, + 26445, + 26446, + 26447, + 26448, + 26449, + 26450, + 26451, + 26452, + 26453, + 26454, + 26455, + 26456, + 26457, + 26458, + 26459, + 26460, + 26461, + 26462, + 26463, + 26464, + 26465, + 26466, + 26467, + 26468, + 26469, + 26470, + 26471, + 26472, + 26473, + 26474, + 26475, + 26476, + 26477, + 26478, + 26479, + 26480, + 26481, + 26482, + 26483, + 26484, + 26485, + 26486, + 26487, + 26488, + 26489, + 26490, + 26491, + 26492, + 26493, + 26494, + 26495, + 26496, + 26497, + 26498, + 26499, + 26500, + 26501, + 26502, + 26503, + 26504, + 26505, + 26506, + 26507, + 26508, + 26509, + 26510, + 26511, + 26512, + 26513, + 26514, + 26515, + 26516, + 26517, + 26518, + 26519, + 26520, + 26521, + 26522, + 26523, + 26524, + 26525, + 26526, + 26527, + 26528, + 26529, + 26530, + 26531, + 26532, + 26533, + 26534, + 26535, + 26536, + 26537, + 26538, + 26539, + 26540, + 26541, + 26542, + 26543, + 26544, + 26545, + 26546, + 26547, + 26548, + 26549, + 26550, + 26551, + 26552, + 26553, + 26554, + 26555, + 26556, + 26557, + 26558, + 26559, + 26560, + 26561, + 26562, + 26563, + 26564, + 26565, + 26566, + 26567, + 26568, + 26569, + 26570, + 26571, + 26572, + 26573, + 26574, + 26575, + 26576, + 26577, + 26578, + 26579, + 26580, + 26581, + 26582, + 26583, + 26584, + 26585, + 26586, + 26587, + 26588, + 26589, + 26590, + 26591, + 26592, + 26593, + 26594, + 26595, + 26596, + 26597, + 26598, + 26599, + 26600, + 26601, + 26602, + 26603, + 26604, + 26605, + 26606, + 26607, + 26608, + 26609, + 26610, + 26611, + 26612, + 26613, + 26614, + 26615, + 26616, + 26617, + 26618, + 26619, + 26620, + 26621, + 26622, + 26623, + 26624, + 26625, + 26626, + 26627, + 26628, + 26629, + 26630, + 26631, + 26632, + 26633, + 26634, + 26635, + 26636, + 26637, + 26638, + 26639, + 26640, + 26641, + 26642, + 26643, + 26644, + 26645, + 26646, + 26647, + 26648, + 26649, + 26650, + 26651, + 26652, + 26653, + 26654, + 26655, + 26656, + 26657, + 26658, + 26659, + 26660, + 26661, + 26662, + 26663, + 26664, + 26665, + 26666, + 26667, + 26668, + 26669, + 26670, + 26671, + 26672, + 26673, + 26674, + 26675, + 26676, + 26677, + 26678, + 26679, + 26680, + 26681, + 26682, + 26683, + 26684, + 26685, + 26686, + 26687, + 26688, + 26689, + 26690, + 26691, + 26692, + 26693, + 26694, + 26695, + 26696, + 26697, + 26698, + 26699, + 26700, + 26701, + 26702, + 26703, + 26704, + 26705, + 26706, + 26707, + 26708, + 26709, + 26710, + 26711, + 26712, + 26713, + 26714, + 26715, + 26716, + 26717, + 26718, + 26719, + 26720, + 26721, + 26722, + 26723, + 26724, + 26725, + 26726, + 26727, + 26728, + 26729, + 26730, + 26731, + 26732, + 26733, + 26734, + 26735, + 26736, + 26737, + 26738, + 26739, + 26740, + 26741, + 26742, + 26743, + 26744, + 26745, + 26746, + 26747, + 26748, + 26749, + 26750, + 26751, + 26752, + 26753, + 26754, + 26755, + 26756, + 26757, + 26758, + 26759, + 26760, + 26761, + 26762, + 26763, + 26764, + 26765, + 26766, + 26767, + 26768, + 26769, + 26770, + 26771, + 26772, + 26773, + 26774, + 26775, + 26776, + 26777, + 26778, + 26779, + 26780, + 26781, + 26782, + 26783, + 26784, + 26785, + 26786, + 26787, + 26788, + 26789, + 26790, + 26791, + 26792, + 26793, + 26794, + 26795, + 26796, + 26797, + 26798, + 26799, + 26800, + 26801, + 26802, + 26803, + 26804, + 26805, + 26806, + 26807, + 26808, + 26809, + 26810, + 26811, + 26812, + 26813, + 26814, + 26815, + 26816, + 26817, + 26818, + 26819, + 26820, + 26821, + 26822, + 26823, + 26824, + 26825, + 26826, + 26827, + 26828, + 26829, + 26830, + 26831, + 26832, + 26833, + 26834, + 26835, + 26836, + 26837, + 26838, + 26839, + 26840, + 26841, + 26842, + 26843, + 26844, + 26845, + 26846, + 26847, + 26848, + 26849, + 26850, + 26851, + 26852, + 26853, + 26854, + 26855, + 26856, + 26857, + 26858, + 26859, + 26860, + 26861, + 26862, + 26863, + 26864, + 26865, + 26866, + 26867, + 26868, + 26869, + 26870, + 26871, + 26872, + 26873, + 26874, + 26875, + 26876, + 26877, + 26878, + 26879, + 26880, + 26881, + 26882, + 26883, + 26884, + 26885, + 26886, + 26887, + 26888, + 26889, + 26890, + 26891, + 26892, + 26893, + 26894, + 26895, + 26896, + 26897, + 26898, + 26899, + 26900, + 26901, + 26902, + 26903, + 26904, + 26905, + 26906, + 26907, + 26908, + 26909, + 26910, + 26911, + 26912, + 26913, + 26914, + 26915, + 26916, + 26917, + 26918, + 26919, + 26920, + 26921, + 26922, + 26923, + 26924, + 26925, + 26926, + 26927, + 26928, + 26929, + 26930, + 26931, + 26932, + 26933, + 26934, + 26935, + 26936, + 26937, + 26938, + 26939, + 26940, + 26941, + 26942, + 26943, + 26944, + 26945, + 26946, + 26947, + 26948, + 26949, + 26950, + 26951, + 26952, + 26953, + 26954, + 26955, + 26956, + 26957, + 26958, + 26959, + 26960, + 26961, + 26962, + 26963, + 26964, + 26965, + 26966, + 26967, + 26968, + 26969, + 26970, + 26971, + 26972, + 26973, + 26974, + 26975, + 26976, + 26977, + 26978, + 26979, + 26980, + 26981, + 26982, + 26983, + 26984, + 26985, + 26986, + 26987, + 26988, + 26989, + 26990, + 26991, + 26992, + 26993, + 26994, + 26995, + 26996, + 26997, + 26998, + 26999, + 27000, + 27001, + 27002, + 27003, + 27004, + 27005, + 27006, + 27007, + 27008, + 27009, + 27010, + 27011, + 27012, + 27013, + 27014, + 27015, + 27016, + 27017, + 27018, + 27019, + 27020, + 27021, + 27022, + 27023, + 27024, + 27025, + 27026, + 27027, + 27028, + 27029, + 27030, + 27031, + 27032, + 27033, + 27034, + 27035, + 27036, + 27037, + 27038, + 27039, + 27040, + 27041, + 27042, + 27043, + 27044, + 27045, + 27046, + 27047, + 27048, + 27049, + 27050, + 27051, + 27052, + 27053, + 27054, + 27055, + 27056, + 27057, + 27058, + 27059, + 27060, + 27061, + 27062, + 27063, + 27064, + 27065, + 27066, + 27067, + 27068, + 27069, + 27070, + 27071, + 27072, + 27073, + 27074, + 27075, + 27076, + 27077, + 27078, + 27079, + 27080, + 27081, + 27082, + 27083, + 27084, + 27085, + 27086, + 27087, + 27088, + 27089, + 27090, + 27091, + 27092, + 27093, + 27094, + 27095, + 27096, + 27097, + 27098, + 27099, + 27100, + 27101, + 27102, + 27103, + 27104, + 27105, + 27106, + 27107, + 27108, + 27109, + 27110, + 27111, + 27112, + 27113, + 27114, + 27115, + 27116, + 27117, + 27118, + 27119, + 27120, + 27121, + 27122, + 27123, + 27124, + 27125, + 27126, + 27127, + 27128, + 27129, + 27130, + 27131, + 27132, + 27133, + 27134, + 27135, + 27136, + 27137, + 27138, + 27139, + 27140, + 27141, + 27142, + 27143, + 27144, + 27145, + 27146, + 27147, + 27148, + 27149, + 27150, + 27151, + 27152, + 27153, + 27154, + 27155, + 27156, + 27157, + 27158, + 27159, + 27160, + 27161, + 27162, + 27163, + 27164, + 27165, + 27166, + 27167, + 27168, + 27169, + 27170, + 27171, + 27172, + 27173, + 27174, + 27175, + 27176, + 27177, + 27178, + 27179, + 27180, + 27181, + 27182, + 27183, + 27184, + 27185, + 27186, + 27187, + 27188, + 27189, + 27190, + 27191, + 27192, + 27193, + 27194, + 27195, + 27196, + 27197, + 27198, + 27199, + 27200, + 27201, + 27202, + 27203, + 27204, + 27205, + 27206, + 27207, + 27208, + 27209, + 27210, + 27211, + 27212, + 27213, + 27214, + 27215, + 27216, + 27217, + 27218, + 27219, + 27220, + 27221, + 27222, + 27223, + 27224, + 27225, + 27226, + 27227, + 27228, + 27229, + 27230, + 27231, + 27232, + 27233, + 27234, + 27235, + 27236, + 27237, + 27238, + 27239, + 27240, + 27241, + 27242, + 27243, + 27244, + 27245, + 27246, + 27247, + 27248, + 27249, + 27250, + 27251, + 27252, + 27253, + 27254, + 27255, + 27256, + 27257, + 27258, + 27259, + 27260, + 27261, + 27262, + 27263, + 27264, + 27265, + 27266, + 27267, + 27268, + 27269, + 27270, + 27271, + 27272, + 27273, + 27274, + 27275, + 27276, + 27277, + 27278, + 27279, + 27280, + 27281, + 27282, + 27283, + 27284, + 27285, + 27286, + 27287, + 27288, + 27289, + 27290, + 27291, + 27292, + 27293, + 27294, + 27295, + 27296, + 27297, + 27298, + 27299, + 27300, + 27301, + 27302, + 27303, + 27304, + 27305, + 27306, + 27307, + 27308, + 27309, + 27310, + 27311, + 27312, + 27313, + 27314, + 27315, + 27316, + 27317, + 27318, + 27319, + 27320, + 27321, + 27322, + 27323, + 27324, + 27325, + 27326, + 27327, + 27328, + 27329, + 27330, + 27331, + 27332, + 27333, + 27334, + 27335, + 27336, + 27337, + 27338, + 27339, + 27340, + 27341, + 27342, + 27343, + 27344, + 27345, + 27346, + 27347, + 27348, + 27349, + 27350, + 27351, + 27352, + 27353, + 27354, + 27355, + 27356, + 27357, + 27358, + 27359, + 27360, + 27361, + 27362, + 27363, + 27364, + 27365, + 27366, + 27367, + 27368, + 27369, + 27370, + 27371, + 27372, + 27373, + 27374, + 27375, + 27376, + 27377, + 27378, + 27379, + 27380, + 27381, + 27382, + 27383, + 27384, + 27385, + 27386, + 27387, + 27388, + 27389, + 27390, + 27391, + 27392, + 27393, + 27394, + 27395, + 27396, + 27397, + 27398, + 27399, + 27400, + 27401, + 27402, + 27403, + 27404, + 27405, + 27406, + 27407, + 27408, + 27409, + 27410, + 27411, + 27412, + 27413, + 27414, + 27415, + 27416, + 27417, + 27418, + 27419, + 27420, + 27421, + 27422, + 27423, + 27424, + 27425, + 27426, + 27427, + 27428, + 27429, + 27430, + 27431, + 27432, + 27433, + 27434, + 27435, + 27436, + 27437, + 27438, + 27439, + 27440, + 27441, + 27442, + 27443, + 27444, + 27445, + 27446, + 27447, + 27448, + 27449, + 27450, + 27451, + 27452, + 27453, + 27454, + 27455, + 27456, + 27457, + 27458, + 27459, + 27460, + 27461, + 27462, + 27463, + 27464, + 27465, + 27466, + 27467, + 27468, + 27469, + 27470, + 27471, + 27472, + 27473, + 27474, + 27475, + 27476, + 27477, + 27478, + 27479, + 27480, + 27481, + 27482, + 27483, + 27484, + 27485, + 27486, + 27487, + 27488, + 27489, + 27490, + 27491, + 27492, + 27493, + 27494, + 27495, + 27496, + 27497, + 27498, + 27499, + 27500, + 27501, + 27502, + 27503, + 27504, + 27505, + 27506, + 27507, + 27508, + 27509, + 27510, + 27511, + 27512, + 27513, + 27514, + 27515, + 27516, + 27517, + 27518, + 27519, + 27520, + 27521, + 27522, + 27523, + 27524, + 27525, + 27526, + 27527, + 27528, + 27529, + 27530, + 27531, + 27532, + 27533, + 27534, + 27535, + 27536, + 27537, + 27538, + 27539, + 27540, + 27541, + 27542, + 27543, + 27544, + 27545, + 27546, + 27547, + 27548, + 27549, + 27550, + 27551, + 27552, + 27553, + 27554, + 27555, + 27556, + 27557, + 27558, + 27559, + 27560, + 27561, + 27562, + 27563, + 27564, + 27565, + 27566, + 27567, + 27568, + 27569, + 27570, + 27571, + 27572, + 27573, + 27574, + 27575, + 27576, + 27577, + 27578, + 27579, + 27580, + 27581, + 27582, + 27583, + 27584, + 27585, + 27586, + 27587, + 27588, + 27589, + 27590, + 27591, + 27592, + 27593, + 27594, + 27595, + 27596, + 27597, + 27598, + 27599, + 27600, + 27601, + 27602, + 27603, + 27604, + 27605, + 27606, + 27607, + 27608, + 27609, + 27610, + 27611, + 27612, + 27613, + 27614, + 27615, + 27616, + 27617, + 27618, + 27619, + 27620, + 27621, + 27622, + 27623, + 27624, + 27625, + 27626, + 27627, + 27628, + 27629, + 27630, + 27631, + 27632, + 27633, + 27634, + 27635, + 27636, + 27637, + 27638, + 27639, + 27640, + 27641, + 27642, + 27643, + 27644, + 27645, + 27646, + 27647, + 27648, + 27649, + 27650, + 27651, + 27652, + 27653, + 27654, + 27655, + 27656, + 27657, + 27658, + 27659, + 27660, + 27661, + 27662, + 27663, + 27664, + 27665, + 27666, + 27667, + 27668, + 27669, + 27670, + 27671, + 27672, + 27673, + 27674, + 27675, + 27676, + 27677, + 27678, + 27679, + 27680, + 27681, + 27682, + 27683, + 27684, + 27685, + 27686, + 27687, + 27688, + 27689, + 27690, + 27691, + 27692, + 27693, + 27694, + 27695, + 27696, + 27697, + 27698, + 27699, + 27700, + 27701, + 27702, + 27703, + 27704, + 27705, + 27706, + 27707, + 27708, + 27709, + 27710, + 27711, + 27712, + 27713, + 27714, + 27715, + 27716, + 27717, + 27718, + 27719, + 27720, + 27721, + 27722, + 27723, + 27724, + 27725, + 27726, + 27727, + 27728, + 27729, + 27730, + 27731, + 27732, + 27733, + 27734, + 27735, + 27736, + 27737, + 27738, + 27739, + 27740, + 27741, + 27742, + 27743, + 27744, + 27745, + 27746, + 27747, + 27748, + 27749, + 27750, + 27751, + 27752, + 27753, + 27754, + 27755, + 27756, + 27757, + 27758, + 27759, + 27760, + 27761, + 27762, + 27763, + 27764, + 27765, + 27766, + 27767, + 27768, + 27769, + 27770, + 27771, + 27772, + 27773, + 27774, + 27775, + 27776, + 27777, + 27778, + 27779, + 27780, + 27781, + 27782, + 27783, + 27784, + 27785, + 27786, + 27787, + 27788, + 27789, + 27790, + 27791, + 27792, + 27793, + 27794, + 27795, + 27796, + 27797, + 27798, + 27799, + 27800, + 27801, + 27802, + 27803, + 27804, + 27805, + 27806, + 27807, + 27808, + 27809, + 27810, + 27811, + 27812, + 27813, + 27814, + 27815, + 27816, + 27817, + 27818, + 27819, + 27820, + 27821, + 27822, + 27823, + 27824, + 27825, + 27826, + 27827, + 27828, + 27829, + 27830, + 27831, + 27832, + 27833, + 27834, + 27835, + 27836, + 27837, + 27838, + 27839, + 27840, + 27841, + 27842, + 27843, + 27844, + 27845, + 27846, + 27847, + 27848, + 27849, + 27850, + 27851, + 27852, + 27853, + 27854, + 27855, + 27856, + 27857, + 27858, + 27859, + 27860, + 27861, + 27862, + 27863, + 27864, + 27865, + 27866, + 27867, + 27868, + 27869, + 27870, + 27871, + 27872, + 27873, + 27874, + 27875, + 27876, + 27877, + 27878, + 27879, + 27880, + 27881, + 27882, + 27883, + 27884, + 27885, + 27886, + 27887, + 27888, + 27889, + 27890, + 27891, + 27892, + 27893, + 27894, + 27895, + 27896, + 27897, + 27898, + 27899, + 27900, + 27901, + 27902, + 27903, + 27904, + 27905, + 27906, + 27907, + 27908, + 27909, + 27910, + 27911, + 27912, + 27913, + 27914, + 27915, + 27916, + 27917, + 27918, + 27919, + 27920, + 27921, + 27922, + 27923, + 27924, + 27925, + 27926, + 27927, + 27928, + 27929, + 27930, + 27931, + 27932, + 27933, + 27934, + 27935, + 27936, + 27937, + 27938, + 27939, + 27940, + 27941, + 27942, + 27943, + 27944, + 27945, + 27946, + 27947, + 27948, + 27949, + 27950, + 27951, + 27952, + 27953, + 27954, + 27955, + 27956, + 27957, + 27958, + 27959, + 27960, + 27961, + 27962, + 27963, + 27964, + 27965, + 27966, + 27967, + 27968, + 27969, + 27970, + 27971, + 27972, + 27973, + 27974, + 27975, + 27976, + 27977, + 27978, + 27979, + 27980, + 27981, + 27982, + 27983, + 27984, + 27985, + 27986, + 27987, + 27988, + 27989, + 27990, + 27991, + 27992, + 27993, + 27994, + 27995, + 27996, + 27997, + 27998, + 27999, + 28000, + 28001, + 28002, + 28003, + 28004, + 28005, + 28006, + 28007, + 28008, + 28009, + 28010, + 28011, + 28012, + 28013, + 28014, + 28015, + 28016, + 28017, + 28018, + 28019, + 28020, + 28021, + 28022, + 28023, + 28024, + 28025, + 28026, + 28027, + 28028, + 28029, + 28030, + 28031, + 28032, + 28033, + 28034, + 28035, + 28036, + 28037, + 28038, + 28039, + 28040, + 28041, + 28042, + 28043, + 28044, + 28045, + 28046, + 28047, + 28048, + 28049, + 28050, + 28051, + 28052, + 28053, + 28054, + 28055, + 28056, + 28057, + 28058, + 28059, + 28060, + 28061, + 28062, + 28063, + 28064, + 28065, + 28066, + 28067, + 28068, + 28069, + 28070, + 28071, + 28072, + 28073, + 28074, + 28075, + 28076, + 28077, + 28078, + 28079, + 28080, + 28081, + 28082, + 28083, + 28084, + 28085, + 28086, + 28087, + 28088, + 28089, + 28090, + 28091, + 28092, + 28093, + 28094, + 28095, + 28096, + 28097, + 28098, + 28099, + 28100, + 28101, + 28102, + 28103, + 28104, + 28105, + 28106, + 28107, + 28108, + 28109, + 28110, + 28111, + 28112, + 28113, + 28114, + 28115, + 28116, + 28117, + 28118, + 28119, + 28120, + 28121, + 28122, + 28123, + 28124, + 28125, + 28126, + 28127, + 28128, + 28129, + 28130, + 28131, + 28132, + 28133, + 28134, + 28135, + 28136, + 28137, + 28138, + 28139, + 28140, + 28141, + 28142, + 28143, + 28144, + 28145, + 28146, + 28147, + 28148, + 28149, + 28150, + 28151, + 28152, + 28153, + 28154, + 28155, + 28156, + 28157, + 28158, + 28159, + 28160, + 28161, + 28162, + 28163, + 28164, + 28165, + 28166, + 28167, + 28168, + 28169, + 28170, + 28171, + 28172, + 28173, + 28174, + 28175, + 28176, + 28177, + 28178, + 28179, + 28180, + 28181, + 28182, + 28183, + 28184, + 28185, + 28186, + 28187, + 28188, + 28189, + 28190, + 28191, + 28192, + 28193, + 28194, + 28195, + 28196, + 28197, + 28198, + 28199, + 28200, + 28201, + 28202, + 28203, + 28204, + 28205, + 28206, + 28207, + 28208, + 28209, + 28210, + 28211, + 28212, + 28213, + 28214, + 28215, + 28216, + 28217, + 28218, + 28219, + 28220, + 28221, + 28222, + 28223, + 28224, + 28225, + 28226, + 28227, + 28228, + 28229, + 28230, + 28231, + 28232, + 28233, + 28234, + 28235, + 28236, + 28237, + 28238, + 28239, + 28240, + 28241, + 28242, + 28243, + 28244, + 28245, + 28246, + 28247, + 28248, + 28249, + 28250, + 28251, + 28252, + 28253, + 28254, + 28255, + 28256, + 28257, + 28258, + 28259, + 28260, + 28261, + 28262, + 28263, + 28264, + 28265, + 28266, + 28267, + 28268, + 28269, + 28270, + 28271, + 28272, + 28273, + 28274, + 28275, + 28276, + 28277, + 28278, + 28279, + 28280, + 28281, + 28282, + 28283, + 28284, + 28285, + 28286, + 28287, + 28288, + 28289, + 28290, + 28291, + 28292, + 28293, + 28294, + 28295, + 28296, + 28297, + 28298, + 28299, + 28300, + 28301, + 28302, + 28303, + 28304, + 28305, + 28306, + 28307, + 28308, + 28309, + 28310, + 28311, + 28312, + 28313, + 28314, + 28315, + 28316, + 28317, + 28318, + 28319, + 28320, + 28321, + 28322, + 28323, + 28324, + 28325, + 28326, + 28327, + 28328, + 28329, + 28330, + 28331, + 28332, + 28333, + 28334, + 28335, + 28336, + 28337, + 28338, + 28339, + 28340, + 28341, + 28342, + 28343, + 28344, + 28345, + 28346, + 28347, + 28348, + 28349, + 28350, + 28351, + 28352, + 28353, + 28354, + 28355, + 28356, + 28357, + 28358, + 28359, + 28360, + 28361, + 28362, + 28363, + 28364, + 28365, + 28366, + 28367, + 28368, + 28369, + 28370, + 28371, + 28372, + 28373, + 28374, + 28375, + 28376, + 28377, + 28378, + 28379, + 28380, + 28381, + 28382, + 28383, + 28384, + 28385, + 28386, + 28387, + 28388, + 28389, + 28390, + 28391, + 28392, + 28393, + 28394, + 28395, + 28396, + 28397, + 28398, + 28399, + 28400, + 28401, + 28402, + 28403, + 28404, + 28405, + 28406, + 28407, + 28408, + 28409, + 28410, + 28411, + 28412, + 28413, + 28414, + 28415, + 28416, + 28417, + 28418, + 28419, + 28420, + 28421, + 28422, + 28423, + 28424, + 28425, + 28426, + 28427, + 28428, + 28429, + 28430, + 28431, + 28432, + 28433, + 28434, + 28435, + 28436, + 28437, + 28438, + 28439, + 28440, + 28441, + 28442, + 28443, + 28444, + 28445, + 28446, + 28447, + 28448, + 28449, + 28450, + 28451, + 28452, + 28453, + 28454, + 28455, + 28456, + 28457, + 28458, + 28459, + 28460, + 28461, + 28462, + 28463, + 28464, + 28465, + 28466, + 28467, + 28468, + 28469, + 28470, + 28471, + 28472, + 28473, + 28474, + 28475, + 28476, + 28477, + 28478, + 28479, + 28480, + 28481, + 28482, + 28483, + 28484, + 28485, + 28486, + 28487, + 28488, + 28489, + 28490, + 28491, + 28492, + 28493, + 28494, + 28495, + 28496, + 28497, + 28498, + 28499, + 28500, + 28501, + 28502, + 28503, + 28504, + 28505, + 28506, + 28507, + 28508, + 28509, + 28510, + 28511, + 28512, + 28513, + 28514, + 28515, + 28516, + 28517, + 28518, + 28519, + 28520, + 28521, + 28522, + 28523, + 28524, + 28525, + 28526, + 28527, + 28528, + 28529, + 28530, + 28531, + 28532, + 28533, + 28534, + 28535, + 28536, + 28537, + 28538, + 28539, + 28540, + 28541, + 28542, + 28543, + 28544, + 28545, + 28546, + 28547, + 28548, + 28549, + 28550, + 28551, + 28552, + 28553, + 28554, + 28555, + 28556, + 28557, + 28558, + 28559, + 28560, + 28561, + 28562, + 28563, + 28564, + 28565, + 28566, + 28567, + 28568, + 28569, + 28570, + 28571, + 28572, + 28573, + 28574, + 28575, + 28576, + 28577, + 28578, + 28579, + 28580, + 28581, + 28582, + 28583, + 28584, + 28585, + 28586, + 28587, + 28588, + 28589, + 28590, + 28591, + 28592, + 28593, + 28594, + 28595, + 28596, + 28597, + 28598, + 28599, + 28600, + 28601, + 28602, + 28603, + 28604, + 28605, + 28606, + 28607, + 28608, + 28609, + 28610, + 28611, + 28612, + 28613, + 28614, + 28615, + 28616, + 28617, + 28618, + 28619, + 28620, + 28621, + 28622, + 28623, + 28624, + 28625, + 28626, + 28627, + 28628, + 28629, + 28630, + 28631, + 28632, + 28633, + 28634, + 28635, + 28636, + 28637, + 28638, + 28639, + 28640, + 28641, + 28642, + 28643, + 28644, + 28645, + 28646, + 28647, + 28648, + 28649, + 28650, + 28651, + 28652, + 28653, + 28654, + 28655, + 28656, + 28657, + 28658, + 28659, + 28660, + 28661, + 28662, + 28663, + 28664, + 28665, + 28666, + 28667, + 28668, + 28669, + 28670, + 28671, + 28672, + 28673, + 28674, + 28675, + 28676, + 28677, + 28678, + 28679, + 28680, + 28681, + 28682, + 28683, + 28684, + 28685, + 28686, + 28687, + 28688, + 28689, + 28690, + 28691, + 28692, + 28693, + 28694, + 28695, + 28696, + 28697, + 28698, + 28699, + 28700, + 28701, + 28702, + 28703, + 28704, + 28705, + 28706, + 28707, + 28708, + 28709, + 28710, + 28711, + 28712, + 28713, + 28714, + 28715, + 28716, + 28717, + 28718, + 28719, + 28720, + 28721, + 28722, + 28723, + 28724, + 28725, + 28726, + 28727, + 28728, + 28729, + 28730, + 28731, + 28732, + 28733, + 28734, + 28735, + 28736, + 28737, + 28738, + 28739, + 28740, + 28741, + 28742, + 28743, + 28744, + 28745, + 28746, + 28747, + 28748, + 28749, + 28750, + 28751, + 28752, + 28753, + 28754, + 28755, + 28756, + 28757, + 28758, + 28759, + 28760, + 28761, + 28762, + 28763, + 28764, + 28765, + 28766, + 28767, + 28768, + 28769, + 28770, + 28771, + 28772, + 28773, + 28774, + 28775, + 28776, + 28777, + 28778, + 28779, + 28780, + 28781, + 28782, + 28783, + 28784, + 28785, + 28786, + 28787, + 28788, + 28789, + 28790, + 28791, + 28792, + 28793, + 28794, + 28795, + 28796, + 28797, + 28798, + 28799, + 28800, + 28801, + 28802, + 28803, + 28804, + 28805, + 28806, + 28807, + 28808, + 28809, + 28810, + 28811, + 28812, + 28813, + 28814, + 28815, + 28816, + 28817, + 28818, + 28819, + 28820, + 28821, + 28822, + 28823, + 28824, + 28825, + 28826, + 28827, + 28828, + 28829, + 28830, + 28831, + 28832, + 28833, + 28834, + 28835, + 28836, + 28837, + 28838, + 28839, + 28840, + 28841, + 28842, + 28843, + 28844, + 28845, + 28846, + 28847, + 28848, + 28849, + 28850, + 28851, + 28852, + 28853, + 28854, + 28855, + 28856, + 28857, + 28858, + 28859, + 28860, + 28861, + 28862, + 28863, + 28864, + 28865, + 28866, + 28867, + 28868, + 28869, + 28870, + 28871, + 28872, + 28873, + 28874, + 28875, + 28876, + 28877, + 28878, + 28879, + 28880, + 28881, + 28882, + 28883, + 28884, + 28885, + 28886, + 28887, + 28888, + 28889, + 28890, + 28891, + 28892, + 28893, + 28894, + 28895, + 28896, + 28897, + 28898, + 28899, + 28900, + 28901, + 28902, + 28903, + 28904, + 28905, + 28906, + 28907, + 28908, + 28909, + 28910, + 28911, + 28912, + 28913, + 28914, + 28915, + 28916, + 28917, + 28918, + 28919, + 28920, + 28921, + 28922, + 28923, + 28924, + 28925, + 28926, + 28927, + 28928, + 28929, + 28930, + 28931, + 28932, + 28933, + 28934, + 28935, + 28936, + 28937, + 28938, + 28939, + 28940, + 28941, + 28942, + 28943, + 28944, + 28945, + 28946, + 28947, + 28948, + 28949, + 28950, + 28951, + 28952, + 28953, + 28954, + 28955, + 28956, + 28957, + 28958, + 28959, + 28960, + 28961, + 28962, + 28963, + 28964, + 28965, + 28966, + 28967, + 28968, + 28969, + 28970, + 28971, + 28972, + 28973, + 28974, + 28975, + 28976, + 28977, + 28978, + 28979, + 28980, + 28981, + 28982, + 28983, + 28984, + 28985, + 28986, + 28987, + 28988, + 28989, + 28990, + 28991, + 28992, + 28993, + 28994, + 28995, + 28996, + 28997, + 28998, + 28999, + 29000, + 29001, + 29002, + 29003, + 29004, + 29005, + 29006, + 29007, + 29008, + 29009, + 29010, + 29011, + 29012, + 29013, + 29014, + 29015, + 29016, + 29017, + 29018, + 29019, + 29020, + 29021, + 29022, + 29023, + 29024, + 29025, + 29026, + 29027, + 29028, + 29029, + 29030, + 29031, + 29032, + 29033, + 29034, + 29035, + 29036, + 29037, + 29038, + 29039, + 29040, + 29041, + 29042, + 29043, + 29044, + 29045, + 29046, + 29047, + 29048, + 29049, + 29050, + 29051, + 29052, + 29053, + 29054, + 29055, + 29056, + 29057, + 29058, + 29059, + 29060, + 29061, + 29062, + 29063, + 29064, + 29065, + 29066, + 29067, + 29068, + 29069, + 29070, + 29071, + 29072, + 29073, + 29074, + 29075, + 29076, + 29077, + 29078, + 29079, + 29080, + 29081, + 29082, + 29083, + 29084, + 29085, + 29086, + 29087, + 29088, + 29089, + 29090, + 29091, + 29092, + 29093, + 29094, + 29095, + 29096, + 29097, + 29098, + 29099, + 29100, + 29101, + 29102, + 29103, + 29104, + 29105, + 29106, + 29107, + 29108, + 29109, + 29110, + 29111, + 29112, + 29113, + 29114, + 29115, + 29116, + 29117, + 29118, + 29119, + 29120, + 29121, + 29122, + 29123, + 29124, + 29125, + 29126, + 29127, + 29128, + 29129, + 29130, + 29131, + 29132, + 29133, + 29134, + 29135, + 29136, + 29137, + 29138, + 29139, + 29140, + 29141, + 29142, + 29143, + 29144, + 29145, + 29146, + 29147, + 29148, + 29149, + 29150, + 29151, + 29152, + 29153, + 29154, + 29155, + 29156, + 29157, + 29158, + 29159, + 29160, + 29161, + 29162, + 29163, + 29164, + 29165, + 29166, + 29167, + 29168, + 29169, + 29170, + 29171, + 29172, + 29173, + 29174, + 29175, + 29176, + 29177, + 29178, + 29179, + 29180, + 29181, + 29182, + 29183, + 29184, + 29185, + 29186, + 29187, + 29188, + 29189, + 29190, + 29191, + 29192, + 29193, + 29194, + 29195, + 29196, + 29197, + 29198, + 29199, + 29200, + 29201, + 29202, + 29203, + 29204, + 29205, + 29206, + 29207, + 29208, + 29209, + 29210, + 29211, + 29212, + 29213, + 29214, + 29215, + 29216, + 29217, + 29218, + 29219, + 29220, + 29221, + 29222, + 29223, + 29224, + 29225, + 29226, + 29227, + 29228, + 29229, + 29230, + 29231, + 29232, + 29233, + 29234, + 29235, + 29236, + 29237, + 29238, + 29239, + 29240, + 29241, + 29242, + 29243, + 29244, + 29245, + 29246, + 29247, + 29248, + 29249, + 29250, + 29251, + 29252, + 29253, + 29254, + 29255, + 29256, + 29257, + 29258, + 29259, + 29260, + 29261, + 29262, + 29263, + 29264, + 29265, + 29266, + 29267, + 29268, + 29269, + 29270, + 29271, + 29272, + 29273, + 29274, + 29275, + 29276, + 29277, + 29278, + 29279, + 29280, + 29281, + 29282, + 29283, + 29284, + 29285, + 29286, + 29287, + 29288, + 29289, + 29290, + 29291, + 29292, + 29293, + 29294, + 29295, + 29296, + 29297, + 29298, + 29299, + 29300, + 29301, + 29302, + 29303, + 29304, + 29305, + 29306, + 29307, + 29308, + 29309, + 29310, + 29311, + 29312, + 29313, + 29314, + 29315, + 29316, + 29317, + 29318, + 29319, + 29320, + 29321, + 29322, + 29323, + 29324, + 29325, + 29326, + 29327, + 29328, + 29329, + 29330, + 29331, + 29332, + 29333, + 29334, + 29335, + 29336, + 29337, + 29338, + 29339, + 29340, + 29341, + 29342, + 29343, + 29344, + 29345, + 29346, + 29347, + 29348, + 29349, + 29350, + 29351, + 29352, + 29353, + 29354, + 29355, + 29356, + 29357, + 29358, + 29359, + 29360, + 29361, + 29362, + 29363, + 29364, + 29365, + 29366, + 29367, + 29368, + 29369, + 29370, + 29371, + 29372, + 29373, + 29374, + 29375, + 29376, + 29377, + 29378, + 29379, + 29380, + 29381, + 29382, + 29383, + 29384, + 29385, + 29386, + 29387, + 29388, + 29389, + 29390, + 29391, + 29392, + 29393, + 29394, + 29395, + 29396, + 29397, + 29398, + 29399, + 29400, + 29401, + 29402, + 29403, + 29404, + 29405, + 29406, + 29407, + 29408, + 29409, + 29410, + 29411, + 29412, + 29413, + 29414, + 29415, + 29416, + 29417, + 29418, + 29419, + 29420, + 29421, + 29422, + 29423, + 29424, + 29425, + 29426, + 29427, + 29428, + 29429, + 29430, + 29431, + 29432, + 29433, + 29434, + 29435, + 29436, + 29437, + 29438, + 29439, + 29440, + 29441, + 29442, + 29443, + 29444, + 29445, + 29446, + 29447, + 29448, + 29449, + 29450, + 29451, + 29452, + 29453, + 29454, + 29455, + 29456, + 29457, + 29458, + 29459, + 29460, + 29461, + 29462, + 29463, + 29464, + 29465, + 29466, + 29467, + 29468, + 29469, + 29470, + 29471, + 29472, + 29473, + 29474, + 29475, + 29476, + 29477, + 29478, + 29479, + 29480, + 29481, + 29482, + 29483, + 29484, + 29485, + 29486, + 29487, + 29488, + 29489, + 29490, + 29491, + 29492, + 29493, + 29494, + 29495, + 29496, + 29497, + 29498, + 29499, + 29500, + 29501, + 29502, + 29503, + 29504, + 29505, + 29506, + 29507, + 29508, + 29509, + 29510, + 29511, + 29512, + 29513, + 29514, + 29515, + 29516, + 29517, + 29518, + 29519, + 29520, + 29521, + 29522, + 29523, + 29524, + 29525, + 29526, + 29527, + 29528, + 29529, + 29530, + 29531, + 29532, + 29533, + 29534, + 29535, + 29536, + 29537, + 29538, + 29539, + 29540, + 29541, + 29542, + 29543, + 29544, + 29545, + 29546, + 29547, + 29548, + 29549, + 29550, + 29551, + 29552, + 29553, + 29554, + 29555, + 29556, + 29557, + 29558, + 29559, + 29560, + 29561, + 29562, + 29563, + 29564, + 29565, + 29566, + 29567, + 29568, + 29569, + 29570, + 29571, + 29572, + 29573, + 29574, + 29575, + 29576, + 29577, + 29578, + 29579, + 29580, + 29581, + 29582, + 29583, + 29584, + 29585, + 29586, + 29587, + 29588, + 29589, + 29590, + 29591, + 29592, + 29593, + 29594, + 29595, + 29596, + 29597, + 29598, + 29599, + 29600, + 29601, + 29602, + 29603, + 29604, + 29605, + 29606, + 29607, + 29608, + 29609, + 29610, + 29611, + 29612, + 29613, + 29614, + 29615, + 29616, + 29617, + 29618, + 29619, + 29620, + 29621, + 29622, + 29623, + 29624, + 29625, + 29626, + 29627, + 29628, + 29629, + 29630, + 29631, + 29632, + 29633, + 29634, + 29635, + 29636, + 29637, + 29638, + 29639, + 29640, + 29641, + 29642, + 29643, + 29644, + 29645, + 29646, + 29647, + 29648, + 29649, + 29650, + 29651, + 29652, + 29653, + 29654, + 29655, + 29656, + 29657, + 29658, + 29659, + 29660, + 29661, + 29662, + 29663, + 29664, + 29665, + 29666, + 29667, + 29668, + 29669, + 29670, + 29671, + 29672, + 29673, + 29674, + 29675, + 29676, + 29677, + 29678, + 29679, + 29680, + 29681, + 29682, + 29683, + 29684, + 29685, + 29686, + 29687, + 29688, + 29689, + 29690, + 29691, + 29692, + 29693, + 29694, + 29695, + 29696, + 29697, + 29698, + 29699, + 29700, + 29701, + 29702, + 29703, + 29704, + 29705, + 29706, + 29707, + 29708, + 29709, + 29710, + 29711, + 29712, + 29713, + 29714, + 29715, + 29716, + 29717, + 29718, + 29719, + 29720, + 29721, + 29722, + 29723, + 29724, + 29725, + 29726, + 29727, + 29728, + 29729, + 29730, + 29731, + 29732, + 29733, + 29734, + 29735, + 29736, + 29737, + 29738, + 29739, + 29740, + 29741, + 29742, + 29743, + 29744, + 29745, + 29746, + 29747, + 29748, + 29749, + 29750, + 29751, + 29752, + 29753, + 29754, + 29755, + 29756, + 29757, + 29758, + 29759, + 29760, + 29761, + 29762, + 29763, + 29764, + 29765, + 29766, + 29767, + 29768, + 29769, + 29770, + 29771, + 29772, + 29773, + 29774, + 29775, + 29776, + 29777, + 29778, + 29779, + 29780, + 29781, + 29782, + 29783, + 29784, + 29785, + 29786, + 29787, + 29788, + 29789, + 29790, + 29791, + 29792, + 29793, + 29794, + 29795, + 29796, + 29797, + 29798, + 29799, + 29800, + 29801, + 29802, + 29803, + 29804, + 29805, + 29806, + 29807, + 29808, + 29809, + 29810, + 29811, + 29812, + 29813, + 29814, + 29815, + 29816, + 29817, + 29818, + 29819, + 29820, + 29821, + 29822, + 29823, + 29824, + 29825, + 29826, + 29827, + 29828, + 29829, + 29830, + 29831, + 29832, + 29833, + 29834, + 29835, + 29836, + 29837, + 29838, + 29839, + 29840, + 29841, + 29842, + 29843, + 29844, + 29845, + 29846, + 29847, + 29848, + 29849, + 29850, + 29851, + 29852, + 29853, + 29854, + 29855, + 29856, + 29857, + 29858, + 29859, + 29860, + 29861, + 29862, + 29863, + 29864, + 29865, + 29866, + 29867, + 29868, + 29869, + 29870, + 29871, + 29872, + 29873, + 29874, + 29875, + 29876, + 29877, + 29878, + 29879, + 29880, + 29881, + 29882, + 29883, + 29884, + 29885, + 29886, + 29887, + 29888, + 29889, + 29890, + 29891, + 29892, + 29893, + 29894, + 29895, + 29896, + 29897, + 29898, + 29899, + 29900, + 29901, + 29902, + 29903, + 29904, + 29905, + 29906, + 29907, + 29908, + 29909, + 29910, + 29911, + 29912, + 29913, + 29914, + 29915, + 29916, + 29917, + 29918, + 29919, + 29920, + 29921, + 29922, + 29923, + 29924, + 29925, + 29926, + 29927, + 29928, + 29929, + 29930, + 29931, + 29932, + 29933, + 29934, + 29935, + 29936, + 29937, + 29938, + 29939, + 29940, + 29941, + 29942, + 29943, + 29944, + 29945, + 29946, + 29947, + 29948, + 29949, + 29950, + 29951, + 29952, + 29953, + 29954, + 29955, + 29956, + 29957, + 29958, + 29959, + 29960, + 29961, + 29962, + 29963, + 29964, + 29965, + 29966, + 29967, + 29968, + 29969, + 29970, + 29971, + 29972, + 29973, + 29974, + 29975, + 29976, + 29977, + 29978, + 29979, + 29980, + 29981, + 29982, + 29983, + 29984, + 29985, + 29986, + 29987, + 29988, + 29989, + 29990, + 29991, + 29992, + 29993, + 29994, + 29995, + 29996, + 29997, + 29998, + 29999, + 30000, + 30001, + 30002, + 30003, + 30004, + 30005, + 30006, + 30007, + 30008, + 30009, + 30010, + 30011, + 30012, + 30013, + 30014, + 30015, + 30016, + 30017, + 30018, + 30019, + 30020, + 30021, + 30022, + 30023, + 30024, + 30025, + 30026, + 30027, + 30028, + 30029, + 30030, + 30031, + 30032, + 30033, + 30034, + 30035, + 30036, + 30037, + 30038, + 30039, + 30040, + 30041, + 30042, + 30043, + 30044, + 30045, + 30046, + 30047, + 30048, + 30049, + 30050, + 30051, + 30052, + 30053, + 30054, + 30055, + 30056, + 30057, + 30058, + 30059, + 30060, + 30061, + 30062, + 30063, + 30064, + 30065, + 30066, + 30067, + 30068, + 30069, + 30070, + 30071, + 30072, + 30073, + 30074, + 30075, + 30076, + 30077, + 30078, + 30079, + 30080, + 30081, + 30082, + 30083, + 30084, + 30085, + 30086, + 30087, + 30088, + 30089, + 30090, + 30091, + 30092, + 30093, + 30094, + 30095, + 30096, + 30097, + 30098, + 30099, + 30100, + 30101, + 30102, + 30103, + 30104, + 30105, + 30106, + 30107, + 30108, + 30109, + 30110, + 30111, + 30112, + 30113, + 30114, + 30115, + 30116, + 30117, + 30118, + 30119, + 30120, + 30121, + 30122, + 30123, + 30124, + 30125, + 30126, + 30127, + 30128, + 30129, + 30130, + 30131, + 30132, + 30133, + 30134, + 30135, + 30136, + 30137, + 30138, + 30139, + 30140, + 30141, + 30142, + 30143, + 30144, + 30145, + 30146, + 30147, + 30148, + 30149, + 30150, + 30151, + 30152, + 30153, + 30154, + 30155, + 30156, + 30157, + 30158, + 30159, + 30160, + 30161, + 30162, + 30163, + 30164, + 30165, + 30166, + 30167, + 30168, + 30169, + 30170, + 30171, + 30172, + 30173, + 30174, + 30175, + 30176, + 30177, + 30178, + 30179, + 30180, + 30181, + 30182, + 30183, + 30184, + 30185, + 30186, + 30187, + 30188, + 30189, + 30190, + 30191, + 30192, + 30193, + 30194, + 30195, + 30196, + 30197, + 30198, + 30199, + 30200, + 30201, + 30202, + 30203, + 30204, + 30205, + 30206, + 30207, + 30208, + 30209, + 30210, + 30211, + 30212, + 30213, + 30214, + 30215, + 30216, + 30217, + 30218, + 30219, + 30220, + 30221, + 30222, + 30223, + 30224, + 30225, + 30226, + 30227, + 30228, + 30229, + 30230, + 30231, + 30232, + 30233, + 30234, + 30235, + 30236, + 30237, + 30238, + 30239, + 30240, + 30241, + 30242, + 30243, + 30244, + 30245, + 30246, + 30247, + 30248, + 30249, + 30250, + 30251, + 30252, + 30253, + 30254, + 30255, + 30256, + 30257, + 30258, + 30259, + 30260, + 30261, + 30262, + 30263, + 30264, + 30265, + 30266, + 30267, + 30268, + 30269, + 30270, + 30271, + 30272, + 30273, + 30274, + 30275, + 30276, + 30277, + 30278, + 30279, + 30280, + 30281, + 30282, + 30283, + 30284, + 30285, + 30286, + 30287, + 30288, + 30289, + 30290, + 30291, + 30292, + 30293, + 30294, + 30295, + 30296, + 30297, + 30298, + 30299, + 30300, + 30301, + 30302, + 30303, + 30304, + 30305, + 30306, + 30307, + 30308, + 30309, + 30310, + 30311, + 30312, + 30313, + 30314, + 30315, + 30316, + 30317, + 30318, + 30319, + 30320, + 30321, + 30322, + 30323, + 30324, + 30325, + 30326, + 30327, + 30328, + 30329, + 30330, + 30331, + 30332, + 30333, + 30334, + 30335, + 30336, + 30337, + 30338, + 30339, + 30340, + 30341, + 30342, + 30343, + 30344, + 30345, + 30346, + 30347, + 30348, + 30349, + 30350, + 30351, + 30352, + 30353, + 30354, + 30355, + 30356, + 30357, + 30358, + 30359, + 30360, + 30361, + 30362, + 30363, + 30364, + 30365, + 30366, + 30367, + 30368, + 30369, + 30370, + 30371, + 30372, + 30373, + 30374, + 30375, + 30376, + 30377, + 30378, + 30379, + 30380, + 30381, + 30382, + 30383, + 30384, + 30385, + 30386, + 30387, + 30388, + 30389, + 30390, + 30391, + 30392, + 30393, + 30394, + 30395, + 30396, + 30397, + 30398, + 30399, + 30400, + 30401, + 30402, + 30403, + 30404, + 30405, + 30406, + 30407, + 30408, + 30409, + 30410, + 30411, + 30412, + 30413, + 30414, + 30415, + 30416, + 30417, + 30418, + 30419, + 30420, + 30421, + 30422, + 30423, + 30424, + 30425, + 30426, + 30427, + 30428, + 30429, + 30430, + 30431, + 30432, + 30433, + 30434, + 30435, + 30436, + 30437, + 30438, + 30439, + 30440, + 30441, + 30442, + 30443, + 30444, + 30445, + 30446, + 30447, + 30448, + 30449, + 30450, + 30451, + 30452, + 30453, + 30454, + 30455, + 30456, + 30457, + 30458, + 30459, + 30460, + 30461, + 30462, + 30463, + 30464, + 30465, + 30466, + 30467, + 30468, + 30469, + 30470, + 30471, + 30472, + 30473, + 30474, + 30475, + 30476, + 30477, + 30478, + 30479, + 30480, + 30481, + 30482, + 30483, + 30484, + 30485, + 30486, + 30487, + 30488, + 30489, + 30490, + 30491, + 30492, + 30493, + 30494, + 30495, + 30496, + 30497, + 30498, + 30499, + 30500, + 30501, + 30502, + 30503, + 30504, + 30505, + 30506, + 30507, + 30508, + 30509, + 30510, + 30511, + 30512, + 30513, + 30514, + 30515, + 30516, + 30517, + 30518, + 30519, + 30520, + 30521, + 30522, + 30523, + 30524, + 30525, + 30526, + 30527, + 30528, + 30529, + 30530, + 30531, + 30532, + 30533, + 30534, + 30535, + 30536, + 30537, + 30538, + 30539, + 30540, + 30541, + 30542, + 30543, + 30544, + 30545, + 30546, + 30547, + 30548, + 30549, + 30550, + 30551, + 30552, + 30553, + 30554, + 30555, + 30556, + 30557, + 30558, + 30559, + 30560, + 30561, + 30562, + 30563, + 30564, + 30565, + 30566, + 30567, + 30568, + 30569, + 30570, + 30571, + 30572, + 30573, + 30574, + 30575, + 30576, + 30577, + 30578, + 30579, + 30580, + 30581, + 30582, + 30583, + 30584, + 30585, + 30586, + 30587, + 30588, + 30589, + 30590, + 30591, + 30592, + 30593, + 30594, + 30595, + 30596, + 30597, + 30598, + 30599, + 30600, + 30601, + 30602, + 30603, + 30604, + 30605, + 30606, + 30607, + 30608, + 30609, + 30610, + 30611, + 30612, + 30613, + 30614, + 30615, + 30616, + 30617, + 30618, + 30619, + 30620, + 30621, + 30622, + 30623, + 30624, + 30625, + 30626, + 30627, + 30628, + 30629, + 30630, + 30631, + 30632, + 30633, + 30634, + 30635, + 30636, + 30637, + 30638, + 30639, + 30640, + 30641, + 30642, + 30643, + 30644, + 30645, + 30646, + 30647, + 30648, + 30649, + 30650, + 30651, + 30652, + 30653, + 30654, + 30655, + 30656, + 30657, + 30658, + 30659, + 30660, + 30661, + 30662, + 30663, + 30664, + 30665, + 30666, + 30667, + 30668, + 30669, + 30670, + 30671, + 30672, + 30673, + 30674, + 30675, + 30676, + 30677, + 30678, + 30679, + 30680, + 30681, + 30682, + 30683, + 30684, + 30685, + 30686, + 30687, + 30688, + 30689, + 30690, + 30691, + 30692, + 30693, + 30694, + 30695, + 30696, + 30697, + 30698, + 30699, + 30700, + 30701, + 30702, + 30703, + 30704, + 30705, + 30706, + 30707, + 30708, + 30709, + 30710, + 30711, + 30712, + 30713, + 30714, + 30715, + 30716, + 30717, + 30718, + 30719, + 30720, + 30721, + 30722, + 30723, + 30724, + 30725, + 30726, + 30727, + 30728, + 30729, + 30730, + 30731, + 30732, + 30733, + 30734, + 30735, + 30736, + 30737, + 30738, + 30739, + 30740, + 30741, + 30742, + 30743, + 30744, + 30745, + 30746, + 30747, + 30748, + 30749, + 30750, + 30751, + 30752, + 30753, + 30754, + 30755, + 30756, + 30757, + 30758, + 30759, + 30760, + 30761, + 30762, + 30763, + 30764, + 30765, + 30766, + 30767, + 30768, + 30769, + 30770, + 30771, + 30772, + 30773, + 30774, + 30775, + 30776, + 30777, + 30778, + 30779, + 30780, + 30781, + 30782, + 30783, + 30784, + 30785, + 30786, + 30787, + 30788, + 30789, + 30790, + 30791, + 30792, + 30793, + 30794, + 30795, + 30796, + 30797, + 30798, + 30799, + 30800, + 30801, + 30802, + 30803, + 30804, + 30805, + 30806, + 30807, + 30808, + 30809, + 30810, + 30811, + 30812, + 30813, + 30814, + 30815, + 30816, + 30817, + 30818, + 30819, + 30820, + 30821, + 30822, + 30823, + 30824, + 30825, + 30826, + 30827, + 30828, + 30829, + 30830, + 30831, + 30832, + 30833, + 30834, + 30835, + 30836, + 30837, + 30838, + 30839, + 30840, + 30841, + 30842, + 30843, + 30844, + 30845, + 30846, + 30847, + 30848, + 30849, + 30850, + 30851, + 30852, + 30853, + 30854, + 30855, + 30856, + 30857, + 30858, + 30859, + 30860, + 30861, + 30862, + 30863, + 30864, + 30865, + 30866, + 30867, + 30868, + 30869, + 30870, + 30871, + 30872, + 30873, + 30874, + 30875, + 30876, + 30877, + 30878, + 30879, + 30880, + 30881, + 30882, + 30883, + 30884, + 30885, + 30886, + 30887, + 30888, + 30889, + 30890, + 30891, + 30892, + 30893, + 30894, + 30895, + 30896, + 30897, + 30898, + 30899, + 30900, + 30901, + 30902, + 30903, + 30904, + 30905, + 30906, + 30907, + 30908, + 30909, + 30910, + 30911, + 30912, + 30913, + 30914, + 30915, + 30916, + 30917, + 30918, + 30919, + 30920, + 30921, + 30922, + 30923, + 30924, + 30925, + 30926, + 30927, + 30928, + 30929, + 30930, + 30931, + 30932, + 30933, + 30934, + 30935, + 30936, + 30937, + 30938, + 30939, + 30940, + 30941, + 30942, + 30943, + 30944, + 30945, + 30946, + 30947, + 30948, + 30949, + 30950, + 30951, + 30952, + 30953, + 30954, + 30955, + 30956, + 30957, + 30958, + 30959, + 30960, + 30961, + 30962, + 30963, + 30964, + 30965, + 30966, + 30967, + 30968, + 30969, + 30970, + 30971, + 30972, + 30973, + 30974, + 30975, + 30976, + 30977, + 30978, + 30979, + 30980, + 30981, + 30982, + 30983, + 30984, + 30985, + 30986, + 30987, + 30988, + 30989, + 30990, + 30991, + 30992, + 30993, + 30994, + 30995, + 30996, + 30997, + 30998, + 30999, + 31000, + 31001, + 31002, + 31003, + 31004, + 31005, + 31006, + 31007, + 31008, + 31009, + 31010, + 31011, + 31012, + 31013, + 31014, + 31015, + 31016, + 31017, + 31018, + 31019, + 31020, + 31021, + 31022, + 31023, + 31024, + 31025, + 31026, + 31027, + 31028, + 31029, + 31030, + 31031, + 31032, + 31033, + 31034, + 31035, + 31036, + 31037, + 31038, + 31039, + 31040, + 31041, + 31042, + 31043, + 31044, + 31045, + 31046, + 31047, + 31048, + 31049, + 31050, + 31051, + 31052, + 31053, + 31054, + 31055, + 31056, + 31057, + 31058, + 31059, + 31060, + 31061, + 31062, + 31063, + 31064, + 31065, + 31066, + 31067, + 31068, + 31069, + 31070, + 31071, + 31072, + 31073, + 31074, + 31075, + 31076, + 31077, + 31078, + 31079, + 31080, + 31081, + 31082, + 31083, + 31084, + 31085, + 31086, + 31087, + 31088, + 31089, + 31090, + 31091, + 31092, + 31093, + 31094, + 31095, + 31096, + 31097, + 31098, + 31099, + 31100, + 31101, + 31102, + 31103, + 31104, + 31105, + 31106, + 31107, + 31108, + 31109, + 31110, + 31111, + 31112, + 31113, + 31114, + 31115, + 31116, + 31117, + 31118, + 31119, + 31120, + 31121, + 31122, + 31123, + 31124, + 31125, + 31126, + 31127, + 31128, + 31129, + 31130, + 31131, + 31132, + 31133, + 31134, + 31135, + 31136, + 31137, + 31138, + 31139, + 31140, + 31141, + 31142, + 31143, + 31144, + 31145, + 31146, + 31147, + 31148, + 31149, + 31150, + 31151, + 31152, + 31153, + 31154, + 31155, + 31156, + 31157, + 31158, + 31159, + 31160, + 31161, + 31162, + 31163, + 31164, + 31165, + 31166, + 31167, + 31168, + 31169, + 31170, + 31171, + 31172, + 31173, + 31174, + 31175, + 31176, + 31177, + 31178, + 31179, + 31180, + 31181, + 31182, + 31183, + 31184, + 31185, + 31186, + 31187, + 31188, + 31189, + 31190, + 31191, + 31192, + 31193, + 31194, + 31195, + 31196, + 31197, + 31198, + 31199, + 31200, + 31201, + 31202, + 31203, + 31204, + 31205, + 31206, + 31207, + 31208, + 31209, + 31210, + 31211, + 31212, + 31213, + 31214, + 31215, + 31216, + 31217, + 31218, + 31219, + 31220, + 31221, + 31222, + 31223, + 31224, + 31225, + 31226, + 31227, + 31228, + 31229, + 31230, + 31231, + 31232, + 31233, + 31234, + 31235, + 31236, + 31237, + 31238, + 31239, + 31240, + 31241, + 31242, + 31243, + 31244, + 31245, + 31246, + 31247, + 31248, + 31249, + 31250, + 31251, + 31252, + 31253, + 31254, + 31255, + 31256, + 31257, + 31258, + 31259, + 31260, + 31261, + 31262, + 31263, + 31264, + 31265, + 31266, + 31267, + 31268, + 31269, + 31270, + 31271, + 31272, + 31273, + 31274, + 31275, + 31276, + 31277, + 31278, + 31279, + 31280, + 31281, + 31282, + 31283, + 31284, + 31285, + 31286, + 31287, + 31288, + 31289, + 31290, + 31291, + 31292, + 31293, + 31294, + 31295, + 31296, + 31297, + 31298, + 31299, + 31300, + 31301, + 31302, + 31303, + 31304, + 31305, + 31306, + 31307, + 31308, + 31309, + 31310, + 31311, + 31312, + 31313, + 31314, + 31315, + 31316, + 31317, + 31318, + 31319, + 31320, + 31321, + 31322, + 31323, + 31324, + 31325, + 31326, + 31327, + 31328, + 31329, + 31330, + 31331, + 31332, + 31333, + 31334, + 31335, + 31336, + 31337, + 31338, + 31339, + 31340, + 31341, + 31342, + 31343, + 31344, + 31345, + 31346, + 31347, + 31348, + 31349, + 31350, + 31351, + 31352, + 31353, + 31354, + 31355, + 31356, + 31357, + 31358, + 31359, + 31360, + 31361, + 31362, + 31363, + 31364, + 31365, + 31366, + 31367, + 31368, + 31369, + 31370, + 31371, + 31372, + 31373, + 31374, + 31375, + 31376, + 31377, + 31378, + 31379, + 31380, + 31381, + 31382, + 31383, + 31384, + 31385, + 31386, + 31387, + 31388, + 31389, + 31390, + 31391, + 31392, + 31393, + 31394, + 31395, + 31396, + 31397, + 31398, + 31399, + 31400, + 31401, + 31402, + 31403, + 31404, + 31405, + 31406, + 31407, + 31408, + 31409, + 31410, + 31411, + 31412, + 31413, + 31414, + 31415, + 31416, + 31417, + 31418, + 31419, + 31420, + 31421, + 31422, + 31423, + 31424, + 31425, + 31426, + 31427, + 31428, + 31429, + 31430, + 31431, + 31432, + 31433, + 31434, + 31435, + 31436, + 31437, + 31438, + 31439, + 31440, + 31441, + 31442, + 31443, + 31444, + 31445, + 31446, + 31447, + 31448, + 31449, + 31450, + 31451, + 31452, + 31453, + 31454, + 31455, + 31456, + 31457, + 31458, + 31459, + 31460, + 31461, + 31462, + 31463, + 31464, + 31465, + 31466, + 31467, + 31468, + 31469, + 31470, + 31471, + 31472, + 31473, + 31474, + 31475, + 31476, + 31477, + 31478, + 31479, + 31480, + 31481, + 31482, + 31483, + 31484, + 31485, + 31486, + 31487, + 31488, + 31489, + 31490, + 31491, + 31492, + 31493, + 31494, + 31495, + 31496, + 31497, + 31498, + 31499, + 31500, + 31501, + 31502, + 31503, + 31504, + 31505, + 31506, + 31507, + 31508, + 31509, + 31510, + 31511, + 31512, + 31513, + 31514, + 31515, + 31516, + 31517, + 31518, + 31519, + 31520, + 31521, + 31522, + 31523, + 31524, + 31525, + 31526, + 31527, + 31528, + 31529, + 31530, + 31531, + 31532, + 31533, + 31534, + 31535, + 31536, + 31537, + 31538, + 31539, + 31540, + 31541, + 31542, + 31543, + 31544, + 31545, + 31546, + 31547, + 31548, + 31549, + 31550, + 31551, + 31552, + 31553, + 31554, + 31555, + 31556, + 31557, + 31558, + 31559, + 31560, + 31561, + 31562, + 31563, + 31564, + 31565, + 31566, + 31567, + 31568, + 31569, + 31570, + 31571, + 31572, + 31573, + 31574, + 31575, + 31576, + 31577, + 31578, + 31579, + 31580, + 31581, + 31582, + 31583, + 31584, + 31585, + 31586, + 31587, + 31588, + 31589, + 31590, + 31591, + 31592, + 31593, + 31594, + 31595, + 31596, + 31597, + 31598, + 31599, + 31600, + 31601, + 31602, + 31603, + 31604, + 31605, + 31606, + 31607, + 31608, + 31609, + 31610, + 31611, + 31612, + 31613, + 31614, + 31615, + 31616, + 31617, + 31618, + 31619, + 31620, + 31621, + 31622, + 31623, + 31624, + 31625, + 31626, + 31627, + 31628, + 31629, + 31630, + 31631, + 31632, + 31633, + 31634, + 31635, + 31636, + 31637, + 31638, + 31639, + 31640, + 31641, + 31642, + 31643, + 31644, + 31645, + 31646, + 31647, + 31648, + 31649, + 31650, + 31651, + 31652, + 31653, + 31654, + 31655, + 31656, + 31657, + 31658, + 31659, + 31660, + 31661, + 31662, + 31663, + 31664, + 31665, + 31666, + 31667, + 31668, + 31669, + 31670, + 31671, + 31672, + 31673, + 31674, + 31675, + 31676, + 31677, + 31678, + 31679, + 31680, + 31681, + 31682, + 31683, + 31684, + 31685, + 31686, + 31687, + 31688, + 31689, + 31690, + 31691, + 31692, + 31693, + 31694, + 31695, + 31696, + 31697, + 31698, + 31699, + 31700, + 31701, + 31702, + 31703, + 31704, + 31705, + 31706, + 31707, + 31708, + 31709, + 31710, + 31711, + 31712, + 31713, + 31714, + 31715, + 31716, + 31717, + 31718, + 31719, + 31720, + 31721, + 31722, + 31723, + 31724, + 31725, + 31726, + 31727, + 31728, + 31729, + 31730, + 31731, + 31732, + 31733, + 31734, + 31735, + 31736, + 31737, + 31738, + 31739, + 31740, + 31741, + 31742, + 31743, + 31744, + 31745, + 31746, + 31747, + 31748, + 31749, + 31750, + 31751, + 31752, + 31753, + 31754, + 31755, + 31756, + 31757, + 31758, + 31759, + 31760, + 31761, + 31762, + 31763, + 31764, + 31765, + 31766, + 31767, + 31768, + 31769, + 31770, + 31771, + 31772, + 31773, + 31774, + 31775, + 31776, + 31777, + 31778, + 31779, + 31780, + 31781, + 31782, + 31783, + 31784, + 31785, + 31786, + 31787, + 31788, + 31789, + 31790, + 31791, + 31792, + 31793, + 31794, + 31795, + 31796, + 31797, + 31798, + 31799, + 31800, + 31801, + 31802, + 31803, + 31804, + 31805, + 31806, + 31807, + 31808, + 31809, + 31810, + 31811, + 31812, + 31813, + 31814, + 31815, + 31816, + 31817, + 31818, + 31819, + 31820, + 31821, + 31822, + 31823, + 31824, + 31825, + 31826, + 31827, + 31828, + 31829, + 31830, + 31831, + 31832, + 31833, + 31834, + 31835, + 31836, + 31837, + 31838, + 31839, + 31840, + 31841, + 31842, + 31843, + 31844, + 31845, + 31846, + 31847, + 31848, + 31849, + 31850, + 31851, + 31852, + 31853, + 31854, + 31855, + 31856, + 31857, + 31858, + 31859, + 31860, + 31861, + 31862, + 31863, + 31864, + 31865, + 31866, + 31867, + 31868, + 31869, + 31870, + 31871, + 31872, + 31873, + 31874, + 31875, + 31876, + 31877, + 31878, + 31879, + 31880, + 31881, + 31882, + 31883, + 31884, + 31885, + 31886, + 31887, + 31888, + 31889, + 31890, + 31891, + 31892, + 31893, + 31894, + 31895, + 31896, + 31897, + 31898, + 31899, + 31900, + 31901, + 31902, + 31903, + 31904, + 31905, + 31906, + 31907, + 31908, + 31909, + 31910, + 31911, + 31912, + 31913, + 31914, + 31915, + 31916, + 31917, + 31918, + 31919, + 31920, + 31921, + 31922, + 31923, + 31924, + 31925, + 31926, + 31927, + 31928, + 31929, + 31930, + 31931, + 31932, + 31933, + 31934, + 31935, + 31936, + 31937, + 31938, + 31939, + 31940, + 31941, + 31942, + 31943, + 31944, + 31945, + 31946, + 31947, + 31948, + 31949, + 31950, + 31951, + 31952, + 31953, + 31954, + 31955, + 31956, + 31957, + 31958, + 31959, + 31960, + 31961, + 31962, + 31963, + 31964, + 31965, + 31966, + 31967, + 31968, + 31969, + 31970, + 31971, + 31972, + 31973, + 31974, + 31975, + 31976, + 31977, + 31978, + 31979, + 31980, + 31981, + 31982, + 31983, + 31984, + 31985, + 31986, + 31987, + 31988, + 31989, + 31990, + 31991, + 31992, + 31993, + 31994, + 31995, + 31996, + 31997, + 31998, + 31999, + 32000, + 32001, + 32002, + 32003, + 32004, + 32005, + 32006, + 32007, + 32008, + 32009, + 32010, + 32011, + 32012, + 32013, + 32014, + 32015, + 32016, + 32017, + 32018, + 32019, + 32020, + 32021, + 32022, + 32023, + 32024, + 32025, + 32026, + 32027, + 32028, + 32029, + 32030, + 32031, + 32032, + 32033, + 32034, + 32035, + 32036, + 32037, + 32038, + 32039, + 32040, + 32041, + 32042, + 32043, + 32044, + 32045, + 32046, + 32047, + 32048, + 32049, + 32050, + 32051, + 32052, + 32053, + 32054, + 32055, + 32056, + 32057, + 32058, + 32059, + 32060, + 32061, + 32062, + 32063, + 32064, + 32065, + 32066, + 32067, + 32068, + 32069, + 32070, + 32071, + 32072, + 32073, + 32074, + 32075, + 32076, + 32077, + 32078, + 32079, + 32080, + 32081, + 32082, + 32083, + 32084, + 32085, + 32086, + 32087, + 32088, + 32089, + 32090, + 32091, + 32092, + 32093, + 32094, + 32095, + 32096, + 32097, + 32098, + 32099, + 32100, + 32101, + 32102, + 32103, + 32104, + 32105, + 32106, + 32107, + 32108, + 32109, + 32110, + 32111, + 32112, + 32113, + 32114, + 32115, + 32116, + 32117, + 32118, + 32119, + 32120, + 32121, + 32122, + 32123, + 32124, + 32125, + 32126, + 32127, + 32128, + 32129, + 32130, + 32131, + 32132, + 32133, + 32134, + 32135, + 32136, + 32137, + 32138, + 32139, + 32140, + 32141, + 32142, + 32143, + 32144, + 32145, + 32146, + 32147, + 32148, + 32149, + 32150, + 32151, + 32152, + 32153, + 32154, + 32155, + 32156, + 32157, + 32158, + 32159, + 32160, + 32161, + 32162, + 32163, + 32164, + 32165, + 32166, + 32167, + 32168, + 32169, + 32170, + 32171, + 32172, + 32173, + 32174, + 32175, + 32176, + 32177, + 32178, + 32179, + 32180, + 32181, + 32182, + 32183, + 32184, + 32185, + 32186, + 32187, + 32188, + 32189, + 32190, + 32191, + 32192, + 32193, + 32194, + 32195, + 32196, + 32197, + 32198, + 32199, + 32200, + 32201, + 32202, + 32203, + 32204, + 32205, + 32206, + 32207, + 32208, + 32209, + 32210, + 32211, + 32212, + 32213, + 32214, + 32215, + 32216, + 32217, + 32218, + 32219, + 32220, + 32221, + 32222, + 32223, + 32224, + 32225, + 32226, + 32227, + 32228, + 32229, + 32230, + 32231, + 32232, + 32233, + 32234, + 32235, + 32236, + 32237, + 32238, + 32239, + 32240, + 32241, + 32242, + 32243, + 32244, + 32245, + 32246, + 32247, + 32248, + 32249, + 32250, + 32251, + 32252, + 32253, + 32254, + 32255, + 32256, + 32257, + 32258, + 32259, + 32260, + 32261, + 32262, + 32263, + 32264, + 32265, + 32266, + 32267, + 32268, + 32269, + 32270, + 32271, + 32272, + 32273, + 32274, + 32275, + 32276, + 32277, + 32278, + 32279, + 32280, + 32281, + 32282, + 32283, + 32284, + 32285, + 32286, + 32287, + 32288, + 32289, + 32290, + 32291, + 32292, + 32293, + 32294, + 32295, + 32296, + 32297, + 32298, + 32299, + 32300, + 32301, + 32302, + 32303, + 32304, + 32305, + 32306, + 32307, + 32308, + 32309, + 32310, + 32311, + 32312, + 32313, + 32314, + 32315, + 32316, + 32317, + 32318, + 32319, + 32320, + 32321, + 32322, + 32323, + 32324, + 32325, + 32326, + 32327, + 32328, + 32329, + 32330, + 32331, + 32332, + 32333, + 32334, + 32335, + 32336, + 32337, + 32338, + 32339, + 32340, + 32341, + 32342, + 32343, + 32344, + 32345, + 32346, + 32347, + 32348, + 32349, + 32350, + 32351, + 32352, + 32353, + 32354, + 32355, + 32356, + 32357, + 32358, + 32359, + 32360, + 32361, + 32362, + 32363, + 32364, + 32365, + 32366, + 32367, + 32368, + 32369, + 32370, + 32371, + 32372, + 32373, + 32374, + 32375, + 32376, + 32377, + 32378, + 32379, + 32380, + 32381, + 32382, + 32383, + 32384, + 32385, + 32386, + 32387, + 32388, + 32389, + 32390, + 32391, + 32392, + 32393, + 32394, + 32395, + 32396, + 32397, + 32398, + 32399, + 32400, + 32401, + 32402, + 32403, + 32404, + 32405, + 32406, + 32407, + 32408, + 32409, + 32410, + 32411, + 32412, + 32413, + 32414, + 32415, + 32416, + 32417, + 32418, + 32419, + 32420, + 32421, + 32422, + 32423, + 32424, + 32425, + 32426, + 32427, + 32428, + 32429, + 32430, + 32431, + 32432, + 32433, + 32434, + 32435, + 32436, + 32437, + 32438, + 32439, + 32440, + 32441, + 32442, + 32443, + 32444, + 32445, + 32446, + 32447, + 32448, + 32449, + 32450, + 32451, + 32452, + 32453, + 32454, + 32455, + 32456, + 32457, + 32458, + 32459, + 32460, + 32461, + 32462, + 32463, + 32464, + 32465, + 32466, + 32467, + 32468, + 32469, + 32470, + 32471, + 32472, + 32473, + 32474, + 32475, + 32476, + 32477, + 32478, + 32479, + 32480, + 32481, + 32482, + 32483, + 32484, + 32485, + 32486, + 32487, + 32488, + 32489, + 32490, + 32491, + 32492, + 32493, + 32494, + 32495, + 32496, + 32497, + 32498, + 32499, + 32500, + 32501, + 32502, + 32503, + 32504, + 32505, + 32506, + 32507, + 32508, + 32509, + 32510, + 32511, + 32512, + 32513, + 32514, + 32515, + 32516, + 32517, + 32518, + 32519, + 32520, + 32521, + 32522, + 32523, + 32524, + 32525, + 32526, + 32527, + 32528, + 32529, + 32530, + 32531, + 32532, + 32533, + 32534, + 32535, + 32536, + 32537, + 32538, + 32539, + 32540, + 32541, + 32542, + 32543, + 32544, + 32545, + 32546, + 32547, + 32548, + 32549, + 32550, + 32551, + 32552, + 32553, + 32554, + 32555, + 32556, + 32557, + 32558, + 32559, + 32560, + 32561, + 32562, + 32563, + 32564, + 32565, + 32566, + 32567, + 32568, + 32569, + 32570, + 32571, + 32572, + 32573, + 32574, + 32575, + 32576, + 32577, + 32578, + 32579, + 32580, + 32581, + 32582, + 32583, + 32584, + 32585, + 32586, + 32587, + 32588, + 32589, + 32590, + 32591, + 32592, + 32593, + 32594, + 32595, + 32596, + 32597, + 32598, + 32599, + 32600, + 32601, + 32602, + 32603, + 32604, + 32605, + 32606, + 32607, + 32608, + 32609, + 32610, + 32611, + 32612, + 32613, + 32614, + 32615, + 32616, + 32617, + 32618, + 32619, + 32620, + 32621, + 32622, + 32623, + 32624, + 32625, + 32626, + 32627, + 32628, + 32629, + 32630, + 32631, + 32632, + 32633, + 32634, + 32635, + 32636, + 32637, + 32638, + 32639, + 32640, + 32641, + 32642, + 32643, + 32644, + 32645, + 32646, + 32647, + 32648, + 32649, + 32650, + 32651, + 32652, + 32653, + 32654, + 32655, + 32656, + 32657, + 32658, + 32659, + 32660, + 32661, + 32662, + 32663, + 32664, + 32665, + 32666, + 32667, + 32668, + 32669, + 32670, + 32671, + 32672, + 32673, + 32674, + 32675, + 32676, + 32677, + 32678, + 32679, + 32680, + 32681, + 32682, + 32683, + 32684, + 32685, + 32686, + 32687, + 32688, + 32689, + 32690, + 32691, + 32692, + 32693, + 32694, + 32695, + 32696, + 32697, + 32698, + 32699, + 32700, + 32701, + 32702, + 32703, + 32704, + 32705, + 32706, + 32707, + 32708, + 32709, + 32710, + 32711, + 32712, + 32713, + 32714, + 32715, + 32716, + 32717, + 32718, + 32719, + 32720, + 32721, + 32722, + 32723, + 32724, + 32725, + 32726, + 32727, + 32728, + 32729, + 32730, + 32731, + 32732, + 32733, + 32734, + 32735, + 32736, + 32737, + 32738, + 32739, + 32740, + 32741, + 32742, + 32743, + 32744, + 32745, + 32746, + 32747, + 32748, + 32749, + 32750, + 32751, + 32752, + 32753, + 32754, + 32755, + 32756, + 32757, + 32758, + 32759, + 32760, + 32761, + 32762, + 32763, + 32764, + 32765, + 32766, + 32767, + 32768, + 32769, + 32770, + 32771, + 32772, + 32773, + 32774, + 32775, + 32776, + 32777, + 32778, + 32779, + 32780, + 32781, + 32782, + 32783, + 32784, + 32785, + 32786, + 32787, + 32788, + 32789, + 32790, + 32791, + 32792, + 32793, + 32794, + 32795, + 32796, + 32797, + 32798, + 32799, + 32800, + 32801, + 32802, + 32803, + 32804, + 32805, + 32806, + 32807, + 32808, + 32809, + 32810, + 32811, + 32812, + 32813, + 32814, + 32815, + 32816, + 32817, + 32818, + 32819, + 32820, + 32821, + 32822, + 32823, + 32824, + 32825, + 32826, + 32827, + 32828, + 32829, + 32830, + 32831, + 32832, + 32833, + 32834, + 32835, + 32836, + 32837, + 32838, + 32839, + 32840, + 32841, + 32842, + 32843, + 32844, + 32845, + 32846, + 32847, + 32848, + 32849, + 32850, + 32851, + 32852, + 32853, + 32854, + 32855, + 32856, + 32857, + 32858, + 32859, + 32860, + 32861, + 32862, + 32863, + 32864, + 32865, + 32866, + 32867, + 32868, + 32869, + 32870, + 32871, + 32872, + 32873, + 32874, + 32875, + 32876, + 32877, + 32878, + 32879, + 32880, + 32881, + 32882, + 32883, + 32884, + 32885, + 32886, + 32887, + 32888, + 32889, + 32890, + 32891, + 32892, + 32893, + 32894, + 32895, + 32896, + 32897, + 32898, + 32899, + 32900, + 32901, + 32902, + 32903, + 32904, + 32905, + 32906, + 32907, + 32908, + 32909, + 32910, + 32911, + 32912, + 32913, + 32914, + 32915, + 32916, + 32917, + 32918, + 32919, + 32920, + 32921, + 32922, + 32923, + 32924, + 32925, + 32926, + 32927, + 32928, + 32929, + 32930, + 32931, + 32932, + 32933, + 32934, + 32935, + 32936, + 32937, + 32938, + 32939, + 32940, + 32941, + 32942, + 32943, + 32944, + 32945, + 32946, + 32947, + 32948, + 32949, + 32950, + 32951, + 32952, + 32953, + 32954, + 32955, + 32956, + 32957, + 32958, + 32959, + 32960, + 32961, + 32962, + 32963, + 32964, + 32965, + 32966, + 32967, + 32968, + 32969, + 32970, + 32971, + 32972, + 32973, + 32974, + 32975, + 32976, + 32977, + 32978, + 32979, + 32980, + 32981, + 32982, + 32983, + 32984, + 32985, + 32986, + 32987, + 32988, + 32989, + 32990, + 32991, + 32992, + 32993, + 32994, + 32995, + 32996, + 32997, + 32998, + 32999, + 33000, + 33001, + 33002, + 33003, + 33004, + 33005, + 33006, + 33007, + 33008, + 33009, + 33010, + 33011, + 33012, + 33013, + 33014, + 33015, + 33016, + 33017, + 33018, + 33019, + 33020, + 33021, + 33022, + 33023, + 33024, + 33025, + 33026, + 33027, + 33028, + 33029, + 33030, + 33031, + 33032, + 33033, + 33034, + 33035, + 33036, + 33037, + 33038, + 33039, + 33040, + 33041, + 33042, + 33043, + 33044, + 33045, + 33046, + 33047, + 33048, + 33049, + 33050, + 33051, + 33052, + 33053, + 33054, + 33055, + 33056, + 33057, + 33058, + 33059, + 33060, + 33061, + 33062, + 33063, + 33064, + 33065, + 33066, + 33067, + 33068, + 33069, + 33070, + 33071, + 33072, + 33073, + 33074, + 33075, + 33076, + 33077, + 33078, + 33079, + 33080, + 33081, + 33082, + 33083, + 33084, + 33085, + 33086, + 33087, + 33088, + 33089, + 33090, + 33091, + 33092, + 33093, + 33094, + 33095, + 33096, + 33097, + 33098, + 33099, + 33100, + 33101, + 33102, + 33103, + 33104, + 33105, + 33106, + 33107, + 33108, + 33109, + 33110, + 33111, + 33112, + 33113, + 33114, + 33115, + 33116, + 33117, + 33118, + 33119, + 33120, + 33121, + 33122, + 33123, + 33124, + 33125, + 33126, + 33127, + 33128, + 33129, + 33130, + 33131, + 33132, + 33133, + 33134, + 33135, + 33136, + 33137, + 33138, + 33139, + 33140, + 33141, + 33142, + 33143, + 33144, + 33145, + 33146, + 33147, + 33148, + 33149, + 33150, + 33151, + 33152, + 33153, + 33154, + 33155, + 33156, + 33157, + 33158, + 33159, + 33160, + 33161, + 33162, + 33163, + 33164, + 33165, + 33166, + 33167, + 33168, + 33169, + 33170, + 33171, + 33172, + 33173, + 33174, + 33175, + 33176, + 33177, + 33178, + 33179, + 33180, + 33181, + 33182, + 33183, + 33184, + 33185, + 33186, + 33187, + 33188, + 33189, + 33190, + 33191, + 33192, + 33193, + 33194, + 33195, + 33196, + 33197, + 33198, + 33199, + 33200, + 33201, + 33202, + 33203, + 33204, + 33205, + 33206, + 33207, + 33208, + 33209, + 33210, + 33211, + 33212, + 33213, + 33214, + 33215, + 33216, + 33217, + 33218, + 33219, + 33220, + 33221, + 33222, + 33223, + 33224, + 33225, + 33226, + 33227, + 33228, + 33229, + 33230, + 33231, + 33232, + 33233, + 33234, + 33235, + 33236, + 33237, + 33238, + 33239, + 33240, + 33241, + 33242, + 33243, + 33244, + 33245, + 33246, + 33247, + 33248, + 33249, + 33250, + 33251, + 33252, + 33253, + 33254, + 33255, + 33256, + 33257, + 33258, + 33259, + 33260, + 33261, + 33262, + 33263, + 33264, + 33265, + 33266, + 33267, + 33268, + 33269, + 33270, + 33271, + 33272, + 33273, + 33274, + 33275, + 33276, + 33277, + 33278, + 33279, + 33280, + 33281, + 33282, + 33283, + 33284, + 33285, + 33286, + 33287, + 33288, + 33289, + 33290, + 33291, + 33292, + 33293, + 33294, + 33295, + 33296, + 33297, + 33298, + 33299, + 33300, + 33301, + 33302, + 33303, + 33304, + 33305, + 33306, + 33307, + 33308, + 33309, + 33310, + 33311, + 33312, + 33313, + 33314, + 33315, + 33316, + 33317, + 33318, + 33319, + 33320, + 33321, + 33322, + 33323, + 33324, + 33325, + 33326, + 33327, + 33328, + 33329, + 33330, + 33331, + 33332, + 33333, + 33334, + 33335, + 33336, + 33337, + 33338, + 33339, + 33340, + 33341, + 33342, + 33343, + 33344, + 33345, + 33346, + 33347, + 33348, + 33349, + 33350, + 33351, + 33352, + 33353, + 33354, + 33355, + 33356, + 33357, + 33358, + 33359, + 33360, + 33361, + 33362, + 33363, + 33364, + 33365, + 33366, + 33367, + 33368, + 33369, + 33370, + 33371, + 33372, + 33373, + 33374, + 33375, + 33376, + 33377, + 33378, + 33379, + 33380, + 33381, + 33382, + 33383, + 33384, + 33385, + 33386, + 33387, + 33388, + 33389, + 33390, + 33391, + 33392, + 33393, + 33394, + 33395, + 33396, + 33397, + 33398, + 33399, + 33400, + 33401, + 33402, + 33403, + 33404, + 33405, + 33406, + 33407, + 33408, + 33409, + 33410, + 33411, + 33412, + 33413, + 33414, + 33415, + 33416, + 33417, + 33418, + 33419, + 33420, + 33421, + 33422, + 33423, + 33424, + 33425, + 33426, + 33427, + 33428, + 33429, + 33430, + 33431, + 33432, + 33433, + 33434, + 33435, + 33436, + 33437, + 33438, + 33439, + 33440, + 33441, + 33442, + 33443, + 33444, + 33445, + 33446, + 33447, + 33448, + 33449, + 33450, + 33451, + 33452, + 33453, + 33454, + 33455, + 33456, + 33457, + 33458, + 33459, + 33460, + 33461, + 33462, + 33463, + 33464, + 33465, + 33466, + 33467, + 33468, + 33469, + 33470, + 33471, + 33472, + 33473, + 33474, + 33475, + 33476, + 33477, + 33478, + 33479, + 33480, + 33481, + 33482, + 33483, + 33484, + 33485, + 33486, + 33487, + 33488, + 33489, + 33490, + 33491, + 33492, + 33493, + 33494, + 33495, + 33496, + 33497, + 33498, + 33499, + 33500, + 33501, + 33502, + 33503, + 33504, + 33505, + 33506, + 33507, + 33508, + 33509, + 33510, + 33511, + 33512, + 33513, + 33514, + 33515, + 33516, + 33517, + 33518, + 33519, + 33520, + 33521, + 33522, + 33523, + 33524, + 33525, + 33526, + 33527, + 33528, + 33529, + 33530, + 33531, + 33532, + 33533, + 33534, + 33535, + 33536, + 33537, + 33538, + 33539, + 33540, + 33541, + 33542, + 33543, + 33544, + 33545, + 33546, + 33547, + 33548, + 33549, + 33550, + 33551, + 33552, + 33553, + 33554, + 33555, + 33556, + 33557, + 33558, + 33559, + 33560, + 33561, + 33562, + 33563, + 33564, + 33565, + 33566, + 33567, + 33568, + 33569, + 33570, + 33571, + 33572, + 33573, + 33574, + 33575, + 33576, + 33577, + 33578, + 33579, + 33580, + 33581, + 33582, + 33583, + 33584, + 33585, + 33586, + 33587, + 33588, + 33589, + 33590, + 33591, + 33592, + 33593, + 33594, + 33595, + 33596, + 33597, + 33598, + 33599, + 33600, + 33601, + 33602, + 33603, + 33604, + 33605, + 33606, + 33607, + 33608, + 33609, + 33610, + 33611, + 33612, + 33613, + 33614, + 33615, + 33616, + 33617, + 33618, + 33619, + 33620, + 33621, + 33622, + 33623, + 33624, + 33625, + 33626, + 33627, + 33628, + 33629, + 33630, + 33631, + 33632, + 33633, + 33634, + 33635, + 33636, + 33637, + 33638, + 33639, + 33640, + 33641, + 33642, + 33643, + 33644, + 33645, + 33646, + 33647, + 33648, + 33649, + 33650, + 33651, + 33652, + 33653, + 33654, + 33655, + 33656, + 33657, + 33658, + 33659, + 33660, + 33661, + 33662, + 33663, + 33664, + 33665, + 33666, + 33667, + 33668, + 33669, + 33670, + 33671, + 33672, + 33673, + 33674, + 33675, + 33676, + 33677, + 33678, + 33679, + 33680, + 33681, + 33682, + 33683, + 33684, + 33685, + 33686, + 33687, + 33688, + 33689, + 33690, + 33691, + 33692, + 33693, + 33694, + 33695, + 33696, + 33697, + 33698, + 33699, + 33700, + 33701, + 33702, + 33703, + 33704, + 33705, + 33706, + 33707, + 33708, + 33709, + 33710, + 33711, + 33712, + 33713, + 33714, + 33715, + 33716, + 33717, + 33718, + 33719, + 33720, + 33721, + 33722, + 33723, + 33724, + 33725, + 33726, + 33727, + 33728, + 33729, + 33730, + 33731, + 33732, + 33733, + 33734, + 33735, + 33736, + 33737, + 33738, + 33739, + 33740, + 33741, + 33742, + 33743, + 33744, + 33745, + 33746, + 33747, + 33748, + 33749, + 33750, + 33751, + 33752, + 33753, + 33754, + 33755, + 33756, + 33757, + 33758, + 33759, + 33760, + 33761, + 33762, + 33763, + 33764, + 33765, + 33766, + 33767, + 33768, + 33769, + 33770, + 33771, + 33772, + 33773, + 33774, + 33775, + 33776, + 33777, + 33778, + 33779, + 33780, + 33781, + 33782, + 33783, + 33784, + 33785, + 33786, + 33787, + 33788, + 33789, + 33790, + 33791, + 33792, + 33793, + 33794, + 33795, + 33796, + 33797, + 33798, + 33799, + 33800, + 33801, + 33802, + 33803, + 33804, + 33805, + 33806, + 33807, + 33808, + 33809, + 33810, + 33811, + 33812, + 33813, + 33814, + 33815, + 33816, + 33817, + 33818, + 33819, + 33820, + 33821, + 33822, + 33823, + 33824, + 33825, + 33826, + 33827, + 33828, + 33829, + 33830, + 33831, + 33832, + 33833, + 33834, + 33835, + 33836, + 33837, + 33838, + 33839, + 33840, + 33841, + 33842, + 33843, + 33844, + 33845, + 33846, + 33847, + 33848, + 33849, + 33850, + 33851, + 33852, + 33853, + 33854, + 33855, + 33856, + 33857, + 33858, + 33859, + 33860, + 33861, + 33862, + 33863, + 33864, + 33865, + 33866, + 33867, + 33868, + 33869, + 33870, + 33871, + 33872, + 33873, + 33874, + 33875, + 33876, + 33877, + 33878, + 33879, + 33880, + 33881, + 33882, + 33883, + 33884, + 33885, + 33886, + 33887, + 33888, + 33889, + 33890, + 33891, + 33892, + 33893, + 33894, + 33895, + 33896, + 33897, + 33898, + 33899, + 33900, + 33901, + 33902, + 33903, + 33904, + 33905, + 33906, + 33907, + 33908, + 33909, + 33910, + 33911, + 33912, + 33913, + 33914, + 33915, + 33916, + 33917, + 33918, + 33919, + 33920, + 33921, + 33922, + 33923, + 33924, + 33925, + 33926, + 33927, + 33928, + 33929, + 33930, + 33931, + 33932, + 33933, + 33934, + 33935, + 33936, + 33937, + 33938, + 33939, + 33940, + 33941, + 33942, + 33943, + 33944, + 33945, + 33946, + 33947, + 33948, + 33949, + 33950, + 33951, + 33952, + 33953, + 33954, + 33955, + 33956, + 33957, + 33958, + 33959, + 33960, + 33961, + 33962, + 33963, + 33964, + 33965, + 33966, + 33967, + 33968, + 33969, + 33970, + 33971, + 33972, + 33973, + 33974, + 33975, + 33976, + 33977, + 33978, + 33979, + 33980, + 33981, + 33982, + 33983, + 33984, + 33985, + 33986, + 33987, + 33988, + 33989, + 33990, + 33991, + 33992, + 33993, + 33994, + 33995, + 33996, + 33997, + 33998, + 33999, + 34000, + 34001, + 34002, + 34003, + 34004, + 34005, + 34006, + 34007, + 34008, + 34009, + 34010, + 34011, + 34012, + 34013, + 34014, + 34015, + 34016, + 34017, + 34018, + 34019, + 34020, + 34021, + 34022, + 34023, + 34024, + 34025, + 34026, + 34027, + 34028, + 34029, + 34030, + 34031, + 34032, + 34033, + 34034, + 34035, + 34036, + 34037, + 34038, + 34039, + 34040, + 34041, + 34042, + 34043, + 34044, + 34045, + 34046, + 34047, + 34048, + 34049, + 34050, + 34051, + 34052, + 34053, + 34054, + 34055, + 34056, + 34057, + 34058, + 34059, + 34060, + 34061, + 34062, + 34063, + 34064, + 34065, + 34066, + 34067, + 34068, + 34069, + 34070, + 34071, + 34072, + 34073, + 34074, + 34075, + 34076, + 34077, + 34078, + 34079, + 34080, + 34081, + 34082, + 34083, + 34084, + 34085, + 34086, + 34087, + 34088, + 34089, + 34090, + 34091, + 34092, + 34093, + 34094, + 34095, + 34096, + 34097, + 34098, + 34099, + 34100, + 34101, + 34102, + 34103, + 34104, + 34105, + 34106, + 34107, + 34108, + 34109, + 34110, + 34111, + 34112, + 34113, + 34114, + 34115, + 34116, + 34117, + 34118, + 34119, + 34120, + 34121, + 34122, + 34123, + 34124, + 34125, + 34126, + 34127, + 34128, + 34129, + 34130, + 34131, + 34132, + 34133, + 34134, + 34135, + 34136, + 34137, + 34138, + 34139, + 34140, + 34141, + 34142, + 34143, + 34144, + 34145, + 34146, + 34147, + 34148, + 34149, + 34150, + 34151, + 34152, + 34153, + 34154, + 34155, + 34156, + 34157, + 34158, + 34159, + 34160, + 34161, + 34162, + 34163, + 34164, + 34165, + 34166, + 34167, + 34168, + 34169, + 34170, + 34171, + 34172, + 34173, + 34174, + 34175, + 34176, + 34177, + 34178, + 34179, + 34180, + 34181, + 34182, + 34183, + 34184, + 34185, + 34186, + 34187, + 34188, + 34189, + 34190, + 34191, + 34192, + 34193, + 34194, + 34195, + 34196, + 34197, + 34198, + 34199, + 34200, + 34201, + 34202, + 34203, + 34204, + 34205, + 34206, + 34207, + 34208, + 34209, + 34210, + 34211, + 34212, + 34213, + 34214, + 34215, + 34216, + 34217, + 34218, + 34219, + 34220, + 34221, + 34222, + 34223, + 34224, + 34225, + 34226, + 34227, + 34228, + 34229, + 34230, + 34231, + 34232, + 34233, + 34234, + 34235, + 34236, + 34237, + 34238, + 34239, + 34240, + 34241, + 34242, + 34243, + 34244, + 34245, + 34246, + 34247, + 34248, + 34249, + 34250, + 34251, + 34252, + 34253, + 34254, + 34255, + 34256, + 34257, + 34258, + 34259, + 34260, + 34261, + 34262, + 34263, + 34264, + 34265, + 34266, + 34267, + 34268, + 34269, + 34270, + 34271, + 34272, + 34273, + 34274, + 34275, + 34276, + 34277, + 34278, + 34279, + 34280, + 34281, + 34282, + 34283, + 34284, + 34285, + 34286, + 34287, + 34288, + 34289, + 34290, + 34291, + 34292, + 34293, + 34294, + 34295, + 34296, + 34297, + 34298, + 34299, + 34300, + 34301, + 34302, + 34303, + 34304, + 34305, + 34306, + 34307, + 34308, + 34309, + 34310, + 34311, + 34312, + 34313, + 34314, + 34315, + 34316, + 34317, + 34318, + 34319, + 34320, + 34321, + 34322, + 34323, + 34324, + 34325, + 34326, + 34327, + 34328, + 34329, + 34330, + 34331, + 34332, + 34333, + 34334, + 34335, + 34336, + 34337, + 34338, + 34339, + 34340, + 34341, + 34342, + 34343, + 34344, + 34345, + 34346, + 34347, + 34348, + 34349, + 34350, + 34351, + 34352, + 34353, + 34354, + 34355, + 34356, + 34357, + 34358, + 34359, + 34360, + 34361, + 34362, + 34363, + 34364, + 34365, + 34366, + 34367, + 34368, + 34369, + 34370, + 34371, + 34372, + 34373, + 34374, + 34375, + 34376, + 34377, + 34378, + 34379, + 34380, + 34381, + 34382, + 34383, + 34384, + 34385, + 34386, + 34387, + 34388, + 34389, + 34390, + 34391, + 34392, + 34393, + 34394, + 34395, + 34396, + 34397, + 34398, + 34399, + 34400, + 34401, + 34402, + 34403, + 34404, + 34405, + 34406, + 34407, + 34408, + 34409, + 34410, + 34411, + 34412, + 34413, + 34414, + 34415, + 34416, + 34417, + 34418, + 34419, + 34420, + 34421, + 34422, + 34423, + 34424, + 34425, + 34426, + 34427, + 34428, + 34429, + 34430, + 34431, + 34432, + 34433, + 34434, + 34435, + 34436, + 34437, + 34438, + 34439, + 34440, + 34441, + 34442, + 34443, + 34444, + 34445, + 34446, + 34447, + 34448, + 34449, + 34450, + 34451, + 34452, + 34453, + 34454, + 34455, + 34456, + 34457, + 34458, + 34459, + 34460, + 34461, + 34462, + 34463, + 34464, + 34465, + 34466, + 34467, + 34468, + 34469, + 34470, + 34471, + 34472, + 34473, + 34474, + 34475, + 34476, + 34477, + 34478, + 34479, + 34480, + 34481, + 34482, + 34483, + 34484, + 34485, + 34486, + 34487, + 34488, + 34489, + 34490, + 34491, + 34492, + 34493, + 34494, + 34495, + 34496, + 34497, + 34498, + 34499, + 34500, + 34501, + 34502, + 34503, + 34504, + 34505, + 34506, + 34507, + 34508, + 34509, + 34510, + 34511, + 34512, + 34513, + 34514, + 34515, + 34516, + 34517, + 34518, + 34519, + 34520, + 34521, + 34522, + 34523, + 34524, + 34525, + 34526, + 34527, + 34528, + 34529, + 34530, + 34531, + 34532, + 34533, + 34534, + 34535, + 34536, + 34537, + 34538, + 34539, + 34540, + 34541, + 34542, + 34543, + 34544, + 34545, + 34546, + 34547, + 34548, + 34549, + 34550, + 34551, + 34552, + 34553, + 34554, + 34555, + 34556, + 34557, + 34558, + 34559, + 34560, + 34561, + 34562, + 34563, + 34564, + 34565, + 34566, + 34567, + 34568, + 34569, + 34570, + 34571, + 34572, + 34573, + 34574, + 34575, + 34576, + 34577, + 34578, + 34579, + 34580, + 34581, + 34582, + 34583, + 34584, + 34585, + 34586, + 34587, + 34588, + 34589, + 34590, + 34591, + 34592, + 34593, + 34594, + 34595, + 34596, + 34597, + 34598, + 34599, + 34600, + 34601, + 34602, + 34603, + 34604, + 34605, + 34606, + 34607, + 34608, + 34609, + 34610, + 34611, + 34612, + 34613, + 34614, + 34615, + 34616, + 34617, + 34618, + 34619, + 34620, + 34621, + 34622, + 34623, + 34624, + 34625, + 34626, + 34627, + 34628, + 34629, + 34630, + 34631, + 34632, + 34633, + 34634, + 34635, + 34636, + 34637, + 34638, + 34639, + 34640, + 34641, + 34642, + 34643, + 34644, + 34645, + 34646, + 34647, + 34648, + 34649, + 34650, + 34651, + 34652, + 34653, + 34654, + 34655, + 34656, + 34657, + 34658, + 34659, + 34660, + 34661, + 34662, + 34663, + 34664, + 34665, + 34666, + 34667, + 34668, + 34669, + 34670, + 34671, + 34672, + 34673, + 34674, + 34675, + 34676, + 34677, + 34678, + 34679, + 34680, + 34681, + 34682, + 34683, + 34684, + 34685, + 34686, + 34687, + 34688, + 34689, + 34690, + 34691, + 34692, + 34693, + 34694, + 34695, + 34696, + 34697, + 34698, + 34699, + 34700, + 34701, + 34702, + 34703, + 34704, + 34705, + 34706, + 34707, + 34708, + 34709, + 34710, + 34711, + 34712, + 34713, + 34714, + 34715, + 34716, + 34717, + 34718, + 34719, + 34720, + 34721, + 34722, + 34723, + 34724, + 34725, + 34726, + 34727, + 34728, + 34729, + 34730, + 34731, + 34732, + 34733, + 34734, + 34735, + 34736, + 34737, + 34738, + 34739, + 34740, + 34741, + 34742, + 34743, + 34744, + 34745, + 34746, + 34747, + 34748, + 34749, + 34750, + 34751, + 34752, + 34753, + 34754, + 34755, + 34756, + 34757, + 34758, + 34759, + 34760, + 34761, + 34762, + 34763, + 34764, + 34765, + 34766, + 34767, + 34768, + 34769, + 34770, + 34771, + 34772, + 34773, + 34774, + 34775, + 34776, + 34777, + 34778, + 34779, + 34780, + 34781, + 34782, + 34783, + 34784, + 34785, + 34786, + 34787, + 34788, + 34789, + 34790, + 34791, + 34792, + 34793, + 34794, + 34795, + 34796, + 34797, + 34798, + 34799, + 34800, + 34801, + 34802, + 34803, + 34804, + 34805, + 34806, + 34807, + 34808, + 34809, + 34810, + 34811, + 34812, + 34813, + 34814, + 34815, + 34816, + 34817, + 34818, + 34819, + 34820, + 34821, + 34822, + 34823, + 34824, + 34825, + 34826, + 34827, + 34828, + 34829, + 34830, + 34831, + 34832, + 34833, + 34834, + 34835, + 34836, + 34837, + 34838, + 34839, + 34840, + 34841, + 34842, + 34843, + 34844, + 34845, + 34846, + 34847, + 34848, + 34849, + 34850, + 34851, + 34852, + 34853, + 34854, + 34855, + 34856, + 34857, + 34858, + 34859, + 34860, + 34861, + 34862, + 34863, + 34864, + 34865, + 34866, + 34867, + 34868, + 34869, + 34870, + 34871, + 34872, + 34873, + 34874, + 34875, + 34876, + 34877, + 34878, + 34879, + 34880, + 34881, + 34882, + 34883, + 34884, + 34885, + 34886, + 34887, + 34888, + 34889, + 34890, + 34891, + 34892, + 34893, + 34894, + 34895, + 34896, + 34897, + 34898, + 34899, + 34900, + 34901, + 34902, + 34903, + 34904, + 34905, + 34906, + 34907, + 34908, + 34909, + 34910, + 34911, + 34912, + 34913, + 34914, + 34915, + 34916, + 34917, + 34918, + 34919, + 34920, + 34921, + 34922, + 34923, + 34924, + 34925, + 34926, + 34927, + 34928, + 34929, + 34930, + 34931, + 34932, + 34933, + 34934, + 34935, + 34936, + 34937, + 34938, + 34939, + 34940, + 34941, + 34942, + 34943, + 34944, + 34945, + 34946, + 34947, + 34948, + 34949, + 34950, + 34951, + 34952, + 34953, + 34954, + 34955, + 34956, + 34957, + 34958, + 34959, + 34960, + 34961, + 34962, + 34963, + 34964, + 34965, + 34966, + 34967, + 34968, + 34969, + 34970, + 34971, + 34972, + 34973, + 34974, + 34975, + 34976, + 34977, + 34978, + 34979, + 34980, + 34981, + 34982, + 34983, + 34984, + 34985, + 34986, + 34987, + 34988, + 34989, + 34990, + 34991, + 34992, + 34993, + 34994, + 34995, + 34996, + 34997, + 34998, + 34999, + 35000, + 35001, + 35002, + 35003, + 35004, + 35005, + 35006, + 35007, + 35008, + 35009, + 35010, + 35011, + 35012, + 35013, + 35014, + 35015, + 35016, + 35017, + 35018, + 35019, + 35020, + 35021, + 35022, + 35023, + 35024, + 35025, + 35026, + 35027, + 35028, + 35029, + 35030, + 35031, + 35032, + 35033, + 35034, + 35035, + 35036, + 35037, + 35038, + 35039, + 35040, + 35041, + 35042, + 35043, + 35044, + 35045, + 35046, + 35047, + 35048, + 35049, + 35050, + 35051, + 35052, + 35053, + 35054, + 35055, + 35056, + 35057, + 35058, + 35059, + 35060, + 35061, + 35062, + 35063, + 35064, + 35065, + 35066, + 35067, + 35068, + 35069, + 35070, + 35071, + 35072, + 35073, + 35074, + 35075, + 35076, + 35077, + 35078, + 35079, + 35080, + 35081, + 35082, + 35083, + 35084, + 35085, + 35086, + 35087, + 35088, + 35089, + 35090, + 35091, + 35092, + 35093, + 35094, + 35095, + 35096, + 35097, + 35098, + 35099, + 35100, + 35101, + 35102, + 35103, + 35104, + 35105, + 35106, + 35107, + 35108, + 35109, + 35110, + 35111, + 35112, + 35113, + 35114, + 35115, + 35116, + 35117, + 35118, + 35119, + 35120, + 35121, + 35122, + 35123, + 35124, + 35125, + 35126, + 35127, + 35128, + 35129, + 35130, + 35131, + 35132, + 35133, + 35134, + 35135, + 35136, + 35137, + 35138, + 35139, + 35140, + 35141, + 35142, + 35143, + 35144, + 35145, + 35146, + 35147, + 35148, + 35149, + 35150, + 35151, + 35152, + 35153, + 35154, + 35155, + 35156, + 35157, + 35158, + 35159, + 35160, + 35161, + 35162, + 35163, + 35164, + 35165, + 35166, + 35167, + 35168, + 35169, + 35170, + 35171, + 35172, + 35173, + 35174, + 35175, + 35176, + 35177, + 35178, + 35179, + 35180, + 35181, + 35182, + 35183, + 35184, + 35185, + 35186, + 35187, + 35188, + 35189, + 35190, + 35191, + 35192, + 35193, + 35194, + 35195, + 35196, + 35197, + 35198, + 35199, + 35200, + 35201, + 35202, + 35203, + 35204, + 35205, + 35206, + 35207, + 35208, + 35209, + 35210, + 35211, + 35212, + 35213, + 35214, + 35215, + 35216, + 35217, + 35218, + 35219, + 35220, + 35221, + 35222, + 35223, + 35224, + 35225, + 35226, + 35227, + 35228, + 35229, + 35230, + 35231, + 35232, + 35233, + 35234, + 35235, + 35236, + 35237, + 35238, + 35239, + 35240, + 35241, + 35242, + 35243, + 35244, + 35245, + 35246, + 35247, + 35248, + 35249, + 35250, + 35251, + 35252, + 35253, + 35254, + 35255, + 35256, + 35257, + 35258, + 35259, + 35260, + 35261, + 35262, + 35263, + 35264, + 35265, + 35266, + 35267, + 35268, + 35269, + 35270, + 35271, + 35272, + 35273, + 35274, + 35275, + 35276, + 35277, + 35278, + 35279, + 35280, + 35281, + 35282, + 35283, + 35284, + 35285, + 35286, + 35287, + 35288, + 35289, + 35290, + 35291, + 35292, + 35293, + 35294, + 35295, + 35296, + 35297, + 35298, + 35299, + 35300, + 35301, + 35302, + 35303, + 35304, + 35305, + 35306, + 35307, + 35308, + 35309, + 35310, + 35311, + 35312, + 35313, + 35314, + 35315, + 35316, + 35317, + 35318, + 35319, + 35320, + 35321, + 35322, + 35323, + 35324, + 35325, + 35326, + 35327, + 35328, + 35329, + 35330, + 35331, + 35332, + 35333, + 35334, + 35335, + 35336, + 35337, + 35338, + 35339, + 35340, + 35341, + 35342, + 35343, + 35344, + 35345, + 35346, + 35347, + 35348, + 35349, + 35350, + 35351, + 35352, + 35353, + 35354, + 35355, + 35356, + 35357, + 35358, + 35359, + 35360, + 35361, + 35362, + 35363, + 35364, + 35365, + 35366, + 35367, + 35368, + 35369, + 35370, + 35371, + 35372, + 35373, + 35374, + 35375, + 35376, + 35377, + 35378, + 35379, + 35380, + 35381, + 35382, + 35383, + 35384, + 35385, + 35386, + 35387, + 35388, + 35389, + 35390, + 35391, + 35392, + 35393, + 35394, + 35395, + 35396, + 35397, + 35398, + 35399, + 35400, + 35401, + 35402, + 35403, + 35404, + 35405, + 35406, + 35407, + 35408, + 35409, + 35410, + 35411, + 35412, + 35413, + 35414, + 35415, + 35416, + 35417, + 35418, + 35419, + 35420, + 35421, + 35422, + 35423, + 35424, + 35425, + 35426, + 35427, + 35428, + 35429, + 35430, + 35431, + 35432, + 35433, + 35434, + 35435, + 35436, + 35437, + 35438, + 35439, + 35440, + 35441, + 35442, + 35443, + 35444, + 35445, + 35446, + 35447, + 35448, + 35449, + 35450, + 35451, + 35452, + 35453, + 35454, + 35455, + 35456, + 35457, + 35458, + 35459, + 35460, + 35461, + 35462, + 35463, + 35464, + 35465, + 35466, + 35467, + 35468, + 35469, + 35470, + 35471, + 35472, + 35473, + 35474, + 35475, + 35476, + 35477, + 35478, + 35479, + 35480, + 35481, + 35482, + 35483, + 35484, + 35485, + 35486, + 35487, + 35488, + 35489, + 35490, + 35491, + 35492, + 35493, + 35494, + 35495, + 35496, + 35497, + 35498, + 35499, + 35500, + 35501, + 35502, + 35503, + 35504, + 35505, + 35506, + 35507, + 35508, + 35509, + 35510, + 35511, + 35512, + 35513, + 35514, + 35515, + 35516, + 35517, + 35518, + 35519, + 35520, + 35521, + 35522, + 35523, + 35524, + 35525, + 35526, + 35527, + 35528, + 35529, + 35530, + 35531, + 35532, + 35533, + 35534, + 35535, + 35536, + 35537, + 35538, + 35539, + 35540, + 35541, + 35542, + 35543, + 35544, + 35545, + 35546, + 35547, + 35548, + 35549, + 35550, + 35551, + 35552, + 35553, + 35554, + 35555, + 35556, + 35557, + 35558, + 35559, + 35560, + 35561, + 35562, + 35563, + 35564, + 35565, + 35566, + 35567, + 35568, + 35569, + 35570, + 35571, + 35572, + 35573, + 35574, + 35575, + 35576, + 35577, + 35578, + 35579, + 35580, + 35581, + 35582, + 35583, + 35584, + 35585, + 35586, + 35587, + 35588, + 35589, + 35590, + 35591, + 35592, + 35593, + 35594, + 35595, + 35596, + 35597, + 35598, + 35599, + 35600, + 35601, + 35602, + 35603, + 35604, + 35605, + 35606, + 35607, + 35608, + 35609, + 35610, + 35611, + 35612, + 35613, + 35614, + 35615, + 35616, + 35617, + 35618, + 35619, + 35620, + 35621, + 35622, + 35623, + 35624, + 35625, + 35626, + 35627, + 35628, + 35629, + 35630, + 35631, + 35632, + 35633, + 35634, + 35635, + 35636, + 35637, + 35638, + 35639, + 35640, + 35641, + 35642, + 35643, + 35644, + 35645, + 35646, + 35647, + 35648, + 35649, + 35650, + 35651, + 35652, + 35653, + 35654, + 35655, + 35656, + 35657, + 35658, + 35659, + 35660, + 35661, + 35662, + 35663, + 35664, + 35665, + 35666, + 35667, + 35668, + 35669, + 35670, + 35671, + 35672, + 35673, + 35674, + 35675, + 35676, + 35677, + 35678, + 35679, + 35680, + 35681, + 35682, + 35683, + 35684, + 35685, + 35686, + 35687, + 35688, + 35689, + 35690, + 35691, + 35692, + 35693, + 35694, + 35695, + 35696, + 35697, + 35698, + 35699, + 35700, + 35701, + 35702, + 35703, + 35704, + 35705, + 35706, + 35707, + 35708, + 35709, + 35710, + 35711, + 35712, + 35713, + 35714, + 35715, + 35716, + 35717, + 35718, + 35719, + 35720, + 35721, + 35722, + 35723, + 35724, + 35725, + 35726, + 35727, + 35728, + 35729, + 35730, + 35731, + 35732, + 35733, + 35734, + 35735, + 35736, + 35737, + 35738, + 35739, + 35740, + 35741, + 35742, + 35743, + 35744, + 35745, + 35746, + 35747, + 35748, + 35749, + 35750, + 35751, + 35752, + 35753, + 35754, + 35755, + 35756, + 35757, + 35758, + 35759, + 35760, + 35761, + 35762, + 35763, + 35764, + 35765, + 35766, + 35767, + 35768, + 35769, + 35770, + 35771, + 35772, + 35773, + 35774, + 35775, + 35776, + 35777, + 35778, + 35779, + 35780, + 35781, + 35782, + 35783, + 35784, + 35785, + 35786, + 35787, + 35788, + 35789, + 35790, + 35791, + 35792, + 35793, + 35794, + 35795, + 35796, + 35797, + 35798, + 35799, + 35800, + 35801, + 35802, + 35803, + 35804, + 35805, + 35806, + 35807, + 35808, + 35809, + 35810, + 35811, + 35812, + 35813, + 35814, + 35815, + 35816, + 35817, + 35818, + 35819, + 35820, + 35821, + 35822, + 35823, + 35824, + 35825, + 35826, + 35827, + 35828, + 35829, + 35830, + 35831, + 35832, + 35833, + 35834, + 35835, + 35836, + 35837, + 35838, + 35839, + 35840, + 35841, + 35842, + 35843, + 35844, + 35845, + 35846, + 35847, + 35848, + 35849, + 35850, + 35851, + 35852, + 35853, + 35854, + 35855, + 35856, + 35857, + 35858, + 35859, + 35860, + 35861, + 35862, + 35863, + 35864, + 35865, + 35866, + 35867, + 35868, + 35869, + 35870, + 35871, + 35872, + 35873, + 35874, + 35875, + 35876, + 35877, + 35878, + 35879, + 35880, + 35881, + 35882, + 35883, + 35884, + 35885, + 35886, + 35887, + 35888, + 35889, + 35890, + 35891, + 35892, + 35893, + 35894, + 35895, + 35896, + 35897, + 35898, + 35899, + 35900, + 35901, + 35902, + 35903, + 35904, + 35905, + 35906, + 35907, + 35908, + 35909, + 35910, + 35911, + 35912, + 35913, + 35914, + 35915, + 35916, + 35917, + 35918, + 35919, + 35920, + 35921, + 35922, + 35923, + 35924, + 35925, + 35926, + 35927, + 35928, + 35929, + 35930, + 35931, + 35932, + 35933, + 35934, + 35935, + 35936, + 35937, + 35938, + 35939, + 35940, + 35941, + 35942, + 35943, + 35944, + 35945, + 35946, + 35947, + 35948, + 35949, + 35950, + 35951, + 35952, + 35953, + 35954, + 35955, + 35956, + 35957, + 35958, + 35959, + 35960, + 35961, + 35962, + 35963, + 35964, + 35965, + 35966, + 35967, + 35968, + 35969, + 35970, + 35971, + 35972, + 35973, + 35974, + 35975, + 35976, + 35977, + 35978, + 35979, + 35980, + 35981, + 35982, + 35983, + 35984, + 35985, + 35986, + 35987, + 35988, + 35989, + 35990, + 35991, + 35992, + 35993, + 35994, + 35995, + 35996, + 35997, + 35998, + 35999, + 36000, + 36001, + 36002, + 36003, + 36004, + 36005, + 36006, + 36007, + 36008, + 36009, + 36010, + 36011, + 36012, + 36013, + 36014, + 36015, + 36016, + 36017, + 36018, + 36019, + 36020, + 36021, + 36022, + 36023, + 36024, + 36025, + 36026, + 36027, + 36028, + 36029, + 36030, + 36031, + 36032, + 36033, + 36034, + 36035, + 36036, + 36037, + 36038, + 36039, + 36040, + 36041, + 36042, + 36043, + 36044, + 36045, + 36046, + 36047, + 36048, + 36049, + 36050, + 36051, + 36052, + 36053, + 36054, + 36055, + 36056, + 36057, + 36058, + 36059, + 36060, + 36061, + 36062, + 36063, + 36064, + 36065, + 36066, + 36067, + 36068, + 36069, + 36070, + 36071, + 36072, + 36073, + 36074, + 36075, + 36076, + 36077, + 36078, + 36079, + 36080, + 36081, + 36082, + 36083, + 36084, + 36085, + 36086, + 36087, + 36088, + 36089, + 36090, + 36091, + 36092, + 36093, + 36094, + 36095, + 36096, + 36097, + 36098, + 36099, + 36100, + 36101, + 36102, + 36103, + 36104, + 36105, + 36106, + 36107, + 36108, + 36109, + 36110, + 36111, + 36112, + 36113, + 36114, + 36115, + 36116, + 36117, + 36118, + 36119, + 36120, + 36121, + 36122, + 36123, + 36124, + 36125, + 36126, + 36127, + 36128, + 36129, + 36130, + 36131, + 36132, + 36133, + 36134, + 36135, + 36136, + 36137, + 36138, + 36139, + 36140, + 36141, + 36142, + 36143, + 36144, + 36145, + 36146, + 36147, + 36148, + 36149, + 36150, + 36151, + 36152, + 36153, + 36154, + 36155, + 36156, + 36157, + 36158, + 36159, + 36160, + 36161, + 36162, + 36163, + 36164, + 36165, + 36166, + 36167, + 36168, + 36169, + 36170, + 36171, + 36172, + 36173, + 36174, + 36175, + 36176, + 36177, + 36178, + 36179, + 36180, + 36181, + 36182, + 36183, + 36184, + 36185, + 36186, + 36187, + 36188, + 36189, + 36190, + 36191, + 36192, + 36193, + 36194, + 36195, + 36196, + 36197, + 36198, + 36199, + 36200, + 36201, + 36202, + 36203, + 36204, + 36205, + 36206, + 36207, + 36208, + 36209, + 36210, + 36211, + 36212, + 36213, + 36214, + 36215, + 36216, + 36217, + 36218, + 36219, + 36220, + 36221, + 36222, + 36223, + 36224, + 36225, + 36226, + 36227, + 36228, + 36229, + 36230, + 36231, + 36232, + 36233, + 36234, + 36235, + 36236, + 36237, + 36238, + 36239, + 36240, + 36241, + 36242, + 36243, + 36244, + 36245, + 36246, + 36247, + 36248, + 36249, + 36250, + 36251, + 36252, + 36253, + 36254, + 36255, + 36256, + 36257, + 36258, + 36259, + 36260, + 36261, + 36262, + 36263, + 36264, + 36265, + 36266, + 36267, + 36268, + 36269, + 36270, + 36271, + 36272, + 36273, + 36274, + 36275, + 36276, + 36277, + 36278, + 36279, + 36280, + 36281, + 36282, + 36283, + 36284, + 36285, + 36286, + 36287, + 36288, + 36289, + 36290, + 36291, + 36292, + 36293, + 36294, + 36295, + 36296, + 36297, + 36298, + 36299, + 36300, + 36301, + 36302, + 36303, + 36304, + 36305, + 36306, + 36307, + 36308, + 36309, + 36310, + 36311, + 36312, + 36313, + 36314, + 36315, + 36316, + 36317, + 36318, + 36319, + 36320, + 36321, + 36322, + 36323, + 36324, + 36325, + 36326, + 36327, + 36328, + 36329, + 36330, + 36331, + 36332, + 36333, + 36334, + 36335, + 36336, + 36337, + 36338, + 36339, + 36340, + 36341, + 36342, + 36343, + 36344, + 36345, + 36346, + 36347, + 36348, + 36349, + 36350, + 36351, + 36352, + 36353, + 36354, + 36355, + 36356, + 36357, + 36358, + 36359, + 36360, + 36361, + 36362, + 36363, + 36364, + 36365, + 36366, + 36367, + 36368, + 36369, + 36370, + 36371, + 36372, + 36373, + 36374, + 36375, + 36376, + 36377, + 36378, + 36379, + 36380, + 36381, + 36382, + 36383, + 36384, + 36385, + 36386, + 36387, + 36388, + 36389, + 36390, + 36391, + 36392, + 36393, + 36394, + 36395, + 36396, + 36397, + 36398, + 36399, + 36400, + 36401, + 36402, + 36403, + 36404, + 36405, + 36406, + 36407, + 36408, + 36409, + 36410, + 36411, + 36412, + 36413, + 36414, + 36415, + 36416, + 36417, + 36418, + 36419, + 36420, + 36421, + 36422, + 36423, + 36424, + 36425, + 36426, + 36427, + 36428, + 36429, + 36430, + 36431, + 36432, + 36433, + 36434, + 36435, + 36436, + 36437, + 36438, + 36439, + 36440, + 36441, + 36442, + 36443, + 36444, + 36445, + 36446, + 36447, + 36448, + 36449, + 36450, + 36451, + 36452, + 36453, + 36454, + 36455, + 36456, + 36457, + 36458, + 36459, + 36460, + 36461, + 36462, + 36463, + 36464, + 36465, + 36466, + 36467, + 36468, + 36469, + 36470, + 36471, + 36472, + 36473, + 36474, + 36475, + 36476, + 36477, + 36478, + 36479, + 36480, + 36481, + 36482, + 36483, + 36484, + 36485, + 36486, + 36487, + 36488, + 36489, + 36490, + 36491, + 36492, + 36493, + 36494, + 36495, + 36496, + 36497, + 36498, + 36499, + 36500, + 36501, + 36502, + 36503, + 36504, + 36505, + 36506, + 36507, + 36508, + 36509, + 36510, + 36511, + 36512, + 36513, + 36514, + 36515, + 36516, + 36517, + 36518, + 36519, + 36520, + 36521, + 36522, + 36523, + 36524, + 36525, + 36526, + 36527, + 36528, + 36529, + 36530, + 36531, + 36532, + 36533, + 36534, + 36535, + 36536, + 36537, + 36538, + 36539, + 36540, + 36541, + 36542, + 36543, + 36544, + 36545, + 36546, + 36547, + 36548, + 36549, + 36550, + 36551, + 36552, + 36553, + 36554, + 36555, + 36556, + 36557, + 36558, + 36559, + 36560, + 36561, + 36562, + 36563, + 36564, + 36565, + 36566, + 36567, + 36568, + 36569, + 36570, + 36571, + 36572, + 36573, + 36574, + 36575, + 36576, + 36577, + 36578, + 36579, + 36580, + 36581, + 36582, + 36583, + 36584, + 36585, + 36586, + 36587, + 36588, + 36589, + 36590, + 36591, + 36592, + 36593, + 36594, + 36595, + 36596, + 36597, + 36598, + 36599, + 36600, + 36601, + 36602, + 36603, + 36604, + 36605, + 36606, + 36607, + 36608, + 36609, + 36610, + 36611, + 36612, + 36613, + 36614, + 36615, + 36616, + 36617, + 36618, + 36619, + 36620, + 36621, + 36622, + 36623, + 36624, + 36625, + 36626, + 36627, + 36628, + 36629, + 36630, + 36631, + 36632, + 36633, + 36634, + 36635, + 36636, + 36637, + 36638, + 36639, + 36640, + 36641, + 36642, + 36643, + 36644, + 36645, + 36646, + 36647, + 36648, + 36649, + 36650, + 36651, + 36652, + 36653, + 36654, + 36655, + 36656, + 36657, + 36658, + 36659, + 36660, + 36661, + 36662, + 36663, + 36664, + 36665, + 36666, + 36667, + 36668, + 36669, + 36670, + 36671, + 36672, + 36673, + 36674, + 36675, + 36676, + 36677, + 36678, + 36679, + 36680, + 36681, + 36682, + 36683, + 36684, + 36685, + 36686, + 36687, + 36688, + 36689, + 36690, + 36691, + 36692, + 36693, + 36694, + 36695, + 36696, + 36697, + 36698, + 36699, + 36700, + 36701, + 36702, + 36703, + 36704, + 36705, + 36706, + 36707, + 36708, + 36709, + 36710, + 36711, + 36712, + 36713, + 36714, + 36715, + 36716, + 36717, + 36718, + 36719, + 36720, + 36721, + 36722, + 36723, + 36724, + 36725, + 36726, + 36727, + 36728, + 36729, + 36730, + 36731, + 36732, + 36733, + 36734, + 36735, + 36736, + 36737, + 36738, + 36739, + 36740, + 36741, + 36742, + 36743, + 36744, + 36745, + 36746, + 36747, + 36748, + 36749, + 36750, + 36751, + 36752, + 36753, + 36754, + 36755, + 36756, + 36757, + 36758, + 36759, + 36760, + 36761, + 36762, + 36763, + 36764, + 36765, + 36766, + 36767, + 36768, + 36769, + 36770, + 36771, + 36772, + 36773, + 36774, + 36775, + 36776, + 36777, + 36778, + 36779, + 36780, + 36781, + 36782, + 36783, + 36784, + 36785, + 36786, + 36787, + 36788, + 36789, + 36790, + 36791, + 36792, + 36793, + 36794, + 36795, + 36796, + 36797, + 36798, + 36799, + 36800, + 36801, + 36802, + 36803, + 36804, + 36805, + 36806, + 36807, + 36808, + 36809, + 36810, + 36811, + 36812, + 36813, + 36814, + 36815, + 36816, + 36817, + 36818, + 36819, + 36820, + 36821, + 36822, + 36823, + 36824, + 36825, + 36826, + 36827, + 36828, + 36829, + 36830, + 36831, + 36832, + 36833, + 36834, + 36835, + 36836, + 36837, + 36838, + 36839, + 36840, + 36841, + 36842, + 36843, + 36844, + 36845, + 36846, + 36847, + 36848, + 36849, + 36850, + 36851, + 36852, + 36853, + 36854, + 36855, + 36856, + 36857, + 36858, + 36859, + 36860, + 36861, + 36862, + 36863, + 36864, + 36865, + 36866, + 36867, + 36868, + 36869, + 36870, + 36871, + 36872, + 36873, + 36874, + 36875, + 36876, + 36877, + 36878, + 36879, + 36880, + 36881, + 36882, + 36883, + 36884, + 36885, + 36886, + 36887, + 36888, + 36889, + 36890, + 36891, + 36892, + 36893, + 36894, + 36895, + 36896, + 36897, + 36898, + 36899, + 36900, + 36901, + 36902, + 36903, + 36904, + 36905, + 36906, + 36907, + 36908, + 36909, + 36910, + 36911, + 36912, + 36913, + 36914, + 36915, + 36916, + 36917, + 36918, + 36919, + 36920, + 36921, + 36922, + 36923, + 36924, + 36925, + 36926, + 36927, + 36928, + 36929, + 36930, + 36931, + 36932, + 36933, + 36934, + 36935, + 36936, + 36937, + 36938, + 36939, + 36940, + 36941, + 36942, + 36943, + 36944, + 36945, + 36946, + 36947, + 36948, + 36949, + 36950, + 36951, + 36952, + 36953, + 36954, + 36955, + 36956, + 36957, + 36958, + 36959, + 36960, + 36961, + 36962, + 36963, + 36964, + 36965, + 36966, + 36967, + 36968, + 36969, + 36970, + 36971, + 36972, + 36973, + 36974, + 36975, + 36976, + 36977, + 36978, + 36979, + 36980, + 36981, + 36982, + 36983, + 36984, + 36985, + 36986, + 36987, + 36988, + 36989, + 36990, + 36991, + 36992, + 36993, + 36994, + 36995, + 36996, + 36997, + 36998, + 36999, + 37000, + 37001, + 37002, + 37003, + 37004, + 37005, + 37006, + 37007, + 37008, + 37009, + 37010, + 37011, + 37012, + 37013, + 37014, + 37015, + 37016, + 37017, + 37018, + 37019, + 37020, + 37021, + 37022, + 37023, + 37024, + 37025, + 37026, + 37027, + 37028, + 37029, + 37030, + 37031, + 37032, + 37033, + 37034, + 37035, + 37036, + 37037, + 37038, + 37039, + 37040, + 37041, + 37042, + 37043, + 37044, + 37045, + 37046, + 37047, + 37048, + 37049, + 37050, + 37051, + 37052, + 37053, + 37054, + 37055, + 37056, + 37057, + 37058, + 37059, + 37060, + 37061, + 37062, + 37063, + 37064, + 37065, + 37066, + 37067, + 37068, + 37069, + 37070, + 37071, + 37072, + 37073, + 37074, + 37075, + 37076, + 37077, + 37078, + 37079, + 37080, + 37081, + 37082, + 37083, + 37084, + 37085, + 37086, + 37087, + 37088, + 37089, + 37090, + 37091, + 37092, + 37093, + 37094, + 37095, + 37096, + 37097, + 37098, + 37099, + 37100, + 37101, + 37102, + 37103, + 37104, + 37105, + 37106, + 37107, + 37108, + 37109, + 37110, + 37111, + 37112, + 37113, + 37114, + 37115, + 37116, + 37117, + 37118, + 37119, + 37120, + 37121, + 37122, + 37123, + 37124, + 37125, + 37126, + 37127, + 37128, + 37129, + 37130, + 37131, + 37132, + 37133, + 37134, + 37135, + 37136, + 37137, + 37138, + 37139, + 37140, + 37141, + 37142, + 37143, + 37144, + 37145, + 37146, + 37147, + 37148, + 37149, + 37150, + 37151, + 37152, + 37153, + 37154, + 37155, + 37156, + 37157, + 37158, + 37159, + 37160, + 37161, + 37162, + 37163, + 37164, + 37165, + 37166, + 37167, + 37168, + 37169, + 37170, + 37171, + 37172, + 37173, + 37174, + 37175, + 37176, + 37177, + 37178, + 37179, + 37180, + 37181, + 37182, + 37183, + 37184, + 37185, + 37186, + 37187, + 37188, + 37189, + 37190, + 37191, + 37192, + 37193, + 37194, + 37195, + 37196, + 37197, + 37198, + 37199, + 37200, + 37201, + 37202, + 37203, + 37204, + 37205, + 37206, + 37207, + 37208, + 37209, + 37210, + 37211, + 37212, + 37213, + 37214, + 37215, + 37216, + 37217, + 37218, + 37219, + 37220, + 37221, + 37222, + 37223, + 37224, + 37225, + 37226, + 37227, + 37228, + 37229, + 37230, + 37231, + 37232, + 37233, + 37234, + 37235, + 37236, + 37237, + 37238, + 37239, + 37240, + 37241, + 37242, + 37243, + 37244, + 37245, + 37246, + 37247, + 37248, + 37249, + 37250, + 37251, + 37252, + 37253, + 37254, + 37255, + 37256, + 37257, + 37258, + 37259, + 37260, + 37261, + 37262, + 37263, + 37264, + 37265, + 37266, + 37267, + 37268, + 37269, + 37270, + 37271, + 37272, + 37273, + 37274, + 37275, + 37276, + 37277, + 37278, + 37279, + 37280, + 37281, + 37282, + 37283, + 37284, + 37285, + 37286, + 37287, + 37288, + 37289, + 37290, + 37291, + 37292, + 37293, + 37294, + 37295, + 37296, + 37297, + 37298, + 37299, + 37300, + 37301, + 37302, + 37303, + 37304, + 37305, + 37306, + 37307, + 37308, + 37309, + 37310, + 37311, + 37312, + 37313, + 37314, + 37315, + 37316, + 37317, + 37318, + 37319, + 37320, + 37321, + 37322, + 37323, + 37324, + 37325, + 37326, + 37327, + 37328, + 37329, + 37330, + 37331, + 37332, + 37333, + 37334, + 37335, + 37336, + 37337, + 37338, + 37339, + 37340, + 37341, + 37342, + 37343, + 37344, + 37345, + 37346, + 37347, + 37348, + 37349, + 37350, + 37351, + 37352, + 37353, + 37354, + 37355, + 37356, + 37357, + 37358, + 37359, + 37360, + 37361, + 37362, + 37363, + 37364, + 37365, + 37366, + 37367, + 37368, + 37369, + 37370, + 37371, + 37372, + 37373, + 37374, + 37375, + 37376, + 37377, + 37378, + 37379, + 37380, + 37381, + 37382, + 37383, + 37384, + 37385, + 37386, + 37387, + 37388, + 37389, + 37390, + 37391, + 37392, + 37393, + 37394, + 37395, + 37396, + 37397, + 37398, + 37399, + 37400, + 37401, + 37402, + 37403, + 37404, + 37405, + 37406, + 37407, + 37408, + 37409, + 37410, + 37411, + 37412, + 37413, + 37414, + 37415, + 37416, + 37417, + 37418, + 37419, + 37420, + 37421, + 37422, + 37423, + 37424, + 37425, + 37426, + 37427, + 37428, + 37429, + 37430, + 37431, + 37432, + 37433, + 37434, + 37435, + 37436, + 37437, + 37438, + 37439, + 37440, + 37441, + 37442, + 37443, + 37444, + 37445, + 37446, + 37447, + 37448, + 37449, + 37450, + 37451, + 37452, + 37453, + 37454, + 37455, + 37456, + 37457, + 37458, + 37459, + 37460, + 37461, + 37462, + 37463, + 37464, + 37465, + 37466, + 37467, + 37468, + 37469, + 37470, + 37471, + 37472, + 37473, + 37474, + 37475, + 37476, + 37477, + 37478, + 37479, + 37480, + 37481, + 37482, + 37483, + 37484, + 37485, + 37486, + 37487, + 37488, + 37489, + 37490, + 37491, + 37492, + 37493, + 37494, + 37495, + 37496, + 37497, + 37498, + 37499, + 37500, + 37501, + 37502, + 37503, + 37504, + 37505, + 37506, + 37507, + 37508, + 37509, + 37510, + 37511, + 37512, + 37513, + 37514, + 37515, + 37516, + 37517, + 37518, + 37519, + 37520, + 37521, + 37522, + 37523, + 37524, + 37525, + 37526, + 37527, + 37528, + 37529, + 37530, + 37531, + 37532, + 37533, + 37534, + 37535, + 37536, + 37537, + 37538, + 37539, + 37540, + 37541, + 37542, + 37543, + 37544, + 37545, + 37546, + 37547, + 37548, + 37549, + 37550, + 37551, + 37552, + 37553, + 37554, + 37555, + 37556, + 37557, + 37558, + 37559, + 37560, + 37561, + 37562, + 37563, + 37564, + 37565, + 37566, + 37567, + 37568, + 37569, + 37570, + 37571, + 37572, + 37573, + 37574, + 37575, + 37576, + 37577, + 37578, + 37579, + 37580, + 37581, + 37582, + 37583, + 37584, + 37585, + 37586, + 37587, + 37588, + 37589, + 37590, + 37591, + 37592, + 37593, + 37594, + 37595, + 37596, + 37597, + 37598, + 37599, + 37600, + 37601, + 37602, + 37603, + 37604, + 37605, + 37606, + 37607, + 37608, + 37609, + 37610, + 37611, + 37612, + 37613, + 37614, + 37615, + 37616, + 37617, + 37618, + 37619, + 37620, + 37621, + 37622, + 37623, + 37624, + 37625, + 37626, + 37627, + 37628, + 37629, + 37630, + 37631, + 37632, + 37633, + 37634, + 37635, + 37636, + 37637, + 37638, + 37639, + 37640, + 37641, + 37642, + 37643, + 37644, + 37645, + 37646, + 37647, + 37648, + 37649, + 37650, + 37651, + 37652, + 37653, + 37654, + 37655, + 37656, + 37657, + 37658, + 37659, + 37660, + 37661, + 37662, + 37663, + 37664, + 37665, + 37666, + 37667, + 37668, + 37669, + 37670, + 37671, + 37672, + 37673, + 37674, + 37675, + 37676, + 37677, + 37678, + 37679, + 37680, + 37681, + 37682, + 37683, + 37684, + 37685, + 37686, + 37687, + 37688, + 37689, + 37690, + 37691, + 37692, + 37693, + 37694, + 37695, + 37696, + 37697, + 37698, + 37699, + 37700, + 37701, + 37702, + 37703, + 37704, + 37705, + 37706, + 37707, + 37708, + 37709, + 37710, + 37711, + 37712, + 37713, + 37714, + 37715, + 37716, + 37717, + 37718, + 37719, + 37720, + 37721, + 37722, + 37723, + 37724, + 37725, + 37726, + 37727, + 37728, + 37729, + 37730, + 37731, + 37732, + 37733, + 37734, + 37735, + 37736, + 37737, + 37738, + 37739, + 37740, + 37741, + 37742, + 37743, + 37744, + 37745, + 37746, + 37747, + 37748, + 37749, + 37750, + 37751, + 37752, + 37753, + 37754, + 37755, + 37756, + 37757, + 37758, + 37759, + 37760, + 37761, + 37762, + 37763, + 37764, + 37765, + 37766, + 37767, + 37768, + 37769, + 37770, + 37771, + 37772, + 37773, + 37774, + 37775, + 37776, + 37777, + 37778, + 37779, + 37780, + 37781, + 37782, + 37783, + 37784, + 37785, + 37786, + 37787, + 37788, + 37789, + 37790, + 37791, + 37792, + 37793, + 37794, + 37795, + 37796, + 37797, + 37798, + 37799, + 37800, + 37801, + 37802, + 37803, + 37804, + 37805, + 37806, + 37807, + 37808, + 37809, + 37810, + 37811, + 37812, + 37813, + 37814, + 37815, + 37816, + 37817, + 37818, + 37819, + 37820, + 37821, + 37822, + 37823, + 37824, + 37825, + 37826, + 37827, + 37828, + 37829, + 37830, + 37831, + 37832, + 37833, + 37834, + 37835, + 37836, + 37837, + 37838, + 37839, + 37840, + 37841, + 37842, + 37843, + 37844, + 37845, + 37846, + 37847, + 37848, + 37849, + 37850, + 37851, + 37852, + 37853, + 37854, + 37855, + 37856, + 37857, + 37858, + 37859, + 37860, + 37861, + 37862, + 37863, + 37864, + 37865, + 37866, + 37867, + 37868, + 37869, + 37870, + 37871, + 37872, + 37873, + 37874, + 37875, + 37876, + 37877, + 37878, + 37879, + 37880, + 37881, + 37882, + 37883, + 37884, + 37885, + 37886, + 37887, + 37888, + 37889, + 37890, + 37891, + 37892, + 37893, + 37894, + 37895, + 37896, + 37897, + 37898, + 37899, + 37900, + 37901, + 37902, + 37903, + 37904, + 37905, + 37906, + 37907, + 37908, + 37909, + 37910, + 37911, + 37912, + 37913, + 37914, + 37915, + 37916, + 37917, + 37918, + 37919, + 37920, + 37921, + 37922, + 37923, + 37924, + 37925, + 37926, + 37927, + 37928, + 37929, + 37930, + 37931, + 37932, + 37933, + 37934, + 37935, + 37936, + 37937, + 37938, + 37939, + 37940, + 37941, + 37942, + 37943, + 37944, + 37945, + 37946, + 37947, + 37948, + 37949, + 37950, + 37951, + 37952, + 37953, + 37954, + 37955, + 37956, + 37957, + 37958, + 37959, + 37960, + 37961, + 37962, + 37963, + 37964, + 37965, + 37966, + 37967, + 37968, + 37969, + 37970, + 37971, + 37972, + 37973, + 37974, + 37975, + 37976, + 37977, + 37978, + 37979, + 37980, + 37981, + 37982, + 37983, + 37984, + 37985, + 37986, + 37987, + 37988, + 37989, + 37990, + 37991, + 37992, + 37993, + 37994, + 37995, + 37996, + 37997, + 37998, + 37999, + 38000, + 38001, + 38002, + 38003, + 38004, + 38005, + 38006, + 38007, + 38008, + 38009, + 38010, + 38011, + 38012, + 38013, + 38014, + 38015, + 38016, + 38017, + 38018, + 38019, + 38020, + 38021, + 38022, + 38023, + 38024, + 38025, + 38026, + 38027, + 38028, + 38029, + 38030, + 38031, + 38032, + 38033, + 38034, + 38035, + 38036, + 38037, + 38038, + 38039, + 38040, + 38041, + 38042, + 38043, + 38044, + 38045, + 38046, + 38047, + 38048, + 38049, + 38050, + 38051, + 38052, + 38053, + 38054, + 38055, + 38056, + 38057, + 38058, + 38059, + 38060, + 38061, + 38062, + 38063, + 38064, + 38065, + 38066, + 38067, + 38068, + 38069, + 38070, + 38071, + 38072, + 38073, + 38074, + 38075, + 38076, + 38077, + 38078, + 38079, + 38080, + 38081, + 38082, + 38083, + 38084, + 38085, + 38086, + 38087, + 38088, + 38089, + 38090, + 38091, + 38092, + 38093, + 38094, + 38095, + 38096, + 38097, + 38098, + 38099, + 38100, + 38101, + 38102, + 38103, + 38104, + 38105, + 38106, + 38107, + 38108, + 38109, + 38110, + 38111, + 38112, + 38113, + 38114, + 38115, + 38116, + 38117, + 38118, + 38119, + 38120, + 38121, + 38122, + 38123, + 38124, + 38125, + 38126, + 38127, + 38128, + 38129, + 38130, + 38131, + 38132, + 38133, + 38134, + 38135, + 38136, + 38137, + 38138, + 38139, + 38140, + 38141, + 38142, + 38143, + 38144, + 38145, + 38146, + 38147, + 38148, + 38149, + 38150, + 38151, + 38152, + 38153, + 38154, + 38155, + 38156, + 38157, + 38158, + 38159, + 38160, + 38161, + 38162, + 38163, + 38164, + 38165, + 38166, + 38167, + 38168, + 38169, + 38170, + 38171, + 38172, + 38173, + 38174, + 38175, + 38176, + 38177, + 38178, + 38179, + 38180, + 38181, + 38182, + 38183, + 38184, + 38185, + 38186, + 38187, + 38188, + 38189, + 38190, + 38191, + 38192, + 38193, + 38194, + 38195, + 38196, + 38197, + 38198, + 38199, + 38200, + 38201, + 38202, + 38203, + 38204, + 38205, + 38206, + 38207, + 38208, + 38209, + 38210, + 38211, + 38212, + 38213, + 38214, + 38215, + 38216, + 38217, + 38218, + 38219, + 38220, + 38221, + 38222, + 38223, + 38224, + 38225, + 38226, + 38227, + 38228, + 38229, + 38230, + 38231, + 38232, + 38233, + 38234, + 38235, + 38236, + 38237, + 38238, + 38239, + 38240, + 38241, + 38242, + 38243, + 38244, + 38245, + 38246, + 38247, + 38248, + 38249, + 38250, + 38251, + 38252, + 38253, + 38254, + 38255, + 38256, + 38257, + 38258, + 38259, + 38260, + 38261, + 38262, + 38263, + 38264, + 38265, + 38266, + 38267, + 38268, + 38269, + 38270, + 38271, + 38272, + 38273, + 38274, + 38275, + 38276, + 38277, + 38278, + 38279, + 38280, + 38281, + 38282, + 38283, + 38284, + 38285, + 38286, + 38287, + 38288, + 38289, + 38290, + 38291, + 38292, + 38293, + 38294, + 38295, + 38296, + 38297, + 38298, + 38299, + 38300, + 38301, + 38302, + 38303, + 38304, + 38305, + 38306, + 38307, + 38308, + 38309, + 38310, + 38311, + 38312, + 38313, + 38314, + 38315, + 38316, + 38317, + 38318, + 38319, + 38320, + 38321, + 38322, + 38323, + 38324, + 38325, + 38326, + 38327, + 38328, + 38329, + 38330, + 38331, + 38332, + 38333, + 38334, + 38335, + 38336, + 38337, + 38338, + 38339, + 38340, + 38341, + 38342, + 38343, + 38344, + 38345, + 38346, + 38347, + 38348, + 38349, + 38350, + 38351, + 38352, + 38353, + 38354, + 38355, + 38356, + 38357, + 38358, + 38359, + 38360, + 38361, + 38362, + 38363, + 38364, + 38365, + 38366, + 38367, + 38368, + 38369, + 38370, + 38371, + 38372, + 38373, + 38374, + 38375, + 38376, + 38377, + 38378, + 38379, + 38380, + 38381, + 38382, + 38383, + 38384, + 38385, + 38386, + 38387, + 38388, + 38389, + 38390, + 38391, + 38392, + 38393, + 38394, + 38395, + 38396, + 38397, + 38398, + 38399, + 38400, + 38401, + 38402, + 38403, + 38404, + 38405, + 38406, + 38407, + 38408, + 38409, + 38410, + 38411, + 38412, + 38413, + 38414, + 38415, + 38416, + 38417, + 38418, + 38419, + 38420, + 38421, + 38422, + 38423, + 38424, + 38425, + 38426, + 38427, + 38428, + 38429, + 38430, + 38431, + 38432, + 38433, + 38434, + 38435, + 38436, + 38437, + 38438, + 38439, + 38440, + 38441, + 38442, + 38443, + 38444, + 38445, + 38446, + 38447, + 38448, + 38449, + 38450, + 38451, + 38452, + 38453, + 38454, + 38455, + 38456, + 38457, + 38458, + 38459, + 38460, + 38461, + 38462, + 38463, + 38464, + 38465, + 38466, + 38467, + 38468, + 38469, + 38470, + 38471, + 38472, + 38473, + 38474, + 38475, + 38476, + 38477, + 38478, + 38479, + 38480, + 38481, + 38482, + 38483, + 38484, + 38485, + 38486, + 38487, + 38488, + 38489, + 38490, + 38491, + 38492, + 38493, + 38494, + 38495, + 38496, + 38497, + 38498, + 38499, + 38500, + 38501, + 38502, + 38503, + 38504, + 38505, + 38506, + 38507, + 38508, + 38509, + 38510, + 38511, + 38512, + 38513, + 38514, + 38515, + 38516, + 38517, + 38518, + 38519, + 38520, + 38521, + 38522, + 38523, + 38524, + 38525, + 38526, + 38527, + 38528, + 38529, + 38530, + 38531, + 38532, + 38533, + 38534, + 38535, + 38536, + 38537, + 38538, + 38539, + 38540, + 38541, + 38542, + 38543, + 38544, + 38545, + 38546, + 38547, + 38548, + 38549, + 38550, + 38551, + 38552, + 38553, + 38554, + 38555, + 38556, + 38557, + 38558, + 38559, + 38560, + 38561, + 38562, + 38563, + 38564, + 38565, + 38566, + 38567, + 38568, + 38569, + 38570, + 38571, + 38572, + 38573, + 38574, + 38575, + 38576, + 38577, + 38578, + 38579, + 38580, + 38581, + 38582, + 38583, + 38584, + 38585, + 38586, + 38587, + 38588, + 38589, + 38590, + 38591, + 38592, + 38593, + 38594, + 38595, + 38596, + 38597, + 38598, + 38599, + 38600, + 38601, + 38602, + 38603, + 38604, + 38605, + 38606, + 38607, + 38608, + 38609, + 38610, + 38611, + 38612, + 38613, + 38614, + 38615, + 38616, + 38617, + 38618, + 38619, + 38620, + 38621, + 38622, + 38623, + 38624, + 38625, + 38626, + 38627, + 38628, + 38629, + 38630, + 38631, + 38632, + 38633, + 38634, + 38635, + 38636, + 38637, + 38638, + 38639, + 38640, + 38641, + 38642, + 38643, + 38644, + 38645, + 38646, + 38647, + 38648, + 38649, + 38650, + 38651, + 38652, + 38653, + 38654, + 38655, + 38656, + 38657, + 38658, + 38659, + 38660, + 38661, + 38662, + 38663, + 38664, + 38665, + 38666, + 38667, + 38668, + 38669, + 38670, + 38671, + 38672, + 38673, + 38674, + 38675, + 38676, + 38677, + 38678, + 38679, + 38680, + 38681, + 38682, + 38683, + 38684, + 38685, + 38686, + 38687, + 38688, + 38689, + 38690, + 38691, + 38692, + 38693, + 38694, + 38695, + 38696, + 38697, + 38698, + 38699, + 38700, + 38701, + 38702, + 38703, + 38704, + 38705, + 38706, + 38707, + 38708, + 38709, + 38710, + 38711, + 38712, + 38713, + 38714, + 38715, + 38716, + 38717, + 38718, + 38719, + 38720, + 38721, + 38722, + 38723, + 38724, + 38725, + 38726, + 38727, + 38728, + 38729, + 38730, + 38731, + 38732, + 38733, + 38734, + 38735, + 38736, + 38737, + 38738, + 38739, + 38740, + 38741, + 38742, + 38743, + 38744, + 38745, + 38746, + 38747, + 38748, + 38749, + 38750, + 38751, + 38752, + 38753, + 38754, + 38755, + 38756, + 38757, + 38758, + 38759, + 38760, + 38761, + 38762, + 38763, + 38764, + 38765, + 38766, + 38767, + 38768, + 38769, + 38770, + 38771, + 38772, + 38773, + 38774, + 38775, + 38776, + 38777, + 38778, + 38779, + 38780, + 38781, + 38782, + 38783, + 38784, + 38785, + 38786, + 38787, + 38788, + 38789, + 38790, + 38791, + 38792, + 38793, + 38794, + 38795, + 38796, + 38797, + 38798, + 38799, + 38800, + 38801, + 38802, + 38803, + 38804, + 38805, + 38806, + 38807, + 38808, + 38809, + 38810, + 38811, + 38812, + 38813, + 38814, + 38815, + 38816, + 38817, + 38818, + 38819, + 38820, + 38821, + 38822, + 38823, + 38824, + 38825, + 38826, + 38827, + 38828, + 38829, + 38830, + 38831, + 38832, + 38833, + 38834, + 38835, + 38836, + 38837, + 38838, + 38839, + 38840, + 38841, + 38842, + 38843, + 38844, + 38845, + 38846, + 38847, + 38848, + 38849, + 38850, + 38851, + 38852, + 38853, + 38854, + 38855, + 38856, + 38857, + 38858, + 38859, + 38860, + 38861, + 38862, + 38863, + 38864, + 38865, + 38866, + 38867, + 38868, + 38869, + 38870, + 38871, + 38872, + 38873, + 38874, + 38875, + 38876, + 38877, + 38878, + 38879, + 38880, + 38881, + 38882, + 38883, + 38884, + 38885, + 38886, + 38887, + 38888, + 38889, + 38890, + 38891, + 38892, + 38893, + 38894, + 38895, + 38896, + 38897, + 38898, + 38899, + 38900, + 38901, + 38902, + 38903, + 38904, + 38905, + 38906, + 38907, + 38908, + 38909, + 38910, + 38911, + 38912, + 38913, + 38914, + 38915, + 38916, + 38917, + 38918, + 38919, + 38920, + 38921, + 38922, + 38923, + 38924, + 38925, + 38926, + 38927, + 38928, + 38929, + 38930, + 38931, + 38932, + 38933, + 38934, + 38935, + 38936, + 38937, + 38938, + 38939, + 38940, + 38941, + 38942, + 38943, + 38944, + 38945, + 38946, + 38947, + 38948, + 38949, + 38950, + 38951, + 38952, + 38953, + 38954, + 38955, + 38956, + 38957, + 38958, + 38959, + 38960, + 38961, + 38962, + 38963, + 38964, + 38965, + 38966, + 38967, + 38968, + 38969, + 38970, + 38971, + 38972, + 38973, + 38974, + 38975, + 38976, + 38977, + 38978, + 38979, + 38980, + 38981, + 38982, + 38983, + 38984, + 38985, + 38986, + 38987, + 38988, + 38989, + 38990, + 38991, + 38992, + 38993, + 38994, + 38995, + 38996, + 38997, + 38998, + 38999, + 39000, + 39001, + 39002, + 39003, + 39004, + 39005, + 39006, + 39007, + 39008, + 39009, + 39010, + 39011, + 39012, + 39013, + 39014, + 39015, + 39016, + 39017, + 39018, + 39019, + 39020, + 39021, + 39022, + 39023, + 39024, + 39025, + 39026, + 39027, + 39028, + 39029, + 39030, + 39031, + 39032, + 39033, + 39034, + 39035, + 39036, + 39037, + 39038, + 39039, + 39040, + 39041, + 39042, + 39043, + 39044, + 39045, + 39046, + 39047, + 39048, + 39049, + 39050, + 39051, + 39052, + 39053, + 39054, + 39055, + 39056, + 39057, + 39058, + 39059, + 39060, + 39061, + 39062, + 39063, + 39064, + 39065, + 39066, + 39067, + 39068, + 39069, + 39070, + 39071, + 39072, + 39073, + 39074, + 39075, + 39076, + 39077, + 39078, + 39079, + 39080, + 39081, + 39082, + 39083, + 39084, + 39085, + 39086, + 39087, + 39088, + 39089, + 39090, + 39091, + 39092, + 39093, + 39094, + 39095, + 39096, + 39097, + 39098, + 39099, + 39100, + 39101, + 39102, + 39103, + 39104, + 39105, + 39106, + 39107, + 39108, + 39109, + 39110, + 39111, + 39112, + 39113, + 39114, + 39115, + 39116, + 39117, + 39118, + 39119, + 39120, + 39121, + 39122, + 39123, + 39124, + 39125, + 39126, + 39127, + 39128, + 39129, + 39130, + 39131, + 39132, + 39133, + 39134, + 39135, + 39136, + 39137, + 39138, + 39139, + 39140, + 39141, + 39142, + 39143, + 39144, + 39145, + 39146, + 39147, + 39148, + 39149, + 39150, + 39151, + 39152, + 39153, + 39154, + 39155, + 39156, + 39157, + 39158, + 39159, + 39160, + 39161, + 39162, + 39163, + 39164, + 39165, + 39166, + 39167, + 39168, + 39169, + 39170, + 39171, + 39172, + 39173, + 39174, + 39175, + 39176, + 39177, + 39178, + 39179, + 39180, + 39181, + 39182, + 39183, + 39184, + 39185, + 39186, + 39187, + 39188, + 39189, + 39190, + 39191, + 39192, + 39193, + 39194, + 39195, + 39196, + 39197, + 39198, + 39199, + 39200, + 39201, + 39202, + 39203, + 39204, + 39205, + 39206, + 39207, + 39208, + 39209, + 39210, + 39211, + 39212, + 39213, + 39214, + 39215, + 39216, + 39217, + 39218, + 39219, + 39220, + 39221, + 39222, + 39223, + 39224, + 39225, + 39226, + 39227, + 39228, + 39229, + 39230, + 39231, + 39232, + 39233, + 39234, + 39235, + 39236, + 39237, + 39238, + 39239, + 39240, + 39241, + 39242, + 39243, + 39244, + 39245, + 39246, + 39247, + 39248, + 39249, + 39250, + 39251, + 39252, + 39253, + 39254, + 39255, + 39256, + 39257, + 39258, + 39259, + 39260, + 39261, + 39262, + 39263, + 39264, + 39265, + 39266, + 39267, + 39268, + 39269, + 39270, + 39271, + 39272, + 39273, + 39274, + 39275, + 39276, + 39277, + 39278, + 39279, + 39280, + 39281, + 39282, + 39283, + 39284, + 39285, + 39286, + 39287, + 39288, + 39289, + 39290, + 39291, + 39292, + 39293, + 39294, + 39295, + 39296, + 39297, + 39298, + 39299, + 39300, + 39301, + 39302, + 39303, + 39304, + 39305, + 39306, + 39307, + 39308, + 39309, + 39310, + 39311, + 39312, + 39313, + 39314, + 39315, + 39316, + 39317, + 39318, + 39319, + 39320, + 39321, + 39322, + 39323, + 39324, + 39325, + 39326, + 39327, + 39328, + 39329, + 39330, + 39331, + 39332, + 39333, + 39334, + 39335, + 39336, + 39337, + 39338, + 39339, + 39340, + 39341, + 39342, + 39343, + 39344, + 39345, + 39346, + 39347, + 39348, + 39349, + 39350, + 39351, + 39352, + 39353, + 39354, + 39355, + 39356, + 39357, + 39358, + 39359, + 39360, + 39361, + 39362, + 39363, + 39364, + 39365, + 39366, + 39367, + 39368, + 39369, + 39370, + 39371, + 39372, + 39373, + 39374, + 39375, + 39376, + 39377, + 39378, + 39379, + 39380, + 39381, + 39382, + 39383, + 39384, + 39385, + 39386, + 39387, + 39388, + 39389, + 39390, + 39391, + 39392, + 39393, + 39394, + 39395, + 39396, + 39397, + 39398, + 39399, + 39400, + 39401, + 39402, + 39403, + 39404, + 39405, + 39406, + 39407, + 39408, + 39409, + 39410, + 39411, + 39412, + 39413, + 39414, + 39415, + 39416, + 39417, + 39418, + 39419, + 39420, + 39421, + 39422, + 39423, + 39424, + 39425, + 39426, + 39427, + 39428, + 39429, + 39430, + 39431, + 39432, + 39433, + 39434, + 39435, + 39436, + 39437, + 39438, + 39439, + 39440, + 39441, + 39442, + 39443, + 39444, + 39445, + 39446, + 39447, + 39448, + 39449, + 39450, + 39451, + 39452, + 39453, + 39454, + 39455, + 39456, + 39457, + 39458, + 39459, + 39460, + 39461, + 39462, + 39463, + 39464, + 39465, + 39466, + 39467, + 39468, + 39469, + 39470, + 39471, + 39472, + 39473, + 39474, + 39475, + 39476, + 39477, + 39478, + 39479, + 39480, + 39481, + 39482, + 39483, + 39484, + 39485, + 39486, + 39487, + 39488, + 39489, + 39490, + 39491, + 39492, + 39493, + 39494, + 39495, + 39496, + 39497, + 39498, + 39499, + 39500, + 39501, + 39502, + 39503, + 39504, + 39505, + 39506, + 39507, + 39508, + 39509, + 39510, + 39511, + 39512, + 39513, + 39514, + 39515, + 39516, + 39517, + 39518, + 39519, + 39520, + 39521, + 39522, + 39523, + 39524, + 39525, + 39526, + 39527, + 39528, + 39529, + 39530, + 39531, + 39532, + 39533, + 39534, + 39535, + 39536, + 39537, + 39538, + 39539, + 39540, + 39541, + 39542, + 39543, + 39544, + 39545, + 39546, + 39547, + 39548, + 39549, + 39550, + 39551, + 39552, + 39553, + 39554, + 39555, + 39556, + 39557, + 39558, + 39559, + 39560, + 39561, + 39562, + 39563, + 39564, + 39565, + 39566, + 39567, + 39568, + 39569, + 39570, + 39571, + 39572, + 39573, + 39574, + 39575, + 39576, + 39577, + 39578, + 39579, + 39580, + 39581, + 39582, + 39583, + 39584, + 39585, + 39586, + 39587, + 39588, + 39589, + 39590, + 39591, + 39592, + 39593, + 39594, + 39595, + 39596, + 39597, + 39598, + 39599, + 39600, + 39601, + 39602, + 39603, + 39604, + 39605, + 39606, + 39607, + 39608, + 39609, + 39610, + 39611, + 39612, + 39613, + 39614, + 39615, + 39616, + 39617, + 39618, + 39619, + 39620, + 39621, + 39622, + 39623, + 39624, + 39625, + 39626, + 39627, + 39628, + 39629, + 39630, + 39631, + 39632, + 39633, + 39634, + 39635, + 39636, + 39637, + 39638, + 39639, + 39640, + 39641, + 39642, + 39643, + 39644, + 39645, + 39646, + 39647, + 39648, + 39649, + 39650, + 39651, + 39652, + 39653, + 39654, + 39655, + 39656, + 39657, + 39658, + 39659, + 39660, + 39661, + 39662, + 39663, + 39664, + 39665, + 39666, + 39667, + 39668, + 39669, + 39670, + 39671, + 39672, + 39673, + 39674, + 39675, + 39676, + 39677, + 39678, + 39679, + 39680, + 39681, + 39682, + 39683, + 39684, + 39685, + 39686, + 39687, + 39688, + 39689, + 39690, + 39691, + 39692, + 39693, + 39694, + 39695, + 39696, + 39697, + 39698, + 39699, + 39700, + 39701, + 39702, + 39703, + 39704, + 39705, + 39706, + 39707, + 39708, + 39709, + 39710, + 39711, + 39712, + 39713, + 39714, + 39715, + 39716, + 39717, + 39718, + 39719, + 39720, + 39721, + 39722, + 39723, + 39724, + 39725, + 39726, + 39727, + 39728, + 39729, + 39730, + 39731, + 39732, + 39733, + 39734, + 39735, + 39736, + 39737, + 39738, + 39739, + 39740, + 39741, + 39742, + 39743, + 39744, + 39745, + 39746, + 39747, + 39748, + 39749, + 39750, + 39751, + 39752, + 39753, + 39754, + 39755, + 39756, + 39757, + 39758, + 39759, + 39760, + 39761, + 39762, + 39763, + 39764, + 39765, + 39766, + 39767, + 39768, + 39769, + 39770, + 39771, + 39772, + 39773, + 39774, + 39775, + 39776, + 39777, + 39778, + 39779, + 39780, + 39781, + 39782, + 39783, + 39784, + 39785, + 39786, + 39787, + 39788, + 39789, + 39790, + 39791, + 39792, + 39793, + 39794, + 39795, + 39796, + 39797, + 39798, + 39799, + 39800, + 39801, + 39802, + 39803, + 39804, + 39805, + 39806, + 39807, + 39808, + 39809, + 39810, + 39811, + 39812, + 39813, + 39814, + 39815, + 39816, + 39817, + 39818, + 39819, + 39820, + 39821, + 39822, + 39823, + 39824, + 39825, + 39826, + 39827, + 39828, + 39829, + 39830, + 39831, + 39832, + 39833, + 39834, + 39835, + 39836, + 39837, + 39838, + 39839, + 39840, + 39841, + 39842, + 39843, + 39844, + 39845, + 39846, + 39847, + 39848, + 39849, + 39850, + 39851, + 39852, + 39853, + 39854, + 39855, + 39856, + 39857, + 39858, + 39859, + 39860, + 39861, + 39862, + 39863, + 39864, + 39865, + 39866, + 39867, + 39868, + 39869, + 39870, + 39871, + 39872, + 39873, + 39874, + 39875, + 39876, + 39877, + 39878, + 39879, + 39880, + 39881, + 39882, + 39883, + 39884, + 39885, + 39886, + 39887, + 39888, + 39889, + 39890, + 39891, + 39892, + 39893, + 39894, + 39895, + 39896, + 39897, + 39898, + 39899, + 39900, + 39901, + 39902, + 39903, + 39904, + 39905, + 39906, + 39907, + 39908, + 39909, + 39910, + 39911, + 39912, + 39913, + 39914, + 39915, + 39916, + 39917, + 39918, + 39919, + 39920, + 39921, + 39922, + 39923, + 39924, + 39925, + 39926, + 39927, + 39928, + 39929, + 39930, + 39931, + 39932, + 39933, + 39934, + 39935, + 39936, + 39937, + 39938, + 39939, + 39940, + 39941, + 39942, + 39943, + 39944, + 39945, + 39946, + 39947, + 39948, + 39949, + 39950, + 39951, + 39952, + 39953, + 39954, + 39955, + 39956, + 39957, + 39958, + 39959, + 39960, + 39961, + 39962, + 39963, + 39964, + 39965, + 39966, + 39967, + 39968, + 39969, + 39970, + 39971, + 39972, + 39973, + 39974, + 39975, + 39976, + 39977, + 39978, + 39979, + 39980, + 39981, + 39982, + 39983, + 39984, + 39985, + 39986, + 39987, + 39988, + 39989, + 39990, + 39991, + 39992, + 39993, + 39994, + 39995, + 39996, + 39997, + 39998, + 39999, + 40000, + 40001, + 40002, + 40003, + 40004, + 40005, + 40006, + 40007, + 40008, + 40009, + 40010, + 40011, + 40012, + 40013, + 40014, + 40015, + 40016, + 40017, + 40018, + 40019, + 40020, + 40021, + 40022, + 40023, + 40024, + 40025, + 40026, + 40027, + 40028, + 40029, + 40030, + 40031, + 40032, + 40033, + 40034, + 40035, + 40036, + 40037, + 40038, + 40039, + 40040, + 40041, + 40042, + 40043, + 40044, + 40045, + 40046, + 40047, + 40048, + 40049, + 40050, + 40051, + 40052, + 40053, + 40054, + 40055, + 40056, + 40057, + 40058, + 40059, + 40060, + 40061, + 40062, + 40063, + 40064, + 40065, + 40066, + 40067, + 40068, + 40069, + 40070, + 40071, + 40072, + 40073, + 40074, + 40075, + 40076, + 40077, + 40078, + 40079, + 40080, + 40081, + 40082, + 40083, + 40084, + 40085, + 40086, + 40087, + 40088, + 40089, + 40090, + 40091, + 40092, + 40093, + 40094, + 40095, + 40096, + 40097, + 40098, + 40099, + 40100, + 40101, + 40102, + 40103, + 40104, + 40105, + 40106, + 40107, + 40108, + 40109, + 40110, + 40111, + 40112, + 40113, + 40114, + 40115, + 40116, + 40117, + 40118, + 40119, + 40120, + 40121, + 40122, + 40123, + 40124, + 40125, + 40126, + 40127, + 40128, + 40129, + 40130, + 40131, + 40132, + 40133, + 40134, + 40135, + 40136, + 40137, + 40138, + 40139, + 40140, + 40141, + 40142, + 40143, + 40144, + 40145, + 40146, + 40147, + 40148, + 40149, + 40150, + 40151, + 40152, + 40153, + 40154, + 40155, + 40156, + 40157, + 40158, + 40159, + 40160, + 40161, + 40162, + 40163, + 40164, + 40165, + 40166, + 40167, + 40168, + 40169, + 40170, + 40171, + 40172, + 40173, + 40174, + 40175, + 40176, + 40177, + 40178, + 40179, + 40180, + 40181, + 40182, + 40183, + 40184, + 40185, + 40186, + 40187, + 40188, + 40189, + 40190, + 40191, + 40192, + 40193, + 40194, + 40195, + 40196, + 40197, + 40198, + 40199, + 40200, + 40201, + 40202, + 40203, + 40204, + 40205, + 40206, + 40207, + 40208, + 40209, + 40210, + 40211, + 40212, + 40213, + 40214, + 40215, + 40216, + 40217, + 40218, + 40219, + 40220, + 40221, + 40222, + 40223, + 40224, + 40225, + 40226, + 40227, + 40228, + 40229, + 40230, + 40231, + 40232, + 40233, + 40234, + 40235, + 40236, + 40237, + 40238, + 40239, + 40240, + 40241, + 40242, + 40243, + 40244, + 40245, + 40246, + 40247, + 40248, + 40249, + 40250, + 40251, + 40252, + 40253, + 40254, + 40255, + 40256, + 40257, + 40258, + 40259, + 40260, + 40261, + 40262, + 40263, + 40264, + 40265, + 40266, + 40267, + 40268, + 40269, + 40270, + 40271, + 40272, + 40273, + 40274, + 40275, + 40276, + 40277, + 40278, + 40279, + 40280, + 40281, + 40282, + 40283, + 40284, + 40285, + 40286, + 40287, + 40288, + 40289, + 40290, + 40291, + 40292, + 40293, + 40294, + 40295, + 40296, + 40297, + 40298, + 40299, + 40300, + 40301, + 40302, + 40303, + 40304, + 40305, + 40306, + 40307, + 40308, + 40309, + 40310, + 40311, + 40312, + 40313, + 40314, + 40315, + 40316, + 40317, + 40318, + 40319, + 40320, + 40321, + 40322, + 40323, + 40324, + 40325, + 40326, + 40327, + 40328, + 40329, + 40330, + 40331, + 40332, + 40333, + 40334, + 40335, + 40336, + 40337, + 40338, + 40339, + 40340, + 40341, + 40342, + 40343, + 40344, + 40345, + 40346, + 40347, + 40348, + 40349, + 40350, + 40351, + 40352, + 40353, + 40354, + 40355, + 40356, + 40357, + 40358, + 40359, + 40360, + 40361, + 40362, + 40363, + 40364, + 40365, + 40366, + 40367, + 40368, + 40369, + 40370, + 40371, + 40372, + 40373, + 40374, + 40375, + 40376, + 40377, + 40378, + 40379, + 40380, + 40381, + 40382, + 40383, + 40384, + 40385, + 40386, + 40387, + 40388, + 40389, + 40390, + 40391, + 40392, + 40393, + 40394, + 40395, + 40396, + 40397, + 40398, + 40399, + 40400, + 40401, + 40402, + 40403, + 40404, + 40405, + 40406, + 40407, + 40408, + 40409, + 40410, + 40411, + 40412, + 40413, + 40414, + 40415, + 40416, + 40417, + 40418, + 40419, + 40420, + 40421, + 40422, + 40423, + 40424, + 40425, + 40426, + 40427, + 40428, + 40429, + 40430, + 40431, + 40432, + 40433, + 40434, + 40435, + 40436, + 40437, + 40438, + 40439, + 40440, + 40441, + 40442, + 40443, + 40444, + 40445, + 40446, + 40447, + 40448, + 40449, + 40450, + 40451, + 40452, + 40453, + 40454, + 40455, + 40456, + 40457, + 40458, + 40459, + 40460, + 40461, + 40462, + 40463, + 40464, + 40465, + 40466, + 40467, + 40468, + 40469, + 40470, + 40471, + 40472, + 40473, + 40474, + 40475, + 40476, + 40477, + 40478, + 40479, + 40480, + 40481, + 40482, + 40483, + 40484, + 40485, + 40486, + 40487, + 40488, + 40489, + 40490, + 40491, + 40492, + 40493, + 40494, + 40495, + 40496, + 40497, + 40498, + 40499, + 40500, + 40501, + 40502, + 40503, + 40504, + 40505, + 40506, + 40507, + 40508, + 40509, + 40510, + 40511, + 40512, + 40513, + 40514, + 40515, + 40516, + 40517, + 40518, + 40519, + 40520, + 40521, + 40522, + 40523, + 40524, + 40525, + 40526, + 40527, + 40528, + 40529, + 40530, + 40531, + 40532, + 40533, + 40534, + 40535, + 40536, + 40537, + 40538, + 40539, + 40540, + 40541, + 40542, + 40543, + 40544, + 40545, + 40546, + 40547, + 40548, + 40549, + 40550, + 40551, + 40552, + 40553, + 40554, + 40555, + 40556, + 40557, + 40558, + 40559, + 40560, + 40561, + 40562, + 40563, + 40564, + 40565, + 40566, + 40567, + 40568, + 40569, + 40570, + 40571, + 40572, + 40573, + 40574, + 40575, + 40576, + 40577, + 40578, + 40579, + 40580, + 40581, + 40582, + 40583, + 40584, + 40585, + 40586, + 40587, + 40588, + 40589, + 40590, + 40591, + 40592, + 40593, + 40594, + 40595, + 40596, + 40597, + 40598, + 40599, + 40600, + 40601, + 40602, + 40603, + 40604, + 40605, + 40606, + 40607, + 40608, + 40609, + 40610, + 40611, + 40612, + 40613, + 40614, + 40615, + 40616, + 40617, + 40618, + 40619, + 40620, + 40621, + 40622, + 40623, + 40624, + 40625, + 40626, + 40627, + 40628, + 40629, + 40630, + 40631, + 40632, + 40633, + 40634, + 40635, + 40636, + 40637, + 40638, + 40639, + 40640, + 40641, + 40642, + 40643, + 40644, + 40645, + 40646, + 40647, + 40648, + 40649, + 40650, + 40651, + 40652, + 40653, + 40654, + 40655, + 40656, + 40657, + 40658, + 40659, + 40660, + 40661, + 40662, + 40663, + 40664, + 40665, + 40666, + 40667, + 40668, + 40669, + 40670, + 40671, + 40672, + 40673, + 40674, + 40675, + 40676, + 40677, + 40678, + 40679, + 40680, + 40681, + 40682, + 40683, + 40684, + 40685, + 40686, + 40687, + 40688, + 40689, + 40690, + 40691, + 40692, + 40693, + 40694, + 40695, + 40696, + 40697, + 40698, + 40699, + 40700, + 40701, + 40702, + 40703, + 40704, + 40705, + 40706, + 40707, + 40708, + 40709, + 40710, + 40711, + 40712, + 40713, + 40714, + 40715, + 40716, + 40717, + 40718, + 40719, + 40720, + 40721, + 40722, + 40723, + 40724, + 40725, + 40726, + 40727, + 40728, + 40729, + 40730, + 40731, + 40732, + 40733, + 40734, + 40735, + 40736, + 40737, + 40738, + 40739, + 40740, + 40741, + 40742, + 40743, + 40744, + 40745, + 40746, + 40747, + 40748, + 40749, + 40750, + 40751, + 40752, + 40753, + 40754, + 40755, + 40756, + 40757, + 40758, + 40759, + 40760, + 40761, + 40762, + 40763, + 40764, + 40765, + 40766, + 40767, + 40768, + 40769, + 40770, + 40771, + 40772, + 40773, + 40774, + 40775, + 40776, + 40777, + 40778, + 40779, + 40780, + 40781, + 40782, + 40783, + 40784, + 40785, + 40786, + 40787, + 40788, + 40789, + 40790, + 40791, + 40792, + 40793, + 40794, + 40795, + 40796, + 40797, + 40798, + 40799, + 40800, + 40801, + 40802, + 40803, + 40804, + 40805, + 40806, + 40807, + 40808, + 40809, + 40810, + 40811, + 40812, + 40813, + 40814, + 40815, + 40816, + 40817, + 40818, + 40819, + 40820, + 40821, + 40822, + 40823, + 40824, + 40825, + 40826, + 40827, + 40828, + 40829, + 40830, + 40831, + 40832, + 40833, + 40834, + 40835, + 40836, + 40837, + 40838, + 40839, + 40840, + 40841, + 40842, + 40843, + 40844, + 40845, + 40846, + 40847, + 40848, + 40849, + 40850, + 40851, + 40852, + 40853, + 40854, + 40855, + 40856, + 40857, + 40858, + 40859, + 40860, + 40861, + 40862, + 40863, + 40864, + 40865, + 40866, + 40867, + 40868, + 40869, + 40870, + 40871, + 40872, + 40873, + 40874, + 40875, + 40876, + 40877, + 40878, + 40879, + 40880, + 40881, + 40882, + 40883, + 40884, + 40885, + 40886, + 40887, + 40888, + 40889, + 40890, + 40891, + 40892, + 40893, + 40894, + 40895, + 40896, + 40897, + 40898, + 40899, + 40900, + 40901, + 40902, + 40903, + 40904, + 40905, + 40906, + 40907, + 40908, + 40960, + 40961, + 40962, + 40963, + 40964, + 40965, + 40966, + 40967, + 40968, + 40969, + 40970, + 40971, + 40972, + 40973, + 40974, + 40975, + 40976, + 40977, + 40978, + 40979, + 40980, + 40981, + 40982, + 40983, + 40984, + 40985, + 40986, + 40987, + 40988, + 40989, + 40990, + 40991, + 40992, + 40993, + 40994, + 40995, + 40996, + 40997, + 40998, + 40999, + 41000, + 41001, + 41002, + 41003, + 41004, + 41005, + 41006, + 41007, + 41008, + 41009, + 41010, + 41011, + 41012, + 41013, + 41014, + 41015, + 41016, + 41017, + 41018, + 41019, + 41020, + 41021, + 41022, + 41023, + 41024, + 41025, + 41026, + 41027, + 41028, + 41029, + 41030, + 41031, + 41032, + 41033, + 41034, + 41035, + 41036, + 41037, + 41038, + 41039, + 41040, + 41041, + 41042, + 41043, + 41044, + 41045, + 41046, + 41047, + 41048, + 41049, + 41050, + 41051, + 41052, + 41053, + 41054, + 41055, + 41056, + 41057, + 41058, + 41059, + 41060, + 41061, + 41062, + 41063, + 41064, + 41065, + 41066, + 41067, + 41068, + 41069, + 41070, + 41071, + 41072, + 41073, + 41074, + 41075, + 41076, + 41077, + 41078, + 41079, + 41080, + 41081, + 41082, + 41083, + 41084, + 41085, + 41086, + 41087, + 41088, + 41089, + 41090, + 41091, + 41092, + 41093, + 41094, + 41095, + 41096, + 41097, + 41098, + 41099, + 41100, + 41101, + 41102, + 41103, + 41104, + 41105, + 41106, + 41107, + 41108, + 41109, + 41110, + 41111, + 41112, + 41113, + 41114, + 41115, + 41116, + 41117, + 41118, + 41119, + 41120, + 41121, + 41122, + 41123, + 41124, + 41125, + 41126, + 41127, + 41128, + 41129, + 41130, + 41131, + 41132, + 41133, + 41134, + 41135, + 41136, + 41137, + 41138, + 41139, + 41140, + 41141, + 41142, + 41143, + 41144, + 41145, + 41146, + 41147, + 41148, + 41149, + 41150, + 41151, + 41152, + 41153, + 41154, + 41155, + 41156, + 41157, + 41158, + 41159, + 41160, + 41161, + 41162, + 41163, + 41164, + 41165, + 41166, + 41167, + 41168, + 41169, + 41170, + 41171, + 41172, + 41173, + 41174, + 41175, + 41176, + 41177, + 41178, + 41179, + 41180, + 41181, + 41182, + 41183, + 41184, + 41185, + 41186, + 41187, + 41188, + 41189, + 41190, + 41191, + 41192, + 41193, + 41194, + 41195, + 41196, + 41197, + 41198, + 41199, + 41200, + 41201, + 41202, + 41203, + 41204, + 41205, + 41206, + 41207, + 41208, + 41209, + 41210, + 41211, + 41212, + 41213, + 41214, + 41215, + 41216, + 41217, + 41218, + 41219, + 41220, + 41221, + 41222, + 41223, + 41224, + 41225, + 41226, + 41227, + 41228, + 41229, + 41230, + 41231, + 41232, + 41233, + 41234, + 41235, + 41236, + 41237, + 41238, + 41239, + 41240, + 41241, + 41242, + 41243, + 41244, + 41245, + 41246, + 41247, + 41248, + 41249, + 41250, + 41251, + 41252, + 41253, + 41254, + 41255, + 41256, + 41257, + 41258, + 41259, + 41260, + 41261, + 41262, + 41263, + 41264, + 41265, + 41266, + 41267, + 41268, + 41269, + 41270, + 41271, + 41272, + 41273, + 41274, + 41275, + 41276, + 41277, + 41278, + 41279, + 41280, + 41281, + 41282, + 41283, + 41284, + 41285, + 41286, + 41287, + 41288, + 41289, + 41290, + 41291, + 41292, + 41293, + 41294, + 41295, + 41296, + 41297, + 41298, + 41299, + 41300, + 41301, + 41302, + 41303, + 41304, + 41305, + 41306, + 41307, + 41308, + 41309, + 41310, + 41311, + 41312, + 41313, + 41314, + 41315, + 41316, + 41317, + 41318, + 41319, + 41320, + 41321, + 41322, + 41323, + 41324, + 41325, + 41326, + 41327, + 41328, + 41329, + 41330, + 41331, + 41332, + 41333, + 41334, + 41335, + 41336, + 41337, + 41338, + 41339, + 41340, + 41341, + 41342, + 41343, + 41344, + 41345, + 41346, + 41347, + 41348, + 41349, + 41350, + 41351, + 41352, + 41353, + 41354, + 41355, + 41356, + 41357, + 41358, + 41359, + 41360, + 41361, + 41362, + 41363, + 41364, + 41365, + 41366, + 41367, + 41368, + 41369, + 41370, + 41371, + 41372, + 41373, + 41374, + 41375, + 41376, + 41377, + 41378, + 41379, + 41380, + 41381, + 41382, + 41383, + 41384, + 41385, + 41386, + 41387, + 41388, + 41389, + 41390, + 41391, + 41392, + 41393, + 41394, + 41395, + 41396, + 41397, + 41398, + 41399, + 41400, + 41401, + 41402, + 41403, + 41404, + 41405, + 41406, + 41407, + 41408, + 41409, + 41410, + 41411, + 41412, + 41413, + 41414, + 41415, + 41416, + 41417, + 41418, + 41419, + 41420, + 41421, + 41422, + 41423, + 41424, + 41425, + 41426, + 41427, + 41428, + 41429, + 41430, + 41431, + 41432, + 41433, + 41434, + 41435, + 41436, + 41437, + 41438, + 41439, + 41440, + 41441, + 41442, + 41443, + 41444, + 41445, + 41446, + 41447, + 41448, + 41449, + 41450, + 41451, + 41452, + 41453, + 41454, + 41455, + 41456, + 41457, + 41458, + 41459, + 41460, + 41461, + 41462, + 41463, + 41464, + 41465, + 41466, + 41467, + 41468, + 41469, + 41470, + 41471, + 41472, + 41473, + 41474, + 41475, + 41476, + 41477, + 41478, + 41479, + 41480, + 41481, + 41482, + 41483, + 41484, + 41485, + 41486, + 41487, + 41488, + 41489, + 41490, + 41491, + 41492, + 41493, + 41494, + 41495, + 41496, + 41497, + 41498, + 41499, + 41500, + 41501, + 41502, + 41503, + 41504, + 41505, + 41506, + 41507, + 41508, + 41509, + 41510, + 41511, + 41512, + 41513, + 41514, + 41515, + 41516, + 41517, + 41518, + 41519, + 41520, + 41521, + 41522, + 41523, + 41524, + 41525, + 41526, + 41527, + 41528, + 41529, + 41530, + 41531, + 41532, + 41533, + 41534, + 41535, + 41536, + 41537, + 41538, + 41539, + 41540, + 41541, + 41542, + 41543, + 41544, + 41545, + 41546, + 41547, + 41548, + 41549, + 41550, + 41551, + 41552, + 41553, + 41554, + 41555, + 41556, + 41557, + 41558, + 41559, + 41560, + 41561, + 41562, + 41563, + 41564, + 41565, + 41566, + 41567, + 41568, + 41569, + 41570, + 41571, + 41572, + 41573, + 41574, + 41575, + 41576, + 41577, + 41578, + 41579, + 41580, + 41581, + 41582, + 41583, + 41584, + 41585, + 41586, + 41587, + 41588, + 41589, + 41590, + 41591, + 41592, + 41593, + 41594, + 41595, + 41596, + 41597, + 41598, + 41599, + 41600, + 41601, + 41602, + 41603, + 41604, + 41605, + 41606, + 41607, + 41608, + 41609, + 41610, + 41611, + 41612, + 41613, + 41614, + 41615, + 41616, + 41617, + 41618, + 41619, + 41620, + 41621, + 41622, + 41623, + 41624, + 41625, + 41626, + 41627, + 41628, + 41629, + 41630, + 41631, + 41632, + 41633, + 41634, + 41635, + 41636, + 41637, + 41638, + 41639, + 41640, + 41641, + 41642, + 41643, + 41644, + 41645, + 41646, + 41647, + 41648, + 41649, + 41650, + 41651, + 41652, + 41653, + 41654, + 41655, + 41656, + 41657, + 41658, + 41659, + 41660, + 41661, + 41662, + 41663, + 41664, + 41665, + 41666, + 41667, + 41668, + 41669, + 41670, + 41671, + 41672, + 41673, + 41674, + 41675, + 41676, + 41677, + 41678, + 41679, + 41680, + 41681, + 41682, + 41683, + 41684, + 41685, + 41686, + 41687, + 41688, + 41689, + 41690, + 41691, + 41692, + 41693, + 41694, + 41695, + 41696, + 41697, + 41698, + 41699, + 41700, + 41701, + 41702, + 41703, + 41704, + 41705, + 41706, + 41707, + 41708, + 41709, + 41710, + 41711, + 41712, + 41713, + 41714, + 41715, + 41716, + 41717, + 41718, + 41719, + 41720, + 41721, + 41722, + 41723, + 41724, + 41725, + 41726, + 41727, + 41728, + 41729, + 41730, + 41731, + 41732, + 41733, + 41734, + 41735, + 41736, + 41737, + 41738, + 41739, + 41740, + 41741, + 41742, + 41743, + 41744, + 41745, + 41746, + 41747, + 41748, + 41749, + 41750, + 41751, + 41752, + 41753, + 41754, + 41755, + 41756, + 41757, + 41758, + 41759, + 41760, + 41761, + 41762, + 41763, + 41764, + 41765, + 41766, + 41767, + 41768, + 41769, + 41770, + 41771, + 41772, + 41773, + 41774, + 41775, + 41776, + 41777, + 41778, + 41779, + 41780, + 41781, + 41782, + 41783, + 41784, + 41785, + 41786, + 41787, + 41788, + 41789, + 41790, + 41791, + 41792, + 41793, + 41794, + 41795, + 41796, + 41797, + 41798, + 41799, + 41800, + 41801, + 41802, + 41803, + 41804, + 41805, + 41806, + 41807, + 41808, + 41809, + 41810, + 41811, + 41812, + 41813, + 41814, + 41815, + 41816, + 41817, + 41818, + 41819, + 41820, + 41821, + 41822, + 41823, + 41824, + 41825, + 41826, + 41827, + 41828, + 41829, + 41830, + 41831, + 41832, + 41833, + 41834, + 41835, + 41836, + 41837, + 41838, + 41839, + 41840, + 41841, + 41842, + 41843, + 41844, + 41845, + 41846, + 41847, + 41848, + 41849, + 41850, + 41851, + 41852, + 41853, + 41854, + 41855, + 41856, + 41857, + 41858, + 41859, + 41860, + 41861, + 41862, + 41863, + 41864, + 41865, + 41866, + 41867, + 41868, + 41869, + 41870, + 41871, + 41872, + 41873, + 41874, + 41875, + 41876, + 41877, + 41878, + 41879, + 41880, + 41881, + 41882, + 41883, + 41884, + 41885, + 41886, + 41887, + 41888, + 41889, + 41890, + 41891, + 41892, + 41893, + 41894, + 41895, + 41896, + 41897, + 41898, + 41899, + 41900, + 41901, + 41902, + 41903, + 41904, + 41905, + 41906, + 41907, + 41908, + 41909, + 41910, + 41911, + 41912, + 41913, + 41914, + 41915, + 41916, + 41917, + 41918, + 41919, + 41920, + 41921, + 41922, + 41923, + 41924, + 41925, + 41926, + 41927, + 41928, + 41929, + 41930, + 41931, + 41932, + 41933, + 41934, + 41935, + 41936, + 41937, + 41938, + 41939, + 41940, + 41941, + 41942, + 41943, + 41944, + 41945, + 41946, + 41947, + 41948, + 41949, + 41950, + 41951, + 41952, + 41953, + 41954, + 41955, + 41956, + 41957, + 41958, + 41959, + 41960, + 41961, + 41962, + 41963, + 41964, + 41965, + 41966, + 41967, + 41968, + 41969, + 41970, + 41971, + 41972, + 41973, + 41974, + 41975, + 41976, + 41977, + 41978, + 41979, + 41980, + 41981, + 41982, + 41983, + 41984, + 41985, + 41986, + 41987, + 41988, + 41989, + 41990, + 41991, + 41992, + 41993, + 41994, + 41995, + 41996, + 41997, + 41998, + 41999, + 42000, + 42001, + 42002, + 42003, + 42004, + 42005, + 42006, + 42007, + 42008, + 42009, + 42010, + 42011, + 42012, + 42013, + 42014, + 42015, + 42016, + 42017, + 42018, + 42019, + 42020, + 42021, + 42022, + 42023, + 42024, + 42025, + 42026, + 42027, + 42028, + 42029, + 42030, + 42031, + 42032, + 42033, + 42034, + 42035, + 42036, + 42037, + 42038, + 42039, + 42040, + 42041, + 42042, + 42043, + 42044, + 42045, + 42046, + 42047, + 42048, + 42049, + 42050, + 42051, + 42052, + 42053, + 42054, + 42055, + 42056, + 42057, + 42058, + 42059, + 42060, + 42061, + 42062, + 42063, + 42064, + 42065, + 42066, + 42067, + 42068, + 42069, + 42070, + 42071, + 42072, + 42073, + 42074, + 42075, + 42076, + 42077, + 42078, + 42079, + 42080, + 42081, + 42082, + 42083, + 42084, + 42085, + 42086, + 42087, + 42088, + 42089, + 42090, + 42091, + 42092, + 42093, + 42094, + 42095, + 42096, + 42097, + 42098, + 42099, + 42100, + 42101, + 42102, + 42103, + 42104, + 42105, + 42106, + 42107, + 42108, + 42109, + 42110, + 42111, + 42112, + 42113, + 42114, + 42115, + 42116, + 42117, + 42118, + 42119, + 42120, + 42121, + 42122, + 42123, + 42124, + 42192, + 42193, + 42194, + 42195, + 42196, + 42197, + 42198, + 42199, + 42200, + 42201, + 42202, + 42203, + 42204, + 42205, + 42206, + 42207, + 42208, + 42209, + 42210, + 42211, + 42212, + 42213, + 42214, + 42215, + 42216, + 42217, + 42218, + 42219, + 42220, + 42221, + 42222, + 42223, + 42224, + 42225, + 42226, + 42227, + 42228, + 42229, + 42230, + 42231, + 42232, + 42233, + 42234, + 42235, + 42236, + 42237, + 42240, + 42241, + 42242, + 42243, + 42244, + 42245, + 42246, + 42247, + 42248, + 42249, + 42250, + 42251, + 42252, + 42253, + 42254, + 42255, + 42256, + 42257, + 42258, + 42259, + 42260, + 42261, + 42262, + 42263, + 42264, + 42265, + 42266, + 42267, + 42268, + 42269, + 42270, + 42271, + 42272, + 42273, + 42274, + 42275, + 42276, + 42277, + 42278, + 42279, + 42280, + 42281, + 42282, + 42283, + 42284, + 42285, + 42286, + 42287, + 42288, + 42289, + 42290, + 42291, + 42292, + 42293, + 42294, + 42295, + 42296, + 42297, + 42298, + 42299, + 42300, + 42301, + 42302, + 42303, + 42304, + 42305, + 42306, + 42307, + 42308, + 42309, + 42310, + 42311, + 42312, + 42313, + 42314, + 42315, + 42316, + 42317, + 42318, + 42319, + 42320, + 42321, + 42322, + 42323, + 42324, + 42325, + 42326, + 42327, + 42328, + 42329, + 42330, + 42331, + 42332, + 42333, + 42334, + 42335, + 42336, + 42337, + 42338, + 42339, + 42340, + 42341, + 42342, + 42343, + 42344, + 42345, + 42346, + 42347, + 42348, + 42349, + 42350, + 42351, + 42352, + 42353, + 42354, + 42355, + 42356, + 42357, + 42358, + 42359, + 42360, + 42361, + 42362, + 42363, + 42364, + 42365, + 42366, + 42367, + 42368, + 42369, + 42370, + 42371, + 42372, + 42373, + 42374, + 42375, + 42376, + 42377, + 42378, + 42379, + 42380, + 42381, + 42382, + 42383, + 42384, + 42385, + 42386, + 42387, + 42388, + 42389, + 42390, + 42391, + 42392, + 42393, + 42394, + 42395, + 42396, + 42397, + 42398, + 42399, + 42400, + 42401, + 42402, + 42403, + 42404, + 42405, + 42406, + 42407, + 42408, + 42409, + 42410, + 42411, + 42412, + 42413, + 42414, + 42415, + 42416, + 42417, + 42418, + 42419, + 42420, + 42421, + 42422, + 42423, + 42424, + 42425, + 42426, + 42427, + 42428, + 42429, + 42430, + 42431, + 42432, + 42433, + 42434, + 42435, + 42436, + 42437, + 42438, + 42439, + 42440, + 42441, + 42442, + 42443, + 42444, + 42445, + 42446, + 42447, + 42448, + 42449, + 42450, + 42451, + 42452, + 42453, + 42454, + 42455, + 42456, + 42457, + 42458, + 42459, + 42460, + 42461, + 42462, + 42463, + 42464, + 42465, + 42466, + 42467, + 42468, + 42469, + 42470, + 42471, + 42472, + 42473, + 42474, + 42475, + 42476, + 42477, + 42478, + 42479, + 42480, + 42481, + 42482, + 42483, + 42484, + 42485, + 42486, + 42487, + 42488, + 42489, + 42490, + 42491, + 42492, + 42493, + 42494, + 42495, + 42496, + 42497, + 42498, + 42499, + 42500, + 42501, + 42502, + 42503, + 42504, + 42505, + 42506, + 42507, + 42508, + 42512, + 42513, + 42514, + 42515, + 42516, + 42517, + 42518, + 42519, + 42520, + 42521, + 42522, + 42523, + 42524, + 42525, + 42526, + 42527, + 42538, + 42539, + 42560, + 42561, + 42562, + 42563, + 42564, + 42565, + 42566, + 42567, + 42568, + 42569, + 42570, + 42571, + 42572, + 42573, + 42574, + 42575, + 42576, + 42577, + 42578, + 42579, + 42580, + 42581, + 42582, + 42583, + 42584, + 42585, + 42586, + 42587, + 42588, + 42589, + 42590, + 42591, + 42592, + 42593, + 42594, + 42595, + 42596, + 42597, + 42598, + 42599, + 42600, + 42601, + 42602, + 42603, + 42604, + 42605, + 42606, + 42623, + 42624, + 42625, + 42626, + 42627, + 42628, + 42629, + 42630, + 42631, + 42632, + 42633, + 42634, + 42635, + 42636, + 42637, + 42638, + 42639, + 42640, + 42641, + 42642, + 42643, + 42644, + 42645, + 42646, + 42647, + 42656, + 42657, + 42658, + 42659, + 42660, + 42661, + 42662, + 42663, + 42664, + 42665, + 42666, + 42667, + 42668, + 42669, + 42670, + 42671, + 42672, + 42673, + 42674, + 42675, + 42676, + 42677, + 42678, + 42679, + 42680, + 42681, + 42682, + 42683, + 42684, + 42685, + 42686, + 42687, + 42688, + 42689, + 42690, + 42691, + 42692, + 42693, + 42694, + 42695, + 42696, + 42697, + 42698, + 42699, + 42700, + 42701, + 42702, + 42703, + 42704, + 42705, + 42706, + 42707, + 42708, + 42709, + 42710, + 42711, + 42712, + 42713, + 42714, + 42715, + 42716, + 42717, + 42718, + 42719, + 42720, + 42721, + 42722, + 42723, + 42724, + 42725, + 42726, + 42727, + 42728, + 42729, + 42730, + 42731, + 42732, + 42733, + 42734, + 42735, + 42775, + 42776, + 42777, + 42778, + 42779, + 42780, + 42781, + 42782, + 42783, + 42786, + 42787, + 42788, + 42789, + 42790, + 42791, + 42792, + 42793, + 42794, + 42795, + 42796, + 42797, + 42798, + 42799, + 42800, + 42801, + 42802, + 42803, + 42804, + 42805, + 42806, + 42807, + 42808, + 42809, + 42810, + 42811, + 42812, + 42813, + 42814, + 42815, + 42816, + 42817, + 42818, + 42819, + 42820, + 42821, + 42822, + 42823, + 42824, + 42825, + 42826, + 42827, + 42828, + 42829, + 42830, + 42831, + 42832, + 42833, + 42834, + 42835, + 42836, + 42837, + 42838, + 42839, + 42840, + 42841, + 42842, + 42843, + 42844, + 42845, + 42846, + 42847, + 42848, + 42849, + 42850, + 42851, + 42852, + 42853, + 42854, + 42855, + 42856, + 42857, + 42858, + 42859, + 42860, + 42861, + 42862, + 42863, + 42864, + 42865, + 42866, + 42867, + 42868, + 42869, + 42870, + 42871, + 42872, + 42873, + 42874, + 42875, + 42876, + 42877, + 42878, + 42879, + 42880, + 42881, + 42882, + 42883, + 42884, + 42885, + 42886, + 42887, + 42888, + 42891, + 42892, + 42893, + 42894, + 42896, + 42897, + 42898, + 42899, + 42912, + 42913, + 42914, + 42915, + 42916, + 42917, + 42918, + 42919, + 42920, + 42921, + 42922, + 43000, + 43001, + 43002, + 43003, + 43004, + 43005, + 43006, + 43007, + 43008, + 43009, + 43011, + 43012, + 43013, + 43015, + 43016, + 43017, + 43018, + 43020, + 43021, + 43022, + 43023, + 43024, + 43025, + 43026, + 43027, + 43028, + 43029, + 43030, + 43031, + 43032, + 43033, + 43034, + 43035, + 43036, + 43037, + 43038, + 43039, + 43040, + 43041, + 43042, + 43072, + 43073, + 43074, + 43075, + 43076, + 43077, + 43078, + 43079, + 43080, + 43081, + 43082, + 43083, + 43084, + 43085, + 43086, + 43087, + 43088, + 43089, + 43090, + 43091, + 43092, + 43093, + 43094, + 43095, + 43096, + 43097, + 43098, + 43099, + 43100, + 43101, + 43102, + 43103, + 43104, + 43105, + 43106, + 43107, + 43108, + 43109, + 43110, + 43111, + 43112, + 43113, + 43114, + 43115, + 43116, + 43117, + 43118, + 43119, + 43120, + 43121, + 43122, + 43123, + 43138, + 43139, + 43140, + 43141, + 43142, + 43143, + 43144, + 43145, + 43146, + 43147, + 43148, + 43149, + 43150, + 43151, + 43152, + 43153, + 43154, + 43155, + 43156, + 43157, + 43158, + 43159, + 43160, + 43161, + 43162, + 43163, + 43164, + 43165, + 43166, + 43167, + 43168, + 43169, + 43170, + 43171, + 43172, + 43173, + 43174, + 43175, + 43176, + 43177, + 43178, + 43179, + 43180, + 43181, + 43182, + 43183, + 43184, + 43185, + 43186, + 43187, + 43250, + 43251, + 43252, + 43253, + 43254, + 43255, + 43259, + 43274, + 43275, + 43276, + 43277, + 43278, + 43279, + 43280, + 43281, + 43282, + 43283, + 43284, + 43285, + 43286, + 43287, + 43288, + 43289, + 43290, + 43291, + 43292, + 43293, + 43294, + 43295, + 43296, + 43297, + 43298, + 43299, + 43300, + 43301, + 43312, + 43313, + 43314, + 43315, + 43316, + 43317, + 43318, + 43319, + 43320, + 43321, + 43322, + 43323, + 43324, + 43325, + 43326, + 43327, + 43328, + 43329, + 43330, + 43331, + 43332, + 43333, + 43334, + 43360, + 43361, + 43362, + 43363, + 43364, + 43365, + 43366, + 43367, + 43368, + 43369, + 43370, + 43371, + 43372, + 43373, + 43374, + 43375, + 43376, + 43377, + 43378, + 43379, + 43380, + 43381, + 43382, + 43383, + 43384, + 43385, + 43386, + 43387, + 43388, + 43396, + 43397, + 43398, + 43399, + 43400, + 43401, + 43402, + 43403, + 43404, + 43405, + 43406, + 43407, + 43408, + 43409, + 43410, + 43411, + 43412, + 43413, + 43414, + 43415, + 43416, + 43417, + 43418, + 43419, + 43420, + 43421, + 43422, + 43423, + 43424, + 43425, + 43426, + 43427, + 43428, + 43429, + 43430, + 43431, + 43432, + 43433, + 43434, + 43435, + 43436, + 43437, + 43438, + 43439, + 43440, + 43441, + 43442, + 43471, + 43520, + 43521, + 43522, + 43523, + 43524, + 43525, + 43526, + 43527, + 43528, + 43529, + 43530, + 43531, + 43532, + 43533, + 43534, + 43535, + 43536, + 43537, + 43538, + 43539, + 43540, + 43541, + 43542, + 43543, + 43544, + 43545, + 43546, + 43547, + 43548, + 43549, + 43550, + 43551, + 43552, + 43553, + 43554, + 43555, + 43556, + 43557, + 43558, + 43559, + 43560, + 43584, + 43585, + 43586, + 43588, + 43589, + 43590, + 43591, + 43592, + 43593, + 43594, + 43595, + 43616, + 43617, + 43618, + 43619, + 43620, + 43621, + 43622, + 43623, + 43624, + 43625, + 43626, + 43627, + 43628, + 43629, + 43630, + 43631, + 43632, + 43633, + 43634, + 43635, + 43636, + 43637, + 43638, + 43642, + 43648, + 43649, + 43650, + 43651, + 43652, + 43653, + 43654, + 43655, + 43656, + 43657, + 43658, + 43659, + 43660, + 43661, + 43662, + 43663, + 43664, + 43665, + 43666, + 43667, + 43668, + 43669, + 43670, + 43671, + 43672, + 43673, + 43674, + 43675, + 43676, + 43677, + 43678, + 43679, + 43680, + 43681, + 43682, + 43683, + 43684, + 43685, + 43686, + 43687, + 43688, + 43689, + 43690, + 43691, + 43692, + 43693, + 43694, + 43695, + 43697, + 43701, + 43702, + 43705, + 43706, + 43707, + 43708, + 43709, + 43712, + 43714, + 43739, + 43740, + 43741, + 43744, + 43745, + 43746, + 43747, + 43748, + 43749, + 43750, + 43751, + 43752, + 43753, + 43754, + 43762, + 43763, + 43764, + 43777, + 43778, + 43779, + 43780, + 43781, + 43782, + 43785, + 43786, + 43787, + 43788, + 43789, + 43790, + 43793, + 43794, + 43795, + 43796, + 43797, + 43798, + 43808, + 43809, + 43810, + 43811, + 43812, + 43813, + 43814, + 43816, + 43817, + 43818, + 43819, + 43820, + 43821, + 43822, + 43968, + 43969, + 43970, + 43971, + 43972, + 43973, + 43974, + 43975, + 43976, + 43977, + 43978, + 43979, + 43980, + 43981, + 43982, + 43983, + 43984, + 43985, + 43986, + 43987, + 43988, + 43989, + 43990, + 43991, + 43992, + 43993, + 43994, + 43995, + 43996, + 43997, + 43998, + 43999, + 44000, + 44001, + 44002, + 44032, + 44033, + 44034, + 44035, + 44036, + 44037, + 44038, + 44039, + 44040, + 44041, + 44042, + 44043, + 44044, + 44045, + 44046, + 44047, + 44048, + 44049, + 44050, + 44051, + 44052, + 44053, + 44054, + 44055, + 44056, + 44057, + 44058, + 44059, + 44060, + 44061, + 44062, + 44063, + 44064, + 44065, + 44066, + 44067, + 44068, + 44069, + 44070, + 44071, + 44072, + 44073, + 44074, + 44075, + 44076, + 44077, + 44078, + 44079, + 44080, + 44081, + 44082, + 44083, + 44084, + 44085, + 44086, + 44087, + 44088, + 44089, + 44090, + 44091, + 44092, + 44093, + 44094, + 44095, + 44096, + 44097, + 44098, + 44099, + 44100, + 44101, + 44102, + 44103, + 44104, + 44105, + 44106, + 44107, + 44108, + 44109, + 44110, + 44111, + 44112, + 44113, + 44114, + 44115, + 44116, + 44117, + 44118, + 44119, + 44120, + 44121, + 44122, + 44123, + 44124, + 44125, + 44126, + 44127, + 44128, + 44129, + 44130, + 44131, + 44132, + 44133, + 44134, + 44135, + 44136, + 44137, + 44138, + 44139, + 44140, + 44141, + 44142, + 44143, + 44144, + 44145, + 44146, + 44147, + 44148, + 44149, + 44150, + 44151, + 44152, + 44153, + 44154, + 44155, + 44156, + 44157, + 44158, + 44159, + 44160, + 44161, + 44162, + 44163, + 44164, + 44165, + 44166, + 44167, + 44168, + 44169, + 44170, + 44171, + 44172, + 44173, + 44174, + 44175, + 44176, + 44177, + 44178, + 44179, + 44180, + 44181, + 44182, + 44183, + 44184, + 44185, + 44186, + 44187, + 44188, + 44189, + 44190, + 44191, + 44192, + 44193, + 44194, + 44195, + 44196, + 44197, + 44198, + 44199, + 44200, + 44201, + 44202, + 44203, + 44204, + 44205, + 44206, + 44207, + 44208, + 44209, + 44210, + 44211, + 44212, + 44213, + 44214, + 44215, + 44216, + 44217, + 44218, + 44219, + 44220, + 44221, + 44222, + 44223, + 44224, + 44225, + 44226, + 44227, + 44228, + 44229, + 44230, + 44231, + 44232, + 44233, + 44234, + 44235, + 44236, + 44237, + 44238, + 44239, + 44240, + 44241, + 44242, + 44243, + 44244, + 44245, + 44246, + 44247, + 44248, + 44249, + 44250, + 44251, + 44252, + 44253, + 44254, + 44255, + 44256, + 44257, + 44258, + 44259, + 44260, + 44261, + 44262, + 44263, + 44264, + 44265, + 44266, + 44267, + 44268, + 44269, + 44270, + 44271, + 44272, + 44273, + 44274, + 44275, + 44276, + 44277, + 44278, + 44279, + 44280, + 44281, + 44282, + 44283, + 44284, + 44285, + 44286, + 44287, + 44288, + 44289, + 44290, + 44291, + 44292, + 44293, + 44294, + 44295, + 44296, + 44297, + 44298, + 44299, + 44300, + 44301, + 44302, + 44303, + 44304, + 44305, + 44306, + 44307, + 44308, + 44309, + 44310, + 44311, + 44312, + 44313, + 44314, + 44315, + 44316, + 44317, + 44318, + 44319, + 44320, + 44321, + 44322, + 44323, + 44324, + 44325, + 44326, + 44327, + 44328, + 44329, + 44330, + 44331, + 44332, + 44333, + 44334, + 44335, + 44336, + 44337, + 44338, + 44339, + 44340, + 44341, + 44342, + 44343, + 44344, + 44345, + 44346, + 44347, + 44348, + 44349, + 44350, + 44351, + 44352, + 44353, + 44354, + 44355, + 44356, + 44357, + 44358, + 44359, + 44360, + 44361, + 44362, + 44363, + 44364, + 44365, + 44366, + 44367, + 44368, + 44369, + 44370, + 44371, + 44372, + 44373, + 44374, + 44375, + 44376, + 44377, + 44378, + 44379, + 44380, + 44381, + 44382, + 44383, + 44384, + 44385, + 44386, + 44387, + 44388, + 44389, + 44390, + 44391, + 44392, + 44393, + 44394, + 44395, + 44396, + 44397, + 44398, + 44399, + 44400, + 44401, + 44402, + 44403, + 44404, + 44405, + 44406, + 44407, + 44408, + 44409, + 44410, + 44411, + 44412, + 44413, + 44414, + 44415, + 44416, + 44417, + 44418, + 44419, + 44420, + 44421, + 44422, + 44423, + 44424, + 44425, + 44426, + 44427, + 44428, + 44429, + 44430, + 44431, + 44432, + 44433, + 44434, + 44435, + 44436, + 44437, + 44438, + 44439, + 44440, + 44441, + 44442, + 44443, + 44444, + 44445, + 44446, + 44447, + 44448, + 44449, + 44450, + 44451, + 44452, + 44453, + 44454, + 44455, + 44456, + 44457, + 44458, + 44459, + 44460, + 44461, + 44462, + 44463, + 44464, + 44465, + 44466, + 44467, + 44468, + 44469, + 44470, + 44471, + 44472, + 44473, + 44474, + 44475, + 44476, + 44477, + 44478, + 44479, + 44480, + 44481, + 44482, + 44483, + 44484, + 44485, + 44486, + 44487, + 44488, + 44489, + 44490, + 44491, + 44492, + 44493, + 44494, + 44495, + 44496, + 44497, + 44498, + 44499, + 44500, + 44501, + 44502, + 44503, + 44504, + 44505, + 44506, + 44507, + 44508, + 44509, + 44510, + 44511, + 44512, + 44513, + 44514, + 44515, + 44516, + 44517, + 44518, + 44519, + 44520, + 44521, + 44522, + 44523, + 44524, + 44525, + 44526, + 44527, + 44528, + 44529, + 44530, + 44531, + 44532, + 44533, + 44534, + 44535, + 44536, + 44537, + 44538, + 44539, + 44540, + 44541, + 44542, + 44543, + 44544, + 44545, + 44546, + 44547, + 44548, + 44549, + 44550, + 44551, + 44552, + 44553, + 44554, + 44555, + 44556, + 44557, + 44558, + 44559, + 44560, + 44561, + 44562, + 44563, + 44564, + 44565, + 44566, + 44567, + 44568, + 44569, + 44570, + 44571, + 44572, + 44573, + 44574, + 44575, + 44576, + 44577, + 44578, + 44579, + 44580, + 44581, + 44582, + 44583, + 44584, + 44585, + 44586, + 44587, + 44588, + 44589, + 44590, + 44591, + 44592, + 44593, + 44594, + 44595, + 44596, + 44597, + 44598, + 44599, + 44600, + 44601, + 44602, + 44603, + 44604, + 44605, + 44606, + 44607, + 44608, + 44609, + 44610, + 44611, + 44612, + 44613, + 44614, + 44615, + 44616, + 44617, + 44618, + 44619, + 44620, + 44621, + 44622, + 44623, + 44624, + 44625, + 44626, + 44627, + 44628, + 44629, + 44630, + 44631, + 44632, + 44633, + 44634, + 44635, + 44636, + 44637, + 44638, + 44639, + 44640, + 44641, + 44642, + 44643, + 44644, + 44645, + 44646, + 44647, + 44648, + 44649, + 44650, + 44651, + 44652, + 44653, + 44654, + 44655, + 44656, + 44657, + 44658, + 44659, + 44660, + 44661, + 44662, + 44663, + 44664, + 44665, + 44666, + 44667, + 44668, + 44669, + 44670, + 44671, + 44672, + 44673, + 44674, + 44675, + 44676, + 44677, + 44678, + 44679, + 44680, + 44681, + 44682, + 44683, + 44684, + 44685, + 44686, + 44687, + 44688, + 44689, + 44690, + 44691, + 44692, + 44693, + 44694, + 44695, + 44696, + 44697, + 44698, + 44699, + 44700, + 44701, + 44702, + 44703, + 44704, + 44705, + 44706, + 44707, + 44708, + 44709, + 44710, + 44711, + 44712, + 44713, + 44714, + 44715, + 44716, + 44717, + 44718, + 44719, + 44720, + 44721, + 44722, + 44723, + 44724, + 44725, + 44726, + 44727, + 44728, + 44729, + 44730, + 44731, + 44732, + 44733, + 44734, + 44735, + 44736, + 44737, + 44738, + 44739, + 44740, + 44741, + 44742, + 44743, + 44744, + 44745, + 44746, + 44747, + 44748, + 44749, + 44750, + 44751, + 44752, + 44753, + 44754, + 44755, + 44756, + 44757, + 44758, + 44759, + 44760, + 44761, + 44762, + 44763, + 44764, + 44765, + 44766, + 44767, + 44768, + 44769, + 44770, + 44771, + 44772, + 44773, + 44774, + 44775, + 44776, + 44777, + 44778, + 44779, + 44780, + 44781, + 44782, + 44783, + 44784, + 44785, + 44786, + 44787, + 44788, + 44789, + 44790, + 44791, + 44792, + 44793, + 44794, + 44795, + 44796, + 44797, + 44798, + 44799, + 44800, + 44801, + 44802, + 44803, + 44804, + 44805, + 44806, + 44807, + 44808, + 44809, + 44810, + 44811, + 44812, + 44813, + 44814, + 44815, + 44816, + 44817, + 44818, + 44819, + 44820, + 44821, + 44822, + 44823, + 44824, + 44825, + 44826, + 44827, + 44828, + 44829, + 44830, + 44831, + 44832, + 44833, + 44834, + 44835, + 44836, + 44837, + 44838, + 44839, + 44840, + 44841, + 44842, + 44843, + 44844, + 44845, + 44846, + 44847, + 44848, + 44849, + 44850, + 44851, + 44852, + 44853, + 44854, + 44855, + 44856, + 44857, + 44858, + 44859, + 44860, + 44861, + 44862, + 44863, + 44864, + 44865, + 44866, + 44867, + 44868, + 44869, + 44870, + 44871, + 44872, + 44873, + 44874, + 44875, + 44876, + 44877, + 44878, + 44879, + 44880, + 44881, + 44882, + 44883, + 44884, + 44885, + 44886, + 44887, + 44888, + 44889, + 44890, + 44891, + 44892, + 44893, + 44894, + 44895, + 44896, + 44897, + 44898, + 44899, + 44900, + 44901, + 44902, + 44903, + 44904, + 44905, + 44906, + 44907, + 44908, + 44909, + 44910, + 44911, + 44912, + 44913, + 44914, + 44915, + 44916, + 44917, + 44918, + 44919, + 44920, + 44921, + 44922, + 44923, + 44924, + 44925, + 44926, + 44927, + 44928, + 44929, + 44930, + 44931, + 44932, + 44933, + 44934, + 44935, + 44936, + 44937, + 44938, + 44939, + 44940, + 44941, + 44942, + 44943, + 44944, + 44945, + 44946, + 44947, + 44948, + 44949, + 44950, + 44951, + 44952, + 44953, + 44954, + 44955, + 44956, + 44957, + 44958, + 44959, + 44960, + 44961, + 44962, + 44963, + 44964, + 44965, + 44966, + 44967, + 44968, + 44969, + 44970, + 44971, + 44972, + 44973, + 44974, + 44975, + 44976, + 44977, + 44978, + 44979, + 44980, + 44981, + 44982, + 44983, + 44984, + 44985, + 44986, + 44987, + 44988, + 44989, + 44990, + 44991, + 44992, + 44993, + 44994, + 44995, + 44996, + 44997, + 44998, + 44999, + 45000, + 45001, + 45002, + 45003, + 45004, + 45005, + 45006, + 45007, + 45008, + 45009, + 45010, + 45011, + 45012, + 45013, + 45014, + 45015, + 45016, + 45017, + 45018, + 45019, + 45020, + 45021, + 45022, + 45023, + 45024, + 45025, + 45026, + 45027, + 45028, + 45029, + 45030, + 45031, + 45032, + 45033, + 45034, + 45035, + 45036, + 45037, + 45038, + 45039, + 45040, + 45041, + 45042, + 45043, + 45044, + 45045, + 45046, + 45047, + 45048, + 45049, + 45050, + 45051, + 45052, + 45053, + 45054, + 45055, + 45056, + 45057, + 45058, + 45059, + 45060, + 45061, + 45062, + 45063, + 45064, + 45065, + 45066, + 45067, + 45068, + 45069, + 45070, + 45071, + 45072, + 45073, + 45074, + 45075, + 45076, + 45077, + 45078, + 45079, + 45080, + 45081, + 45082, + 45083, + 45084, + 45085, + 45086, + 45087, + 45088, + 45089, + 45090, + 45091, + 45092, + 45093, + 45094, + 45095, + 45096, + 45097, + 45098, + 45099, + 45100, + 45101, + 45102, + 45103, + 45104, + 45105, + 45106, + 45107, + 45108, + 45109, + 45110, + 45111, + 45112, + 45113, + 45114, + 45115, + 45116, + 45117, + 45118, + 45119, + 45120, + 45121, + 45122, + 45123, + 45124, + 45125, + 45126, + 45127, + 45128, + 45129, + 45130, + 45131, + 45132, + 45133, + 45134, + 45135, + 45136, + 45137, + 45138, + 45139, + 45140, + 45141, + 45142, + 45143, + 45144, + 45145, + 45146, + 45147, + 45148, + 45149, + 45150, + 45151, + 45152, + 45153, + 45154, + 45155, + 45156, + 45157, + 45158, + 45159, + 45160, + 45161, + 45162, + 45163, + 45164, + 45165, + 45166, + 45167, + 45168, + 45169, + 45170, + 45171, + 45172, + 45173, + 45174, + 45175, + 45176, + 45177, + 45178, + 45179, + 45180, + 45181, + 45182, + 45183, + 45184, + 45185, + 45186, + 45187, + 45188, + 45189, + 45190, + 45191, + 45192, + 45193, + 45194, + 45195, + 45196, + 45197, + 45198, + 45199, + 45200, + 45201, + 45202, + 45203, + 45204, + 45205, + 45206, + 45207, + 45208, + 45209, + 45210, + 45211, + 45212, + 45213, + 45214, + 45215, + 45216, + 45217, + 45218, + 45219, + 45220, + 45221, + 45222, + 45223, + 45224, + 45225, + 45226, + 45227, + 45228, + 45229, + 45230, + 45231, + 45232, + 45233, + 45234, + 45235, + 45236, + 45237, + 45238, + 45239, + 45240, + 45241, + 45242, + 45243, + 45244, + 45245, + 45246, + 45247, + 45248, + 45249, + 45250, + 45251, + 45252, + 45253, + 45254, + 45255, + 45256, + 45257, + 45258, + 45259, + 45260, + 45261, + 45262, + 45263, + 45264, + 45265, + 45266, + 45267, + 45268, + 45269, + 45270, + 45271, + 45272, + 45273, + 45274, + 45275, + 45276, + 45277, + 45278, + 45279, + 45280, + 45281, + 45282, + 45283, + 45284, + 45285, + 45286, + 45287, + 45288, + 45289, + 45290, + 45291, + 45292, + 45293, + 45294, + 45295, + 45296, + 45297, + 45298, + 45299, + 45300, + 45301, + 45302, + 45303, + 45304, + 45305, + 45306, + 45307, + 45308, + 45309, + 45310, + 45311, + 45312, + 45313, + 45314, + 45315, + 45316, + 45317, + 45318, + 45319, + 45320, + 45321, + 45322, + 45323, + 45324, + 45325, + 45326, + 45327, + 45328, + 45329, + 45330, + 45331, + 45332, + 45333, + 45334, + 45335, + 45336, + 45337, + 45338, + 45339, + 45340, + 45341, + 45342, + 45343, + 45344, + 45345, + 45346, + 45347, + 45348, + 45349, + 45350, + 45351, + 45352, + 45353, + 45354, + 45355, + 45356, + 45357, + 45358, + 45359, + 45360, + 45361, + 45362, + 45363, + 45364, + 45365, + 45366, + 45367, + 45368, + 45369, + 45370, + 45371, + 45372, + 45373, + 45374, + 45375, + 45376, + 45377, + 45378, + 45379, + 45380, + 45381, + 45382, + 45383, + 45384, + 45385, + 45386, + 45387, + 45388, + 45389, + 45390, + 45391, + 45392, + 45393, + 45394, + 45395, + 45396, + 45397, + 45398, + 45399, + 45400, + 45401, + 45402, + 45403, + 45404, + 45405, + 45406, + 45407, + 45408, + 45409, + 45410, + 45411, + 45412, + 45413, + 45414, + 45415, + 45416, + 45417, + 45418, + 45419, + 45420, + 45421, + 45422, + 45423, + 45424, + 45425, + 45426, + 45427, + 45428, + 45429, + 45430, + 45431, + 45432, + 45433, + 45434, + 45435, + 45436, + 45437, + 45438, + 45439, + 45440, + 45441, + 45442, + 45443, + 45444, + 45445, + 45446, + 45447, + 45448, + 45449, + 45450, + 45451, + 45452, + 45453, + 45454, + 45455, + 45456, + 45457, + 45458, + 45459, + 45460, + 45461, + 45462, + 45463, + 45464, + 45465, + 45466, + 45467, + 45468, + 45469, + 45470, + 45471, + 45472, + 45473, + 45474, + 45475, + 45476, + 45477, + 45478, + 45479, + 45480, + 45481, + 45482, + 45483, + 45484, + 45485, + 45486, + 45487, + 45488, + 45489, + 45490, + 45491, + 45492, + 45493, + 45494, + 45495, + 45496, + 45497, + 45498, + 45499, + 45500, + 45501, + 45502, + 45503, + 45504, + 45505, + 45506, + 45507, + 45508, + 45509, + 45510, + 45511, + 45512, + 45513, + 45514, + 45515, + 45516, + 45517, + 45518, + 45519, + 45520, + 45521, + 45522, + 45523, + 45524, + 45525, + 45526, + 45527, + 45528, + 45529, + 45530, + 45531, + 45532, + 45533, + 45534, + 45535, + 45536, + 45537, + 45538, + 45539, + 45540, + 45541, + 45542, + 45543, + 45544, + 45545, + 45546, + 45547, + 45548, + 45549, + 45550, + 45551, + 45552, + 45553, + 45554, + 45555, + 45556, + 45557, + 45558, + 45559, + 45560, + 45561, + 45562, + 45563, + 45564, + 45565, + 45566, + 45567, + 45568, + 45569, + 45570, + 45571, + 45572, + 45573, + 45574, + 45575, + 45576, + 45577, + 45578, + 45579, + 45580, + 45581, + 45582, + 45583, + 45584, + 45585, + 45586, + 45587, + 45588, + 45589, + 45590, + 45591, + 45592, + 45593, + 45594, + 45595, + 45596, + 45597, + 45598, + 45599, + 45600, + 45601, + 45602, + 45603, + 45604, + 45605, + 45606, + 45607, + 45608, + 45609, + 45610, + 45611, + 45612, + 45613, + 45614, + 45615, + 45616, + 45617, + 45618, + 45619, + 45620, + 45621, + 45622, + 45623, + 45624, + 45625, + 45626, + 45627, + 45628, + 45629, + 45630, + 45631, + 45632, + 45633, + 45634, + 45635, + 45636, + 45637, + 45638, + 45639, + 45640, + 45641, + 45642, + 45643, + 45644, + 45645, + 45646, + 45647, + 45648, + 45649, + 45650, + 45651, + 45652, + 45653, + 45654, + 45655, + 45656, + 45657, + 45658, + 45659, + 45660, + 45661, + 45662, + 45663, + 45664, + 45665, + 45666, + 45667, + 45668, + 45669, + 45670, + 45671, + 45672, + 45673, + 45674, + 45675, + 45676, + 45677, + 45678, + 45679, + 45680, + 45681, + 45682, + 45683, + 45684, + 45685, + 45686, + 45687, + 45688, + 45689, + 45690, + 45691, + 45692, + 45693, + 45694, + 45695, + 45696, + 45697, + 45698, + 45699, + 45700, + 45701, + 45702, + 45703, + 45704, + 45705, + 45706, + 45707, + 45708, + 45709, + 45710, + 45711, + 45712, + 45713, + 45714, + 45715, + 45716, + 45717, + 45718, + 45719, + 45720, + 45721, + 45722, + 45723, + 45724, + 45725, + 45726, + 45727, + 45728, + 45729, + 45730, + 45731, + 45732, + 45733, + 45734, + 45735, + 45736, + 45737, + 45738, + 45739, + 45740, + 45741, + 45742, + 45743, + 45744, + 45745, + 45746, + 45747, + 45748, + 45749, + 45750, + 45751, + 45752, + 45753, + 45754, + 45755, + 45756, + 45757, + 45758, + 45759, + 45760, + 45761, + 45762, + 45763, + 45764, + 45765, + 45766, + 45767, + 45768, + 45769, + 45770, + 45771, + 45772, + 45773, + 45774, + 45775, + 45776, + 45777, + 45778, + 45779, + 45780, + 45781, + 45782, + 45783, + 45784, + 45785, + 45786, + 45787, + 45788, + 45789, + 45790, + 45791, + 45792, + 45793, + 45794, + 45795, + 45796, + 45797, + 45798, + 45799, + 45800, + 45801, + 45802, + 45803, + 45804, + 45805, + 45806, + 45807, + 45808, + 45809, + 45810, + 45811, + 45812, + 45813, + 45814, + 45815, + 45816, + 45817, + 45818, + 45819, + 45820, + 45821, + 45822, + 45823, + 45824, + 45825, + 45826, + 45827, + 45828, + 45829, + 45830, + 45831, + 45832, + 45833, + 45834, + 45835, + 45836, + 45837, + 45838, + 45839, + 45840, + 45841, + 45842, + 45843, + 45844, + 45845, + 45846, + 45847, + 45848, + 45849, + 45850, + 45851, + 45852, + 45853, + 45854, + 45855, + 45856, + 45857, + 45858, + 45859, + 45860, + 45861, + 45862, + 45863, + 45864, + 45865, + 45866, + 45867, + 45868, + 45869, + 45870, + 45871, + 45872, + 45873, + 45874, + 45875, + 45876, + 45877, + 45878, + 45879, + 45880, + 45881, + 45882, + 45883, + 45884, + 45885, + 45886, + 45887, + 45888, + 45889, + 45890, + 45891, + 45892, + 45893, + 45894, + 45895, + 45896, + 45897, + 45898, + 45899, + 45900, + 45901, + 45902, + 45903, + 45904, + 45905, + 45906, + 45907, + 45908, + 45909, + 45910, + 45911, + 45912, + 45913, + 45914, + 45915, + 45916, + 45917, + 45918, + 45919, + 45920, + 45921, + 45922, + 45923, + 45924, + 45925, + 45926, + 45927, + 45928, + 45929, + 45930, + 45931, + 45932, + 45933, + 45934, + 45935, + 45936, + 45937, + 45938, + 45939, + 45940, + 45941, + 45942, + 45943, + 45944, + 45945, + 45946, + 45947, + 45948, + 45949, + 45950, + 45951, + 45952, + 45953, + 45954, + 45955, + 45956, + 45957, + 45958, + 45959, + 45960, + 45961, + 45962, + 45963, + 45964, + 45965, + 45966, + 45967, + 45968, + 45969, + 45970, + 45971, + 45972, + 45973, + 45974, + 45975, + 45976, + 45977, + 45978, + 45979, + 45980, + 45981, + 45982, + 45983, + 45984, + 45985, + 45986, + 45987, + 45988, + 45989, + 45990, + 45991, + 45992, + 45993, + 45994, + 45995, + 45996, + 45997, + 45998, + 45999, + 46000, + 46001, + 46002, + 46003, + 46004, + 46005, + 46006, + 46007, + 46008, + 46009, + 46010, + 46011, + 46012, + 46013, + 46014, + 46015, + 46016, + 46017, + 46018, + 46019, + 46020, + 46021, + 46022, + 46023, + 46024, + 46025, + 46026, + 46027, + 46028, + 46029, + 46030, + 46031, + 46032, + 46033, + 46034, + 46035, + 46036, + 46037, + 46038, + 46039, + 46040, + 46041, + 46042, + 46043, + 46044, + 46045, + 46046, + 46047, + 46048, + 46049, + 46050, + 46051, + 46052, + 46053, + 46054, + 46055, + 46056, + 46057, + 46058, + 46059, + 46060, + 46061, + 46062, + 46063, + 46064, + 46065, + 46066, + 46067, + 46068, + 46069, + 46070, + 46071, + 46072, + 46073, + 46074, + 46075, + 46076, + 46077, + 46078, + 46079, + 46080, + 46081, + 46082, + 46083, + 46084, + 46085, + 46086, + 46087, + 46088, + 46089, + 46090, + 46091, + 46092, + 46093, + 46094, + 46095, + 46096, + 46097, + 46098, + 46099, + 46100, + 46101, + 46102, + 46103, + 46104, + 46105, + 46106, + 46107, + 46108, + 46109, + 46110, + 46111, + 46112, + 46113, + 46114, + 46115, + 46116, + 46117, + 46118, + 46119, + 46120, + 46121, + 46122, + 46123, + 46124, + 46125, + 46126, + 46127, + 46128, + 46129, + 46130, + 46131, + 46132, + 46133, + 46134, + 46135, + 46136, + 46137, + 46138, + 46139, + 46140, + 46141, + 46142, + 46143, + 46144, + 46145, + 46146, + 46147, + 46148, + 46149, + 46150, + 46151, + 46152, + 46153, + 46154, + 46155, + 46156, + 46157, + 46158, + 46159, + 46160, + 46161, + 46162, + 46163, + 46164, + 46165, + 46166, + 46167, + 46168, + 46169, + 46170, + 46171, + 46172, + 46173, + 46174, + 46175, + 46176, + 46177, + 46178, + 46179, + 46180, + 46181, + 46182, + 46183, + 46184, + 46185, + 46186, + 46187, + 46188, + 46189, + 46190, + 46191, + 46192, + 46193, + 46194, + 46195, + 46196, + 46197, + 46198, + 46199, + 46200, + 46201, + 46202, + 46203, + 46204, + 46205, + 46206, + 46207, + 46208, + 46209, + 46210, + 46211, + 46212, + 46213, + 46214, + 46215, + 46216, + 46217, + 46218, + 46219, + 46220, + 46221, + 46222, + 46223, + 46224, + 46225, + 46226, + 46227, + 46228, + 46229, + 46230, + 46231, + 46232, + 46233, + 46234, + 46235, + 46236, + 46237, + 46238, + 46239, + 46240, + 46241, + 46242, + 46243, + 46244, + 46245, + 46246, + 46247, + 46248, + 46249, + 46250, + 46251, + 46252, + 46253, + 46254, + 46255, + 46256, + 46257, + 46258, + 46259, + 46260, + 46261, + 46262, + 46263, + 46264, + 46265, + 46266, + 46267, + 46268, + 46269, + 46270, + 46271, + 46272, + 46273, + 46274, + 46275, + 46276, + 46277, + 46278, + 46279, + 46280, + 46281, + 46282, + 46283, + 46284, + 46285, + 46286, + 46287, + 46288, + 46289, + 46290, + 46291, + 46292, + 46293, + 46294, + 46295, + 46296, + 46297, + 46298, + 46299, + 46300, + 46301, + 46302, + 46303, + 46304, + 46305, + 46306, + 46307, + 46308, + 46309, + 46310, + 46311, + 46312, + 46313, + 46314, + 46315, + 46316, + 46317, + 46318, + 46319, + 46320, + 46321, + 46322, + 46323, + 46324, + 46325, + 46326, + 46327, + 46328, + 46329, + 46330, + 46331, + 46332, + 46333, + 46334, + 46335, + 46336, + 46337, + 46338, + 46339, + 46340, + 46341, + 46342, + 46343, + 46344, + 46345, + 46346, + 46347, + 46348, + 46349, + 46350, + 46351, + 46352, + 46353, + 46354, + 46355, + 46356, + 46357, + 46358, + 46359, + 46360, + 46361, + 46362, + 46363, + 46364, + 46365, + 46366, + 46367, + 46368, + 46369, + 46370, + 46371, + 46372, + 46373, + 46374, + 46375, + 46376, + 46377, + 46378, + 46379, + 46380, + 46381, + 46382, + 46383, + 46384, + 46385, + 46386, + 46387, + 46388, + 46389, + 46390, + 46391, + 46392, + 46393, + 46394, + 46395, + 46396, + 46397, + 46398, + 46399, + 46400, + 46401, + 46402, + 46403, + 46404, + 46405, + 46406, + 46407, + 46408, + 46409, + 46410, + 46411, + 46412, + 46413, + 46414, + 46415, + 46416, + 46417, + 46418, + 46419, + 46420, + 46421, + 46422, + 46423, + 46424, + 46425, + 46426, + 46427, + 46428, + 46429, + 46430, + 46431, + 46432, + 46433, + 46434, + 46435, + 46436, + 46437, + 46438, + 46439, + 46440, + 46441, + 46442, + 46443, + 46444, + 46445, + 46446, + 46447, + 46448, + 46449, + 46450, + 46451, + 46452, + 46453, + 46454, + 46455, + 46456, + 46457, + 46458, + 46459, + 46460, + 46461, + 46462, + 46463, + 46464, + 46465, + 46466, + 46467, + 46468, + 46469, + 46470, + 46471, + 46472, + 46473, + 46474, + 46475, + 46476, + 46477, + 46478, + 46479, + 46480, + 46481, + 46482, + 46483, + 46484, + 46485, + 46486, + 46487, + 46488, + 46489, + 46490, + 46491, + 46492, + 46493, + 46494, + 46495, + 46496, + 46497, + 46498, + 46499, + 46500, + 46501, + 46502, + 46503, + 46504, + 46505, + 46506, + 46507, + 46508, + 46509, + 46510, + 46511, + 46512, + 46513, + 46514, + 46515, + 46516, + 46517, + 46518, + 46519, + 46520, + 46521, + 46522, + 46523, + 46524, + 46525, + 46526, + 46527, + 46528, + 46529, + 46530, + 46531, + 46532, + 46533, + 46534, + 46535, + 46536, + 46537, + 46538, + 46539, + 46540, + 46541, + 46542, + 46543, + 46544, + 46545, + 46546, + 46547, + 46548, + 46549, + 46550, + 46551, + 46552, + 46553, + 46554, + 46555, + 46556, + 46557, + 46558, + 46559, + 46560, + 46561, + 46562, + 46563, + 46564, + 46565, + 46566, + 46567, + 46568, + 46569, + 46570, + 46571, + 46572, + 46573, + 46574, + 46575, + 46576, + 46577, + 46578, + 46579, + 46580, + 46581, + 46582, + 46583, + 46584, + 46585, + 46586, + 46587, + 46588, + 46589, + 46590, + 46591, + 46592, + 46593, + 46594, + 46595, + 46596, + 46597, + 46598, + 46599, + 46600, + 46601, + 46602, + 46603, + 46604, + 46605, + 46606, + 46607, + 46608, + 46609, + 46610, + 46611, + 46612, + 46613, + 46614, + 46615, + 46616, + 46617, + 46618, + 46619, + 46620, + 46621, + 46622, + 46623, + 46624, + 46625, + 46626, + 46627, + 46628, + 46629, + 46630, + 46631, + 46632, + 46633, + 46634, + 46635, + 46636, + 46637, + 46638, + 46639, + 46640, + 46641, + 46642, + 46643, + 46644, + 46645, + 46646, + 46647, + 46648, + 46649, + 46650, + 46651, + 46652, + 46653, + 46654, + 46655, + 46656, + 46657, + 46658, + 46659, + 46660, + 46661, + 46662, + 46663, + 46664, + 46665, + 46666, + 46667, + 46668, + 46669, + 46670, + 46671, + 46672, + 46673, + 46674, + 46675, + 46676, + 46677, + 46678, + 46679, + 46680, + 46681, + 46682, + 46683, + 46684, + 46685, + 46686, + 46687, + 46688, + 46689, + 46690, + 46691, + 46692, + 46693, + 46694, + 46695, + 46696, + 46697, + 46698, + 46699, + 46700, + 46701, + 46702, + 46703, + 46704, + 46705, + 46706, + 46707, + 46708, + 46709, + 46710, + 46711, + 46712, + 46713, + 46714, + 46715, + 46716, + 46717, + 46718, + 46719, + 46720, + 46721, + 46722, + 46723, + 46724, + 46725, + 46726, + 46727, + 46728, + 46729, + 46730, + 46731, + 46732, + 46733, + 46734, + 46735, + 46736, + 46737, + 46738, + 46739, + 46740, + 46741, + 46742, + 46743, + 46744, + 46745, + 46746, + 46747, + 46748, + 46749, + 46750, + 46751, + 46752, + 46753, + 46754, + 46755, + 46756, + 46757, + 46758, + 46759, + 46760, + 46761, + 46762, + 46763, + 46764, + 46765, + 46766, + 46767, + 46768, + 46769, + 46770, + 46771, + 46772, + 46773, + 46774, + 46775, + 46776, + 46777, + 46778, + 46779, + 46780, + 46781, + 46782, + 46783, + 46784, + 46785, + 46786, + 46787, + 46788, + 46789, + 46790, + 46791, + 46792, + 46793, + 46794, + 46795, + 46796, + 46797, + 46798, + 46799, + 46800, + 46801, + 46802, + 46803, + 46804, + 46805, + 46806, + 46807, + 46808, + 46809, + 46810, + 46811, + 46812, + 46813, + 46814, + 46815, + 46816, + 46817, + 46818, + 46819, + 46820, + 46821, + 46822, + 46823, + 46824, + 46825, + 46826, + 46827, + 46828, + 46829, + 46830, + 46831, + 46832, + 46833, + 46834, + 46835, + 46836, + 46837, + 46838, + 46839, + 46840, + 46841, + 46842, + 46843, + 46844, + 46845, + 46846, + 46847, + 46848, + 46849, + 46850, + 46851, + 46852, + 46853, + 46854, + 46855, + 46856, + 46857, + 46858, + 46859, + 46860, + 46861, + 46862, + 46863, + 46864, + 46865, + 46866, + 46867, + 46868, + 46869, + 46870, + 46871, + 46872, + 46873, + 46874, + 46875, + 46876, + 46877, + 46878, + 46879, + 46880, + 46881, + 46882, + 46883, + 46884, + 46885, + 46886, + 46887, + 46888, + 46889, + 46890, + 46891, + 46892, + 46893, + 46894, + 46895, + 46896, + 46897, + 46898, + 46899, + 46900, + 46901, + 46902, + 46903, + 46904, + 46905, + 46906, + 46907, + 46908, + 46909, + 46910, + 46911, + 46912, + 46913, + 46914, + 46915, + 46916, + 46917, + 46918, + 46919, + 46920, + 46921, + 46922, + 46923, + 46924, + 46925, + 46926, + 46927, + 46928, + 46929, + 46930, + 46931, + 46932, + 46933, + 46934, + 46935, + 46936, + 46937, + 46938, + 46939, + 46940, + 46941, + 46942, + 46943, + 46944, + 46945, + 46946, + 46947, + 46948, + 46949, + 46950, + 46951, + 46952, + 46953, + 46954, + 46955, + 46956, + 46957, + 46958, + 46959, + 46960, + 46961, + 46962, + 46963, + 46964, + 46965, + 46966, + 46967, + 46968, + 46969, + 46970, + 46971, + 46972, + 46973, + 46974, + 46975, + 46976, + 46977, + 46978, + 46979, + 46980, + 46981, + 46982, + 46983, + 46984, + 46985, + 46986, + 46987, + 46988, + 46989, + 46990, + 46991, + 46992, + 46993, + 46994, + 46995, + 46996, + 46997, + 46998, + 46999, + 47000, + 47001, + 47002, + 47003, + 47004, + 47005, + 47006, + 47007, + 47008, + 47009, + 47010, + 47011, + 47012, + 47013, + 47014, + 47015, + 47016, + 47017, + 47018, + 47019, + 47020, + 47021, + 47022, + 47023, + 47024, + 47025, + 47026, + 47027, + 47028, + 47029, + 47030, + 47031, + 47032, + 47033, + 47034, + 47035, + 47036, + 47037, + 47038, + 47039, + 47040, + 47041, + 47042, + 47043, + 47044, + 47045, + 47046, + 47047, + 47048, + 47049, + 47050, + 47051, + 47052, + 47053, + 47054, + 47055, + 47056, + 47057, + 47058, + 47059, + 47060, + 47061, + 47062, + 47063, + 47064, + 47065, + 47066, + 47067, + 47068, + 47069, + 47070, + 47071, + 47072, + 47073, + 47074, + 47075, + 47076, + 47077, + 47078, + 47079, + 47080, + 47081, + 47082, + 47083, + 47084, + 47085, + 47086, + 47087, + 47088, + 47089, + 47090, + 47091, + 47092, + 47093, + 47094, + 47095, + 47096, + 47097, + 47098, + 47099, + 47100, + 47101, + 47102, + 47103, + 47104, + 47105, + 47106, + 47107, + 47108, + 47109, + 47110, + 47111, + 47112, + 47113, + 47114, + 47115, + 47116, + 47117, + 47118, + 47119, + 47120, + 47121, + 47122, + 47123, + 47124, + 47125, + 47126, + 47127, + 47128, + 47129, + 47130, + 47131, + 47132, + 47133, + 47134, + 47135, + 47136, + 47137, + 47138, + 47139, + 47140, + 47141, + 47142, + 47143, + 47144, + 47145, + 47146, + 47147, + 47148, + 47149, + 47150, + 47151, + 47152, + 47153, + 47154, + 47155, + 47156, + 47157, + 47158, + 47159, + 47160, + 47161, + 47162, + 47163, + 47164, + 47165, + 47166, + 47167, + 47168, + 47169, + 47170, + 47171, + 47172, + 47173, + 47174, + 47175, + 47176, + 47177, + 47178, + 47179, + 47180, + 47181, + 47182, + 47183, + 47184, + 47185, + 47186, + 47187, + 47188, + 47189, + 47190, + 47191, + 47192, + 47193, + 47194, + 47195, + 47196, + 47197, + 47198, + 47199, + 47200, + 47201, + 47202, + 47203, + 47204, + 47205, + 47206, + 47207, + 47208, + 47209, + 47210, + 47211, + 47212, + 47213, + 47214, + 47215, + 47216, + 47217, + 47218, + 47219, + 47220, + 47221, + 47222, + 47223, + 47224, + 47225, + 47226, + 47227, + 47228, + 47229, + 47230, + 47231, + 47232, + 47233, + 47234, + 47235, + 47236, + 47237, + 47238, + 47239, + 47240, + 47241, + 47242, + 47243, + 47244, + 47245, + 47246, + 47247, + 47248, + 47249, + 47250, + 47251, + 47252, + 47253, + 47254, + 47255, + 47256, + 47257, + 47258, + 47259, + 47260, + 47261, + 47262, + 47263, + 47264, + 47265, + 47266, + 47267, + 47268, + 47269, + 47270, + 47271, + 47272, + 47273, + 47274, + 47275, + 47276, + 47277, + 47278, + 47279, + 47280, + 47281, + 47282, + 47283, + 47284, + 47285, + 47286, + 47287, + 47288, + 47289, + 47290, + 47291, + 47292, + 47293, + 47294, + 47295, + 47296, + 47297, + 47298, + 47299, + 47300, + 47301, + 47302, + 47303, + 47304, + 47305, + 47306, + 47307, + 47308, + 47309, + 47310, + 47311, + 47312, + 47313, + 47314, + 47315, + 47316, + 47317, + 47318, + 47319, + 47320, + 47321, + 47322, + 47323, + 47324, + 47325, + 47326, + 47327, + 47328, + 47329, + 47330, + 47331, + 47332, + 47333, + 47334, + 47335, + 47336, + 47337, + 47338, + 47339, + 47340, + 47341, + 47342, + 47343, + 47344, + 47345, + 47346, + 47347, + 47348, + 47349, + 47350, + 47351, + 47352, + 47353, + 47354, + 47355, + 47356, + 47357, + 47358, + 47359, + 47360, + 47361, + 47362, + 47363, + 47364, + 47365, + 47366, + 47367, + 47368, + 47369, + 47370, + 47371, + 47372, + 47373, + 47374, + 47375, + 47376, + 47377, + 47378, + 47379, + 47380, + 47381, + 47382, + 47383, + 47384, + 47385, + 47386, + 47387, + 47388, + 47389, + 47390, + 47391, + 47392, + 47393, + 47394, + 47395, + 47396, + 47397, + 47398, + 47399, + 47400, + 47401, + 47402, + 47403, + 47404, + 47405, + 47406, + 47407, + 47408, + 47409, + 47410, + 47411, + 47412, + 47413, + 47414, + 47415, + 47416, + 47417, + 47418, + 47419, + 47420, + 47421, + 47422, + 47423, + 47424, + 47425, + 47426, + 47427, + 47428, + 47429, + 47430, + 47431, + 47432, + 47433, + 47434, + 47435, + 47436, + 47437, + 47438, + 47439, + 47440, + 47441, + 47442, + 47443, + 47444, + 47445, + 47446, + 47447, + 47448, + 47449, + 47450, + 47451, + 47452, + 47453, + 47454, + 47455, + 47456, + 47457, + 47458, + 47459, + 47460, + 47461, + 47462, + 47463, + 47464, + 47465, + 47466, + 47467, + 47468, + 47469, + 47470, + 47471, + 47472, + 47473, + 47474, + 47475, + 47476, + 47477, + 47478, + 47479, + 47480, + 47481, + 47482, + 47483, + 47484, + 47485, + 47486, + 47487, + 47488, + 47489, + 47490, + 47491, + 47492, + 47493, + 47494, + 47495, + 47496, + 47497, + 47498, + 47499, + 47500, + 47501, + 47502, + 47503, + 47504, + 47505, + 47506, + 47507, + 47508, + 47509, + 47510, + 47511, + 47512, + 47513, + 47514, + 47515, + 47516, + 47517, + 47518, + 47519, + 47520, + 47521, + 47522, + 47523, + 47524, + 47525, + 47526, + 47527, + 47528, + 47529, + 47530, + 47531, + 47532, + 47533, + 47534, + 47535, + 47536, + 47537, + 47538, + 47539, + 47540, + 47541, + 47542, + 47543, + 47544, + 47545, + 47546, + 47547, + 47548, + 47549, + 47550, + 47551, + 47552, + 47553, + 47554, + 47555, + 47556, + 47557, + 47558, + 47559, + 47560, + 47561, + 47562, + 47563, + 47564, + 47565, + 47566, + 47567, + 47568, + 47569, + 47570, + 47571, + 47572, + 47573, + 47574, + 47575, + 47576, + 47577, + 47578, + 47579, + 47580, + 47581, + 47582, + 47583, + 47584, + 47585, + 47586, + 47587, + 47588, + 47589, + 47590, + 47591, + 47592, + 47593, + 47594, + 47595, + 47596, + 47597, + 47598, + 47599, + 47600, + 47601, + 47602, + 47603, + 47604, + 47605, + 47606, + 47607, + 47608, + 47609, + 47610, + 47611, + 47612, + 47613, + 47614, + 47615, + 47616, + 47617, + 47618, + 47619, + 47620, + 47621, + 47622, + 47623, + 47624, + 47625, + 47626, + 47627, + 47628, + 47629, + 47630, + 47631, + 47632, + 47633, + 47634, + 47635, + 47636, + 47637, + 47638, + 47639, + 47640, + 47641, + 47642, + 47643, + 47644, + 47645, + 47646, + 47647, + 47648, + 47649, + 47650, + 47651, + 47652, + 47653, + 47654, + 47655, + 47656, + 47657, + 47658, + 47659, + 47660, + 47661, + 47662, + 47663, + 47664, + 47665, + 47666, + 47667, + 47668, + 47669, + 47670, + 47671, + 47672, + 47673, + 47674, + 47675, + 47676, + 47677, + 47678, + 47679, + 47680, + 47681, + 47682, + 47683, + 47684, + 47685, + 47686, + 47687, + 47688, + 47689, + 47690, + 47691, + 47692, + 47693, + 47694, + 47695, + 47696, + 47697, + 47698, + 47699, + 47700, + 47701, + 47702, + 47703, + 47704, + 47705, + 47706, + 47707, + 47708, + 47709, + 47710, + 47711, + 47712, + 47713, + 47714, + 47715, + 47716, + 47717, + 47718, + 47719, + 47720, + 47721, + 47722, + 47723, + 47724, + 47725, + 47726, + 47727, + 47728, + 47729, + 47730, + 47731, + 47732, + 47733, + 47734, + 47735, + 47736, + 47737, + 47738, + 47739, + 47740, + 47741, + 47742, + 47743, + 47744, + 47745, + 47746, + 47747, + 47748, + 47749, + 47750, + 47751, + 47752, + 47753, + 47754, + 47755, + 47756, + 47757, + 47758, + 47759, + 47760, + 47761, + 47762, + 47763, + 47764, + 47765, + 47766, + 47767, + 47768, + 47769, + 47770, + 47771, + 47772, + 47773, + 47774, + 47775, + 47776, + 47777, + 47778, + 47779, + 47780, + 47781, + 47782, + 47783, + 47784, + 47785, + 47786, + 47787, + 47788, + 47789, + 47790, + 47791, + 47792, + 47793, + 47794, + 47795, + 47796, + 47797, + 47798, + 47799, + 47800, + 47801, + 47802, + 47803, + 47804, + 47805, + 47806, + 47807, + 47808, + 47809, + 47810, + 47811, + 47812, + 47813, + 47814, + 47815, + 47816, + 47817, + 47818, + 47819, + 47820, + 47821, + 47822, + 47823, + 47824, + 47825, + 47826, + 47827, + 47828, + 47829, + 47830, + 47831, + 47832, + 47833, + 47834, + 47835, + 47836, + 47837, + 47838, + 47839, + 47840, + 47841, + 47842, + 47843, + 47844, + 47845, + 47846, + 47847, + 47848, + 47849, + 47850, + 47851, + 47852, + 47853, + 47854, + 47855, + 47856, + 47857, + 47858, + 47859, + 47860, + 47861, + 47862, + 47863, + 47864, + 47865, + 47866, + 47867, + 47868, + 47869, + 47870, + 47871, + 47872, + 47873, + 47874, + 47875, + 47876, + 47877, + 47878, + 47879, + 47880, + 47881, + 47882, + 47883, + 47884, + 47885, + 47886, + 47887, + 47888, + 47889, + 47890, + 47891, + 47892, + 47893, + 47894, + 47895, + 47896, + 47897, + 47898, + 47899, + 47900, + 47901, + 47902, + 47903, + 47904, + 47905, + 47906, + 47907, + 47908, + 47909, + 47910, + 47911, + 47912, + 47913, + 47914, + 47915, + 47916, + 47917, + 47918, + 47919, + 47920, + 47921, + 47922, + 47923, + 47924, + 47925, + 47926, + 47927, + 47928, + 47929, + 47930, + 47931, + 47932, + 47933, + 47934, + 47935, + 47936, + 47937, + 47938, + 47939, + 47940, + 47941, + 47942, + 47943, + 47944, + 47945, + 47946, + 47947, + 47948, + 47949, + 47950, + 47951, + 47952, + 47953, + 47954, + 47955, + 47956, + 47957, + 47958, + 47959, + 47960, + 47961, + 47962, + 47963, + 47964, + 47965, + 47966, + 47967, + 47968, + 47969, + 47970, + 47971, + 47972, + 47973, + 47974, + 47975, + 47976, + 47977, + 47978, + 47979, + 47980, + 47981, + 47982, + 47983, + 47984, + 47985, + 47986, + 47987, + 47988, + 47989, + 47990, + 47991, + 47992, + 47993, + 47994, + 47995, + 47996, + 47997, + 47998, + 47999, + 48000, + 48001, + 48002, + 48003, + 48004, + 48005, + 48006, + 48007, + 48008, + 48009, + 48010, + 48011, + 48012, + 48013, + 48014, + 48015, + 48016, + 48017, + 48018, + 48019, + 48020, + 48021, + 48022, + 48023, + 48024, + 48025, + 48026, + 48027, + 48028, + 48029, + 48030, + 48031, + 48032, + 48033, + 48034, + 48035, + 48036, + 48037, + 48038, + 48039, + 48040, + 48041, + 48042, + 48043, + 48044, + 48045, + 48046, + 48047, + 48048, + 48049, + 48050, + 48051, + 48052, + 48053, + 48054, + 48055, + 48056, + 48057, + 48058, + 48059, + 48060, + 48061, + 48062, + 48063, + 48064, + 48065, + 48066, + 48067, + 48068, + 48069, + 48070, + 48071, + 48072, + 48073, + 48074, + 48075, + 48076, + 48077, + 48078, + 48079, + 48080, + 48081, + 48082, + 48083, + 48084, + 48085, + 48086, + 48087, + 48088, + 48089, + 48090, + 48091, + 48092, + 48093, + 48094, + 48095, + 48096, + 48097, + 48098, + 48099, + 48100, + 48101, + 48102, + 48103, + 48104, + 48105, + 48106, + 48107, + 48108, + 48109, + 48110, + 48111, + 48112, + 48113, + 48114, + 48115, + 48116, + 48117, + 48118, + 48119, + 48120, + 48121, + 48122, + 48123, + 48124, + 48125, + 48126, + 48127, + 48128, + 48129, + 48130, + 48131, + 48132, + 48133, + 48134, + 48135, + 48136, + 48137, + 48138, + 48139, + 48140, + 48141, + 48142, + 48143, + 48144, + 48145, + 48146, + 48147, + 48148, + 48149, + 48150, + 48151, + 48152, + 48153, + 48154, + 48155, + 48156, + 48157, + 48158, + 48159, + 48160, + 48161, + 48162, + 48163, + 48164, + 48165, + 48166, + 48167, + 48168, + 48169, + 48170, + 48171, + 48172, + 48173, + 48174, + 48175, + 48176, + 48177, + 48178, + 48179, + 48180, + 48181, + 48182, + 48183, + 48184, + 48185, + 48186, + 48187, + 48188, + 48189, + 48190, + 48191, + 48192, + 48193, + 48194, + 48195, + 48196, + 48197, + 48198, + 48199, + 48200, + 48201, + 48202, + 48203, + 48204, + 48205, + 48206, + 48207, + 48208, + 48209, + 48210, + 48211, + 48212, + 48213, + 48214, + 48215, + 48216, + 48217, + 48218, + 48219, + 48220, + 48221, + 48222, + 48223, + 48224, + 48225, + 48226, + 48227, + 48228, + 48229, + 48230, + 48231, + 48232, + 48233, + 48234, + 48235, + 48236, + 48237, + 48238, + 48239, + 48240, + 48241, + 48242, + 48243, + 48244, + 48245, + 48246, + 48247, + 48248, + 48249, + 48250, + 48251, + 48252, + 48253, + 48254, + 48255, + 48256, + 48257, + 48258, + 48259, + 48260, + 48261, + 48262, + 48263, + 48264, + 48265, + 48266, + 48267, + 48268, + 48269, + 48270, + 48271, + 48272, + 48273, + 48274, + 48275, + 48276, + 48277, + 48278, + 48279, + 48280, + 48281, + 48282, + 48283, + 48284, + 48285, + 48286, + 48287, + 48288, + 48289, + 48290, + 48291, + 48292, + 48293, + 48294, + 48295, + 48296, + 48297, + 48298, + 48299, + 48300, + 48301, + 48302, + 48303, + 48304, + 48305, + 48306, + 48307, + 48308, + 48309, + 48310, + 48311, + 48312, + 48313, + 48314, + 48315, + 48316, + 48317, + 48318, + 48319, + 48320, + 48321, + 48322, + 48323, + 48324, + 48325, + 48326, + 48327, + 48328, + 48329, + 48330, + 48331, + 48332, + 48333, + 48334, + 48335, + 48336, + 48337, + 48338, + 48339, + 48340, + 48341, + 48342, + 48343, + 48344, + 48345, + 48346, + 48347, + 48348, + 48349, + 48350, + 48351, + 48352, + 48353, + 48354, + 48355, + 48356, + 48357, + 48358, + 48359, + 48360, + 48361, + 48362, + 48363, + 48364, + 48365, + 48366, + 48367, + 48368, + 48369, + 48370, + 48371, + 48372, + 48373, + 48374, + 48375, + 48376, + 48377, + 48378, + 48379, + 48380, + 48381, + 48382, + 48383, + 48384, + 48385, + 48386, + 48387, + 48388, + 48389, + 48390, + 48391, + 48392, + 48393, + 48394, + 48395, + 48396, + 48397, + 48398, + 48399, + 48400, + 48401, + 48402, + 48403, + 48404, + 48405, + 48406, + 48407, + 48408, + 48409, + 48410, + 48411, + 48412, + 48413, + 48414, + 48415, + 48416, + 48417, + 48418, + 48419, + 48420, + 48421, + 48422, + 48423, + 48424, + 48425, + 48426, + 48427, + 48428, + 48429, + 48430, + 48431, + 48432, + 48433, + 48434, + 48435, + 48436, + 48437, + 48438, + 48439, + 48440, + 48441, + 48442, + 48443, + 48444, + 48445, + 48446, + 48447, + 48448, + 48449, + 48450, + 48451, + 48452, + 48453, + 48454, + 48455, + 48456, + 48457, + 48458, + 48459, + 48460, + 48461, + 48462, + 48463, + 48464, + 48465, + 48466, + 48467, + 48468, + 48469, + 48470, + 48471, + 48472, + 48473, + 48474, + 48475, + 48476, + 48477, + 48478, + 48479, + 48480, + 48481, + 48482, + 48483, + 48484, + 48485, + 48486, + 48487, + 48488, + 48489, + 48490, + 48491, + 48492, + 48493, + 48494, + 48495, + 48496, + 48497, + 48498, + 48499, + 48500, + 48501, + 48502, + 48503, + 48504, + 48505, + 48506, + 48507, + 48508, + 48509, + 48510, + 48511, + 48512, + 48513, + 48514, + 48515, + 48516, + 48517, + 48518, + 48519, + 48520, + 48521, + 48522, + 48523, + 48524, + 48525, + 48526, + 48527, + 48528, + 48529, + 48530, + 48531, + 48532, + 48533, + 48534, + 48535, + 48536, + 48537, + 48538, + 48539, + 48540, + 48541, + 48542, + 48543, + 48544, + 48545, + 48546, + 48547, + 48548, + 48549, + 48550, + 48551, + 48552, + 48553, + 48554, + 48555, + 48556, + 48557, + 48558, + 48559, + 48560, + 48561, + 48562, + 48563, + 48564, + 48565, + 48566, + 48567, + 48568, + 48569, + 48570, + 48571, + 48572, + 48573, + 48574, + 48575, + 48576, + 48577, + 48578, + 48579, + 48580, + 48581, + 48582, + 48583, + 48584, + 48585, + 48586, + 48587, + 48588, + 48589, + 48590, + 48591, + 48592, + 48593, + 48594, + 48595, + 48596, + 48597, + 48598, + 48599, + 48600, + 48601, + 48602, + 48603, + 48604, + 48605, + 48606, + 48607, + 48608, + 48609, + 48610, + 48611, + 48612, + 48613, + 48614, + 48615, + 48616, + 48617, + 48618, + 48619, + 48620, + 48621, + 48622, + 48623, + 48624, + 48625, + 48626, + 48627, + 48628, + 48629, + 48630, + 48631, + 48632, + 48633, + 48634, + 48635, + 48636, + 48637, + 48638, + 48639, + 48640, + 48641, + 48642, + 48643, + 48644, + 48645, + 48646, + 48647, + 48648, + 48649, + 48650, + 48651, + 48652, + 48653, + 48654, + 48655, + 48656, + 48657, + 48658, + 48659, + 48660, + 48661, + 48662, + 48663, + 48664, + 48665, + 48666, + 48667, + 48668, + 48669, + 48670, + 48671, + 48672, + 48673, + 48674, + 48675, + 48676, + 48677, + 48678, + 48679, + 48680, + 48681, + 48682, + 48683, + 48684, + 48685, + 48686, + 48687, + 48688, + 48689, + 48690, + 48691, + 48692, + 48693, + 48694, + 48695, + 48696, + 48697, + 48698, + 48699, + 48700, + 48701, + 48702, + 48703, + 48704, + 48705, + 48706, + 48707, + 48708, + 48709, + 48710, + 48711, + 48712, + 48713, + 48714, + 48715, + 48716, + 48717, + 48718, + 48719, + 48720, + 48721, + 48722, + 48723, + 48724, + 48725, + 48726, + 48727, + 48728, + 48729, + 48730, + 48731, + 48732, + 48733, + 48734, + 48735, + 48736, + 48737, + 48738, + 48739, + 48740, + 48741, + 48742, + 48743, + 48744, + 48745, + 48746, + 48747, + 48748, + 48749, + 48750, + 48751, + 48752, + 48753, + 48754, + 48755, + 48756, + 48757, + 48758, + 48759, + 48760, + 48761, + 48762, + 48763, + 48764, + 48765, + 48766, + 48767, + 48768, + 48769, + 48770, + 48771, + 48772, + 48773, + 48774, + 48775, + 48776, + 48777, + 48778, + 48779, + 48780, + 48781, + 48782, + 48783, + 48784, + 48785, + 48786, + 48787, + 48788, + 48789, + 48790, + 48791, + 48792, + 48793, + 48794, + 48795, + 48796, + 48797, + 48798, + 48799, + 48800, + 48801, + 48802, + 48803, + 48804, + 48805, + 48806, + 48807, + 48808, + 48809, + 48810, + 48811, + 48812, + 48813, + 48814, + 48815, + 48816, + 48817, + 48818, + 48819, + 48820, + 48821, + 48822, + 48823, + 48824, + 48825, + 48826, + 48827, + 48828, + 48829, + 48830, + 48831, + 48832, + 48833, + 48834, + 48835, + 48836, + 48837, + 48838, + 48839, + 48840, + 48841, + 48842, + 48843, + 48844, + 48845, + 48846, + 48847, + 48848, + 48849, + 48850, + 48851, + 48852, + 48853, + 48854, + 48855, + 48856, + 48857, + 48858, + 48859, + 48860, + 48861, + 48862, + 48863, + 48864, + 48865, + 48866, + 48867, + 48868, + 48869, + 48870, + 48871, + 48872, + 48873, + 48874, + 48875, + 48876, + 48877, + 48878, + 48879, + 48880, + 48881, + 48882, + 48883, + 48884, + 48885, + 48886, + 48887, + 48888, + 48889, + 48890, + 48891, + 48892, + 48893, + 48894, + 48895, + 48896, + 48897, + 48898, + 48899, + 48900, + 48901, + 48902, + 48903, + 48904, + 48905, + 48906, + 48907, + 48908, + 48909, + 48910, + 48911, + 48912, + 48913, + 48914, + 48915, + 48916, + 48917, + 48918, + 48919, + 48920, + 48921, + 48922, + 48923, + 48924, + 48925, + 48926, + 48927, + 48928, + 48929, + 48930, + 48931, + 48932, + 48933, + 48934, + 48935, + 48936, + 48937, + 48938, + 48939, + 48940, + 48941, + 48942, + 48943, + 48944, + 48945, + 48946, + 48947, + 48948, + 48949, + 48950, + 48951, + 48952, + 48953, + 48954, + 48955, + 48956, + 48957, + 48958, + 48959, + 48960, + 48961, + 48962, + 48963, + 48964, + 48965, + 48966, + 48967, + 48968, + 48969, + 48970, + 48971, + 48972, + 48973, + 48974, + 48975, + 48976, + 48977, + 48978, + 48979, + 48980, + 48981, + 48982, + 48983, + 48984, + 48985, + 48986, + 48987, + 48988, + 48989, + 48990, + 48991, + 48992, + 48993, + 48994, + 48995, + 48996, + 48997, + 48998, + 48999, + 49000, + 49001, + 49002, + 49003, + 49004, + 49005, + 49006, + 49007, + 49008, + 49009, + 49010, + 49011, + 49012, + 49013, + 49014, + 49015, + 49016, + 49017, + 49018, + 49019, + 49020, + 49021, + 49022, + 49023, + 49024, + 49025, + 49026, + 49027, + 49028, + 49029, + 49030, + 49031, + 49032, + 49033, + 49034, + 49035, + 49036, + 49037, + 49038, + 49039, + 49040, + 49041, + 49042, + 49043, + 49044, + 49045, + 49046, + 49047, + 49048, + 49049, + 49050, + 49051, + 49052, + 49053, + 49054, + 49055, + 49056, + 49057, + 49058, + 49059, + 49060, + 49061, + 49062, + 49063, + 49064, + 49065, + 49066, + 49067, + 49068, + 49069, + 49070, + 49071, + 49072, + 49073, + 49074, + 49075, + 49076, + 49077, + 49078, + 49079, + 49080, + 49081, + 49082, + 49083, + 49084, + 49085, + 49086, + 49087, + 49088, + 49089, + 49090, + 49091, + 49092, + 49093, + 49094, + 49095, + 49096, + 49097, + 49098, + 49099, + 49100, + 49101, + 49102, + 49103, + 49104, + 49105, + 49106, + 49107, + 49108, + 49109, + 49110, + 49111, + 49112, + 49113, + 49114, + 49115, + 49116, + 49117, + 49118, + 49119, + 49120, + 49121, + 49122, + 49123, + 49124, + 49125, + 49126, + 49127, + 49128, + 49129, + 49130, + 49131, + 49132, + 49133, + 49134, + 49135, + 49136, + 49137, + 49138, + 49139, + 49140, + 49141, + 49142, + 49143, + 49144, + 49145, + 49146, + 49147, + 49148, + 49149, + 49150, + 49151, + 49152, + 49153, + 49154, + 49155, + 49156, + 49157, + 49158, + 49159, + 49160, + 49161, + 49162, + 49163, + 49164, + 49165, + 49166, + 49167, + 49168, + 49169, + 49170, + 49171, + 49172, + 49173, + 49174, + 49175, + 49176, + 49177, + 49178, + 49179, + 49180, + 49181, + 49182, + 49183, + 49184, + 49185, + 49186, + 49187, + 49188, + 49189, + 49190, + 49191, + 49192, + 49193, + 49194, + 49195, + 49196, + 49197, + 49198, + 49199, + 49200, + 49201, + 49202, + 49203, + 49204, + 49205, + 49206, + 49207, + 49208, + 49209, + 49210, + 49211, + 49212, + 49213, + 49214, + 49215, + 49216, + 49217, + 49218, + 49219, + 49220, + 49221, + 49222, + 49223, + 49224, + 49225, + 49226, + 49227, + 49228, + 49229, + 49230, + 49231, + 49232, + 49233, + 49234, + 49235, + 49236, + 49237, + 49238, + 49239, + 49240, + 49241, + 49242, + 49243, + 49244, + 49245, + 49246, + 49247, + 49248, + 49249, + 49250, + 49251, + 49252, + 49253, + 49254, + 49255, + 49256, + 49257, + 49258, + 49259, + 49260, + 49261, + 49262, + 49263, + 49264, + 49265, + 49266, + 49267, + 49268, + 49269, + 49270, + 49271, + 49272, + 49273, + 49274, + 49275, + 49276, + 49277, + 49278, + 49279, + 49280, + 49281, + 49282, + 49283, + 49284, + 49285, + 49286, + 49287, + 49288, + 49289, + 49290, + 49291, + 49292, + 49293, + 49294, + 49295, + 49296, + 49297, + 49298, + 49299, + 49300, + 49301, + 49302, + 49303, + 49304, + 49305, + 49306, + 49307, + 49308, + 49309, + 49310, + 49311, + 49312, + 49313, + 49314, + 49315, + 49316, + 49317, + 49318, + 49319, + 49320, + 49321, + 49322, + 49323, + 49324, + 49325, + 49326, + 49327, + 49328, + 49329, + 49330, + 49331, + 49332, + 49333, + 49334, + 49335, + 49336, + 49337, + 49338, + 49339, + 49340, + 49341, + 49342, + 49343, + 49344, + 49345, + 49346, + 49347, + 49348, + 49349, + 49350, + 49351, + 49352, + 49353, + 49354, + 49355, + 49356, + 49357, + 49358, + 49359, + 49360, + 49361, + 49362, + 49363, + 49364, + 49365, + 49366, + 49367, + 49368, + 49369, + 49370, + 49371, + 49372, + 49373, + 49374, + 49375, + 49376, + 49377, + 49378, + 49379, + 49380, + 49381, + 49382, + 49383, + 49384, + 49385, + 49386, + 49387, + 49388, + 49389, + 49390, + 49391, + 49392, + 49393, + 49394, + 49395, + 49396, + 49397, + 49398, + 49399, + 49400, + 49401, + 49402, + 49403, + 49404, + 49405, + 49406, + 49407, + 49408, + 49409, + 49410, + 49411, + 49412, + 49413, + 49414, + 49415, + 49416, + 49417, + 49418, + 49419, + 49420, + 49421, + 49422, + 49423, + 49424, + 49425, + 49426, + 49427, + 49428, + 49429, + 49430, + 49431, + 49432, + 49433, + 49434, + 49435, + 49436, + 49437, + 49438, + 49439, + 49440, + 49441, + 49442, + 49443, + 49444, + 49445, + 49446, + 49447, + 49448, + 49449, + 49450, + 49451, + 49452, + 49453, + 49454, + 49455, + 49456, + 49457, + 49458, + 49459, + 49460, + 49461, + 49462, + 49463, + 49464, + 49465, + 49466, + 49467, + 49468, + 49469, + 49470, + 49471, + 49472, + 49473, + 49474, + 49475, + 49476, + 49477, + 49478, + 49479, + 49480, + 49481, + 49482, + 49483, + 49484, + 49485, + 49486, + 49487, + 49488, + 49489, + 49490, + 49491, + 49492, + 49493, + 49494, + 49495, + 49496, + 49497, + 49498, + 49499, + 49500, + 49501, + 49502, + 49503, + 49504, + 49505, + 49506, + 49507, + 49508, + 49509, + 49510, + 49511, + 49512, + 49513, + 49514, + 49515, + 49516, + 49517, + 49518, + 49519, + 49520, + 49521, + 49522, + 49523, + 49524, + 49525, + 49526, + 49527, + 49528, + 49529, + 49530, + 49531, + 49532, + 49533, + 49534, + 49535, + 49536, + 49537, + 49538, + 49539, + 49540, + 49541, + 49542, + 49543, + 49544, + 49545, + 49546, + 49547, + 49548, + 49549, + 49550, + 49551, + 49552, + 49553, + 49554, + 49555, + 49556, + 49557, + 49558, + 49559, + 49560, + 49561, + 49562, + 49563, + 49564, + 49565, + 49566, + 49567, + 49568, + 49569, + 49570, + 49571, + 49572, + 49573, + 49574, + 49575, + 49576, + 49577, + 49578, + 49579, + 49580, + 49581, + 49582, + 49583, + 49584, + 49585, + 49586, + 49587, + 49588, + 49589, + 49590, + 49591, + 49592, + 49593, + 49594, + 49595, + 49596, + 49597, + 49598, + 49599, + 49600, + 49601, + 49602, + 49603, + 49604, + 49605, + 49606, + 49607, + 49608, + 49609, + 49610, + 49611, + 49612, + 49613, + 49614, + 49615, + 49616, + 49617, + 49618, + 49619, + 49620, + 49621, + 49622, + 49623, + 49624, + 49625, + 49626, + 49627, + 49628, + 49629, + 49630, + 49631, + 49632, + 49633, + 49634, + 49635, + 49636, + 49637, + 49638, + 49639, + 49640, + 49641, + 49642, + 49643, + 49644, + 49645, + 49646, + 49647, + 49648, + 49649, + 49650, + 49651, + 49652, + 49653, + 49654, + 49655, + 49656, + 49657, + 49658, + 49659, + 49660, + 49661, + 49662, + 49663, + 49664, + 49665, + 49666, + 49667, + 49668, + 49669, + 49670, + 49671, + 49672, + 49673, + 49674, + 49675, + 49676, + 49677, + 49678, + 49679, + 49680, + 49681, + 49682, + 49683, + 49684, + 49685, + 49686, + 49687, + 49688, + 49689, + 49690, + 49691, + 49692, + 49693, + 49694, + 49695, + 49696, + 49697, + 49698, + 49699, + 49700, + 49701, + 49702, + 49703, + 49704, + 49705, + 49706, + 49707, + 49708, + 49709, + 49710, + 49711, + 49712, + 49713, + 49714, + 49715, + 49716, + 49717, + 49718, + 49719, + 49720, + 49721, + 49722, + 49723, + 49724, + 49725, + 49726, + 49727, + 49728, + 49729, + 49730, + 49731, + 49732, + 49733, + 49734, + 49735, + 49736, + 49737, + 49738, + 49739, + 49740, + 49741, + 49742, + 49743, + 49744, + 49745, + 49746, + 49747, + 49748, + 49749, + 49750, + 49751, + 49752, + 49753, + 49754, + 49755, + 49756, + 49757, + 49758, + 49759, + 49760, + 49761, + 49762, + 49763, + 49764, + 49765, + 49766, + 49767, + 49768, + 49769, + 49770, + 49771, + 49772, + 49773, + 49774, + 49775, + 49776, + 49777, + 49778, + 49779, + 49780, + 49781, + 49782, + 49783, + 49784, + 49785, + 49786, + 49787, + 49788, + 49789, + 49790, + 49791, + 49792, + 49793, + 49794, + 49795, + 49796, + 49797, + 49798, + 49799, + 49800, + 49801, + 49802, + 49803, + 49804, + 49805, + 49806, + 49807, + 49808, + 49809, + 49810, + 49811, + 49812, + 49813, + 49814, + 49815, + 49816, + 49817, + 49818, + 49819, + 49820, + 49821, + 49822, + 49823, + 49824, + 49825, + 49826, + 49827, + 49828, + 49829, + 49830, + 49831, + 49832, + 49833, + 49834, + 49835, + 49836, + 49837, + 49838, + 49839, + 49840, + 49841, + 49842, + 49843, + 49844, + 49845, + 49846, + 49847, + 49848, + 49849, + 49850, + 49851, + 49852, + 49853, + 49854, + 49855, + 49856, + 49857, + 49858, + 49859, + 49860, + 49861, + 49862, + 49863, + 49864, + 49865, + 49866, + 49867, + 49868, + 49869, + 49870, + 49871, + 49872, + 49873, + 49874, + 49875, + 49876, + 49877, + 49878, + 49879, + 49880, + 49881, + 49882, + 49883, + 49884, + 49885, + 49886, + 49887, + 49888, + 49889, + 49890, + 49891, + 49892, + 49893, + 49894, + 49895, + 49896, + 49897, + 49898, + 49899, + 49900, + 49901, + 49902, + 49903, + 49904, + 49905, + 49906, + 49907, + 49908, + 49909, + 49910, + 49911, + 49912, + 49913, + 49914, + 49915, + 49916, + 49917, + 49918, + 49919, + 49920, + 49921, + 49922, + 49923, + 49924, + 49925, + 49926, + 49927, + 49928, + 49929, + 49930, + 49931, + 49932, + 49933, + 49934, + 49935, + 49936, + 49937, + 49938, + 49939, + 49940, + 49941, + 49942, + 49943, + 49944, + 49945, + 49946, + 49947, + 49948, + 49949, + 49950, + 49951, + 49952, + 49953, + 49954, + 49955, + 49956, + 49957, + 49958, + 49959, + 49960, + 49961, + 49962, + 49963, + 49964, + 49965, + 49966, + 49967, + 49968, + 49969, + 49970, + 49971, + 49972, + 49973, + 49974, + 49975, + 49976, + 49977, + 49978, + 49979, + 49980, + 49981, + 49982, + 49983, + 49984, + 49985, + 49986, + 49987, + 49988, + 49989, + 49990, + 49991, + 49992, + 49993, + 49994, + 49995, + 49996, + 49997, + 49998, + 49999, + 50000, + 50001, + 50002, + 50003, + 50004, + 50005, + 50006, + 50007, + 50008, + 50009, + 50010, + 50011, + 50012, + 50013, + 50014, + 50015, + 50016, + 50017, + 50018, + 50019, + 50020, + 50021, + 50022, + 50023, + 50024, + 50025, + 50026, + 50027, + 50028, + 50029, + 50030, + 50031, + 50032, + 50033, + 50034, + 50035, + 50036, + 50037, + 50038, + 50039, + 50040, + 50041, + 50042, + 50043, + 50044, + 50045, + 50046, + 50047, + 50048, + 50049, + 50050, + 50051, + 50052, + 50053, + 50054, + 50055, + 50056, + 50057, + 50058, + 50059, + 50060, + 50061, + 50062, + 50063, + 50064, + 50065, + 50066, + 50067, + 50068, + 50069, + 50070, + 50071, + 50072, + 50073, + 50074, + 50075, + 50076, + 50077, + 50078, + 50079, + 50080, + 50081, + 50082, + 50083, + 50084, + 50085, + 50086, + 50087, + 50088, + 50089, + 50090, + 50091, + 50092, + 50093, + 50094, + 50095, + 50096, + 50097, + 50098, + 50099, + 50100, + 50101, + 50102, + 50103, + 50104, + 50105, + 50106, + 50107, + 50108, + 50109, + 50110, + 50111, + 50112, + 50113, + 50114, + 50115, + 50116, + 50117, + 50118, + 50119, + 50120, + 50121, + 50122, + 50123, + 50124, + 50125, + 50126, + 50127, + 50128, + 50129, + 50130, + 50131, + 50132, + 50133, + 50134, + 50135, + 50136, + 50137, + 50138, + 50139, + 50140, + 50141, + 50142, + 50143, + 50144, + 50145, + 50146, + 50147, + 50148, + 50149, + 50150, + 50151, + 50152, + 50153, + 50154, + 50155, + 50156, + 50157, + 50158, + 50159, + 50160, + 50161, + 50162, + 50163, + 50164, + 50165, + 50166, + 50167, + 50168, + 50169, + 50170, + 50171, + 50172, + 50173, + 50174, + 50175, + 50176, + 50177, + 50178, + 50179, + 50180, + 50181, + 50182, + 50183, + 50184, + 50185, + 50186, + 50187, + 50188, + 50189, + 50190, + 50191, + 50192, + 50193, + 50194, + 50195, + 50196, + 50197, + 50198, + 50199, + 50200, + 50201, + 50202, + 50203, + 50204, + 50205, + 50206, + 50207, + 50208, + 50209, + 50210, + 50211, + 50212, + 50213, + 50214, + 50215, + 50216, + 50217, + 50218, + 50219, + 50220, + 50221, + 50222, + 50223, + 50224, + 50225, + 50226, + 50227, + 50228, + 50229, + 50230, + 50231, + 50232, + 50233, + 50234, + 50235, + 50236, + 50237, + 50238, + 50239, + 50240, + 50241, + 50242, + 50243, + 50244, + 50245, + 50246, + 50247, + 50248, + 50249, + 50250, + 50251, + 50252, + 50253, + 50254, + 50255, + 50256, + 50257, + 50258, + 50259, + 50260, + 50261, + 50262, + 50263, + 50264, + 50265, + 50266, + 50267, + 50268, + 50269, + 50270, + 50271, + 50272, + 50273, + 50274, + 50275, + 50276, + 50277, + 50278, + 50279, + 50280, + 50281, + 50282, + 50283, + 50284, + 50285, + 50286, + 50287, + 50288, + 50289, + 50290, + 50291, + 50292, + 50293, + 50294, + 50295, + 50296, + 50297, + 50298, + 50299, + 50300, + 50301, + 50302, + 50303, + 50304, + 50305, + 50306, + 50307, + 50308, + 50309, + 50310, + 50311, + 50312, + 50313, + 50314, + 50315, + 50316, + 50317, + 50318, + 50319, + 50320, + 50321, + 50322, + 50323, + 50324, + 50325, + 50326, + 50327, + 50328, + 50329, + 50330, + 50331, + 50332, + 50333, + 50334, + 50335, + 50336, + 50337, + 50338, + 50339, + 50340, + 50341, + 50342, + 50343, + 50344, + 50345, + 50346, + 50347, + 50348, + 50349, + 50350, + 50351, + 50352, + 50353, + 50354, + 50355, + 50356, + 50357, + 50358, + 50359, + 50360, + 50361, + 50362, + 50363, + 50364, + 50365, + 50366, + 50367, + 50368, + 50369, + 50370, + 50371, + 50372, + 50373, + 50374, + 50375, + 50376, + 50377, + 50378, + 50379, + 50380, + 50381, + 50382, + 50383, + 50384, + 50385, + 50386, + 50387, + 50388, + 50389, + 50390, + 50391, + 50392, + 50393, + 50394, + 50395, + 50396, + 50397, + 50398, + 50399, + 50400, + 50401, + 50402, + 50403, + 50404, + 50405, + 50406, + 50407, + 50408, + 50409, + 50410, + 50411, + 50412, + 50413, + 50414, + 50415, + 50416, + 50417, + 50418, + 50419, + 50420, + 50421, + 50422, + 50423, + 50424, + 50425, + 50426, + 50427, + 50428, + 50429, + 50430, + 50431, + 50432, + 50433, + 50434, + 50435, + 50436, + 50437, + 50438, + 50439, + 50440, + 50441, + 50442, + 50443, + 50444, + 50445, + 50446, + 50447, + 50448, + 50449, + 50450, + 50451, + 50452, + 50453, + 50454, + 50455, + 50456, + 50457, + 50458, + 50459, + 50460, + 50461, + 50462, + 50463, + 50464, + 50465, + 50466, + 50467, + 50468, + 50469, + 50470, + 50471, + 50472, + 50473, + 50474, + 50475, + 50476, + 50477, + 50478, + 50479, + 50480, + 50481, + 50482, + 50483, + 50484, + 50485, + 50486, + 50487, + 50488, + 50489, + 50490, + 50491, + 50492, + 50493, + 50494, + 50495, + 50496, + 50497, + 50498, + 50499, + 50500, + 50501, + 50502, + 50503, + 50504, + 50505, + 50506, + 50507, + 50508, + 50509, + 50510, + 50511, + 50512, + 50513, + 50514, + 50515, + 50516, + 50517, + 50518, + 50519, + 50520, + 50521, + 50522, + 50523, + 50524, + 50525, + 50526, + 50527, + 50528, + 50529, + 50530, + 50531, + 50532, + 50533, + 50534, + 50535, + 50536, + 50537, + 50538, + 50539, + 50540, + 50541, + 50542, + 50543, + 50544, + 50545, + 50546, + 50547, + 50548, + 50549, + 50550, + 50551, + 50552, + 50553, + 50554, + 50555, + 50556, + 50557, + 50558, + 50559, + 50560, + 50561, + 50562, + 50563, + 50564, + 50565, + 50566, + 50567, + 50568, + 50569, + 50570, + 50571, + 50572, + 50573, + 50574, + 50575, + 50576, + 50577, + 50578, + 50579, + 50580, + 50581, + 50582, + 50583, + 50584, + 50585, + 50586, + 50587, + 50588, + 50589, + 50590, + 50591, + 50592, + 50593, + 50594, + 50595, + 50596, + 50597, + 50598, + 50599, + 50600, + 50601, + 50602, + 50603, + 50604, + 50605, + 50606, + 50607, + 50608, + 50609, + 50610, + 50611, + 50612, + 50613, + 50614, + 50615, + 50616, + 50617, + 50618, + 50619, + 50620, + 50621, + 50622, + 50623, + 50624, + 50625, + 50626, + 50627, + 50628, + 50629, + 50630, + 50631, + 50632, + 50633, + 50634, + 50635, + 50636, + 50637, + 50638, + 50639, + 50640, + 50641, + 50642, + 50643, + 50644, + 50645, + 50646, + 50647, + 50648, + 50649, + 50650, + 50651, + 50652, + 50653, + 50654, + 50655, + 50656, + 50657, + 50658, + 50659, + 50660, + 50661, + 50662, + 50663, + 50664, + 50665, + 50666, + 50667, + 50668, + 50669, + 50670, + 50671, + 50672, + 50673, + 50674, + 50675, + 50676, + 50677, + 50678, + 50679, + 50680, + 50681, + 50682, + 50683, + 50684, + 50685, + 50686, + 50687, + 50688, + 50689, + 50690, + 50691, + 50692, + 50693, + 50694, + 50695, + 50696, + 50697, + 50698, + 50699, + 50700, + 50701, + 50702, + 50703, + 50704, + 50705, + 50706, + 50707, + 50708, + 50709, + 50710, + 50711, + 50712, + 50713, + 50714, + 50715, + 50716, + 50717, + 50718, + 50719, + 50720, + 50721, + 50722, + 50723, + 50724, + 50725, + 50726, + 50727, + 50728, + 50729, + 50730, + 50731, + 50732, + 50733, + 50734, + 50735, + 50736, + 50737, + 50738, + 50739, + 50740, + 50741, + 50742, + 50743, + 50744, + 50745, + 50746, + 50747, + 50748, + 50749, + 50750, + 50751, + 50752, + 50753, + 50754, + 50755, + 50756, + 50757, + 50758, + 50759, + 50760, + 50761, + 50762, + 50763, + 50764, + 50765, + 50766, + 50767, + 50768, + 50769, + 50770, + 50771, + 50772, + 50773, + 50774, + 50775, + 50776, + 50777, + 50778, + 50779, + 50780, + 50781, + 50782, + 50783, + 50784, + 50785, + 50786, + 50787, + 50788, + 50789, + 50790, + 50791, + 50792, + 50793, + 50794, + 50795, + 50796, + 50797, + 50798, + 50799, + 50800, + 50801, + 50802, + 50803, + 50804, + 50805, + 50806, + 50807, + 50808, + 50809, + 50810, + 50811, + 50812, + 50813, + 50814, + 50815, + 50816, + 50817, + 50818, + 50819, + 50820, + 50821, + 50822, + 50823, + 50824, + 50825, + 50826, + 50827, + 50828, + 50829, + 50830, + 50831, + 50832, + 50833, + 50834, + 50835, + 50836, + 50837, + 50838, + 50839, + 50840, + 50841, + 50842, + 50843, + 50844, + 50845, + 50846, + 50847, + 50848, + 50849, + 50850, + 50851, + 50852, + 50853, + 50854, + 50855, + 50856, + 50857, + 50858, + 50859, + 50860, + 50861, + 50862, + 50863, + 50864, + 50865, + 50866, + 50867, + 50868, + 50869, + 50870, + 50871, + 50872, + 50873, + 50874, + 50875, + 50876, + 50877, + 50878, + 50879, + 50880, + 50881, + 50882, + 50883, + 50884, + 50885, + 50886, + 50887, + 50888, + 50889, + 50890, + 50891, + 50892, + 50893, + 50894, + 50895, + 50896, + 50897, + 50898, + 50899, + 50900, + 50901, + 50902, + 50903, + 50904, + 50905, + 50906, + 50907, + 50908, + 50909, + 50910, + 50911, + 50912, + 50913, + 50914, + 50915, + 50916, + 50917, + 50918, + 50919, + 50920, + 50921, + 50922, + 50923, + 50924, + 50925, + 50926, + 50927, + 50928, + 50929, + 50930, + 50931, + 50932, + 50933, + 50934, + 50935, + 50936, + 50937, + 50938, + 50939, + 50940, + 50941, + 50942, + 50943, + 50944, + 50945, + 50946, + 50947, + 50948, + 50949, + 50950, + 50951, + 50952, + 50953, + 50954, + 50955, + 50956, + 50957, + 50958, + 50959, + 50960, + 50961, + 50962, + 50963, + 50964, + 50965, + 50966, + 50967, + 50968, + 50969, + 50970, + 50971, + 50972, + 50973, + 50974, + 50975, + 50976, + 50977, + 50978, + 50979, + 50980, + 50981, + 50982, + 50983, + 50984, + 50985, + 50986, + 50987, + 50988, + 50989, + 50990, + 50991, + 50992, + 50993, + 50994, + 50995, + 50996, + 50997, + 50998, + 50999, + 51000, + 51001, + 51002, + 51003, + 51004, + 51005, + 51006, + 51007, + 51008, + 51009, + 51010, + 51011, + 51012, + 51013, + 51014, + 51015, + 51016, + 51017, + 51018, + 51019, + 51020, + 51021, + 51022, + 51023, + 51024, + 51025, + 51026, + 51027, + 51028, + 51029, + 51030, + 51031, + 51032, + 51033, + 51034, + 51035, + 51036, + 51037, + 51038, + 51039, + 51040, + 51041, + 51042, + 51043, + 51044, + 51045, + 51046, + 51047, + 51048, + 51049, + 51050, + 51051, + 51052, + 51053, + 51054, + 51055, + 51056, + 51057, + 51058, + 51059, + 51060, + 51061, + 51062, + 51063, + 51064, + 51065, + 51066, + 51067, + 51068, + 51069, + 51070, + 51071, + 51072, + 51073, + 51074, + 51075, + 51076, + 51077, + 51078, + 51079, + 51080, + 51081, + 51082, + 51083, + 51084, + 51085, + 51086, + 51087, + 51088, + 51089, + 51090, + 51091, + 51092, + 51093, + 51094, + 51095, + 51096, + 51097, + 51098, + 51099, + 51100, + 51101, + 51102, + 51103, + 51104, + 51105, + 51106, + 51107, + 51108, + 51109, + 51110, + 51111, + 51112, + 51113, + 51114, + 51115, + 51116, + 51117, + 51118, + 51119, + 51120, + 51121, + 51122, + 51123, + 51124, + 51125, + 51126, + 51127, + 51128, + 51129, + 51130, + 51131, + 51132, + 51133, + 51134, + 51135, + 51136, + 51137, + 51138, + 51139, + 51140, + 51141, + 51142, + 51143, + 51144, + 51145, + 51146, + 51147, + 51148, + 51149, + 51150, + 51151, + 51152, + 51153, + 51154, + 51155, + 51156, + 51157, + 51158, + 51159, + 51160, + 51161, + 51162, + 51163, + 51164, + 51165, + 51166, + 51167, + 51168, + 51169, + 51170, + 51171, + 51172, + 51173, + 51174, + 51175, + 51176, + 51177, + 51178, + 51179, + 51180, + 51181, + 51182, + 51183, + 51184, + 51185, + 51186, + 51187, + 51188, + 51189, + 51190, + 51191, + 51192, + 51193, + 51194, + 51195, + 51196, + 51197, + 51198, + 51199, + 51200, + 51201, + 51202, + 51203, + 51204, + 51205, + 51206, + 51207, + 51208, + 51209, + 51210, + 51211, + 51212, + 51213, + 51214, + 51215, + 51216, + 51217, + 51218, + 51219, + 51220, + 51221, + 51222, + 51223, + 51224, + 51225, + 51226, + 51227, + 51228, + 51229, + 51230, + 51231, + 51232, + 51233, + 51234, + 51235, + 51236, + 51237, + 51238, + 51239, + 51240, + 51241, + 51242, + 51243, + 51244, + 51245, + 51246, + 51247, + 51248, + 51249, + 51250, + 51251, + 51252, + 51253, + 51254, + 51255, + 51256, + 51257, + 51258, + 51259, + 51260, + 51261, + 51262, + 51263, + 51264, + 51265, + 51266, + 51267, + 51268, + 51269, + 51270, + 51271, + 51272, + 51273, + 51274, + 51275, + 51276, + 51277, + 51278, + 51279, + 51280, + 51281, + 51282, + 51283, + 51284, + 51285, + 51286, + 51287, + 51288, + 51289, + 51290, + 51291, + 51292, + 51293, + 51294, + 51295, + 51296, + 51297, + 51298, + 51299, + 51300, + 51301, + 51302, + 51303, + 51304, + 51305, + 51306, + 51307, + 51308, + 51309, + 51310, + 51311, + 51312, + 51313, + 51314, + 51315, + 51316, + 51317, + 51318, + 51319, + 51320, + 51321, + 51322, + 51323, + 51324, + 51325, + 51326, + 51327, + 51328, + 51329, + 51330, + 51331, + 51332, + 51333, + 51334, + 51335, + 51336, + 51337, + 51338, + 51339, + 51340, + 51341, + 51342, + 51343, + 51344, + 51345, + 51346, + 51347, + 51348, + 51349, + 51350, + 51351, + 51352, + 51353, + 51354, + 51355, + 51356, + 51357, + 51358, + 51359, + 51360, + 51361, + 51362, + 51363, + 51364, + 51365, + 51366, + 51367, + 51368, + 51369, + 51370, + 51371, + 51372, + 51373, + 51374, + 51375, + 51376, + 51377, + 51378, + 51379, + 51380, + 51381, + 51382, + 51383, + 51384, + 51385, + 51386, + 51387, + 51388, + 51389, + 51390, + 51391, + 51392, + 51393, + 51394, + 51395, + 51396, + 51397, + 51398, + 51399, + 51400, + 51401, + 51402, + 51403, + 51404, + 51405, + 51406, + 51407, + 51408, + 51409, + 51410, + 51411, + 51412, + 51413, + 51414, + 51415, + 51416, + 51417, + 51418, + 51419, + 51420, + 51421, + 51422, + 51423, + 51424, + 51425, + 51426, + 51427, + 51428, + 51429, + 51430, + 51431, + 51432, + 51433, + 51434, + 51435, + 51436, + 51437, + 51438, + 51439, + 51440, + 51441, + 51442, + 51443, + 51444, + 51445, + 51446, + 51447, + 51448, + 51449, + 51450, + 51451, + 51452, + 51453, + 51454, + 51455, + 51456, + 51457, + 51458, + 51459, + 51460, + 51461, + 51462, + 51463, + 51464, + 51465, + 51466, + 51467, + 51468, + 51469, + 51470, + 51471, + 51472, + 51473, + 51474, + 51475, + 51476, + 51477, + 51478, + 51479, + 51480, + 51481, + 51482, + 51483, + 51484, + 51485, + 51486, + 51487, + 51488, + 51489, + 51490, + 51491, + 51492, + 51493, + 51494, + 51495, + 51496, + 51497, + 51498, + 51499, + 51500, + 51501, + 51502, + 51503, + 51504, + 51505, + 51506, + 51507, + 51508, + 51509, + 51510, + 51511, + 51512, + 51513, + 51514, + 51515, + 51516, + 51517, + 51518, + 51519, + 51520, + 51521, + 51522, + 51523, + 51524, + 51525, + 51526, + 51527, + 51528, + 51529, + 51530, + 51531, + 51532, + 51533, + 51534, + 51535, + 51536, + 51537, + 51538, + 51539, + 51540, + 51541, + 51542, + 51543, + 51544, + 51545, + 51546, + 51547, + 51548, + 51549, + 51550, + 51551, + 51552, + 51553, + 51554, + 51555, + 51556, + 51557, + 51558, + 51559, + 51560, + 51561, + 51562, + 51563, + 51564, + 51565, + 51566, + 51567, + 51568, + 51569, + 51570, + 51571, + 51572, + 51573, + 51574, + 51575, + 51576, + 51577, + 51578, + 51579, + 51580, + 51581, + 51582, + 51583, + 51584, + 51585, + 51586, + 51587, + 51588, + 51589, + 51590, + 51591, + 51592, + 51593, + 51594, + 51595, + 51596, + 51597, + 51598, + 51599, + 51600, + 51601, + 51602, + 51603, + 51604, + 51605, + 51606, + 51607, + 51608, + 51609, + 51610, + 51611, + 51612, + 51613, + 51614, + 51615, + 51616, + 51617, + 51618, + 51619, + 51620, + 51621, + 51622, + 51623, + 51624, + 51625, + 51626, + 51627, + 51628, + 51629, + 51630, + 51631, + 51632, + 51633, + 51634, + 51635, + 51636, + 51637, + 51638, + 51639, + 51640, + 51641, + 51642, + 51643, + 51644, + 51645, + 51646, + 51647, + 51648, + 51649, + 51650, + 51651, + 51652, + 51653, + 51654, + 51655, + 51656, + 51657, + 51658, + 51659, + 51660, + 51661, + 51662, + 51663, + 51664, + 51665, + 51666, + 51667, + 51668, + 51669, + 51670, + 51671, + 51672, + 51673, + 51674, + 51675, + 51676, + 51677, + 51678, + 51679, + 51680, + 51681, + 51682, + 51683, + 51684, + 51685, + 51686, + 51687, + 51688, + 51689, + 51690, + 51691, + 51692, + 51693, + 51694, + 51695, + 51696, + 51697, + 51698, + 51699, + 51700, + 51701, + 51702, + 51703, + 51704, + 51705, + 51706, + 51707, + 51708, + 51709, + 51710, + 51711, + 51712, + 51713, + 51714, + 51715, + 51716, + 51717, + 51718, + 51719, + 51720, + 51721, + 51722, + 51723, + 51724, + 51725, + 51726, + 51727, + 51728, + 51729, + 51730, + 51731, + 51732, + 51733, + 51734, + 51735, + 51736, + 51737, + 51738, + 51739, + 51740, + 51741, + 51742, + 51743, + 51744, + 51745, + 51746, + 51747, + 51748, + 51749, + 51750, + 51751, + 51752, + 51753, + 51754, + 51755, + 51756, + 51757, + 51758, + 51759, + 51760, + 51761, + 51762, + 51763, + 51764, + 51765, + 51766, + 51767, + 51768, + 51769, + 51770, + 51771, + 51772, + 51773, + 51774, + 51775, + 51776, + 51777, + 51778, + 51779, + 51780, + 51781, + 51782, + 51783, + 51784, + 51785, + 51786, + 51787, + 51788, + 51789, + 51790, + 51791, + 51792, + 51793, + 51794, + 51795, + 51796, + 51797, + 51798, + 51799, + 51800, + 51801, + 51802, + 51803, + 51804, + 51805, + 51806, + 51807, + 51808, + 51809, + 51810, + 51811, + 51812, + 51813, + 51814, + 51815, + 51816, + 51817, + 51818, + 51819, + 51820, + 51821, + 51822, + 51823, + 51824, + 51825, + 51826, + 51827, + 51828, + 51829, + 51830, + 51831, + 51832, + 51833, + 51834, + 51835, + 51836, + 51837, + 51838, + 51839, + 51840, + 51841, + 51842, + 51843, + 51844, + 51845, + 51846, + 51847, + 51848, + 51849, + 51850, + 51851, + 51852, + 51853, + 51854, + 51855, + 51856, + 51857, + 51858, + 51859, + 51860, + 51861, + 51862, + 51863, + 51864, + 51865, + 51866, + 51867, + 51868, + 51869, + 51870, + 51871, + 51872, + 51873, + 51874, + 51875, + 51876, + 51877, + 51878, + 51879, + 51880, + 51881, + 51882, + 51883, + 51884, + 51885, + 51886, + 51887, + 51888, + 51889, + 51890, + 51891, + 51892, + 51893, + 51894, + 51895, + 51896, + 51897, + 51898, + 51899, + 51900, + 51901, + 51902, + 51903, + 51904, + 51905, + 51906, + 51907, + 51908, + 51909, + 51910, + 51911, + 51912, + 51913, + 51914, + 51915, + 51916, + 51917, + 51918, + 51919, + 51920, + 51921, + 51922, + 51923, + 51924, + 51925, + 51926, + 51927, + 51928, + 51929, + 51930, + 51931, + 51932, + 51933, + 51934, + 51935, + 51936, + 51937, + 51938, + 51939, + 51940, + 51941, + 51942, + 51943, + 51944, + 51945, + 51946, + 51947, + 51948, + 51949, + 51950, + 51951, + 51952, + 51953, + 51954, + 51955, + 51956, + 51957, + 51958, + 51959, + 51960, + 51961, + 51962, + 51963, + 51964, + 51965, + 51966, + 51967, + 51968, + 51969, + 51970, + 51971, + 51972, + 51973, + 51974, + 51975, + 51976, + 51977, + 51978, + 51979, + 51980, + 51981, + 51982, + 51983, + 51984, + 51985, + 51986, + 51987, + 51988, + 51989, + 51990, + 51991, + 51992, + 51993, + 51994, + 51995, + 51996, + 51997, + 51998, + 51999, + 52000, + 52001, + 52002, + 52003, + 52004, + 52005, + 52006, + 52007, + 52008, + 52009, + 52010, + 52011, + 52012, + 52013, + 52014, + 52015, + 52016, + 52017, + 52018, + 52019, + 52020, + 52021, + 52022, + 52023, + 52024, + 52025, + 52026, + 52027, + 52028, + 52029, + 52030, + 52031, + 52032, + 52033, + 52034, + 52035, + 52036, + 52037, + 52038, + 52039, + 52040, + 52041, + 52042, + 52043, + 52044, + 52045, + 52046, + 52047, + 52048, + 52049, + 52050, + 52051, + 52052, + 52053, + 52054, + 52055, + 52056, + 52057, + 52058, + 52059, + 52060, + 52061, + 52062, + 52063, + 52064, + 52065, + 52066, + 52067, + 52068, + 52069, + 52070, + 52071, + 52072, + 52073, + 52074, + 52075, + 52076, + 52077, + 52078, + 52079, + 52080, + 52081, + 52082, + 52083, + 52084, + 52085, + 52086, + 52087, + 52088, + 52089, + 52090, + 52091, + 52092, + 52093, + 52094, + 52095, + 52096, + 52097, + 52098, + 52099, + 52100, + 52101, + 52102, + 52103, + 52104, + 52105, + 52106, + 52107, + 52108, + 52109, + 52110, + 52111, + 52112, + 52113, + 52114, + 52115, + 52116, + 52117, + 52118, + 52119, + 52120, + 52121, + 52122, + 52123, + 52124, + 52125, + 52126, + 52127, + 52128, + 52129, + 52130, + 52131, + 52132, + 52133, + 52134, + 52135, + 52136, + 52137, + 52138, + 52139, + 52140, + 52141, + 52142, + 52143, + 52144, + 52145, + 52146, + 52147, + 52148, + 52149, + 52150, + 52151, + 52152, + 52153, + 52154, + 52155, + 52156, + 52157, + 52158, + 52159, + 52160, + 52161, + 52162, + 52163, + 52164, + 52165, + 52166, + 52167, + 52168, + 52169, + 52170, + 52171, + 52172, + 52173, + 52174, + 52175, + 52176, + 52177, + 52178, + 52179, + 52180, + 52181, + 52182, + 52183, + 52184, + 52185, + 52186, + 52187, + 52188, + 52189, + 52190, + 52191, + 52192, + 52193, + 52194, + 52195, + 52196, + 52197, + 52198, + 52199, + 52200, + 52201, + 52202, + 52203, + 52204, + 52205, + 52206, + 52207, + 52208, + 52209, + 52210, + 52211, + 52212, + 52213, + 52214, + 52215, + 52216, + 52217, + 52218, + 52219, + 52220, + 52221, + 52222, + 52223, + 52224, + 52225, + 52226, + 52227, + 52228, + 52229, + 52230, + 52231, + 52232, + 52233, + 52234, + 52235, + 52236, + 52237, + 52238, + 52239, + 52240, + 52241, + 52242, + 52243, + 52244, + 52245, + 52246, + 52247, + 52248, + 52249, + 52250, + 52251, + 52252, + 52253, + 52254, + 52255, + 52256, + 52257, + 52258, + 52259, + 52260, + 52261, + 52262, + 52263, + 52264, + 52265, + 52266, + 52267, + 52268, + 52269, + 52270, + 52271, + 52272, + 52273, + 52274, + 52275, + 52276, + 52277, + 52278, + 52279, + 52280, + 52281, + 52282, + 52283, + 52284, + 52285, + 52286, + 52287, + 52288, + 52289, + 52290, + 52291, + 52292, + 52293, + 52294, + 52295, + 52296, + 52297, + 52298, + 52299, + 52300, + 52301, + 52302, + 52303, + 52304, + 52305, + 52306, + 52307, + 52308, + 52309, + 52310, + 52311, + 52312, + 52313, + 52314, + 52315, + 52316, + 52317, + 52318, + 52319, + 52320, + 52321, + 52322, + 52323, + 52324, + 52325, + 52326, + 52327, + 52328, + 52329, + 52330, + 52331, + 52332, + 52333, + 52334, + 52335, + 52336, + 52337, + 52338, + 52339, + 52340, + 52341, + 52342, + 52343, + 52344, + 52345, + 52346, + 52347, + 52348, + 52349, + 52350, + 52351, + 52352, + 52353, + 52354, + 52355, + 52356, + 52357, + 52358, + 52359, + 52360, + 52361, + 52362, + 52363, + 52364, + 52365, + 52366, + 52367, + 52368, + 52369, + 52370, + 52371, + 52372, + 52373, + 52374, + 52375, + 52376, + 52377, + 52378, + 52379, + 52380, + 52381, + 52382, + 52383, + 52384, + 52385, + 52386, + 52387, + 52388, + 52389, + 52390, + 52391, + 52392, + 52393, + 52394, + 52395, + 52396, + 52397, + 52398, + 52399, + 52400, + 52401, + 52402, + 52403, + 52404, + 52405, + 52406, + 52407, + 52408, + 52409, + 52410, + 52411, + 52412, + 52413, + 52414, + 52415, + 52416, + 52417, + 52418, + 52419, + 52420, + 52421, + 52422, + 52423, + 52424, + 52425, + 52426, + 52427, + 52428, + 52429, + 52430, + 52431, + 52432, + 52433, + 52434, + 52435, + 52436, + 52437, + 52438, + 52439, + 52440, + 52441, + 52442, + 52443, + 52444, + 52445, + 52446, + 52447, + 52448, + 52449, + 52450, + 52451, + 52452, + 52453, + 52454, + 52455, + 52456, + 52457, + 52458, + 52459, + 52460, + 52461, + 52462, + 52463, + 52464, + 52465, + 52466, + 52467, + 52468, + 52469, + 52470, + 52471, + 52472, + 52473, + 52474, + 52475, + 52476, + 52477, + 52478, + 52479, + 52480, + 52481, + 52482, + 52483, + 52484, + 52485, + 52486, + 52487, + 52488, + 52489, + 52490, + 52491, + 52492, + 52493, + 52494, + 52495, + 52496, + 52497, + 52498, + 52499, + 52500, + 52501, + 52502, + 52503, + 52504, + 52505, + 52506, + 52507, + 52508, + 52509, + 52510, + 52511, + 52512, + 52513, + 52514, + 52515, + 52516, + 52517, + 52518, + 52519, + 52520, + 52521, + 52522, + 52523, + 52524, + 52525, + 52526, + 52527, + 52528, + 52529, + 52530, + 52531, + 52532, + 52533, + 52534, + 52535, + 52536, + 52537, + 52538, + 52539, + 52540, + 52541, + 52542, + 52543, + 52544, + 52545, + 52546, + 52547, + 52548, + 52549, + 52550, + 52551, + 52552, + 52553, + 52554, + 52555, + 52556, + 52557, + 52558, + 52559, + 52560, + 52561, + 52562, + 52563, + 52564, + 52565, + 52566, + 52567, + 52568, + 52569, + 52570, + 52571, + 52572, + 52573, + 52574, + 52575, + 52576, + 52577, + 52578, + 52579, + 52580, + 52581, + 52582, + 52583, + 52584, + 52585, + 52586, + 52587, + 52588, + 52589, + 52590, + 52591, + 52592, + 52593, + 52594, + 52595, + 52596, + 52597, + 52598, + 52599, + 52600, + 52601, + 52602, + 52603, + 52604, + 52605, + 52606, + 52607, + 52608, + 52609, + 52610, + 52611, + 52612, + 52613, + 52614, + 52615, + 52616, + 52617, + 52618, + 52619, + 52620, + 52621, + 52622, + 52623, + 52624, + 52625, + 52626, + 52627, + 52628, + 52629, + 52630, + 52631, + 52632, + 52633, + 52634, + 52635, + 52636, + 52637, + 52638, + 52639, + 52640, + 52641, + 52642, + 52643, + 52644, + 52645, + 52646, + 52647, + 52648, + 52649, + 52650, + 52651, + 52652, + 52653, + 52654, + 52655, + 52656, + 52657, + 52658, + 52659, + 52660, + 52661, + 52662, + 52663, + 52664, + 52665, + 52666, + 52667, + 52668, + 52669, + 52670, + 52671, + 52672, + 52673, + 52674, + 52675, + 52676, + 52677, + 52678, + 52679, + 52680, + 52681, + 52682, + 52683, + 52684, + 52685, + 52686, + 52687, + 52688, + 52689, + 52690, + 52691, + 52692, + 52693, + 52694, + 52695, + 52696, + 52697, + 52698, + 52699, + 52700, + 52701, + 52702, + 52703, + 52704, + 52705, + 52706, + 52707, + 52708, + 52709, + 52710, + 52711, + 52712, + 52713, + 52714, + 52715, + 52716, + 52717, + 52718, + 52719, + 52720, + 52721, + 52722, + 52723, + 52724, + 52725, + 52726, + 52727, + 52728, + 52729, + 52730, + 52731, + 52732, + 52733, + 52734, + 52735, + 52736, + 52737, + 52738, + 52739, + 52740, + 52741, + 52742, + 52743, + 52744, + 52745, + 52746, + 52747, + 52748, + 52749, + 52750, + 52751, + 52752, + 52753, + 52754, + 52755, + 52756, + 52757, + 52758, + 52759, + 52760, + 52761, + 52762, + 52763, + 52764, + 52765, + 52766, + 52767, + 52768, + 52769, + 52770, + 52771, + 52772, + 52773, + 52774, + 52775, + 52776, + 52777, + 52778, + 52779, + 52780, + 52781, + 52782, + 52783, + 52784, + 52785, + 52786, + 52787, + 52788, + 52789, + 52790, + 52791, + 52792, + 52793, + 52794, + 52795, + 52796, + 52797, + 52798, + 52799, + 52800, + 52801, + 52802, + 52803, + 52804, + 52805, + 52806, + 52807, + 52808, + 52809, + 52810, + 52811, + 52812, + 52813, + 52814, + 52815, + 52816, + 52817, + 52818, + 52819, + 52820, + 52821, + 52822, + 52823, + 52824, + 52825, + 52826, + 52827, + 52828, + 52829, + 52830, + 52831, + 52832, + 52833, + 52834, + 52835, + 52836, + 52837, + 52838, + 52839, + 52840, + 52841, + 52842, + 52843, + 52844, + 52845, + 52846, + 52847, + 52848, + 52849, + 52850, + 52851, + 52852, + 52853, + 52854, + 52855, + 52856, + 52857, + 52858, + 52859, + 52860, + 52861, + 52862, + 52863, + 52864, + 52865, + 52866, + 52867, + 52868, + 52869, + 52870, + 52871, + 52872, + 52873, + 52874, + 52875, + 52876, + 52877, + 52878, + 52879, + 52880, + 52881, + 52882, + 52883, + 52884, + 52885, + 52886, + 52887, + 52888, + 52889, + 52890, + 52891, + 52892, + 52893, + 52894, + 52895, + 52896, + 52897, + 52898, + 52899, + 52900, + 52901, + 52902, + 52903, + 52904, + 52905, + 52906, + 52907, + 52908, + 52909, + 52910, + 52911, + 52912, + 52913, + 52914, + 52915, + 52916, + 52917, + 52918, + 52919, + 52920, + 52921, + 52922, + 52923, + 52924, + 52925, + 52926, + 52927, + 52928, + 52929, + 52930, + 52931, + 52932, + 52933, + 52934, + 52935, + 52936, + 52937, + 52938, + 52939, + 52940, + 52941, + 52942, + 52943, + 52944, + 52945, + 52946, + 52947, + 52948, + 52949, + 52950, + 52951, + 52952, + 52953, + 52954, + 52955, + 52956, + 52957, + 52958, + 52959, + 52960, + 52961, + 52962, + 52963, + 52964, + 52965, + 52966, + 52967, + 52968, + 52969, + 52970, + 52971, + 52972, + 52973, + 52974, + 52975, + 52976, + 52977, + 52978, + 52979, + 52980, + 52981, + 52982, + 52983, + 52984, + 52985, + 52986, + 52987, + 52988, + 52989, + 52990, + 52991, + 52992, + 52993, + 52994, + 52995, + 52996, + 52997, + 52998, + 52999, + 53000, + 53001, + 53002, + 53003, + 53004, + 53005, + 53006, + 53007, + 53008, + 53009, + 53010, + 53011, + 53012, + 53013, + 53014, + 53015, + 53016, + 53017, + 53018, + 53019, + 53020, + 53021, + 53022, + 53023, + 53024, + 53025, + 53026, + 53027, + 53028, + 53029, + 53030, + 53031, + 53032, + 53033, + 53034, + 53035, + 53036, + 53037, + 53038, + 53039, + 53040, + 53041, + 53042, + 53043, + 53044, + 53045, + 53046, + 53047, + 53048, + 53049, + 53050, + 53051, + 53052, + 53053, + 53054, + 53055, + 53056, + 53057, + 53058, + 53059, + 53060, + 53061, + 53062, + 53063, + 53064, + 53065, + 53066, + 53067, + 53068, + 53069, + 53070, + 53071, + 53072, + 53073, + 53074, + 53075, + 53076, + 53077, + 53078, + 53079, + 53080, + 53081, + 53082, + 53083, + 53084, + 53085, + 53086, + 53087, + 53088, + 53089, + 53090, + 53091, + 53092, + 53093, + 53094, + 53095, + 53096, + 53097, + 53098, + 53099, + 53100, + 53101, + 53102, + 53103, + 53104, + 53105, + 53106, + 53107, + 53108, + 53109, + 53110, + 53111, + 53112, + 53113, + 53114, + 53115, + 53116, + 53117, + 53118, + 53119, + 53120, + 53121, + 53122, + 53123, + 53124, + 53125, + 53126, + 53127, + 53128, + 53129, + 53130, + 53131, + 53132, + 53133, + 53134, + 53135, + 53136, + 53137, + 53138, + 53139, + 53140, + 53141, + 53142, + 53143, + 53144, + 53145, + 53146, + 53147, + 53148, + 53149, + 53150, + 53151, + 53152, + 53153, + 53154, + 53155, + 53156, + 53157, + 53158, + 53159, + 53160, + 53161, + 53162, + 53163, + 53164, + 53165, + 53166, + 53167, + 53168, + 53169, + 53170, + 53171, + 53172, + 53173, + 53174, + 53175, + 53176, + 53177, + 53178, + 53179, + 53180, + 53181, + 53182, + 53183, + 53184, + 53185, + 53186, + 53187, + 53188, + 53189, + 53190, + 53191, + 53192, + 53193, + 53194, + 53195, + 53196, + 53197, + 53198, + 53199, + 53200, + 53201, + 53202, + 53203, + 53204, + 53205, + 53206, + 53207, + 53208, + 53209, + 53210, + 53211, + 53212, + 53213, + 53214, + 53215, + 53216, + 53217, + 53218, + 53219, + 53220, + 53221, + 53222, + 53223, + 53224, + 53225, + 53226, + 53227, + 53228, + 53229, + 53230, + 53231, + 53232, + 53233, + 53234, + 53235, + 53236, + 53237, + 53238, + 53239, + 53240, + 53241, + 53242, + 53243, + 53244, + 53245, + 53246, + 53247, + 53248, + 53249, + 53250, + 53251, + 53252, + 53253, + 53254, + 53255, + 53256, + 53257, + 53258, + 53259, + 53260, + 53261, + 53262, + 53263, + 53264, + 53265, + 53266, + 53267, + 53268, + 53269, + 53270, + 53271, + 53272, + 53273, + 53274, + 53275, + 53276, + 53277, + 53278, + 53279, + 53280, + 53281, + 53282, + 53283, + 53284, + 53285, + 53286, + 53287, + 53288, + 53289, + 53290, + 53291, + 53292, + 53293, + 53294, + 53295, + 53296, + 53297, + 53298, + 53299, + 53300, + 53301, + 53302, + 53303, + 53304, + 53305, + 53306, + 53307, + 53308, + 53309, + 53310, + 53311, + 53312, + 53313, + 53314, + 53315, + 53316, + 53317, + 53318, + 53319, + 53320, + 53321, + 53322, + 53323, + 53324, + 53325, + 53326, + 53327, + 53328, + 53329, + 53330, + 53331, + 53332, + 53333, + 53334, + 53335, + 53336, + 53337, + 53338, + 53339, + 53340, + 53341, + 53342, + 53343, + 53344, + 53345, + 53346, + 53347, + 53348, + 53349, + 53350, + 53351, + 53352, + 53353, + 53354, + 53355, + 53356, + 53357, + 53358, + 53359, + 53360, + 53361, + 53362, + 53363, + 53364, + 53365, + 53366, + 53367, + 53368, + 53369, + 53370, + 53371, + 53372, + 53373, + 53374, + 53375, + 53376, + 53377, + 53378, + 53379, + 53380, + 53381, + 53382, + 53383, + 53384, + 53385, + 53386, + 53387, + 53388, + 53389, + 53390, + 53391, + 53392, + 53393, + 53394, + 53395, + 53396, + 53397, + 53398, + 53399, + 53400, + 53401, + 53402, + 53403, + 53404, + 53405, + 53406, + 53407, + 53408, + 53409, + 53410, + 53411, + 53412, + 53413, + 53414, + 53415, + 53416, + 53417, + 53418, + 53419, + 53420, + 53421, + 53422, + 53423, + 53424, + 53425, + 53426, + 53427, + 53428, + 53429, + 53430, + 53431, + 53432, + 53433, + 53434, + 53435, + 53436, + 53437, + 53438, + 53439, + 53440, + 53441, + 53442, + 53443, + 53444, + 53445, + 53446, + 53447, + 53448, + 53449, + 53450, + 53451, + 53452, + 53453, + 53454, + 53455, + 53456, + 53457, + 53458, + 53459, + 53460, + 53461, + 53462, + 53463, + 53464, + 53465, + 53466, + 53467, + 53468, + 53469, + 53470, + 53471, + 53472, + 53473, + 53474, + 53475, + 53476, + 53477, + 53478, + 53479, + 53480, + 53481, + 53482, + 53483, + 53484, + 53485, + 53486, + 53487, + 53488, + 53489, + 53490, + 53491, + 53492, + 53493, + 53494, + 53495, + 53496, + 53497, + 53498, + 53499, + 53500, + 53501, + 53502, + 53503, + 53504, + 53505, + 53506, + 53507, + 53508, + 53509, + 53510, + 53511, + 53512, + 53513, + 53514, + 53515, + 53516, + 53517, + 53518, + 53519, + 53520, + 53521, + 53522, + 53523, + 53524, + 53525, + 53526, + 53527, + 53528, + 53529, + 53530, + 53531, + 53532, + 53533, + 53534, + 53535, + 53536, + 53537, + 53538, + 53539, + 53540, + 53541, + 53542, + 53543, + 53544, + 53545, + 53546, + 53547, + 53548, + 53549, + 53550, + 53551, + 53552, + 53553, + 53554, + 53555, + 53556, + 53557, + 53558, + 53559, + 53560, + 53561, + 53562, + 53563, + 53564, + 53565, + 53566, + 53567, + 53568, + 53569, + 53570, + 53571, + 53572, + 53573, + 53574, + 53575, + 53576, + 53577, + 53578, + 53579, + 53580, + 53581, + 53582, + 53583, + 53584, + 53585, + 53586, + 53587, + 53588, + 53589, + 53590, + 53591, + 53592, + 53593, + 53594, + 53595, + 53596, + 53597, + 53598, + 53599, + 53600, + 53601, + 53602, + 53603, + 53604, + 53605, + 53606, + 53607, + 53608, + 53609, + 53610, + 53611, + 53612, + 53613, + 53614, + 53615, + 53616, + 53617, + 53618, + 53619, + 53620, + 53621, + 53622, + 53623, + 53624, + 53625, + 53626, + 53627, + 53628, + 53629, + 53630, + 53631, + 53632, + 53633, + 53634, + 53635, + 53636, + 53637, + 53638, + 53639, + 53640, + 53641, + 53642, + 53643, + 53644, + 53645, + 53646, + 53647, + 53648, + 53649, + 53650, + 53651, + 53652, + 53653, + 53654, + 53655, + 53656, + 53657, + 53658, + 53659, + 53660, + 53661, + 53662, + 53663, + 53664, + 53665, + 53666, + 53667, + 53668, + 53669, + 53670, + 53671, + 53672, + 53673, + 53674, + 53675, + 53676, + 53677, + 53678, + 53679, + 53680, + 53681, + 53682, + 53683, + 53684, + 53685, + 53686, + 53687, + 53688, + 53689, + 53690, + 53691, + 53692, + 53693, + 53694, + 53695, + 53696, + 53697, + 53698, + 53699, + 53700, + 53701, + 53702, + 53703, + 53704, + 53705, + 53706, + 53707, + 53708, + 53709, + 53710, + 53711, + 53712, + 53713, + 53714, + 53715, + 53716, + 53717, + 53718, + 53719, + 53720, + 53721, + 53722, + 53723, + 53724, + 53725, + 53726, + 53727, + 53728, + 53729, + 53730, + 53731, + 53732, + 53733, + 53734, + 53735, + 53736, + 53737, + 53738, + 53739, + 53740, + 53741, + 53742, + 53743, + 53744, + 53745, + 53746, + 53747, + 53748, + 53749, + 53750, + 53751, + 53752, + 53753, + 53754, + 53755, + 53756, + 53757, + 53758, + 53759, + 53760, + 53761, + 53762, + 53763, + 53764, + 53765, + 53766, + 53767, + 53768, + 53769, + 53770, + 53771, + 53772, + 53773, + 53774, + 53775, + 53776, + 53777, + 53778, + 53779, + 53780, + 53781, + 53782, + 53783, + 53784, + 53785, + 53786, + 53787, + 53788, + 53789, + 53790, + 53791, + 53792, + 53793, + 53794, + 53795, + 53796, + 53797, + 53798, + 53799, + 53800, + 53801, + 53802, + 53803, + 53804, + 53805, + 53806, + 53807, + 53808, + 53809, + 53810, + 53811, + 53812, + 53813, + 53814, + 53815, + 53816, + 53817, + 53818, + 53819, + 53820, + 53821, + 53822, + 53823, + 53824, + 53825, + 53826, + 53827, + 53828, + 53829, + 53830, + 53831, + 53832, + 53833, + 53834, + 53835, + 53836, + 53837, + 53838, + 53839, + 53840, + 53841, + 53842, + 53843, + 53844, + 53845, + 53846, + 53847, + 53848, + 53849, + 53850, + 53851, + 53852, + 53853, + 53854, + 53855, + 53856, + 53857, + 53858, + 53859, + 53860, + 53861, + 53862, + 53863, + 53864, + 53865, + 53866, + 53867, + 53868, + 53869, + 53870, + 53871, + 53872, + 53873, + 53874, + 53875, + 53876, + 53877, + 53878, + 53879, + 53880, + 53881, + 53882, + 53883, + 53884, + 53885, + 53886, + 53887, + 53888, + 53889, + 53890, + 53891, + 53892, + 53893, + 53894, + 53895, + 53896, + 53897, + 53898, + 53899, + 53900, + 53901, + 53902, + 53903, + 53904, + 53905, + 53906, + 53907, + 53908, + 53909, + 53910, + 53911, + 53912, + 53913, + 53914, + 53915, + 53916, + 53917, + 53918, + 53919, + 53920, + 53921, + 53922, + 53923, + 53924, + 53925, + 53926, + 53927, + 53928, + 53929, + 53930, + 53931, + 53932, + 53933, + 53934, + 53935, + 53936, + 53937, + 53938, + 53939, + 53940, + 53941, + 53942, + 53943, + 53944, + 53945, + 53946, + 53947, + 53948, + 53949, + 53950, + 53951, + 53952, + 53953, + 53954, + 53955, + 53956, + 53957, + 53958, + 53959, + 53960, + 53961, + 53962, + 53963, + 53964, + 53965, + 53966, + 53967, + 53968, + 53969, + 53970, + 53971, + 53972, + 53973, + 53974, + 53975, + 53976, + 53977, + 53978, + 53979, + 53980, + 53981, + 53982, + 53983, + 53984, + 53985, + 53986, + 53987, + 53988, + 53989, + 53990, + 53991, + 53992, + 53993, + 53994, + 53995, + 53996, + 53997, + 53998, + 53999, + 54000, + 54001, + 54002, + 54003, + 54004, + 54005, + 54006, + 54007, + 54008, + 54009, + 54010, + 54011, + 54012, + 54013, + 54014, + 54015, + 54016, + 54017, + 54018, + 54019, + 54020, + 54021, + 54022, + 54023, + 54024, + 54025, + 54026, + 54027, + 54028, + 54029, + 54030, + 54031, + 54032, + 54033, + 54034, + 54035, + 54036, + 54037, + 54038, + 54039, + 54040, + 54041, + 54042, + 54043, + 54044, + 54045, + 54046, + 54047, + 54048, + 54049, + 54050, + 54051, + 54052, + 54053, + 54054, + 54055, + 54056, + 54057, + 54058, + 54059, + 54060, + 54061, + 54062, + 54063, + 54064, + 54065, + 54066, + 54067, + 54068, + 54069, + 54070, + 54071, + 54072, + 54073, + 54074, + 54075, + 54076, + 54077, + 54078, + 54079, + 54080, + 54081, + 54082, + 54083, + 54084, + 54085, + 54086, + 54087, + 54088, + 54089, + 54090, + 54091, + 54092, + 54093, + 54094, + 54095, + 54096, + 54097, + 54098, + 54099, + 54100, + 54101, + 54102, + 54103, + 54104, + 54105, + 54106, + 54107, + 54108, + 54109, + 54110, + 54111, + 54112, + 54113, + 54114, + 54115, + 54116, + 54117, + 54118, + 54119, + 54120, + 54121, + 54122, + 54123, + 54124, + 54125, + 54126, + 54127, + 54128, + 54129, + 54130, + 54131, + 54132, + 54133, + 54134, + 54135, + 54136, + 54137, + 54138, + 54139, + 54140, + 54141, + 54142, + 54143, + 54144, + 54145, + 54146, + 54147, + 54148, + 54149, + 54150, + 54151, + 54152, + 54153, + 54154, + 54155, + 54156, + 54157, + 54158, + 54159, + 54160, + 54161, + 54162, + 54163, + 54164, + 54165, + 54166, + 54167, + 54168, + 54169, + 54170, + 54171, + 54172, + 54173, + 54174, + 54175, + 54176, + 54177, + 54178, + 54179, + 54180, + 54181, + 54182, + 54183, + 54184, + 54185, + 54186, + 54187, + 54188, + 54189, + 54190, + 54191, + 54192, + 54193, + 54194, + 54195, + 54196, + 54197, + 54198, + 54199, + 54200, + 54201, + 54202, + 54203, + 54204, + 54205, + 54206, + 54207, + 54208, + 54209, + 54210, + 54211, + 54212, + 54213, + 54214, + 54215, + 54216, + 54217, + 54218, + 54219, + 54220, + 54221, + 54222, + 54223, + 54224, + 54225, + 54226, + 54227, + 54228, + 54229, + 54230, + 54231, + 54232, + 54233, + 54234, + 54235, + 54236, + 54237, + 54238, + 54239, + 54240, + 54241, + 54242, + 54243, + 54244, + 54245, + 54246, + 54247, + 54248, + 54249, + 54250, + 54251, + 54252, + 54253, + 54254, + 54255, + 54256, + 54257, + 54258, + 54259, + 54260, + 54261, + 54262, + 54263, + 54264, + 54265, + 54266, + 54267, + 54268, + 54269, + 54270, + 54271, + 54272, + 54273, + 54274, + 54275, + 54276, + 54277, + 54278, + 54279, + 54280, + 54281, + 54282, + 54283, + 54284, + 54285, + 54286, + 54287, + 54288, + 54289, + 54290, + 54291, + 54292, + 54293, + 54294, + 54295, + 54296, + 54297, + 54298, + 54299, + 54300, + 54301, + 54302, + 54303, + 54304, + 54305, + 54306, + 54307, + 54308, + 54309, + 54310, + 54311, + 54312, + 54313, + 54314, + 54315, + 54316, + 54317, + 54318, + 54319, + 54320, + 54321, + 54322, + 54323, + 54324, + 54325, + 54326, + 54327, + 54328, + 54329, + 54330, + 54331, + 54332, + 54333, + 54334, + 54335, + 54336, + 54337, + 54338, + 54339, + 54340, + 54341, + 54342, + 54343, + 54344, + 54345, + 54346, + 54347, + 54348, + 54349, + 54350, + 54351, + 54352, + 54353, + 54354, + 54355, + 54356, + 54357, + 54358, + 54359, + 54360, + 54361, + 54362, + 54363, + 54364, + 54365, + 54366, + 54367, + 54368, + 54369, + 54370, + 54371, + 54372, + 54373, + 54374, + 54375, + 54376, + 54377, + 54378, + 54379, + 54380, + 54381, + 54382, + 54383, + 54384, + 54385, + 54386, + 54387, + 54388, + 54389, + 54390, + 54391, + 54392, + 54393, + 54394, + 54395, + 54396, + 54397, + 54398, + 54399, + 54400, + 54401, + 54402, + 54403, + 54404, + 54405, + 54406, + 54407, + 54408, + 54409, + 54410, + 54411, + 54412, + 54413, + 54414, + 54415, + 54416, + 54417, + 54418, + 54419, + 54420, + 54421, + 54422, + 54423, + 54424, + 54425, + 54426, + 54427, + 54428, + 54429, + 54430, + 54431, + 54432, + 54433, + 54434, + 54435, + 54436, + 54437, + 54438, + 54439, + 54440, + 54441, + 54442, + 54443, + 54444, + 54445, + 54446, + 54447, + 54448, + 54449, + 54450, + 54451, + 54452, + 54453, + 54454, + 54455, + 54456, + 54457, + 54458, + 54459, + 54460, + 54461, + 54462, + 54463, + 54464, + 54465, + 54466, + 54467, + 54468, + 54469, + 54470, + 54471, + 54472, + 54473, + 54474, + 54475, + 54476, + 54477, + 54478, + 54479, + 54480, + 54481, + 54482, + 54483, + 54484, + 54485, + 54486, + 54487, + 54488, + 54489, + 54490, + 54491, + 54492, + 54493, + 54494, + 54495, + 54496, + 54497, + 54498, + 54499, + 54500, + 54501, + 54502, + 54503, + 54504, + 54505, + 54506, + 54507, + 54508, + 54509, + 54510, + 54511, + 54512, + 54513, + 54514, + 54515, + 54516, + 54517, + 54518, + 54519, + 54520, + 54521, + 54522, + 54523, + 54524, + 54525, + 54526, + 54527, + 54528, + 54529, + 54530, + 54531, + 54532, + 54533, + 54534, + 54535, + 54536, + 54537, + 54538, + 54539, + 54540, + 54541, + 54542, + 54543, + 54544, + 54545, + 54546, + 54547, + 54548, + 54549, + 54550, + 54551, + 54552, + 54553, + 54554, + 54555, + 54556, + 54557, + 54558, + 54559, + 54560, + 54561, + 54562, + 54563, + 54564, + 54565, + 54566, + 54567, + 54568, + 54569, + 54570, + 54571, + 54572, + 54573, + 54574, + 54575, + 54576, + 54577, + 54578, + 54579, + 54580, + 54581, + 54582, + 54583, + 54584, + 54585, + 54586, + 54587, + 54588, + 54589, + 54590, + 54591, + 54592, + 54593, + 54594, + 54595, + 54596, + 54597, + 54598, + 54599, + 54600, + 54601, + 54602, + 54603, + 54604, + 54605, + 54606, + 54607, + 54608, + 54609, + 54610, + 54611, + 54612, + 54613, + 54614, + 54615, + 54616, + 54617, + 54618, + 54619, + 54620, + 54621, + 54622, + 54623, + 54624, + 54625, + 54626, + 54627, + 54628, + 54629, + 54630, + 54631, + 54632, + 54633, + 54634, + 54635, + 54636, + 54637, + 54638, + 54639, + 54640, + 54641, + 54642, + 54643, + 54644, + 54645, + 54646, + 54647, + 54648, + 54649, + 54650, + 54651, + 54652, + 54653, + 54654, + 54655, + 54656, + 54657, + 54658, + 54659, + 54660, + 54661, + 54662, + 54663, + 54664, + 54665, + 54666, + 54667, + 54668, + 54669, + 54670, + 54671, + 54672, + 54673, + 54674, + 54675, + 54676, + 54677, + 54678, + 54679, + 54680, + 54681, + 54682, + 54683, + 54684, + 54685, + 54686, + 54687, + 54688, + 54689, + 54690, + 54691, + 54692, + 54693, + 54694, + 54695, + 54696, + 54697, + 54698, + 54699, + 54700, + 54701, + 54702, + 54703, + 54704, + 54705, + 54706, + 54707, + 54708, + 54709, + 54710, + 54711, + 54712, + 54713, + 54714, + 54715, + 54716, + 54717, + 54718, + 54719, + 54720, + 54721, + 54722, + 54723, + 54724, + 54725, + 54726, + 54727, + 54728, + 54729, + 54730, + 54731, + 54732, + 54733, + 54734, + 54735, + 54736, + 54737, + 54738, + 54739, + 54740, + 54741, + 54742, + 54743, + 54744, + 54745, + 54746, + 54747, + 54748, + 54749, + 54750, + 54751, + 54752, + 54753, + 54754, + 54755, + 54756, + 54757, + 54758, + 54759, + 54760, + 54761, + 54762, + 54763, + 54764, + 54765, + 54766, + 54767, + 54768, + 54769, + 54770, + 54771, + 54772, + 54773, + 54774, + 54775, + 54776, + 54777, + 54778, + 54779, + 54780, + 54781, + 54782, + 54783, + 54784, + 54785, + 54786, + 54787, + 54788, + 54789, + 54790, + 54791, + 54792, + 54793, + 54794, + 54795, + 54796, + 54797, + 54798, + 54799, + 54800, + 54801, + 54802, + 54803, + 54804, + 54805, + 54806, + 54807, + 54808, + 54809, + 54810, + 54811, + 54812, + 54813, + 54814, + 54815, + 54816, + 54817, + 54818, + 54819, + 54820, + 54821, + 54822, + 54823, + 54824, + 54825, + 54826, + 54827, + 54828, + 54829, + 54830, + 54831, + 54832, + 54833, + 54834, + 54835, + 54836, + 54837, + 54838, + 54839, + 54840, + 54841, + 54842, + 54843, + 54844, + 54845, + 54846, + 54847, + 54848, + 54849, + 54850, + 54851, + 54852, + 54853, + 54854, + 54855, + 54856, + 54857, + 54858, + 54859, + 54860, + 54861, + 54862, + 54863, + 54864, + 54865, + 54866, + 54867, + 54868, + 54869, + 54870, + 54871, + 54872, + 54873, + 54874, + 54875, + 54876, + 54877, + 54878, + 54879, + 54880, + 54881, + 54882, + 54883, + 54884, + 54885, + 54886, + 54887, + 54888, + 54889, + 54890, + 54891, + 54892, + 54893, + 54894, + 54895, + 54896, + 54897, + 54898, + 54899, + 54900, + 54901, + 54902, + 54903, + 54904, + 54905, + 54906, + 54907, + 54908, + 54909, + 54910, + 54911, + 54912, + 54913, + 54914, + 54915, + 54916, + 54917, + 54918, + 54919, + 54920, + 54921, + 54922, + 54923, + 54924, + 54925, + 54926, + 54927, + 54928, + 54929, + 54930, + 54931, + 54932, + 54933, + 54934, + 54935, + 54936, + 54937, + 54938, + 54939, + 54940, + 54941, + 54942, + 54943, + 54944, + 54945, + 54946, + 54947, + 54948, + 54949, + 54950, + 54951, + 54952, + 54953, + 54954, + 54955, + 54956, + 54957, + 54958, + 54959, + 54960, + 54961, + 54962, + 54963, + 54964, + 54965, + 54966, + 54967, + 54968, + 54969, + 54970, + 54971, + 54972, + 54973, + 54974, + 54975, + 54976, + 54977, + 54978, + 54979, + 54980, + 54981, + 54982, + 54983, + 54984, + 54985, + 54986, + 54987, + 54988, + 54989, + 54990, + 54991, + 54992, + 54993, + 54994, + 54995, + 54996, + 54997, + 54998, + 54999, + 55000, + 55001, + 55002, + 55003, + 55004, + 55005, + 55006, + 55007, + 55008, + 55009, + 55010, + 55011, + 55012, + 55013, + 55014, + 55015, + 55016, + 55017, + 55018, + 55019, + 55020, + 55021, + 55022, + 55023, + 55024, + 55025, + 55026, + 55027, + 55028, + 55029, + 55030, + 55031, + 55032, + 55033, + 55034, + 55035, + 55036, + 55037, + 55038, + 55039, + 55040, + 55041, + 55042, + 55043, + 55044, + 55045, + 55046, + 55047, + 55048, + 55049, + 55050, + 55051, + 55052, + 55053, + 55054, + 55055, + 55056, + 55057, + 55058, + 55059, + 55060, + 55061, + 55062, + 55063, + 55064, + 55065, + 55066, + 55067, + 55068, + 55069, + 55070, + 55071, + 55072, + 55073, + 55074, + 55075, + 55076, + 55077, + 55078, + 55079, + 55080, + 55081, + 55082, + 55083, + 55084, + 55085, + 55086, + 55087, + 55088, + 55089, + 55090, + 55091, + 55092, + 55093, + 55094, + 55095, + 55096, + 55097, + 55098, + 55099, + 55100, + 55101, + 55102, + 55103, + 55104, + 55105, + 55106, + 55107, + 55108, + 55109, + 55110, + 55111, + 55112, + 55113, + 55114, + 55115, + 55116, + 55117, + 55118, + 55119, + 55120, + 55121, + 55122, + 55123, + 55124, + 55125, + 55126, + 55127, + 55128, + 55129, + 55130, + 55131, + 55132, + 55133, + 55134, + 55135, + 55136, + 55137, + 55138, + 55139, + 55140, + 55141, + 55142, + 55143, + 55144, + 55145, + 55146, + 55147, + 55148, + 55149, + 55150, + 55151, + 55152, + 55153, + 55154, + 55155, + 55156, + 55157, + 55158, + 55159, + 55160, + 55161, + 55162, + 55163, + 55164, + 55165, + 55166, + 55167, + 55168, + 55169, + 55170, + 55171, + 55172, + 55173, + 55174, + 55175, + 55176, + 55177, + 55178, + 55179, + 55180, + 55181, + 55182, + 55183, + 55184, + 55185, + 55186, + 55187, + 55188, + 55189, + 55190, + 55191, + 55192, + 55193, + 55194, + 55195, + 55196, + 55197, + 55198, + 55199, + 55200, + 55201, + 55202, + 55203, + 55216, + 55217, + 55218, + 55219, + 55220, + 55221, + 55222, + 55223, + 55224, + 55225, + 55226, + 55227, + 55228, + 55229, + 55230, + 55231, + 55232, + 55233, + 55234, + 55235, + 55236, + 55237, + 55238, + 55243, + 55244, + 55245, + 55246, + 55247, + 55248, + 55249, + 55250, + 55251, + 55252, + 55253, + 55254, + 55255, + 55256, + 55257, + 55258, + 55259, + 55260, + 55261, + 55262, + 55263, + 55264, + 55265, + 55266, + 55267, + 55268, + 55269, + 55270, + 55271, + 55272, + 55273, + 55274, + 55275, + 55276, + 55277, + 55278, + 55279, + 55280, + 55281, + 55282, + 55283, + 55284, + 55285, + 55286, + 55287, + 55288, + 55289, + 55290, + 55291, + 63744, + 63745, + 63746, + 63747, + 63748, + 63749, + 63750, + 63751, + 63752, + 63753, + 63754, + 63755, + 63756, + 63757, + 63758, + 63759, + 63760, + 63761, + 63762, + 63763, + 63764, + 63765, + 63766, + 63767, + 63768, + 63769, + 63770, + 63771, + 63772, + 63773, + 63774, + 63775, + 63776, + 63777, + 63778, + 63779, + 63780, + 63781, + 63782, + 63783, + 63784, + 63785, + 63786, + 63787, + 63788, + 63789, + 63790, + 63791, + 63792, + 63793, + 63794, + 63795, + 63796, + 63797, + 63798, + 63799, + 63800, + 63801, + 63802, + 63803, + 63804, + 63805, + 63806, + 63807, + 63808, + 63809, + 63810, + 63811, + 63812, + 63813, + 63814, + 63815, + 63816, + 63817, + 63818, + 63819, + 63820, + 63821, + 63822, + 63823, + 63824, + 63825, + 63826, + 63827, + 63828, + 63829, + 63830, + 63831, + 63832, + 63833, + 63834, + 63835, + 63836, + 63837, + 63838, + 63839, + 63840, + 63841, + 63842, + 63843, + 63844, + 63845, + 63846, + 63847, + 63848, + 63849, + 63850, + 63851, + 63852, + 63853, + 63854, + 63855, + 63856, + 63857, + 63858, + 63859, + 63860, + 63861, + 63862, + 63863, + 63864, + 63865, + 63866, + 63867, + 63868, + 63869, + 63870, + 63871, + 63872, + 63873, + 63874, + 63875, + 63876, + 63877, + 63878, + 63879, + 63880, + 63881, + 63882, + 63883, + 63884, + 63885, + 63886, + 63887, + 63888, + 63889, + 63890, + 63891, + 63892, + 63893, + 63894, + 63895, + 63896, + 63897, + 63898, + 63899, + 63900, + 63901, + 63902, + 63903, + 63904, + 63905, + 63906, + 63907, + 63908, + 63909, + 63910, + 63911, + 63912, + 63913, + 63914, + 63915, + 63916, + 63917, + 63918, + 63919, + 63920, + 63921, + 63922, + 63923, + 63924, + 63925, + 63926, + 63927, + 63928, + 63929, + 63930, + 63931, + 63932, + 63933, + 63934, + 63935, + 63936, + 63937, + 63938, + 63939, + 63940, + 63941, + 63942, + 63943, + 63944, + 63945, + 63946, + 63947, + 63948, + 63949, + 63950, + 63951, + 63952, + 63953, + 63954, + 63955, + 63956, + 63957, + 63958, + 63959, + 63960, + 63961, + 63962, + 63963, + 63964, + 63965, + 63966, + 63967, + 63968, + 63969, + 63970, + 63971, + 63972, + 63973, + 63974, + 63975, + 63976, + 63977, + 63978, + 63979, + 63980, + 63981, + 63982, + 63983, + 63984, + 63985, + 63986, + 63987, + 63988, + 63989, + 63990, + 63991, + 63992, + 63993, + 63994, + 63995, + 63996, + 63997, + 63998, + 63999, + 64000, + 64001, + 64002, + 64003, + 64004, + 64005, + 64006, + 64007, + 64008, + 64009, + 64010, + 64011, + 64012, + 64013, + 64014, + 64015, + 64016, + 64017, + 64018, + 64019, + 64020, + 64021, + 64022, + 64023, + 64024, + 64025, + 64026, + 64027, + 64028, + 64029, + 64030, + 64031, + 64032, + 64033, + 64034, + 64035, + 64036, + 64037, + 64038, + 64039, + 64040, + 64041, + 64042, + 64043, + 64044, + 64045, + 64046, + 64047, + 64048, + 64049, + 64050, + 64051, + 64052, + 64053, + 64054, + 64055, + 64056, + 64057, + 64058, + 64059, + 64060, + 64061, + 64062, + 64063, + 64064, + 64065, + 64066, + 64067, + 64068, + 64069, + 64070, + 64071, + 64072, + 64073, + 64074, + 64075, + 64076, + 64077, + 64078, + 64079, + 64080, + 64081, + 64082, + 64083, + 64084, + 64085, + 64086, + 64087, + 64088, + 64089, + 64090, + 64091, + 64092, + 64093, + 64094, + 64095, + 64096, + 64097, + 64098, + 64099, + 64100, + 64101, + 64102, + 64103, + 64104, + 64105, + 64106, + 64107, + 64108, + 64109, + 64112, + 64113, + 64114, + 64115, + 64116, + 64117, + 64118, + 64119, + 64120, + 64121, + 64122, + 64123, + 64124, + 64125, + 64126, + 64127, + 64128, + 64129, + 64130, + 64131, + 64132, + 64133, + 64134, + 64135, + 64136, + 64137, + 64138, + 64139, + 64140, + 64141, + 64142, + 64143, + 64144, + 64145, + 64146, + 64147, + 64148, + 64149, + 64150, + 64151, + 64152, + 64153, + 64154, + 64155, + 64156, + 64157, + 64158, + 64159, + 64160, + 64161, + 64162, + 64163, + 64164, + 64165, + 64166, + 64167, + 64168, + 64169, + 64170, + 64171, + 64172, + 64173, + 64174, + 64175, + 64176, + 64177, + 64178, + 64179, + 64180, + 64181, + 64182, + 64183, + 64184, + 64185, + 64186, + 64187, + 64188, + 64189, + 64190, + 64191, + 64192, + 64193, + 64194, + 64195, + 64196, + 64197, + 64198, + 64199, + 64200, + 64201, + 64202, + 64203, + 64204, + 64205, + 64206, + 64207, + 64208, + 64209, + 64210, + 64211, + 64212, + 64213, + 64214, + 64215, + 64216, + 64217, + 64256, + 64257, + 64258, + 64259, + 64260, + 64261, + 64262, + 64275, + 64276, + 64277, + 64278, + 64279, + 64285, + 64287, + 64288, + 64289, + 64290, + 64291, + 64292, + 64293, + 64294, + 64295, + 64296, + 64298, + 64299, + 64300, + 64301, + 64302, + 64303, + 64304, + 64305, + 64306, + 64307, + 64308, + 64309, + 64310, + 64312, + 64313, + 64314, + 64315, + 64316, + 64318, + 64320, + 64321, + 64323, + 64324, + 64326, + 64327, + 64328, + 64329, + 64330, + 64331, + 64332, + 64333, + 64334, + 64335, + 64336, + 64337, + 64338, + 64339, + 64340, + 64341, + 64342, + 64343, + 64344, + 64345, + 64346, + 64347, + 64348, + 64349, + 64350, + 64351, + 64352, + 64353, + 64354, + 64355, + 64356, + 64357, + 64358, + 64359, + 64360, + 64361, + 64362, + 64363, + 64364, + 64365, + 64366, + 64367, + 64368, + 64369, + 64370, + 64371, + 64372, + 64373, + 64374, + 64375, + 64376, + 64377, + 64378, + 64379, + 64380, + 64381, + 64382, + 64383, + 64384, + 64385, + 64386, + 64387, + 64388, + 64389, + 64390, + 64391, + 64392, + 64393, + 64394, + 64395, + 64396, + 64397, + 64398, + 64399, + 64400, + 64401, + 64402, + 64403, + 64404, + 64405, + 64406, + 64407, + 64408, + 64409, + 64410, + 64411, + 64412, + 64413, + 64414, + 64415, + 64416, + 64417, + 64418, + 64419, + 64420, + 64421, + 64422, + 64423, + 64424, + 64425, + 64426, + 64427, + 64428, + 64429, + 64430, + 64431, + 64432, + 64433, + 64467, + 64468, + 64469, + 64470, + 64471, + 64472, + 64473, + 64474, + 64475, + 64476, + 64477, + 64478, + 64479, + 64480, + 64481, + 64482, + 64483, + 64484, + 64485, + 64486, + 64487, + 64488, + 64489, + 64490, + 64491, + 64492, + 64493, + 64494, + 64495, + 64496, + 64497, + 64498, + 64499, + 64500, + 64501, + 64502, + 64503, + 64504, + 64505, + 64506, + 64507, + 64508, + 64509, + 64510, + 64511, + 64512, + 64513, + 64514, + 64515, + 64516, + 64517, + 64518, + 64519, + 64520, + 64521, + 64522, + 64523, + 64524, + 64525, + 64526, + 64527, + 64528, + 64529, + 64530, + 64531, + 64532, + 64533, + 64534, + 64535, + 64536, + 64537, + 64538, + 64539, + 64540, + 64541, + 64542, + 64543, + 64544, + 64545, + 64546, + 64547, + 64548, + 64549, + 64550, + 64551, + 64552, + 64553, + 64554, + 64555, + 64556, + 64557, + 64558, + 64559, + 64560, + 64561, + 64562, + 64563, + 64564, + 64565, + 64566, + 64567, + 64568, + 64569, + 64570, + 64571, + 64572, + 64573, + 64574, + 64575, + 64576, + 64577, + 64578, + 64579, + 64580, + 64581, + 64582, + 64583, + 64584, + 64585, + 64586, + 64587, + 64588, + 64589, + 64590, + 64591, + 64592, + 64593, + 64594, + 64595, + 64596, + 64597, + 64598, + 64599, + 64600, + 64601, + 64602, + 64603, + 64604, + 64605, + 64606, + 64607, + 64608, + 64609, + 64610, + 64611, + 64612, + 64613, + 64614, + 64615, + 64616, + 64617, + 64618, + 64619, + 64620, + 64621, + 64622, + 64623, + 64624, + 64625, + 64626, + 64627, + 64628, + 64629, + 64630, + 64631, + 64632, + 64633, + 64634, + 64635, + 64636, + 64637, + 64638, + 64639, + 64640, + 64641, + 64642, + 64643, + 64644, + 64645, + 64646, + 64647, + 64648, + 64649, + 64650, + 64651, + 64652, + 64653, + 64654, + 64655, + 64656, + 64657, + 64658, + 64659, + 64660, + 64661, + 64662, + 64663, + 64664, + 64665, + 64666, + 64667, + 64668, + 64669, + 64670, + 64671, + 64672, + 64673, + 64674, + 64675, + 64676, + 64677, + 64678, + 64679, + 64680, + 64681, + 64682, + 64683, + 64684, + 64685, + 64686, + 64687, + 64688, + 64689, + 64690, + 64691, + 64692, + 64693, + 64694, + 64695, + 64696, + 64697, + 64698, + 64699, + 64700, + 64701, + 64702, + 64703, + 64704, + 64705, + 64706, + 64707, + 64708, + 64709, + 64710, + 64711, + 64712, + 64713, + 64714, + 64715, + 64716, + 64717, + 64718, + 64719, + 64720, + 64721, + 64722, + 64723, + 64724, + 64725, + 64726, + 64727, + 64728, + 64729, + 64730, + 64731, + 64732, + 64733, + 64734, + 64735, + 64736, + 64737, + 64738, + 64739, + 64740, + 64741, + 64742, + 64743, + 64744, + 64745, + 64746, + 64747, + 64748, + 64749, + 64750, + 64751, + 64752, + 64753, + 64754, + 64755, + 64756, + 64757, + 64758, + 64759, + 64760, + 64761, + 64762, + 64763, + 64764, + 64765, + 64766, + 64767, + 64768, + 64769, + 64770, + 64771, + 64772, + 64773, + 64774, + 64775, + 64776, + 64777, + 64778, + 64779, + 64780, + 64781, + 64782, + 64783, + 64784, + 64785, + 64786, + 64787, + 64788, + 64789, + 64790, + 64791, + 64792, + 64793, + 64794, + 64795, + 64796, + 64797, + 64798, + 64799, + 64800, + 64801, + 64802, + 64803, + 64804, + 64805, + 64806, + 64807, + 64808, + 64809, + 64810, + 64811, + 64812, + 64813, + 64814, + 64815, + 64816, + 64817, + 64818, + 64819, + 64820, + 64821, + 64822, + 64823, + 64824, + 64825, + 64826, + 64827, + 64828, + 64829, + 64848, + 64849, + 64850, + 64851, + 64852, + 64853, + 64854, + 64855, + 64856, + 64857, + 64858, + 64859, + 64860, + 64861, + 64862, + 64863, + 64864, + 64865, + 64866, + 64867, + 64868, + 64869, + 64870, + 64871, + 64872, + 64873, + 64874, + 64875, + 64876, + 64877, + 64878, + 64879, + 64880, + 64881, + 64882, + 64883, + 64884, + 64885, + 64886, + 64887, + 64888, + 64889, + 64890, + 64891, + 64892, + 64893, + 64894, + 64895, + 64896, + 64897, + 64898, + 64899, + 64900, + 64901, + 64902, + 64903, + 64904, + 64905, + 64906, + 64907, + 64908, + 64909, + 64910, + 64911, + 64914, + 64915, + 64916, + 64917, + 64918, + 64919, + 64920, + 64921, + 64922, + 64923, + 64924, + 64925, + 64926, + 64927, + 64928, + 64929, + 64930, + 64931, + 64932, + 64933, + 64934, + 64935, + 64936, + 64937, + 64938, + 64939, + 64940, + 64941, + 64942, + 64943, + 64944, + 64945, + 64946, + 64947, + 64948, + 64949, + 64950, + 64951, + 64952, + 64953, + 64954, + 64955, + 64956, + 64957, + 64958, + 64959, + 64960, + 64961, + 64962, + 64963, + 64964, + 64965, + 64966, + 64967, + 65008, + 65009, + 65010, + 65011, + 65012, + 65013, + 65014, + 65015, + 65016, + 65017, + 65018, + 65019, + 65136, + 65137, + 65138, + 65139, + 65140, + 65142, + 65143, + 65144, + 65145, + 65146, + 65147, + 65148, + 65149, + 65150, + 65151, + 65152, + 65153, + 65154, + 65155, + 65156, + 65157, + 65158, + 65159, + 65160, + 65161, + 65162, + 65163, + 65164, + 65165, + 65166, + 65167, + 65168, + 65169, + 65170, + 65171, + 65172, + 65173, + 65174, + 65175, + 65176, + 65177, + 65178, + 65179, + 65180, + 65181, + 65182, + 65183, + 65184, + 65185, + 65186, + 65187, + 65188, + 65189, + 65190, + 65191, + 65192, + 65193, + 65194, + 65195, + 65196, + 65197, + 65198, + 65199, + 65200, + 65201, + 65202, + 65203, + 65204, + 65205, + 65206, + 65207, + 65208, + 65209, + 65210, + 65211, + 65212, + 65213, + 65214, + 65215, + 65216, + 65217, + 65218, + 65219, + 65220, + 65221, + 65222, + 65223, + 65224, + 65225, + 65226, + 65227, + 65228, + 65229, + 65230, + 65231, + 65232, + 65233, + 65234, + 65235, + 65236, + 65237, + 65238, + 65239, + 65240, + 65241, + 65242, + 65243, + 65244, + 65245, + 65246, + 65247, + 65248, + 65249, + 65250, + 65251, + 65252, + 65253, + 65254, + 65255, + 65256, + 65257, + 65258, + 65259, + 65260, + 65261, + 65262, + 65263, + 65264, + 65265, + 65266, + 65267, + 65268, + 65269, + 65270, + 65271, + 65272, + 65273, + 65274, + 65275, + 65276, + 65313, + 65314, + 65315, + 65316, + 65317, + 65318, + 65319, + 65320, + 65321, + 65322, + 65323, + 65324, + 65325, + 65326, + 65327, + 65328, + 65329, + 65330, + 65331, + 65332, + 65333, + 65334, + 65335, + 65336, + 65337, + 65338, + 65345, + 65346, + 65347, + 65348, + 65349, + 65350, + 65351, + 65352, + 65353, + 65354, + 65355, + 65356, + 65357, + 65358, + 65359, + 65360, + 65361, + 65362, + 65363, + 65364, + 65365, + 65366, + 65367, + 65368, + 65369, + 65370, + 65382, + 65383, + 65384, + 65385, + 65386, + 65387, + 65388, + 65389, + 65390, + 65391, + 65392, + 65393, + 65394, + 65395, + 65396, + 65397, + 65398, + 65399, + 65400, + 65401, + 65402, + 65403, + 65404, + 65405, + 65406, + 65407, + 65408, + 65409, + 65410, + 65411, + 65412, + 65413, + 65414, + 65415, + 65416, + 65417, + 65418, + 65419, + 65420, + 65421, + 65422, + 65423, + 65424, + 65425, + 65426, + 65427, + 65428, + 65429, + 65430, + 65431, + 65432, + 65433, + 65434, + 65435, + 65436, + 65437, + 65438, + 65439, + 65440, + 65441, + 65442, + 65443, + 65444, + 65445, + 65446, + 65447, + 65448, + 65449, + 65450, + 65451, + 65452, + 65453, + 65454, + 65455, + 65456, + 65457, + 65458, + 65459, + 65460, + 65461, + 65462, + 65463, + 65464, + 65465, + 65466, + 65467, + 65468, + 65469, + 65470, + 65474, + 65475, + 65476, + 65477, + 65478, + 65479, + 65482, + 65483, + 65484, + 65485, + 65486, + 65487, + 65490, + 65491, + 65492, + 65493, + 65494, + 65495, + 65498, + 65499, + 65500 +]; + +},{}],4:[function(require,module,exports){ +// http://wiki.commonjs.org/wiki/Unit_Testing/1.0 +// +// THIS IS NOT TESTED NOR LIKELY TO WORK OUTSIDE V8! +// +// Originally from narwhal.js (http://narwhaljs.org) +// Copyright (c) 2009 Thomas Robinson <280north.com> +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the 'Software'), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +// when used in node, this will actually load the util module we depend on +// versus loading the builtin util module as happens otherwise +// this is a bug in node module loading as far as I am concerned +var util = require('util/'); + +var pSlice = Array.prototype.slice; +var hasOwn = Object.prototype.hasOwnProperty; + +// 1. The assert module provides functions that throw +// AssertionError's when particular conditions are not met. The +// assert module must conform to the following interface. + +var assert = module.exports = ok; + +// 2. The AssertionError is defined in assert. +// new assert.AssertionError({ message: message, +// actual: actual, +// expected: expected }) + +assert.AssertionError = function AssertionError(options) { + this.name = 'AssertionError'; + this.actual = options.actual; + this.expected = options.expected; + this.operator = options.operator; + if (options.message) { + this.message = options.message; + this.generatedMessage = false; + } else { + this.message = getMessage(this); + this.generatedMessage = true; + } + var stackStartFunction = options.stackStartFunction || fail; + + if (Error.captureStackTrace) { + Error.captureStackTrace(this, stackStartFunction); + } + else { + // non v8 browsers so we can have a stacktrace + var err = new Error(); + if (err.stack) { + var out = err.stack; + + // try to strip useless frames + var fn_name = stackStartFunction.name; + var idx = out.indexOf('\n' + fn_name); + if (idx >= 0) { + // once we have located the function frame + // we need to strip out everything before it (and its line) + var next_line = out.indexOf('\n', idx + 1); + out = out.substring(next_line + 1); + } + + this.stack = out; + } + } +}; + +// assert.AssertionError instanceof Error +util.inherits(assert.AssertionError, Error); + +function replacer(key, value) { + if (util.isUndefined(value)) { + return '' + value; + } + if (util.isNumber(value) && (isNaN(value) || !isFinite(value))) { + return value.toString(); + } + if (util.isFunction(value) || util.isRegExp(value)) { + return value.toString(); + } + return value; +} + +function truncate(s, n) { + if (util.isString(s)) { + return s.length < n ? s : s.slice(0, n); + } else { + return s; + } +} + +function getMessage(self) { + return truncate(JSON.stringify(self.actual, replacer), 128) + ' ' + + self.operator + ' ' + + truncate(JSON.stringify(self.expected, replacer), 128); +} + +// At present only the three keys mentioned above are used and +// understood by the spec. Implementations or sub modules can pass +// other keys to the AssertionError's constructor - they will be +// ignored. + +// 3. All of the following functions must throw an AssertionError +// when a corresponding condition is not met, with a message that +// may be undefined if not provided. All assertion methods provide +// both the actual and expected values to the assertion error for +// display purposes. + +function fail(actual, expected, message, operator, stackStartFunction) { + throw new assert.AssertionError({ + message: message, + actual: actual, + expected: expected, + operator: operator, + stackStartFunction: stackStartFunction + }); +} + +// EXTENSION! allows for well behaved errors defined elsewhere. +assert.fail = fail; + +// 4. Pure assertion tests whether a value is truthy, as determined +// by !!guard. +// assert.ok(guard, message_opt); +// This statement is equivalent to assert.equal(true, !!guard, +// message_opt);. To test strictly for the value true, use +// assert.strictEqual(true, guard, message_opt);. + +function ok(value, message) { + if (!value) fail(value, true, message, '==', assert.ok); +} +assert.ok = ok; + +// 5. The equality assertion tests shallow, coercive equality with +// ==. +// assert.equal(actual, expected, message_opt); + +assert.equal = function equal(actual, expected, message) { + if (actual != expected) fail(actual, expected, message, '==', assert.equal); +}; + +// 6. The non-equality assertion tests for whether two objects are not equal +// with != assert.notEqual(actual, expected, message_opt); + +assert.notEqual = function notEqual(actual, expected, message) { + if (actual == expected) { + fail(actual, expected, message, '!=', assert.notEqual); + } +}; + +// 7. The equivalence assertion tests a deep equality relation. +// assert.deepEqual(actual, expected, message_opt); + +assert.deepEqual = function deepEqual(actual, expected, message) { + if (!_deepEqual(actual, expected)) { + fail(actual, expected, message, 'deepEqual', assert.deepEqual); + } +}; + +function _deepEqual(actual, expected) { + // 7.1. All identical values are equivalent, as determined by ===. + if (actual === expected) { + return true; + + } else if (util.isBuffer(actual) && util.isBuffer(expected)) { + if (actual.length != expected.length) return false; + + for (var i = 0; i < actual.length; i++) { + if (actual[i] !== expected[i]) return false; + } + + return true; + + // 7.2. If the expected value is a Date object, the actual value is + // equivalent if it is also a Date object that refers to the same time. + } else if (util.isDate(actual) && util.isDate(expected)) { + return actual.getTime() === expected.getTime(); + + // 7.3 If the expected value is a RegExp object, the actual value is + // equivalent if it is also a RegExp object with the same source and + // properties (`global`, `multiline`, `lastIndex`, `ignoreCase`). + } else if (util.isRegExp(actual) && util.isRegExp(expected)) { + return actual.source === expected.source && + actual.global === expected.global && + actual.multiline === expected.multiline && + actual.lastIndex === expected.lastIndex && + actual.ignoreCase === expected.ignoreCase; + + // 7.4. Other pairs that do not both pass typeof value == 'object', + // equivalence is determined by ==. + } else if (!util.isObject(actual) && !util.isObject(expected)) { + return actual == expected; + + // 7.5 For all other Object pairs, including Array objects, equivalence is + // determined by having the same number of owned properties (as verified + // with Object.prototype.hasOwnProperty.call), the same set of keys + // (although not necessarily the same order), equivalent values for every + // corresponding key, and an identical 'prototype' property. Note: this + // accounts for both named and indexed properties on Arrays. + } else { + return objEquiv(actual, expected); + } +} + +function isArguments(object) { + return Object.prototype.toString.call(object) == '[object Arguments]'; +} + +function objEquiv(a, b) { + if (util.isNullOrUndefined(a) || util.isNullOrUndefined(b)) + return false; + // an identical 'prototype' property. + if (a.prototype !== b.prototype) return false; + //~~~I've managed to break Object.keys through screwy arguments passing. + // Converting to array solves the problem. + if (isArguments(a)) { + if (!isArguments(b)) { + return false; + } + a = pSlice.call(a); + b = pSlice.call(b); + return _deepEqual(a, b); + } + try { + var ka = objectKeys(a), + kb = objectKeys(b), + key, i; + } catch (e) {//happens when one is a string literal and the other isn't + return false; + } + // having the same number of owned properties (keys incorporates + // hasOwnProperty) + if (ka.length != kb.length) + return false; + //the same set of keys (although not necessarily the same order), + ka.sort(); + kb.sort(); + //~~~cheap key test + for (i = ka.length - 1; i >= 0; i--) { + if (ka[i] != kb[i]) + return false; + } + //equivalent values for every corresponding key, and + //~~~possibly expensive deep test + for (i = ka.length - 1; i >= 0; i--) { + key = ka[i]; + if (!_deepEqual(a[key], b[key])) return false; + } + return true; +} + +// 8. The non-equivalence assertion tests for any deep inequality. +// assert.notDeepEqual(actual, expected, message_opt); + +assert.notDeepEqual = function notDeepEqual(actual, expected, message) { + if (_deepEqual(actual, expected)) { + fail(actual, expected, message, 'notDeepEqual', assert.notDeepEqual); + } +}; + +// 9. The strict equality assertion tests strict equality, as determined by ===. +// assert.strictEqual(actual, expected, message_opt); + +assert.strictEqual = function strictEqual(actual, expected, message) { + if (actual !== expected) { + fail(actual, expected, message, '===', assert.strictEqual); + } +}; + +// 10. The strict non-equality assertion tests for strict inequality, as +// determined by !==. assert.notStrictEqual(actual, expected, message_opt); + +assert.notStrictEqual = function notStrictEqual(actual, expected, message) { + if (actual === expected) { + fail(actual, expected, message, '!==', assert.notStrictEqual); + } +}; + +function expectedException(actual, expected) { + if (!actual || !expected) { + return false; + } + + if (Object.prototype.toString.call(expected) == '[object RegExp]') { + return expected.test(actual); + } else if (actual instanceof expected) { + return true; + } else if (expected.call({}, actual) === true) { + return true; + } + + return false; +} + +function _throws(shouldThrow, block, expected, message) { + var actual; + + if (util.isString(expected)) { + message = expected; + expected = null; + } + + try { + block(); + } catch (e) { + actual = e; + } + + message = (expected && expected.name ? ' (' + expected.name + ').' : '.') + + (message ? ' ' + message : '.'); + + if (shouldThrow && !actual) { + fail(actual, expected, 'Missing expected exception' + message); + } + + if (!shouldThrow && expectedException(actual, expected)) { + fail(actual, expected, 'Got unwanted exception' + message); + } + + if ((shouldThrow && actual && expected && + !expectedException(actual, expected)) || (!shouldThrow && actual)) { + throw actual; + } +} + +// 11. Expected to throw an error: +// assert.throws(block, Error_opt, message_opt); + +assert.throws = function(block, /*optional*/error, /*optional*/message) { + _throws.apply(this, [true].concat(pSlice.call(arguments))); +}; + +// EXTENSION! This is annoying to write outside this module. +assert.doesNotThrow = function(block, /*optional*/message) { + _throws.apply(this, [false].concat(pSlice.call(arguments))); +}; + +assert.ifError = function(err) { if (err) {throw err;}}; + +var objectKeys = Object.keys || function (obj) { + var keys = []; + for (var key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) keys.push(key); + } + return keys; +}; + +},{"util/":9}],5:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +function EventEmitter() { + this._events = this._events || {}; + this._maxListeners = this._maxListeners || undefined; +} +module.exports = EventEmitter; + +// Backwards-compat with node 0.10.x +EventEmitter.EventEmitter = EventEmitter; + +EventEmitter.prototype._events = undefined; +EventEmitter.prototype._maxListeners = undefined; + +// By default EventEmitters will print a warning if more than 10 listeners are +// added to it. This is a useful default which helps finding memory leaks. +EventEmitter.defaultMaxListeners = 10; + +// Obviously not all Emitters should be limited to 10. This function allows +// that to be increased. Set to zero for unlimited. +EventEmitter.prototype.setMaxListeners = function(n) { + if (!isNumber(n) || n < 0 || isNaN(n)) + throw TypeError('n must be a positive number'); + this._maxListeners = n; + return this; +}; + +EventEmitter.prototype.emit = function(type) { + var er, handler, len, args, i, listeners; + + if (!this._events) + this._events = {}; + + // If there is no 'error' event listener then throw. + if (type === 'error') { + if (!this._events.error || + (isObject(this._events.error) && !this._events.error.length)) { + er = arguments[1]; + if (er instanceof Error) { + throw er; // Unhandled 'error' event + } else { + throw TypeError('Uncaught, unspecified "error" event.'); + } + return false; + } + } + + handler = this._events[type]; + + if (isUndefined(handler)) + return false; + + if (isFunction(handler)) { + switch (arguments.length) { + // fast cases + case 1: + handler.call(this); + break; + case 2: + handler.call(this, arguments[1]); + break; + case 3: + handler.call(this, arguments[1], arguments[2]); + break; + // slower + default: + len = arguments.length; + args = new Array(len - 1); + for (i = 1; i < len; i++) + args[i - 1] = arguments[i]; + handler.apply(this, args); + } + } else if (isObject(handler)) { + len = arguments.length; + args = new Array(len - 1); + for (i = 1; i < len; i++) + args[i - 1] = arguments[i]; + + listeners = handler.slice(); + len = listeners.length; + for (i = 0; i < len; i++) + listeners[i].apply(this, args); + } + + return true; +}; + +EventEmitter.prototype.addListener = function(type, listener) { + var m; + + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + if (!this._events) + this._events = {}; + + // To avoid recursion in the case that type === "newListener"! Before + // adding it to the listeners, first emit "newListener". + if (this._events.newListener) + this.emit('newListener', type, + isFunction(listener.listener) ? + listener.listener : listener); + + if (!this._events[type]) + // Optimize the case of one listener. Don't need the extra array object. + this._events[type] = listener; + else if (isObject(this._events[type])) + // If we've already got an array, just append. + this._events[type].push(listener); + else + // Adding the second element, need to change to array. + this._events[type] = [this._events[type], listener]; + + // Check for listener leak + if (isObject(this._events[type]) && !this._events[type].warned) { + var m; + if (!isUndefined(this._maxListeners)) { + m = this._maxListeners; + } else { + m = EventEmitter.defaultMaxListeners; + } + + if (m && m > 0 && this._events[type].length > m) { + this._events[type].warned = true; + console.error('(node) warning: possible EventEmitter memory ' + + 'leak detected. %d listeners added. ' + + 'Use emitter.setMaxListeners() to increase limit.', + this._events[type].length); + console.trace(); + } + } + + return this; +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +EventEmitter.prototype.once = function(type, listener) { + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + var fired = false; + + function g() { + this.removeListener(type, g); + + if (!fired) { + fired = true; + listener.apply(this, arguments); + } + } + + g.listener = listener; + this.on(type, g); + + return this; +}; + +// emits a 'removeListener' event iff the listener was removed +EventEmitter.prototype.removeListener = function(type, listener) { + var list, position, length, i; + + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + if (!this._events || !this._events[type]) + return this; + + list = this._events[type]; + length = list.length; + position = -1; + + if (list === listener || + (isFunction(list.listener) && list.listener === listener)) { + delete this._events[type]; + if (this._events.removeListener) + this.emit('removeListener', type, listener); + + } else if (isObject(list)) { + for (i = length; i-- > 0;) { + if (list[i] === listener || + (list[i].listener && list[i].listener === listener)) { + position = i; + break; + } + } + + if (position < 0) + return this; + + if (list.length === 1) { + list.length = 0; + delete this._events[type]; + } else { + list.splice(position, 1); + } + + if (this._events.removeListener) + this.emit('removeListener', type, listener); + } + + return this; +}; + +EventEmitter.prototype.removeAllListeners = function(type) { + var key, listeners; + + if (!this._events) + return this; + + // not listening for removeListener, no need to emit + if (!this._events.removeListener) { + if (arguments.length === 0) + this._events = {}; + else if (this._events[type]) + delete this._events[type]; + return this; + } + + // emit removeListener for all listeners on all events + if (arguments.length === 0) { + for (key in this._events) { + if (key === 'removeListener') continue; + this.removeAllListeners(key); + } + this.removeAllListeners('removeListener'); + this._events = {}; + return this; + } + + listeners = this._events[type]; + + if (isFunction(listeners)) { + this.removeListener(type, listeners); + } else { + // LIFO order + while (listeners.length) + this.removeListener(type, listeners[listeners.length - 1]); + } + delete this._events[type]; + + return this; +}; + +EventEmitter.prototype.listeners = function(type) { + var ret; + if (!this._events || !this._events[type]) + ret = []; + else if (isFunction(this._events[type])) + ret = [this._events[type]]; + else + ret = this._events[type].slice(); + return ret; +}; + +EventEmitter.listenerCount = function(emitter, type) { + var ret; + if (!emitter._events || !emitter._events[type]) + ret = 0; + else if (isFunction(emitter._events[type])) + ret = 1; + else + ret = emitter._events[type].length; + return ret; +}; + +function isFunction(arg) { + return typeof arg === 'function'; +} + +function isNumber(arg) { + return typeof arg === 'number'; +} + +function isObject(arg) { + return typeof arg === 'object' && arg !== null; +} + +function isUndefined(arg) { + return arg === void 0; +} + +},{}],6:[function(require,module,exports){ +if (typeof Object.create === 'function') { + // implementation from standard node.js 'util' module + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + ctor.prototype = Object.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }); + }; +} else { + // old school shim for old browsers + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + var TempCtor = function () {} + TempCtor.prototype = superCtor.prototype + ctor.prototype = new TempCtor() + ctor.prototype.constructor = ctor + } +} + +},{}],7:[function(require,module,exports){ +// shim for using process in browser + +var process = module.exports = {}; + +process.nextTick = (function () { + var canSetImmediate = typeof window !== 'undefined' + && window.setImmediate; + var canPost = typeof window !== 'undefined' + && window.postMessage && window.addEventListener + ; + + if (canSetImmediate) { + return function (f) { return window.setImmediate(f) }; + } + + if (canPost) { + var queue = []; + window.addEventListener('message', function (ev) { + var source = ev.source; + if ((source === window || source === null) && ev.data === 'process-tick') { + ev.stopPropagation(); + if (queue.length > 0) { + var fn = queue.shift(); + fn(); + } + } + }, true); + + return function nextTick(fn) { + queue.push(fn); + window.postMessage('process-tick', '*'); + }; + } + + return function nextTick(fn) { + setTimeout(fn, 0); + }; +})(); + +process.title = 'browser'; +process.browser = true; +process.env = {}; +process.argv = []; + +process.binding = function (name) { + throw new Error('process.binding is not supported'); +} + +// TODO(shtylman) +process.cwd = function () { return '/' }; +process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); +}; + +},{}],8:[function(require,module,exports){ +module.exports = function isBuffer(arg) { + return arg && typeof arg === 'object' + && typeof arg.copy === 'function' + && typeof arg.fill === 'function' + && typeof arg.readUInt8 === 'function'; +} +},{}],9:[function(require,module,exports){ +var process=require("__browserify_process"),global=typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {};// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var formatRegExp = /%[sdj%]/g; +exports.format = function(f) { + if (!isString(f)) { + var objects = []; + for (var i = 0; i < arguments.length; i++) { + objects.push(inspect(arguments[i])); + } + return objects.join(' '); + } + + var i = 1; + var args = arguments; + var len = args.length; + var str = String(f).replace(formatRegExp, function(x) { + if (x === '%%') return '%'; + if (i >= len) return x; + switch (x) { + case '%s': return String(args[i++]); + case '%d': return Number(args[i++]); + case '%j': + try { + return JSON.stringify(args[i++]); + } catch (_) { + return '[Circular]'; + } + default: + return x; + } + }); + for (var x = args[i]; i < len; x = args[++i]) { + if (isNull(x) || !isObject(x)) { + str += ' ' + x; + } else { + str += ' ' + inspect(x); + } + } + return str; +}; + + +// Mark that a method should not be used. +// Returns a modified function which warns once by default. +// If --no-deprecation is set, then it is a no-op. +exports.deprecate = function(fn, msg) { + // Allow for deprecating things in the process of starting up. + if (isUndefined(global.process)) { + return function() { + return exports.deprecate(fn, msg).apply(this, arguments); + }; + } + + if (process.noDeprecation === true) { + return fn; + } + + var warned = false; + function deprecated() { + if (!warned) { + if (process.throwDeprecation) { + throw new Error(msg); + } else if (process.traceDeprecation) { + console.trace(msg); + } else { + console.error(msg); + } + warned = true; + } + return fn.apply(this, arguments); + } + + return deprecated; +}; + + +var debugs = {}; +var debugEnviron; +exports.debuglog = function(set) { + if (isUndefined(debugEnviron)) + debugEnviron = process.env.NODE_DEBUG || ''; + set = set.toUpperCase(); + if (!debugs[set]) { + if (new RegExp('\\b' + set + '\\b', 'i').test(debugEnviron)) { + var pid = process.pid; + debugs[set] = function() { + var msg = exports.format.apply(exports, arguments); + console.error('%s %d: %s', set, pid, msg); + }; + } else { + debugs[set] = function() {}; + } + } + return debugs[set]; +}; + + +/** + * Echos the value of a value. Trys to print the value out + * in the best way possible given the different types. + * + * @param {Object} obj The object to print out. + * @param {Object} opts Optional options object that alters the output. + */ +/* legacy: obj, showHidden, depth, colors*/ +function inspect(obj, opts) { + // default options + var ctx = { + seen: [], + stylize: stylizeNoColor + }; + // legacy... + if (arguments.length >= 3) ctx.depth = arguments[2]; + if (arguments.length >= 4) ctx.colors = arguments[3]; + if (isBoolean(opts)) { + // legacy... + ctx.showHidden = opts; + } else if (opts) { + // got an "options" object + exports._extend(ctx, opts); + } + // set default options + if (isUndefined(ctx.showHidden)) ctx.showHidden = false; + if (isUndefined(ctx.depth)) ctx.depth = 2; + if (isUndefined(ctx.colors)) ctx.colors = false; + if (isUndefined(ctx.customInspect)) ctx.customInspect = true; + if (ctx.colors) ctx.stylize = stylizeWithColor; + return formatValue(ctx, obj, ctx.depth); +} +exports.inspect = inspect; + + +// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics +inspect.colors = { + 'bold' : [1, 22], + 'italic' : [3, 23], + 'underline' : [4, 24], + 'inverse' : [7, 27], + 'white' : [37, 39], + 'grey' : [90, 39], + 'black' : [30, 39], + 'blue' : [34, 39], + 'cyan' : [36, 39], + 'green' : [32, 39], + 'magenta' : [35, 39], + 'red' : [31, 39], + 'yellow' : [33, 39] +}; + +// Don't use 'blue' not visible on cmd.exe +inspect.styles = { + 'special': 'cyan', + 'number': 'yellow', + 'boolean': 'yellow', + 'undefined': 'grey', + 'null': 'bold', + 'string': 'green', + 'date': 'magenta', + // "name": intentionally not styling + 'regexp': 'red' +}; + + +function stylizeWithColor(str, styleType) { + var style = inspect.styles[styleType]; + + if (style) { + return '\u001b[' + inspect.colors[style][0] + 'm' + str + + '\u001b[' + inspect.colors[style][1] + 'm'; + } else { + return str; + } +} + + +function stylizeNoColor(str, styleType) { + return str; +} + + +function arrayToHash(array) { + var hash = {}; + + array.forEach(function(val, idx) { + hash[val] = true; + }); + + return hash; +} + + +function formatValue(ctx, value, recurseTimes) { + // Provide a hook for user-specified inspect functions. + // Check that value is an object with an inspect function on it + if (ctx.customInspect && + value && + isFunction(value.inspect) && + // Filter out the util module, it's inspect function is special + value.inspect !== exports.inspect && + // Also filter out any prototype objects using the circular check. + !(value.constructor && value.constructor.prototype === value)) { + var ret = value.inspect(recurseTimes, ctx); + if (!isString(ret)) { + ret = formatValue(ctx, ret, recurseTimes); + } + return ret; + } + + // Primitive types cannot have properties + var primitive = formatPrimitive(ctx, value); + if (primitive) { + return primitive; + } + + // Look up the keys of the object. + var keys = Object.keys(value); + var visibleKeys = arrayToHash(keys); + + if (ctx.showHidden) { + keys = Object.getOwnPropertyNames(value); + } + + // IE doesn't make error fields non-enumerable + // http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx + if (isError(value) + && (keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0)) { + return formatError(value); + } + + // Some type of object without properties can be shortcutted. + if (keys.length === 0) { + if (isFunction(value)) { + var name = value.name ? ': ' + value.name : ''; + return ctx.stylize('[Function' + name + ']', 'special'); + } + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } + if (isDate(value)) { + return ctx.stylize(Date.prototype.toString.call(value), 'date'); + } + if (isError(value)) { + return formatError(value); + } + } + + var base = '', array = false, braces = ['{', '}']; + + // Make Array say that they are Array + if (isArray(value)) { + array = true; + braces = ['[', ']']; + } + + // Make functions say that they are functions + if (isFunction(value)) { + var n = value.name ? ': ' + value.name : ''; + base = ' [Function' + n + ']'; + } + + // Make RegExps say that they are RegExps + if (isRegExp(value)) { + base = ' ' + RegExp.prototype.toString.call(value); + } + + // Make dates with properties first say the date + if (isDate(value)) { + base = ' ' + Date.prototype.toUTCString.call(value); + } + + // Make error with message first say the error + if (isError(value)) { + base = ' ' + formatError(value); + } + + if (keys.length === 0 && (!array || value.length == 0)) { + return braces[0] + base + braces[1]; + } + + if (recurseTimes < 0) { + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } else { + return ctx.stylize('[Object]', 'special'); + } + } + + ctx.seen.push(value); + + var output; + if (array) { + output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); + } else { + output = keys.map(function(key) { + return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array); + }); + } + + ctx.seen.pop(); + + return reduceToSingleString(output, base, braces); +} + + +function formatPrimitive(ctx, value) { + if (isUndefined(value)) + return ctx.stylize('undefined', 'undefined'); + if (isString(value)) { + var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') + .replace(/'/g, "\\'") + .replace(/\\"/g, '"') + '\''; + return ctx.stylize(simple, 'string'); + } + if (isNumber(value)) + return ctx.stylize('' + value, 'number'); + if (isBoolean(value)) + return ctx.stylize('' + value, 'boolean'); + // For some reason typeof null is "object", so special case here. + if (isNull(value)) + return ctx.stylize('null', 'null'); +} + + +function formatError(value) { + return '[' + Error.prototype.toString.call(value) + ']'; +} + + +function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { + var output = []; + for (var i = 0, l = value.length; i < l; ++i) { + if (hasOwnProperty(value, String(i))) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + String(i), true)); + } else { + output.push(''); + } + } + keys.forEach(function(key) { + if (!key.match(/^\d+$/)) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + key, true)); + } + }); + return output; +} + + +function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { + var name, str, desc; + desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] }; + if (desc.get) { + if (desc.set) { + str = ctx.stylize('[Getter/Setter]', 'special'); + } else { + str = ctx.stylize('[Getter]', 'special'); + } + } else { + if (desc.set) { + str = ctx.stylize('[Setter]', 'special'); + } + } + if (!hasOwnProperty(visibleKeys, key)) { + name = '[' + key + ']'; + } + if (!str) { + if (ctx.seen.indexOf(desc.value) < 0) { + if (isNull(recurseTimes)) { + str = formatValue(ctx, desc.value, null); + } else { + str = formatValue(ctx, desc.value, recurseTimes - 1); + } + if (str.indexOf('\n') > -1) { + if (array) { + str = str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n').substr(2); + } else { + str = '\n' + str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n'); + } + } + } else { + str = ctx.stylize('[Circular]', 'special'); + } + } + if (isUndefined(name)) { + if (array && key.match(/^\d+$/)) { + return str; + } + name = JSON.stringify('' + key); + if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { + name = name.substr(1, name.length - 2); + name = ctx.stylize(name, 'name'); + } else { + name = name.replace(/'/g, "\\'") + .replace(/\\"/g, '"') + .replace(/(^"|"$)/g, "'"); + name = ctx.stylize(name, 'string'); + } + } + + return name + ': ' + str; +} + + +function reduceToSingleString(output, base, braces) { + var numLinesEst = 0; + var length = output.reduce(function(prev, cur) { + numLinesEst++; + if (cur.indexOf('\n') >= 0) numLinesEst++; + return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1; + }, 0); + + if (length > 60) { + return braces[0] + + (base === '' ? '' : base + '\n ') + + ' ' + + output.join(',\n ') + + ' ' + + braces[1]; + } + + return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; +} + + +// NOTE: These type checking functions intentionally don't use `instanceof` +// because it is fragile and can be easily faked with `Object.create()`. +function isArray(ar) { + return Array.isArray(ar); +} +exports.isArray = isArray; + +function isBoolean(arg) { + return typeof arg === 'boolean'; +} +exports.isBoolean = isBoolean; + +function isNull(arg) { + return arg === null; +} +exports.isNull = isNull; + +function isNullOrUndefined(arg) { + return arg == null; +} +exports.isNullOrUndefined = isNullOrUndefined; + +function isNumber(arg) { + return typeof arg === 'number'; +} +exports.isNumber = isNumber; + +function isString(arg) { + return typeof arg === 'string'; +} +exports.isString = isString; + +function isSymbol(arg) { + return typeof arg === 'symbol'; +} +exports.isSymbol = isSymbol; + +function isUndefined(arg) { + return arg === void 0; +} +exports.isUndefined = isUndefined; + +function isRegExp(re) { + return isObject(re) && objectToString(re) === '[object RegExp]'; +} +exports.isRegExp = isRegExp; + +function isObject(arg) { + return typeof arg === 'object' && arg !== null; +} +exports.isObject = isObject; + +function isDate(d) { + return isObject(d) && objectToString(d) === '[object Date]'; +} +exports.isDate = isDate; + +function isError(e) { + return isObject(e) && + (objectToString(e) === '[object Error]' || e instanceof Error); +} +exports.isError = isError; + +function isFunction(arg) { + return typeof arg === 'function'; +} +exports.isFunction = isFunction; + +function isPrimitive(arg) { + return arg === null || + typeof arg === 'boolean' || + typeof arg === 'number' || + typeof arg === 'string' || + typeof arg === 'symbol' || // ES6 symbol + typeof arg === 'undefined'; +} +exports.isPrimitive = isPrimitive; + +exports.isBuffer = require('./support/isBuffer'); + +function objectToString(o) { + return Object.prototype.toString.call(o); +} + + +function pad(n) { + return n < 10 ? '0' + n.toString(10) : n.toString(10); +} + + +var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', + 'Oct', 'Nov', 'Dec']; + +// 26 Feb 16:19:34 +function timestamp() { + var d = new Date(); + var time = [pad(d.getHours()), + pad(d.getMinutes()), + pad(d.getSeconds())].join(':'); + return [d.getDate(), months[d.getMonth()], time].join(' '); +} + + +// log is just a thin wrapper to console.log that prepends a timestamp +exports.log = function() { + console.log('%s - %s', timestamp(), exports.format.apply(exports, arguments)); +}; + + +/** + * Inherit the prototype methods from one constructor into another. + * + * The Function.prototype.inherits from lang.js rewritten as a standalone + * function (not on Function.prototype). NOTE: If this file is to be loaded + * during bootstrapping this function needs to be rewritten using some native + * functions as prototype setup using normal JavaScript does not work as + * expected during bootstrapping (see mirror.js in r114903). + * + * @param {function} ctor Constructor function which needs to inherit the + * prototype. + * @param {function} superCtor Constructor function to inherit prototype from. + */ +exports.inherits = require('inherits'); + +exports._extend = function(origin, add) { + // Don't do anything if add isn't an object + if (!add || !isObject(add)) return origin; + + var keys = Object.keys(add); + var i = keys.length; + while (i--) { + origin[keys[i]] = add[keys[i]]; + } + return origin; +}; + +function hasOwnProperty(obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop); +} + +},{"./support/isBuffer":8,"__browserify_process":7,"inherits":6}],10:[function(require,module,exports){ +var global=typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {};/*global window, global*/ +var util = require("util") +var assert = require("assert") + +var slice = Array.prototype.slice +var console +var times = {} + +if (typeof global !== "undefined" && global.console) { + console = global.console +} else if (typeof window !== "undefined" && window.console) { + console = window.console +} else { + console = window.console = {} +} + +var functions = [ + [log, "log"] + , [info, "info"] + , [warn, "warn"] + , [error, "error"] + , [time, "time"] + , [timeEnd, "timeEnd"] + , [trace, "trace"] + , [dir, "dir"] + , [assert, "assert"] +] + +for (var i = 0; i < functions.length; i++) { + var tuple = functions[i] + var f = tuple[0] + var name = tuple[1] + + if (!console[name]) { + console[name] = f + } +} + +module.exports = console + +function log() {} + +function info() { + console.log.apply(console, arguments) +} + +function warn() { + console.log.apply(console, arguments) +} + +function error() { + console.warn.apply(console, arguments) +} + +function time(label) { + times[label] = Date.now() +} + +function timeEnd(label) { + var time = times[label] + if (!time) { + throw new Error("No such label: " + label) + } + + var duration = Date.now() - time + console.log(label + ": " + duration + "ms") +} + +function trace() { + var err = new Error() + err.name = "Trace" + err.message = util.format.apply(null, arguments) + console.error(err.stack) +} + +function dir(object) { + console.log(util.inspect(object) + "\n") +} + +function assert(expression) { + if (!expression) { + var arr = slice.call(arguments, 1) + assert.ok(false, util.format.apply(null, arr)) + } +} + +},{"assert":4,"util":9}],11:[function(require,module,exports){ +// Underscore.js 1.4.4 +// http://underscorejs.org +// (c) 2009-2013 Jeremy Ashkenas, DocumentCloud Inc. +// Underscore may be freely distributed under the MIT license. + +(function() { + + // Baseline setup + // -------------- + + // Establish the root object, `window` in the browser, or `global` on the server. + var root = this; + + // Save the previous value of the `_` variable. + var previousUnderscore = root._; + + // Establish the object that gets returned to break out of a loop iteration. + var breaker = {}; + + // Save bytes in the minified (but not gzipped) version: + var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; + + // Create quick reference variables for speed access to core prototypes. + var push = ArrayProto.push, + slice = ArrayProto.slice, + concat = ArrayProto.concat, + toString = ObjProto.toString, + hasOwnProperty = ObjProto.hasOwnProperty; + + // All **ECMAScript 5** native function implementations that we hope to use + // are declared here. + var + nativeForEach = ArrayProto.forEach, + nativeMap = ArrayProto.map, + nativeReduce = ArrayProto.reduce, + nativeReduceRight = ArrayProto.reduceRight, + nativeFilter = ArrayProto.filter, + nativeEvery = ArrayProto.every, + nativeSome = ArrayProto.some, + nativeIndexOf = ArrayProto.indexOf, + nativeLastIndexOf = ArrayProto.lastIndexOf, + nativeIsArray = Array.isArray, + nativeKeys = Object.keys, + nativeBind = FuncProto.bind; + + // Create a safe reference to the Underscore object for use below. + var _ = function(obj) { + if (obj instanceof _) return obj; + if (!(this instanceof _)) return new _(obj); + this._wrapped = obj; + }; + + // Export the Underscore object for **Node.js**, with + // backwards-compatibility for the old `require()` API. If we're in + // the browser, add `_` as a global object via a string identifier, + // for Closure Compiler "advanced" mode. + if (typeof exports !== 'undefined') { + if (typeof module !== 'undefined' && module.exports) { + exports = module.exports = _; + } + exports._ = _; + } else { + root._ = _; + } + + // Current version. + _.VERSION = '1.4.4'; + + // Collection Functions + // -------------------- + + // The cornerstone, an `each` implementation, aka `forEach`. + // Handles objects with the built-in `forEach`, arrays, and raw objects. + // Delegates to **ECMAScript 5**'s native `forEach` if available. + var each = _.each = _.forEach = function(obj, iterator, context) { + if (obj == null) return; + if (nativeForEach && obj.forEach === nativeForEach) { + obj.forEach(iterator, context); + } else if (obj.length === +obj.length) { + for (var i = 0, l = obj.length; i < l; i++) { + if (iterator.call(context, obj[i], i, obj) === breaker) return; + } + } else { + for (var key in obj) { + if (_.has(obj, key)) { + if (iterator.call(context, obj[key], key, obj) === breaker) return; + } + } + } + }; + + // Return the results of applying the iterator to each element. + // Delegates to **ECMAScript 5**'s native `map` if available. + _.map = _.collect = function(obj, iterator, context) { + var results = []; + if (obj == null) return results; + if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); + each(obj, function(value, index, list) { + results[results.length] = iterator.call(context, value, index, list); + }); + return results; + }; + + var reduceError = 'Reduce of empty array with no initial value'; + + // **Reduce** builds up a single result from a list of values, aka `inject`, + // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. + _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { + var initial = arguments.length > 2; + if (obj == null) obj = []; + if (nativeReduce && obj.reduce === nativeReduce) { + if (context) iterator = _.bind(iterator, context); + return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); + } + each(obj, function(value, index, list) { + if (!initial) { + memo = value; + initial = true; + } else { + memo = iterator.call(context, memo, value, index, list); + } + }); + if (!initial) throw new TypeError(reduceError); + return memo; + }; + + // The right-associative version of reduce, also known as `foldr`. + // Delegates to **ECMAScript 5**'s native `reduceRight` if available. + _.reduceRight = _.foldr = function(obj, iterator, memo, context) { + var initial = arguments.length > 2; + if (obj == null) obj = []; + if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { + if (context) iterator = _.bind(iterator, context); + return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); + } + var length = obj.length; + if (length !== +length) { + var keys = _.keys(obj); + length = keys.length; + } + each(obj, function(value, index, list) { + index = keys ? keys[--length] : --length; + if (!initial) { + memo = obj[index]; + initial = true; + } else { + memo = iterator.call(context, memo, obj[index], index, list); + } + }); + if (!initial) throw new TypeError(reduceError); + return memo; + }; + + // Return the first value which passes a truth test. Aliased as `detect`. + _.find = _.detect = function(obj, iterator, context) { + var result; + any(obj, function(value, index, list) { + if (iterator.call(context, value, index, list)) { + result = value; + return true; + } + }); + return result; + }; + + // Return all the elements that pass a truth test. + // Delegates to **ECMAScript 5**'s native `filter` if available. + // Aliased as `select`. + _.filter = _.select = function(obj, iterator, context) { + var results = []; + if (obj == null) return results; + if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); + each(obj, function(value, index, list) { + if (iterator.call(context, value, index, list)) results[results.length] = value; + }); + return results; + }; + + // Return all the elements for which a truth test fails. + _.reject = function(obj, iterator, context) { + return _.filter(obj, function(value, index, list) { + return !iterator.call(context, value, index, list); + }, context); + }; + + // Determine whether all of the elements match a truth test. + // Delegates to **ECMAScript 5**'s native `every` if available. + // Aliased as `all`. + _.every = _.all = function(obj, iterator, context) { + iterator || (iterator = _.identity); + var result = true; + if (obj == null) return result; + if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); + each(obj, function(value, index, list) { + if (!(result = result && iterator.call(context, value, index, list))) return breaker; + }); + return !!result; + }; + + // Determine if at least one element in the object matches a truth test. + // Delegates to **ECMAScript 5**'s native `some` if available. + // Aliased as `any`. + var any = _.some = _.any = function(obj, iterator, context) { + iterator || (iterator = _.identity); + var result = false; + if (obj == null) return result; + if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); + each(obj, function(value, index, list) { + if (result || (result = iterator.call(context, value, index, list))) return breaker; + }); + return !!result; + }; + + // Determine if the array or object contains a given value (using `===`). + // Aliased as `include`. + _.contains = _.include = function(obj, target) { + if (obj == null) return false; + if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; + return any(obj, function(value) { + return value === target; + }); + }; + + // Invoke a method (with arguments) on every item in a collection. + _.invoke = function(obj, method) { + var args = slice.call(arguments, 2); + var isFunc = _.isFunction(method); + return _.map(obj, function(value) { + return (isFunc ? method : value[method]).apply(value, args); + }); + }; + + // Convenience version of a common use case of `map`: fetching a property. + _.pluck = function(obj, key) { + return _.map(obj, function(value){ return value[key]; }); + }; + + // Convenience version of a common use case of `filter`: selecting only objects + // containing specific `key:value` pairs. + _.where = function(obj, attrs, first) { + if (_.isEmpty(attrs)) return first ? null : []; + return _[first ? 'find' : 'filter'](obj, function(value) { + for (var key in attrs) { + if (attrs[key] !== value[key]) return false; + } + return true; + }); + }; + + // Convenience version of a common use case of `find`: getting the first object + // containing specific `key:value` pairs. + _.findWhere = function(obj, attrs) { + return _.where(obj, attrs, true); + }; + + // Return the maximum element or (element-based computation). + // Can't optimize arrays of integers longer than 65,535 elements. + // See: https://bugs.webkit.org/show_bug.cgi?id=80797 + _.max = function(obj, iterator, context) { + if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { + return Math.max.apply(Math, obj); + } + if (!iterator && _.isEmpty(obj)) return -Infinity; + var result = {computed : -Infinity, value: -Infinity}; + each(obj, function(value, index, list) { + var computed = iterator ? iterator.call(context, value, index, list) : value; + computed >= result.computed && (result = {value : value, computed : computed}); + }); + return result.value; + }; + + // Return the minimum element (or element-based computation). + _.min = function(obj, iterator, context) { + if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { + return Math.min.apply(Math, obj); + } + if (!iterator && _.isEmpty(obj)) return Infinity; + var result = {computed : Infinity, value: Infinity}; + each(obj, function(value, index, list) { + var computed = iterator ? iterator.call(context, value, index, list) : value; + computed < result.computed && (result = {value : value, computed : computed}); + }); + return result.value; + }; + + // Shuffle an array. + _.shuffle = function(obj) { + var rand; + var index = 0; + var shuffled = []; + each(obj, function(value) { + rand = _.random(index++); + shuffled[index - 1] = shuffled[rand]; + shuffled[rand] = value; + }); + return shuffled; + }; + + // An internal function to generate lookup iterators. + var lookupIterator = function(value) { + return _.isFunction(value) ? value : function(obj){ return obj[value]; }; + }; + + // Sort the object's values by a criterion produced by an iterator. + _.sortBy = function(obj, value, context) { + var iterator = lookupIterator(value); + return _.pluck(_.map(obj, function(value, index, list) { + return { + value : value, + index : index, + criteria : iterator.call(context, value, index, list) + }; + }).sort(function(left, right) { + var a = left.criteria; + var b = right.criteria; + if (a !== b) { + if (a > b || a === void 0) return 1; + if (a < b || b === void 0) return -1; + } + return left.index < right.index ? -1 : 1; + }), 'value'); + }; + + // An internal function used for aggregate "group by" operations. + var group = function(obj, value, context, behavior) { + var result = {}; + var iterator = lookupIterator(value || _.identity); + each(obj, function(value, index) { + var key = iterator.call(context, value, index, obj); + behavior(result, key, value); + }); + return result; + }; + + // Groups the object's values by a criterion. Pass either a string attribute + // to group by, or a function that returns the criterion. + _.groupBy = function(obj, value, context) { + return group(obj, value, context, function(result, key, value) { + (_.has(result, key) ? result[key] : (result[key] = [])).push(value); + }); + }; + + // Counts instances of an object that group by a certain criterion. Pass + // either a string attribute to count by, or a function that returns the + // criterion. + _.countBy = function(obj, value, context) { + return group(obj, value, context, function(result, key) { + if (!_.has(result, key)) result[key] = 0; + result[key]++; + }); + }; + + // Use a comparator function to figure out the smallest index at which + // an object should be inserted so as to maintain order. Uses binary search. + _.sortedIndex = function(array, obj, iterator, context) { + iterator = iterator == null ? _.identity : lookupIterator(iterator); + var value = iterator.call(context, obj); + var low = 0, high = array.length; + while (low < high) { + var mid = (low + high) >>> 1; + iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid; + } + return low; + }; + + // Safely convert anything iterable into a real, live array. + _.toArray = function(obj) { + if (!obj) return []; + if (_.isArray(obj)) return slice.call(obj); + if (obj.length === +obj.length) return _.map(obj, _.identity); + return _.values(obj); + }; + + // Return the number of elements in an object. + _.size = function(obj) { + if (obj == null) return 0; + return (obj.length === +obj.length) ? obj.length : _.keys(obj).length; + }; + + // Array Functions + // --------------- + + // Get the first element of an array. Passing **n** will return the first N + // values in the array. Aliased as `head` and `take`. The **guard** check + // allows it to work with `_.map`. + _.first = _.head = _.take = function(array, n, guard) { + if (array == null) return void 0; + return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; + }; + + // Returns everything but the last entry of the array. Especially useful on + // the arguments object. Passing **n** will return all the values in + // the array, excluding the last N. The **guard** check allows it to work with + // `_.map`. + _.initial = function(array, n, guard) { + return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); + }; + + // Get the last element of an array. Passing **n** will return the last N + // values in the array. The **guard** check allows it to work with `_.map`. + _.last = function(array, n, guard) { + if (array == null) return void 0; + if ((n != null) && !guard) { + return slice.call(array, Math.max(array.length - n, 0)); + } else { + return array[array.length - 1]; + } + }; + + // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. + // Especially useful on the arguments object. Passing an **n** will return + // the rest N values in the array. The **guard** + // check allows it to work with `_.map`. + _.rest = _.tail = _.drop = function(array, n, guard) { + return slice.call(array, (n == null) || guard ? 1 : n); + }; + + // Trim out all falsy values from an array. + _.compact = function(array) { + return _.filter(array, _.identity); + }; + + // Internal implementation of a recursive `flatten` function. + var flatten = function(input, shallow, output) { + each(input, function(value) { + if (_.isArray(value)) { + shallow ? push.apply(output, value) : flatten(value, shallow, output); + } else { + output.push(value); + } + }); + return output; + }; + + // Return a completely flattened version of an array. + _.flatten = function(array, shallow) { + return flatten(array, shallow, []); + }; + + // Return a version of the array that does not contain the specified value(s). + _.without = function(array) { + return _.difference(array, slice.call(arguments, 1)); + }; + + // Produce a duplicate-free version of the array. If the array has already + // been sorted, you have the option of using a faster algorithm. + // Aliased as `unique`. + _.uniq = _.unique = function(array, isSorted, iterator, context) { + if (_.isFunction(isSorted)) { + context = iterator; + iterator = isSorted; + isSorted = false; + } + var initial = iterator ? _.map(array, iterator, context) : array; + var results = []; + var seen = []; + each(initial, function(value, index) { + if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) { + seen.push(value); + results.push(array[index]); + } + }); + return results; + }; + + // Produce an array that contains the union: each distinct element from all of + // the passed-in arrays. + _.union = function() { + return _.uniq(concat.apply(ArrayProto, arguments)); + }; + + // Produce an array that contains every item shared between all the + // passed-in arrays. + _.intersection = function(array) { + var rest = slice.call(arguments, 1); + return _.filter(_.uniq(array), function(item) { + return _.every(rest, function(other) { + return _.indexOf(other, item) >= 0; + }); + }); + }; + + // Take the difference between one array and a number of other arrays. + // Only the elements present in just the first array will remain. + _.difference = function(array) { + var rest = concat.apply(ArrayProto, slice.call(arguments, 1)); + return _.filter(array, function(value){ return !_.contains(rest, value); }); + }; + + // Zip together multiple lists into a single array -- elements that share + // an index go together. + _.zip = function() { + var args = slice.call(arguments); + var length = _.max(_.pluck(args, 'length')); + var results = new Array(length); + for (var i = 0; i < length; i++) { + results[i] = _.pluck(args, "" + i); + } + return results; + }; + + // Converts lists into objects. Pass either a single array of `[key, value]` + // pairs, or two parallel arrays of the same length -- one of keys, and one of + // the corresponding values. + _.object = function(list, values) { + if (list == null) return {}; + var result = {}; + for (var i = 0, l = list.length; i < l; i++) { + if (values) { + result[list[i]] = values[i]; + } else { + result[list[i][0]] = list[i][1]; + } + } + return result; + }; + + // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), + // we need this function. Return the position of the first occurrence of an + // item in an array, or -1 if the item is not included in the array. + // Delegates to **ECMAScript 5**'s native `indexOf` if available. + // If the array is large and already in sort order, pass `true` + // for **isSorted** to use binary search. + _.indexOf = function(array, item, isSorted) { + if (array == null) return -1; + var i = 0, l = array.length; + if (isSorted) { + if (typeof isSorted == 'number') { + i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted); + } else { + i = _.sortedIndex(array, item); + return array[i] === item ? i : -1; + } + } + if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted); + for (; i < l; i++) if (array[i] === item) return i; + return -1; + }; + + // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. + _.lastIndexOf = function(array, item, from) { + if (array == null) return -1; + var hasIndex = from != null; + if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) { + return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item); + } + var i = (hasIndex ? from : array.length); + while (i--) if (array[i] === item) return i; + return -1; + }; + + // Generate an integer Array containing an arithmetic progression. A port of + // the native Python `range()` function. See + // [the Python documentation](http://docs.python.org/library/functions.html#range). + _.range = function(start, stop, step) { + if (arguments.length <= 1) { + stop = start || 0; + start = 0; + } + step = arguments[2] || 1; + + var len = Math.max(Math.ceil((stop - start) / step), 0); + var idx = 0; + var range = new Array(len); + + while(idx < len) { + range[idx++] = start; + start += step; + } + + return range; + }; + + // Function (ahem) Functions + // ------------------ + + // Create a function bound to a given object (assigning `this`, and arguments, + // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if + // available. + _.bind = function(func, context) { + if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); + var args = slice.call(arguments, 2); + return function() { + return func.apply(context, args.concat(slice.call(arguments))); + }; + }; + + // Partially apply a function by creating a version that has had some of its + // arguments pre-filled, without changing its dynamic `this` context. + _.partial = function(func) { + var args = slice.call(arguments, 1); + return function() { + return func.apply(this, args.concat(slice.call(arguments))); + }; + }; + + // Bind all of an object's methods to that object. Useful for ensuring that + // all callbacks defined on an object belong to it. + _.bindAll = function(obj) { + var funcs = slice.call(arguments, 1); + if (funcs.length === 0) funcs = _.functions(obj); + each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); + return obj; + }; + + // Memoize an expensive function by storing its results. + _.memoize = function(func, hasher) { + var memo = {}; + hasher || (hasher = _.identity); + return function() { + var key = hasher.apply(this, arguments); + return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); + }; + }; + + // Delays a function for the given number of milliseconds, and then calls + // it with the arguments supplied. + _.delay = function(func, wait) { + var args = slice.call(arguments, 2); + return setTimeout(function(){ return func.apply(null, args); }, wait); + }; + + // Defers a function, scheduling it to run after the current call stack has + // cleared. + _.defer = function(func) { + return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); + }; + + // Returns a function, that, when invoked, will only be triggered at most once + // during a given window of time. + _.throttle = function(func, wait) { + var context, args, timeout, result; + var previous = 0; + var later = function() { + previous = new Date; + timeout = null; + result = func.apply(context, args); + }; + return function() { + var now = new Date; + var remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0) { + clearTimeout(timeout); + timeout = null; + previous = now; + result = func.apply(context, args); + } else if (!timeout) { + timeout = setTimeout(later, remaining); + } + return result; + }; + }; + + // Returns a function, that, as long as it continues to be invoked, will not + // be triggered. The function will be called after it stops being called for + // N milliseconds. If `immediate` is passed, trigger the function on the + // leading edge, instead of the trailing. + _.debounce = function(func, wait, immediate) { + var timeout, result; + return function() { + var context = this, args = arguments; + var later = function() { + timeout = null; + if (!immediate) result = func.apply(context, args); + }; + var callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) result = func.apply(context, args); + return result; + }; + }; + + // Returns a function that will be executed at most one time, no matter how + // often you call it. Useful for lazy initialization. + _.once = function(func) { + var ran = false, memo; + return function() { + if (ran) return memo; + ran = true; + memo = func.apply(this, arguments); + func = null; + return memo; + }; + }; + + // Returns the first function passed as an argument to the second, + // allowing you to adjust arguments, run code before and after, and + // conditionally execute the original function. + _.wrap = function(func, wrapper) { + return function() { + var args = [func]; + push.apply(args, arguments); + return wrapper.apply(this, args); + }; + }; + + // Returns a function that is the composition of a list of functions, each + // consuming the return value of the function that follows. + _.compose = function() { + var funcs = arguments; + return function() { + var args = arguments; + for (var i = funcs.length - 1; i >= 0; i--) { + args = [funcs[i].apply(this, args)]; + } + return args[0]; + }; + }; + + // Returns a function that will only be executed after being called N times. + _.after = function(times, func) { + if (times <= 0) return func(); + return function() { + if (--times < 1) { + return func.apply(this, arguments); + } + }; + }; + + // Object Functions + // ---------------- + + // Retrieve the names of an object's properties. + // Delegates to **ECMAScript 5**'s native `Object.keys` + _.keys = nativeKeys || function(obj) { + if (obj !== Object(obj)) throw new TypeError('Invalid object'); + var keys = []; + for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key; + return keys; + }; + + // Retrieve the values of an object's properties. + _.values = function(obj) { + var values = []; + for (var key in obj) if (_.has(obj, key)) values.push(obj[key]); + return values; + }; + + // Convert an object into a list of `[key, value]` pairs. + _.pairs = function(obj) { + var pairs = []; + for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]); + return pairs; + }; + + // Invert the keys and values of an object. The values must be serializable. + _.invert = function(obj) { + var result = {}; + for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key; + return result; + }; + + // Return a sorted list of the function names available on the object. + // Aliased as `methods` + _.functions = _.methods = function(obj) { + var names = []; + for (var key in obj) { + if (_.isFunction(obj[key])) names.push(key); + } + return names.sort(); + }; + + // Extend a given object with all the properties in passed-in object(s). + _.extend = function(obj) { + each(slice.call(arguments, 1), function(source) { + if (source) { + for (var prop in source) { + obj[prop] = source[prop]; + } + } + }); + return obj; + }; + + // Return a copy of the object only containing the whitelisted properties. + _.pick = function(obj) { + var copy = {}; + var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); + each(keys, function(key) { + if (key in obj) copy[key] = obj[key]; + }); + return copy; + }; + + // Return a copy of the object without the blacklisted properties. + _.omit = function(obj) { + var copy = {}; + var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); + for (var key in obj) { + if (!_.contains(keys, key)) copy[key] = obj[key]; + } + return copy; + }; + + // Fill in a given object with default properties. + _.defaults = function(obj) { + each(slice.call(arguments, 1), function(source) { + if (source) { + for (var prop in source) { + if (obj[prop] == null) obj[prop] = source[prop]; + } + } + }); + return obj; + }; + + // Create a (shallow-cloned) duplicate of an object. + _.clone = function(obj) { + if (!_.isObject(obj)) return obj; + return _.isArray(obj) ? obj.slice() : _.extend({}, obj); + }; + + // Invokes interceptor with the obj, and then returns obj. + // The primary purpose of this method is to "tap into" a method chain, in + // order to perform operations on intermediate results within the chain. + _.tap = function(obj, interceptor) { + interceptor(obj); + return obj; + }; + + // Internal recursive comparison function for `isEqual`. + var eq = function(a, b, aStack, bStack) { + // Identical objects are equal. `0 === -0`, but they aren't identical. + // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal. + if (a === b) return a !== 0 || 1 / a == 1 / b; + // A strict comparison is necessary because `null == undefined`. + if (a == null || b == null) return a === b; + // Unwrap any wrapped objects. + if (a instanceof _) a = a._wrapped; + if (b instanceof _) b = b._wrapped; + // Compare `[[Class]]` names. + var className = toString.call(a); + if (className != toString.call(b)) return false; + switch (className) { + // Strings, numbers, dates, and booleans are compared by value. + case '[object String]': + // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is + // equivalent to `new String("5")`. + return a == String(b); + case '[object Number]': + // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for + // other numeric values. + return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); + case '[object Date]': + case '[object Boolean]': + // Coerce dates and booleans to numeric primitive values. Dates are compared by their + // millisecond representations. Note that invalid dates with millisecond representations + // of `NaN` are not equivalent. + return +a == +b; + // RegExps are compared by their source patterns and flags. + case '[object RegExp]': + return a.source == b.source && + a.global == b.global && + a.multiline == b.multiline && + a.ignoreCase == b.ignoreCase; + } + if (typeof a != 'object' || typeof b != 'object') return false; + // Assume equality for cyclic structures. The algorithm for detecting cyclic + // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. + var length = aStack.length; + while (length--) { + // Linear search. Performance is inversely proportional to the number of + // unique nested structures. + if (aStack[length] == a) return bStack[length] == b; + } + // Add the first object to the stack of traversed objects. + aStack.push(a); + bStack.push(b); + var size = 0, result = true; + // Recursively compare objects and arrays. + if (className == '[object Array]') { + // Compare array lengths to determine if a deep comparison is necessary. + size = a.length; + result = size == b.length; + if (result) { + // Deep compare the contents, ignoring non-numeric properties. + while (size--) { + if (!(result = eq(a[size], b[size], aStack, bStack))) break; + } + } + } else { + // Objects with different constructors are not equivalent, but `Object`s + // from different frames are. + var aCtor = a.constructor, bCtor = b.constructor; + if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) && + _.isFunction(bCtor) && (bCtor instanceof bCtor))) { + return false; + } + // Deep compare objects. + for (var key in a) { + if (_.has(a, key)) { + // Count the expected number of properties. + size++; + // Deep compare each member. + if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break; + } + } + // Ensure that both objects contain the same number of properties. + if (result) { + for (key in b) { + if (_.has(b, key) && !(size--)) break; + } + result = !size; + } + } + // Remove the first object from the stack of traversed objects. + aStack.pop(); + bStack.pop(); + return result; + }; + + // Perform a deep comparison to check if two objects are equal. + _.isEqual = function(a, b) { + return eq(a, b, [], []); + }; + + // Is a given array, string, or object empty? + // An "empty" object has no enumerable own-properties. + _.isEmpty = function(obj) { + if (obj == null) return true; + if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; + for (var key in obj) if (_.has(obj, key)) return false; + return true; + }; + + // Is a given value a DOM element? + _.isElement = function(obj) { + return !!(obj && obj.nodeType === 1); + }; + + // Is a given value an array? + // Delegates to ECMA5's native Array.isArray + _.isArray = nativeIsArray || function(obj) { + return toString.call(obj) == '[object Array]'; + }; + + // Is a given variable an object? + _.isObject = function(obj) { + return obj === Object(obj); + }; + + // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp. + each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) { + _['is' + name] = function(obj) { + return toString.call(obj) == '[object ' + name + ']'; + }; + }); + + // Define a fallback version of the method in browsers (ahem, IE), where + // there isn't any inspectable "Arguments" type. + if (!_.isArguments(arguments)) { + _.isArguments = function(obj) { + return !!(obj && _.has(obj, 'callee')); + }; + } + + // Optimize `isFunction` if appropriate. + if (typeof (/./) !== 'function') { + _.isFunction = function(obj) { + return typeof obj === 'function'; + }; + } + + // Is a given object a finite number? + _.isFinite = function(obj) { + return isFinite(obj) && !isNaN(parseFloat(obj)); + }; + + // Is the given value `NaN`? (NaN is the only number which does not equal itself). + _.isNaN = function(obj) { + return _.isNumber(obj) && obj != +obj; + }; + + // Is a given value a boolean? + _.isBoolean = function(obj) { + return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; + }; + + // Is a given value equal to null? + _.isNull = function(obj) { + return obj === null; + }; + + // Is a given variable undefined? + _.isUndefined = function(obj) { + return obj === void 0; + }; + + // Shortcut function for checking if an object has a given property directly + // on itself (in other words, not on a prototype). + _.has = function(obj, key) { + return hasOwnProperty.call(obj, key); + }; + + // Utility Functions + // ----------------- + + // Run Underscore.js in *noConflict* mode, returning the `_` variable to its + // previous owner. Returns a reference to the Underscore object. + _.noConflict = function() { + root._ = previousUnderscore; + return this; + }; + + // Keep the identity function around for default iterators. + _.identity = function(value) { + return value; + }; + + // Run a function **n** times. + _.times = function(n, iterator, context) { + var accum = Array(n); + for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i); + return accum; + }; + + // Return a random integer between min and max (inclusive). + _.random = function(min, max) { + if (max == null) { + max = min; + min = 0; + } + return min + Math.floor(Math.random() * (max - min + 1)); + }; + + // List of HTML entities for escaping. + var entityMap = { + escape: { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '/': '/' + } + }; + entityMap.unescape = _.invert(entityMap.escape); + + // Regexes containing the keys and values listed immediately above. + var entityRegexes = { + escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'), + unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g') + }; + + // Functions for escaping and unescaping strings to/from HTML interpolation. + _.each(['escape', 'unescape'], function(method) { + _[method] = function(string) { + if (string == null) return ''; + return ('' + string).replace(entityRegexes[method], function(match) { + return entityMap[method][match]; + }); + }; + }); + + // If the value of the named property is a function then invoke it; + // otherwise, return it. + _.result = function(object, property) { + if (object == null) return null; + var value = object[property]; + return _.isFunction(value) ? value.call(object) : value; + }; + + // Add your own custom functions to the Underscore object. + _.mixin = function(obj) { + each(_.functions(obj), function(name){ + var func = _[name] = obj[name]; + _.prototype[name] = function() { + var args = [this._wrapped]; + push.apply(args, arguments); + return result.call(this, func.apply(_, args)); + }; + }); + }; + + // Generate a unique integer id (unique within the entire client session). + // Useful for temporary DOM ids. + var idCounter = 0; + _.uniqueId = function(prefix) { + var id = ++idCounter + ''; + return prefix ? prefix + id : id; + }; + + // By default, Underscore uses ERB-style template delimiters, change the + // following template settings to use alternative delimiters. + _.templateSettings = { + evaluate : /<%([\s\S]+?)%>/g, + interpolate : /<%=([\s\S]+?)%>/g, + escape : /<%-([\s\S]+?)%>/g + }; + + // When customizing `templateSettings`, if you don't want to define an + // interpolation, evaluation or escaping regex, we need one that is + // guaranteed not to match. + var noMatch = /(.)^/; + + // Certain characters need to be escaped so that they can be put into a + // string literal. + var escapes = { + "'": "'", + '\\': '\\', + '\r': 'r', + '\n': 'n', + '\t': 't', + '\u2028': 'u2028', + '\u2029': 'u2029' + }; + + var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; + + // JavaScript micro-templating, similar to John Resig's implementation. + // Underscore templating handles arbitrary delimiters, preserves whitespace, + // and correctly escapes quotes within interpolated code. + _.template = function(text, data, settings) { + var render; + settings = _.defaults({}, settings, _.templateSettings); + + // Combine delimiters into one regular expression via alternation. + var matcher = new RegExp([ + (settings.escape || noMatch).source, + (settings.interpolate || noMatch).source, + (settings.evaluate || noMatch).source + ].join('|') + '|$', 'g'); + + // Compile the template source, escaping string literals appropriately. + var index = 0; + var source = "__p+='"; + text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { + source += text.slice(index, offset) + .replace(escaper, function(match) { return '\\' + escapes[match]; }); + + if (escape) { + source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; + } + if (interpolate) { + source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; + } + if (evaluate) { + source += "';\n" + evaluate + "\n__p+='"; + } + index = offset + match.length; + return match; + }); + source += "';\n"; + + // If a variable is not specified, place data values in local scope. + if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; + + source = "var __t,__p='',__j=Array.prototype.join," + + "print=function(){__p+=__j.call(arguments,'');};\n" + + source + "return __p;\n"; + + try { + render = new Function(settings.variable || 'obj', '_', source); + } catch (e) { + e.source = source; + throw e; + } + + if (data) return render(data, _); + var template = function(data) { + return render.call(this, data, _); + }; + + // Provide the compiled function source as a convenience for precompilation. + template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; + + return template; + }; + + // Add a "chain" function, which will delegate to the wrapper. + _.chain = function(obj) { + return _(obj).chain(); + }; + + // OOP + // --------------- + // If Underscore is called as a function, it returns a wrapped object that + // can be used OO-style. This wrapper holds altered versions of all the + // underscore functions. Wrapped objects may be chained. + + // Helper function to continue chaining intermediate results. + var result = function(obj) { + return this._chain ? _(obj).chain() : obj; + }; + + // Add all of the Underscore functions to the wrapper object. + _.mixin(_); + + // Add all mutator Array functions to the wrapper. + each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { + var method = ArrayProto[name]; + _.prototype[name] = function() { + var obj = this._wrapped; + method.apply(obj, arguments); + if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0]; + return result.call(this, obj); + }; + }); + + // Add all accessor Array functions to the wrapper. + each(['concat', 'join', 'slice'], function(name) { + var method = ArrayProto[name]; + _.prototype[name] = function() { + return result.call(this, method.apply(this._wrapped, arguments)); + }; + }); + + _.extend(_.prototype, { + + // Start chaining a wrapped Underscore object. + chain: function() { + this._chain = true; + return this; + }, + + // Extracts the result from a wrapped and chained object. + value: function() { + return this._wrapped; + } + + }); + +}).call(this); + +},{}],"jshint":[function(require,module,exports){ +module.exports=require('nr+AlQ'); +},{}],"nr+AlQ":[function(require,module,exports){ +/*! + * JSHint, by JSHint Community. + * + * This file (and this file only) is licensed under the same slightly modified + * MIT license that JSLint is. It stops evil-doers everywhere: + * + * Copyright (c) 2002 Douglas Crockford (www.JSLint.com) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom + * the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * The Software shall be used for Good, not Evil. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +/*jshint quotmark:double */ +/*global console:true */ +/*exported console */ + +var _ = require("underscore"); +var events = require("events"); +var vars = require("./vars.js"); +var messages = require("./messages.js"); +var Lexer = require("./lex.js").Lexer; +var reg = require("./reg.js"); +var state = require("./state.js").state; +var style = require("./style.js"); + +// We need this module here because environments such as IE and Rhino +// don't necessarilly expose the 'console' API and browserify uses +// it to log things. It's a sad state of affair, really. +var console = require("console-browserify"); + +// We build the application inside a function so that we produce only a singleton +// variable. That function will be invoked immediately, and its return value is +// the JSHINT function itself. + +var JSHINT = (function () { + "use strict"; + + var anonname, // The guessed name for anonymous functions. + api, // Extension API + + // These are operators that should not be used with the ! operator. + bang = { + "<" : true, + "<=" : true, + "==" : true, + "===": true, + "!==": true, + "!=" : true, + ">" : true, + ">=" : true, + "+" : true, + "-" : true, + "*" : true, + "/" : true, + "%" : true + }, + + // These are the JSHint boolean options. + boolOptions = { + asi : true, // if automatic semicolon insertion should be tolerated + bitwise : true, // if bitwise operators should not be allowed + boss : true, // if advanced usage of assignments should be allowed + browser : true, // if the standard browser globals should be predefined + camelcase : true, // if identifiers should be required in camel case + couch : true, // if CouchDB globals should be predefined + curly : true, // if curly braces around all blocks should be required + debug : true, // if debugger statements should be allowed + devel : true, // if logging globals should be predefined (console, alert, etc.) + dojo : true, // if Dojo Toolkit globals should be predefined + eqeqeq : true, // if === should be required + eqnull : true, // if == null comparisons should be tolerated + notypeof : true, // if should report typos in typeof comparisons + es3 : true, // if ES3 syntax should be allowed + es5 : true, // if ES5 syntax should be allowed (is now set per default) + esnext : true, // if es.next specific syntax should be allowed + moz : true, // if mozilla specific syntax should be allowed + evil : true, // if eval should be allowed + expr : true, // if ExpressionStatement should be allowed as Programs + forin : true, // if for in statements must filter + funcscope : true, // if only function scope should be used for scope tests + gcl : true, // if JSHint should be compatible with Google Closure Linter + globalstrict: true, // if global "use strict"; should be allowed (also enables 'strict') + immed : true, // if immediate invocations must be wrapped in parens + iterator : true, // if the `__iterator__` property should be allowed + jquery : true, // if jQuery globals should be predefined + lastsemic : true, // if semicolons may be ommitted for the trailing + // statements inside of a one-line blocks. + laxbreak : true, // if line breaks should not be checked + laxcomma : true, // if line breaks should not be checked around commas + loopfunc : true, // if functions should be allowed to be defined within + // loops + mootools : true, // if MooTools globals should be predefined + multistr : true, // allow multiline strings + freeze : true, // if modifying native object prototypes should be disallowed + newcap : true, // if constructor names must be capitalized + noarg : true, // if arguments.caller and arguments.callee should be + // disallowed + node : true, // if the Node.js environment globals should be + // predefined + noempty : true, // if empty blocks should be disallowed + nonbsp : true, // if non-breaking spaces should be disallowed + nonew : true, // if using `new` for side-effects should be disallowed + nonstandard : true, // if non-standard (but widely adopted) globals should + // be predefined + nomen : true, // if names should be checked + onevar : true, // if only one var statement per function should be + // allowed + passfail : true, // if the scan should stop on first error + phantom : true, // if PhantomJS symbols should be allowed + plusplus : true, // if increment/decrement should not be allowed + proto : true, // if the `__proto__` property should be allowed + prototypejs : true, // if Prototype and Scriptaculous globals should be + // predefined + rhino : true, // if the Rhino environment globals should be predefined + shelljs : true, // if ShellJS globals should be predefined + typed : true, // if typed array globals should be predefined + undef : true, // if variables should be declared before used + scripturl : true, // if script-targeted URLs should be tolerated + smarttabs : true, // if smarttabs should be tolerated + // (http://www.emacswiki.org/emacs/SmartTabs) + strict : true, // require the "use strict"; pragma + sub : true, // if all forms of subscript notation are tolerated + supernew : true, // if `new function () { ... };` and `new Object;` + // should be tolerated + trailing : true, // if trailing whitespace rules apply + validthis : true, // if 'this' inside a non-constructor function is valid. + // This is a function scoped option only. + withstmt : true, // if with statements should be allowed + white : true, // if strict whitespace rules apply + worker : true, // if Web Worker script symbols should be allowed + wsh : true, // if the Windows Scripting Host environment globals + // should be predefined + yui : true, // YUI variables should be predefined + noyield : true, // allow generators without a yield + + // Obsolete options + onecase : true, // if one case switch statements should be allowed + regexp : true, // if the . should not be allowed in regexp literals + regexdash : true // if unescaped first/last dash (-) inside brackets + // should be tolerated + }, + + // These are the JSHint options that can take any value + // (we use this object to detect invalid options) + valOptions = { + maxlen : false, + indent : false, + maxerr : false, + predef : false, //predef is deprecated and being replaced by globals + globals : false, + quotmark : false, //'single'|'double'|true + scope : false, + maxstatements: false, // {int} max statements per function + maxdepth : false, // {int} max nested block depth per function + maxparams : false, // {int} max params per function + maxcomplexity: false, // {int} max cyclomatic complexity per function + shadow : false, // if variable shadowing should be tolerated + // "inner" - check for variables defined in the same scope only + // "outer" - check for variables defined in outer scopes as well + // false - same as inner + // true - allow variable shadowing + unused : true, // warn if variables are unused. Available options: + // false - don't check for unused variables + // true - "vars" + check last function param + // "vars" - skip checking unused function params + // "strict" - "vars" + check all function params + latedef : false, // warn if the variable is used before its definition + // false - don't emit any warnings + // true - warn if any variable is used before its definition + // "nofunc" - warn for any variable but function declarations + ignore : false // start/end ignoring lines of code, bypassing the lexer + // start - start ignoring lines, including the current line + // end - stop ignoring lines, starting on the next line + // line - ignore warnings / errors for just a single line + // (this option does not bypass the lexer) + }, + + // These are JSHint boolean options which are shared with JSLint + // where the definition in JSHint is opposite JSLint + invertedOptions = { + bitwise : true, + forin : true, + newcap : true, + nomen : true, + plusplus: true, + regexp : true, + undef : true, + white : true, + + // Inverted and renamed, use JSHint name here + eqeqeq : true, + onevar : true, + strict : true + }, + + // These are JSHint boolean options which are shared with JSLint + // where the name has been changed but the effect is unchanged + renamedOptions = { + eqeq : "eqeqeq", + vars : "onevar", + windows: "wsh", + sloppy : "strict" + }, + + declared, // Globals that were declared using /*global ... */ syntax. + exported, // Variables that are used outside of the current file. + + functionicity = [ + "closure", "exception", "global", "label", + "outer", "unused", "var" + ], + + funct, // The current function + functions, // All of the functions + + global, // The global scope + implied, // Implied globals + inblock, + indent, + lookahead, + lex, + member, + membersOnly, + noreach, + predefined, // Global variables defined by option + + scope, // The current scope + stack, + unuseds, + urls, + warnings, + + extraModules = [], + emitter = new events.EventEmitter(); + + function checkOption(name, t) { + name = name.trim(); + + if (/^[+-]W\d{3}$/g.test(name)) { + return true; + } + + if (valOptions[name] === undefined && boolOptions[name] === undefined) { + if (t.type !== "jslint") { + error("E001", t, name); + return false; + } + } + + return true; + } + + function isString(obj) { + return Object.prototype.toString.call(obj) === "[object String]"; + } + + function isIdentifier(tkn, value) { + if (!tkn) + return false; + + if (!tkn.identifier || tkn.value !== value) + return false; + + return true; + } + + function isReserved(token) { + if (!token.reserved) { + return false; + } + var meta = token.meta; + + if (meta && meta.isFutureReservedWord && state.option.inES5()) { + // ES3 FutureReservedWord in an ES5 environment. + if (!meta.es5) { + return false; + } + + // Some ES5 FutureReservedWord identifiers are active only + // within a strict mode environment. + if (meta.strictOnly) { + if (!state.option.strict && !state.directive["use strict"]) { + return false; + } + } + + if (token.isProperty) { + return false; + } + } + + return true; + } + + function supplant(str, data) { + return str.replace(/\{([^{}]*)\}/g, function (a, b) { + var r = data[b]; + return typeof r === "string" || typeof r === "number" ? r : a; + }); + } + + function combine(dest, src) { + Object.keys(src).forEach(function (name) { + if (JSHINT.blacklist.hasOwnProperty(name)) return; + dest[name] = src[name]; + }); + } + + function assume() { + if (state.option.es5) { + warning("I003"); + } + if (state.option.couch) { + combine(predefined, vars.couch); + } + + if (state.option.rhino) { + combine(predefined, vars.rhino); + } + + if (state.option.shelljs) { + combine(predefined, vars.shelljs); + combine(predefined, vars.node); + } + if (state.option.typed) { + combine(predefined, vars.typed); + } + + if (state.option.phantom) { + combine(predefined, vars.phantom); + } + + if (state.option.prototypejs) { + combine(predefined, vars.prototypejs); + } + + if (state.option.node) { + combine(predefined, vars.node); + combine(predefined, vars.typed); + } + + if (state.option.devel) { + combine(predefined, vars.devel); + } + + if (state.option.dojo) { + combine(predefined, vars.dojo); + } + + if (state.option.browser) { + combine(predefined, vars.browser); + combine(predefined, vars.typed); + } + + if (state.option.nonstandard) { + combine(predefined, vars.nonstandard); + } + + if (state.option.jquery) { + combine(predefined, vars.jquery); + } + + if (state.option.mootools) { + combine(predefined, vars.mootools); + } + + if (state.option.worker) { + combine(predefined, vars.worker); + } + + if (state.option.wsh) { + combine(predefined, vars.wsh); + } + + if (state.option.globalstrict && state.option.strict !== false) { + state.option.strict = true; + } + + if (state.option.yui) { + combine(predefined, vars.yui); + } + + // Let's assume that chronologically ES3 < ES5 < ES6/ESNext < Moz + + state.option.inMoz = function (strict) { + if (strict) { + return state.option.moz && !state.option.esnext; + } + return state.option.moz; + }; + + state.option.inESNext = function (strict) { + if (strict) { + return !state.option.moz && state.option.esnext; + } + return state.option.moz || state.option.esnext; + }; + + state.option.inES5 = function (/* strict */) { + return !state.option.es3; + }; + + state.option.inES3 = function (strict) { + if (strict) { + return !state.option.moz && !state.option.esnext && state.option.es3; + } + return state.option.es3; + }; + } + + // Produce an error warning. + function quit(code, line, chr) { + var percentage = Math.floor((line / state.lines.length) * 100); + var message = messages.errors[code].desc; + + throw { + name: "JSHintError", + line: line, + character: chr, + message: message + " (" + percentage + "% scanned).", + raw: message, + code: code + }; + } + + function isundef(scope, code, token, a) { + return JSHINT.undefs.push([scope, code, token, a]); + } + + function warning(code, t, a, b, c, d) { + var ch, l, w, msg; + + if (/^W\d{3}$/.test(code)) { + if (state.ignored[code]) + return; + + msg = messages.warnings[code]; + } else if (/E\d{3}/.test(code)) { + msg = messages.errors[code]; + } else if (/I\d{3}/.test(code)) { + msg = messages.info[code]; + } + + t = t || state.tokens.next; + if (t.id === "(end)") { // `~ + t = state.tokens.curr; + } + + l = t.line || 0; + ch = t.from || 0; + + w = { + id: "(error)", + raw: msg.desc, + code: msg.code, + evidence: state.lines[l - 1] || "", + line: l, + character: ch, + scope: JSHINT.scope, + a: a, + b: b, + c: c, + d: d + }; + + w.reason = supplant(msg.desc, w); + JSHINT.errors.push(w); + + if (state.option.passfail) { + quit("E042", l, ch); + } + + warnings += 1; + if (warnings >= state.option.maxerr) { + quit("E043", l, ch); + } + + return w; + } + + function warningAt(m, l, ch, a, b, c, d) { + return warning(m, { + line: l, + from: ch + }, a, b, c, d); + } + + function error(m, t, a, b, c, d) { + warning(m, t, a, b, c, d); + } + + function errorAt(m, l, ch, a, b, c, d) { + return error(m, { + line: l, + from: ch + }, a, b, c, d); + } + + // Tracking of "internal" scripts, like eval containing a static string + function addInternalSrc(elem, src) { + var i; + i = { + id: "(internal)", + elem: elem, + value: src + }; + JSHINT.internals.push(i); + return i; + } + + // name: string + // opts: { type: string, token: token, islet: bool } + function addlabel(name, opts) { + opts = opts || {}; + + var type = opts.type; + var token = opts.token; + var islet = opts.islet; + + // Define label in the current function in the current scope. + if (type === "exception") { + if (_.has(funct["(context)"], name)) { + if (funct[name] !== true && !state.option.node) { + warning("W002", state.tokens.next, name); + } + } + } + + if (_.has(funct, name) && !funct["(global)"]) { + if (funct[name] === true) { + if (state.option.latedef) { + if ((state.option.latedef === true && _.contains([funct[name], type], "unction")) || + !_.contains([funct[name], type], "unction")) { + warning("W003", state.tokens.next, name); + } + } + } else { + if ((!state.option.shadow || _.contains([ "inner", "outer" ], state.option.shadow)) && + type !== "exception" || funct["(blockscope)"].getlabel(name)) { + warning("W004", state.tokens.next, name); + } + } + } + + if (funct["(context)"] && _.has(funct["(context)"], name) && type !== "function") { + if (state.option.shadow === "outer") { + warning("W123", state.tokens.next, name); + } + } + + // if the identifier is from a let, adds it only to the current blockscope + if (islet) { + funct["(blockscope)"].current.add(name, type, state.tokens.curr); + } else { + funct["(blockscope)"].shadow(name); + funct[name] = type; + + if (token) { + funct["(tokens)"][name] = token; + } + + setprop(funct, name, { unused: opts.unused || false }); + + if (funct["(global)"]) { + global[name] = funct; + if (_.has(implied, name)) { + if (state.option.latedef) { + if ((state.option.latedef === true && _.contains([funct[name], type], "unction")) || + !_.contains([funct[name], type], "unction")) { + warning("W003", state.tokens.next, name); + } + } + + delete implied[name]; + } + } else { + scope[name] = funct; + } + } + } + + function doOption() { + var nt = state.tokens.next; + var body = nt.body.split(",").map(function (s) { return s.trim(); }); + var predef = {}; + + if (nt.type === "globals") { + body.forEach(function (g) { + g = g.split(":"); + var key = (g[0] || "").trim(); + var val = (g[1] || "").trim(); + + if (key.charAt(0) === "-") { + key = key.slice(1); + val = false; + + JSHINT.blacklist[key] = key; + delete predefined[key]; + } else { + predef[key] = (val === "true"); + } + }); + + combine(predefined, predef); + + for (var key in predef) { + if (_.has(predef, key)) { + declared[key] = nt; + } + } + } + + if (nt.type === "exported") { + body.forEach(function (e) { + exported[e] = true; + }); + } + + if (nt.type === "members") { + membersOnly = membersOnly || {}; + + body.forEach(function (m) { + var ch1 = m.charAt(0); + var ch2 = m.charAt(m.length - 1); + + if (ch1 === ch2 && (ch1 === "\"" || ch1 === "'")) { + m = m + .substr(1, m.length - 2) + .replace("\\\"", "\""); + } + + membersOnly[m] = false; + }); + } + + var numvals = [ + "maxstatements", + "maxparams", + "maxdepth", + "maxcomplexity", + "maxerr", + "maxlen", + "indent" + ]; + + if (nt.type === "jshint" || nt.type === "jslint") { + body.forEach(function (g) { + g = g.split(":"); + var key = (g[0] || "").trim(); + var val = (g[1] || "").trim(); + + if (!checkOption(key, nt)) { + return; + } + + if (numvals.indexOf(key) >= 0) { + + // GH988 - numeric options can be disabled by setting them to `false` + if (val !== "false") { + val = +val; + + if (typeof val !== "number" || !isFinite(val) || val <= 0 || Math.floor(val) !== val) { + error("E032", nt, g[1].trim()); + return; + } + + if (key === "indent") { + state.option["(explicitIndent)"] = true; + } + state.option[key] = val; + } else { + if (key === "indent") { + state.option["(explicitIndent)"] = false; + } else { + state.option[key] = false; + } + } + + return; + } + + if (key === "validthis") { + // `validthis` is valid only within a function scope. + + if (funct["(global)"]) + return void error("E009"); + + if (val !== "true" && val !== "false") + return void error("E002", nt); + + state.option.validthis = (val === "true"); + return; + } + + if (key === "quotmark") { + switch (val) { + case "true": + case "false": + state.option.quotmark = (val === "true"); + break; + case "double": + case "single": + state.option.quotmark = val; + break; + default: + error("E002", nt); + } + return; + } + + if (key === "shadow") { + switch (val) { + case "true": + state.option.shadow = true; + break; + case "outer": + state.option.shadow = "outer"; + break; + case "false": + case "inner": + state.option.shadow = "inner"; + break; + default: + error("E002", nt); + } + return; + } + + if (key === "unused") { + switch (val) { + case "true": + state.option.unused = true; + break; + case "false": + state.option.unused = false; + break; + case "vars": + case "strict": + state.option.unused = val; + break; + default: + error("E002", nt); + } + return; + } + + if (key === "latedef") { + switch (val) { + case "true": + state.option.latedef = true; + break; + case "false": + state.option.latedef = false; + break; + case "nofunc": + state.option.latedef = "nofunc"; + break; + default: + error("E002", nt); + } + return; + } + + if (key === "ignore") { + switch (val) { + case "start": + state.ignoreLinterErrors = true; + break; + case "end": + state.ignoreLinterErrors = false; + break; + case "line": + // Any errors or warnings that happened on the current line, make them go away. + JSHINT.errors = _.reject(JSHINT.errors, function (error) { + // nt.line returns to the current line + return error.line === nt.line; + }); + break; + default: + error("E002", nt); + } + return; + } + + var match = /^([+-])(W\d{3})$/g.exec(key); + if (match) { + // ignore for -W..., unignore for +W... + state.ignored[match[2]] = (match[1] === "-"); + return; + } + + var tn; + if (val === "true" || val === "false") { + if (nt.type === "jslint") { + tn = renamedOptions[key] || key; + state.option[tn] = (val === "true"); + + if (invertedOptions[tn] !== undefined) { + state.option[tn] = !state.option[tn]; + } + } else { + state.option[key] = (val === "true"); + } + + if (key === "newcap") { + state.option["(explicitNewcap)"] = true; + } + return; + } + + error("E002", nt); + }); + + assume(); + } + } + + // We need a peek function. If it has an argument, it peeks that much farther + // ahead. It is used to distinguish + // for ( var i in ... + // from + // for ( var i = ... + + function peek(p) { + var i = p || 0, j = 0, t; + + while (j <= i) { + t = lookahead[j]; + if (!t) { + t = lookahead[j] = lex.token(); + } + j += 1; + } + return t; + } + + // Produce the next token. It looks for programming errors. + + function advance(id, t) { + switch (state.tokens.curr.id) { + case "(number)": + if (state.tokens.next.id === ".") { + warning("W005", state.tokens.curr); + } + break; + case "-": + if (state.tokens.next.id === "-" || state.tokens.next.id === "--") { + warning("W006"); + } + break; + case "+": + if (state.tokens.next.id === "+" || state.tokens.next.id === "++") { + warning("W007"); + } + break; + } + + if (state.tokens.curr.type === "(string)" || state.tokens.curr.identifier) { + anonname = state.tokens.curr.value; + } + + if (id && state.tokens.next.id !== id) { + if (t) { + if (state.tokens.next.id === "(end)") { + error("E019", t, t.id); + } else { + error("E020", state.tokens.next, id, t.id, t.line, state.tokens.next.value); + } + } else if (state.tokens.next.type !== "(identifier)" || state.tokens.next.value !== id) { + warning("W116", state.tokens.next, id, state.tokens.next.value); + } + } + + state.tokens.prev = state.tokens.curr; + state.tokens.curr = state.tokens.next; + for (;;) { + state.tokens.next = lookahead.shift() || lex.token(); + + if (!state.tokens.next) { // No more tokens left, give up + quit("E041", state.tokens.curr.line); + } + + if (state.tokens.next.id === "(end)" || state.tokens.next.id === "(error)") { + return; + } + + if (state.tokens.next.check) { + state.tokens.next.check(); + } + + if (state.tokens.next.isSpecial) { + doOption(); + } else { + if (state.tokens.next.id !== "(endline)") { + break; + } + } + } + } + + function isInfix(token) { + return token.infix || (!token.identifier && !!token.led); + } + + function isEndOfExpr() { + var curr = state.tokens.curr; + var next = state.tokens.next; + if (next.id === ";" || next.id === "}" || next.id === ":") { + return true; + } + if (isInfix(next) === isInfix(curr) || (curr.id === "yield" && state.option.inMoz(true))) { + return curr.line !== next.line; + } + return false; + } + + // This is the heart of JSHINT, the Pratt parser. In addition to parsing, it + // is looking for ad hoc lint patterns. We add .fud to Pratt's model, which is + // like .nud except that it is only used on the first token of a statement. + // Having .fud makes it much easier to define statement-oriented languages like + // JavaScript. I retained Pratt's nomenclature. + + // .nud Null denotation + // .fud First null denotation + // .led Left denotation + // lbp Left binding power + // rbp Right binding power + + // They are elements of the parsing method called Top Down Operator Precedence. + + function expression(rbp, initial) { + var left, isArray = false, isObject = false, isLetExpr = false; + + // if current expression is a let expression + if (!initial && state.tokens.next.value === "let" && peek(0).value === "(") { + if (!state.option.inMoz(true)) { + warning("W118", state.tokens.next, "let expressions"); + } + isLetExpr = true; + // create a new block scope we use only for the current expression + funct["(blockscope)"].stack(); + advance("let"); + advance("("); + state.syntax["let"].fud.call(state.syntax["let"].fud, false); + advance(")"); + } + + if (state.tokens.next.id === "(end)") + error("E006", state.tokens.curr); + + advance(); + + if (initial) { + anonname = "anonymous"; + funct["(verb)"] = state.tokens.curr.value; + } + + if (initial === true && state.tokens.curr.fud) { + left = state.tokens.curr.fud(); + } else { + if (state.tokens.curr.nud) { + left = state.tokens.curr.nud(); + } else { + error("E030", state.tokens.curr, state.tokens.curr.id); + } + + while (rbp < state.tokens.next.lbp && !isEndOfExpr()) { + isArray = state.tokens.curr.value === "Array"; + isObject = state.tokens.curr.value === "Object"; + + // #527, new Foo.Array(), Foo.Array(), new Foo.Object(), Foo.Object() + // Line breaks in IfStatement heads exist to satisfy the checkJSHint + // "Line too long." error. + if (left && (left.value || (left.first && left.first.value))) { + // If the left.value is not "new", or the left.first.value is a "." + // then safely assume that this is not "new Array()" and possibly + // not "new Object()"... + if (left.value !== "new" || + (left.first && left.first.value && left.first.value === ".")) { + isArray = false; + // ...In the case of Object, if the left.value and state.tokens.curr.value + // are not equal, then safely assume that this not "new Object()" + if (left.value !== state.tokens.curr.value) { + isObject = false; + } + } + } + + advance(); + + if (isArray && state.tokens.curr.id === "(" && state.tokens.next.id === ")") { + warning("W009", state.tokens.curr); + } + + if (isObject && state.tokens.curr.id === "(" && state.tokens.next.id === ")") { + warning("W010", state.tokens.curr); + } + + if (left && state.tokens.curr.led) { + left = state.tokens.curr.led(left); + } else { + error("E033", state.tokens.curr, state.tokens.curr.id); + } + } + } + if (isLetExpr) { + funct["(blockscope)"].unstack(); + } + return left; + } + + +// Functions for conformance of style. + + function adjacent(left, right) { + left = left || state.tokens.curr; + right = right || state.tokens.next; + if (state.option.white) { + if (left.character !== right.from && left.line === right.line) { + left.from += (left.character - left.from); + warning("W011", left, left.value); + } + } + } + + function nobreak(left, right) { + left = left || state.tokens.curr; + right = right || state.tokens.next; + if (state.option.white && (left.character !== right.from || left.line !== right.line)) { + warning("W012", right, right.value); + } + } + + function nospace(left, right) { + left = left || state.tokens.curr; + right = right || state.tokens.next; + if (state.option.white && !left.comment) { + if (left.line === right.line) { + adjacent(left, right); + } + } + } + + function nonadjacent(left, right) { + if (state.option.white) { + left = left || state.tokens.curr; + right = right || state.tokens.next; + + if (left.value === ";" && right.value === ";") { + return; + } + + if (left.line === right.line && left.character === right.from) { + left.from += (left.character - left.from); + warning("W013", left, left.value); + } + } + } + + function nobreaknonadjacent(left, right) { + left = left || state.tokens.curr; + right = right || state.tokens.next; + if (!state.option.laxbreak && left.line !== right.line) { + warning("W014", right, right.value); + } else if (state.option.white) { + left = left || state.tokens.curr; + right = right || state.tokens.next; + if (left.character === right.from) { + left.from += (left.character - left.from); + warning("W013", left, left.value); + } + } + } + + function indentation(bias) { + if (!state.option.white && !state.option["(explicitIndent)"]) { + return; + } + + if (state.tokens.next.id === "(end)") { + return; + } + + var i = indent + (bias || 0); + if (state.tokens.next.from !== i) { + warning("W015", state.tokens.next, state.tokens.next.value, i, state.tokens.next.from); + } + } + + function nolinebreak(t) { + t = t || state.tokens.curr; + if (t.line !== state.tokens.next.line) { + warning("E022", t, t.value); + } + } + + function nobreakcomma(left, right) { + if (left.line !== right.line) { + if (!state.option.laxcomma) { + if (comma.first) { + warning("I001"); + comma.first = false; + } + warning("W014", left, right.value); + } + } else if (!left.comment && left.character !== right.from && state.option.white) { + left.from += (left.character - left.from); + warning("W011", left, left.value); + } + } + + function comma(opts) { + opts = opts || {}; + + if (!opts.peek) { + nobreakcomma(state.tokens.curr, state.tokens.next); + advance(","); + } else { + nobreakcomma(state.tokens.prev, state.tokens.curr); + } + + // TODO: This is a temporary solution to fight against false-positives in + // arrays and objects with trailing commas (see GH-363). The best solution + // would be to extract all whitespace rules out of parser. + + if (state.tokens.next.value !== "]" && state.tokens.next.value !== "}") { + nonadjacent(state.tokens.curr, state.tokens.next); + } + + if (state.tokens.next.identifier && !(opts.property && state.option.inES5())) { + // Keywords that cannot follow a comma operator. + switch (state.tokens.next.value) { + case "break": + case "case": + case "catch": + case "continue": + case "default": + case "do": + case "else": + case "finally": + case "for": + case "if": + case "in": + case "instanceof": + case "return": + case "switch": + case "throw": + case "try": + case "var": + case "let": + case "while": + case "with": + error("E024", state.tokens.next, state.tokens.next.value); + return false; + } + } + + if (state.tokens.next.type === "(punctuator)") { + switch (state.tokens.next.value) { + case "}": + case "]": + case ",": + if (opts.allowTrailing) { + return true; + } + + /* falls through */ + case ")": + error("E024", state.tokens.next, state.tokens.next.value); + return false; + } + } + return true; + } + + // Functional constructors for making the symbols that will be inherited by + // tokens. + + function symbol(s, p) { + var x = state.syntax[s]; + if (!x || typeof x !== "object") { + state.syntax[s] = x = { + id: s, + lbp: p, + value: s + }; + } + return x; + } + + function delim(s) { + return symbol(s, 0); + } + + function stmt(s, f) { + var x = delim(s); + x.identifier = x.reserved = true; + x.fud = f; + return x; + } + + function blockstmt(s, f) { + var x = stmt(s, f); + x.block = true; + return x; + } + + function reserveName(x) { + var c = x.id.charAt(0); + if ((c >= "a" && c <= "z") || (c >= "A" && c <= "Z")) { + x.identifier = x.reserved = true; + } + return x; + } + + function prefix(s, f) { + var x = symbol(s, 150); + reserveName(x); + + x.nud = (typeof f === "function") ? f : function () { + this.right = expression(150); + this.arity = "unary"; + + if (this.id === "++" || this.id === "--") { + if (state.option.plusplus) { + warning("W016", this, this.id); + } else if (this.right && (!this.right.identifier || isReserved(this.right)) && + this.right.id !== "." && this.right.id !== "[") { + warning("W017", this); + } + } + + return this; + }; + + return x; + } + + function type(s, f) { + var x = delim(s); + x.type = s; + x.nud = f; + return x; + } + + function reserve(name, func) { + var x = type(name, func); + x.identifier = true; + x.reserved = true; + return x; + } + + function FutureReservedWord(name, meta) { + var x = type(name, (meta && meta.nud) || function () { + return this; + }); + + meta = meta || {}; + meta.isFutureReservedWord = true; + + x.value = name; + x.identifier = true; + x.reserved = true; + x.meta = meta; + + return x; + } + + function reservevar(s, v) { + return reserve(s, function () { + if (typeof v === "function") { + v(this); + } + return this; + }); + } + + function infix(s, f, p, w) { + var x = symbol(s, p); + reserveName(x); + x.infix = true; + x.led = function (left) { + if (!w) { + nobreaknonadjacent(state.tokens.prev, state.tokens.curr); + nonadjacent(state.tokens.curr, state.tokens.next); + } + if (s === "in" && left.id === "!") { + warning("W018", left, "!"); + } + if (typeof f === "function") { + return f(left, this); + } else { + this.left = left; + this.right = expression(p); + return this; + } + }; + return x; + } + + + function application(s) { + var x = symbol(s, 42); + + x.led = function (left) { + if (!state.option.inESNext()) { + warning("W104", state.tokens.curr, "arrow function syntax (=>)"); + } + + nobreaknonadjacent(state.tokens.prev, state.tokens.curr); + nonadjacent(state.tokens.curr, state.tokens.next); + + this.left = left; + this.right = doFunction(undefined, undefined, false, left); + return this; + }; + return x; + } + + function relation(s, f) { + var x = symbol(s, 100); + + x.led = function (left) { + nobreaknonadjacent(state.tokens.prev, state.tokens.curr); + nonadjacent(state.tokens.curr, state.tokens.next); + var right = expression(100); + + if (isIdentifier(left, "NaN") || isIdentifier(right, "NaN")) { + warning("W019", this); + } else if (f) { + f.apply(this, [left, right]); + } + + if (!left || !right) { + quit("E041", state.tokens.curr.line); + } + + if (left.id === "!") { + warning("W018", left, "!"); + } + + if (right.id === "!") { + warning("W018", right, "!"); + } + + this.left = left; + this.right = right; + return this; + }; + return x; + } + + function isPoorRelation(node) { + return node && + ((node.type === "(number)" && +node.value === 0) || + (node.type === "(string)" && node.value === "") || + (node.type === "null" && !state.option.eqnull) || + node.type === "true" || + node.type === "false" || + node.type === "undefined"); + } + + // Checks whether the 'typeof' operator is used with the correct + // value. For docs on 'typeof' see: + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof + + function isTypoTypeof(left, right) { + if (state.option.notypeof) + return false; + + if (!left || !right) + return false; + + var values = [ + "undefined", "object", "boolean", "number", + "string", "function", "xml", "object", "unknown" + ]; + + if (right.type === "(identifier)" && right.value === "typeof" && left.type === "(string)") + return !_.contains(values, left.value); + + return false; + } + + function findNativePrototype(left) { + var natives = [ + "Array", "ArrayBuffer", "Boolean", "Collator", "DataView", "Date", + "DateTimeFormat", "Error", "EvalError", "Float32Array", "Float64Array", + "Function", "Infinity", "Intl", "Int16Array", "Int32Array", "Int8Array", + "Iterator", "Number", "NumberFormat", "Object", "RangeError", + "ReferenceError", "RegExp", "StopIteration", "String", "SyntaxError", + "TypeError", "Uint16Array", "Uint32Array", "Uint8Array", "Uint8ClampedArray", + "URIError" + ]; + + function walkPrototype(obj) { + if (typeof obj !== "object") return; + return obj.right === "prototype" ? obj : walkPrototype(obj.left); + } + + function walkNative(obj) { + while (!obj.identifier && typeof obj.left === "object") + obj = obj.left; + + if (obj.identifier && natives.indexOf(obj.value) >= 0) + return obj.value; + } + + var prototype = walkPrototype(left); + if (prototype) return walkNative(prototype); + } + + function assignop(s, f, p) { + var x = infix(s, typeof f === "function" ? f : function (left, that) { + that.left = left; + + if (left) { + if (state.option.freeze) { + var nativeObject = findNativePrototype(left); + if (nativeObject) + warning("W121", left, nativeObject); + } + + if (predefined[left.value] === false && + scope[left.value]["(global)"] === true) { + warning("W020", left); + } else if (left["function"]) { + warning("W021", left, left.value); + } + + if (funct[left.value] === "const") { + error("E013", left, left.value); + } + + if (left.id === ".") { + if (!left.left) { + warning("E031", that); + } else if (left.left.value === "arguments" && !state.directive["use strict"]) { + warning("E031", that); + } + + that.right = expression(10); + return that; + } else if (left.id === "[") { + if (state.tokens.curr.left.first) { + state.tokens.curr.left.first.forEach(function (t) { + if (funct[t.value] === "const") { + error("E013", t, t.value); + } + }); + } else if (!left.left) { + warning("E031", that); + } else if (left.left.value === "arguments" && !state.directive["use strict"]) { + warning("E031", that); + } + that.right = expression(10); + return that; + } else if (left.identifier && !isReserved(left)) { + if (funct[left.value] === "exception") { + warning("W022", left); + } + that.right = expression(10); + return that; + } + + if (left === state.syntax["function"]) { + warning("W023", state.tokens.curr); + } + } + + error("E031", that); + }, p); + + x.exps = true; + x.assign = true; + return x; + } + + + function bitwise(s, f, p) { + var x = symbol(s, p); + reserveName(x); + x.led = (typeof f === "function") ? f : function (left) { + if (state.option.bitwise) { + warning("W016", this, this.id); + } + this.left = left; + this.right = expression(p); + return this; + }; + return x; + } + + + function bitwiseassignop(s) { + return assignop(s, function (left, that) { + if (state.option.bitwise) { + warning("W016", that, that.id); + } + nonadjacent(state.tokens.prev, state.tokens.curr); + nonadjacent(state.tokens.curr, state.tokens.next); + if (left) { + if (left.id === "." || left.id === "[" || + (left.identifier && !isReserved(left))) { + expression(10); + return that; + } + if (left === state.syntax["function"]) { + warning("W023", state.tokens.curr); + } + return that; + } + error("E031", that); + }, 20); + } + + + function suffix(s) { + var x = symbol(s, 150); + + x.led = function (left) { + if (state.option.plusplus) { + warning("W016", this, this.id); + } else if ((!left.identifier || isReserved(left)) && left.id !== "." && left.id !== "[") { + warning("W017", this); + } + + this.left = left; + return this; + }; + return x; + } + + // fnparam means that this identifier is being defined as a function + // argument (see identifier()) + // prop means that this identifier is that of an object property + + function optionalidentifier(fnparam, prop) { + if (!state.tokens.next.identifier) { + return; + } + + advance(); + + var curr = state.tokens.curr; + var val = state.tokens.curr.value; + + if (!isReserved(curr)) { + return val; + } + + if (prop) { + if (state.option.inES5()) { + return val; + } + } + + if (fnparam && val === "undefined") { + return val; + } + + // Display an info message about reserved words as properties + // and ES5 but do it only once. + if (prop && !api.getCache("displayed:I002")) { + api.setCache("displayed:I002", true); + warning("I002"); + } + + warning("W024", state.tokens.curr, state.tokens.curr.id); + return val; + } + + // fnparam means that this identifier is being defined as a function + // argument + // prop means that this identifier is that of an object property + function identifier(fnparam, prop) { + var i = optionalidentifier(fnparam, prop); + if (i) { + return i; + } + if (state.tokens.curr.id === "function" && state.tokens.next.id === "(") { + warning("W025"); + } else { + error("E030", state.tokens.next, state.tokens.next.value); + } + } + + + function reachable(s) { + var i = 0, t; + if (state.tokens.next.id !== ";" || noreach) { + return; + } + for (;;) { + do { + t = peek(i); + i += 1; + } while (t.id != "(end)" && t.id === "(comment)"); + + if (t.reach) { + return; + } + if (t.id !== "(endline)") { + if (t.id === "function") { + if (state.option.latedef === true) { + warning("W026", t); + } + break; + } + + warning("W027", t, t.value, s); + break; + } + } + } + + + function statement(noindent) { + var values; + var i = indent, r, s = scope, t = state.tokens.next; + + if (t.id === ";") { + advance(";"); + return; + } + + // Is this a labelled statement? + var res = isReserved(t); + + // We're being more tolerant here: if someone uses + // a FutureReservedWord as a label, we warn but proceed + // anyway. + + if (res && t.meta && t.meta.isFutureReservedWord && peek().id === ":") { + warning("W024", t, t.id); + res = false; + } + + // detect a destructuring assignment + if (_.has(["[", "{"], t.value)) { + if (lookupBlockType().isDestAssign) { + if (!state.option.inESNext()) { + warning("W104", state.tokens.curr, "destructuring expression"); + } + values = destructuringExpression(); + values.forEach(function (tok) { + isundef(funct, "W117", tok.token, tok.id); + }); + advance("="); + destructuringExpressionMatch(values, expression(10, true)); + advance(";"); + return; + } + } + if (t.identifier && !res && peek().id === ":") { + advance(); + advance(":"); + scope = Object.create(s); + addlabel(t.value, { type: "label" }); + + if (!state.tokens.next.labelled && state.tokens.next.value !== "{") { + warning("W028", state.tokens.next, t.value, state.tokens.next.value); + } + + state.tokens.next.label = t.value; + t = state.tokens.next; + } + + // Is it a lonely block? + + if (t.id === "{") { + // Is it a switch case block? + // + // switch (foo) { + // case bar: { <= here. + // ... + // } + // } + var iscase = (funct["(verb)"] === "case" && state.tokens.curr.value === ":"); + block(true, true, false, false, iscase); + return; + } + + // Parse the statement. + + if (!noindent) { + indentation(); + } + r = expression(0, true); + + if (r && (!r.identifier || r.value !== "function") && (r.type !== "(punctuator)")) { + if (!state.directive["use strict"] && + state.option.globalstrict && + state.option.strict) { + warning("E007"); + } + } + + // Look for the final semicolon. + + if (!t.block) { + if (!state.option.expr && (!r || !r.exps)) { + warning("W030", state.tokens.curr); + } else if (state.option.nonew && r && r.left && r.id === "(" && r.left.id === "new") { + warning("W031", t); + } + + if (state.tokens.next.id !== ";") { + if (!state.option.asi) { + // If this is the last statement in a block that ends on + // the same line *and* option lastsemic is on, ignore the warning. + // Otherwise, complain about missing semicolon. + if (!state.option.lastsemic || state.tokens.next.id !== "}" || + state.tokens.next.line !== state.tokens.curr.line) { + warningAt("W033", state.tokens.curr.line, state.tokens.curr.character); + } + } + } else { + adjacent(state.tokens.curr, state.tokens.next); + advance(";"); + nonadjacent(state.tokens.curr, state.tokens.next); + } + } + + // Restore the indentation. + + indent = i; + scope = s; + return r; + } + + + function statements(startLine) { + var a = [], p; + + while (!state.tokens.next.reach && state.tokens.next.id !== "(end)") { + if (state.tokens.next.id === ";") { + p = peek(); + + if (!p || (p.id !== "(" && p.id !== "[")) { + warning("W032"); + } + + advance(";"); + } else { + a.push(statement(startLine === state.tokens.next.line)); + } + } + return a; + } + + + /* + * read all directives + * recognizes a simple form of asi, but always + * warns, if it is used + */ + function directives() { + var i, p, pn; + + for (;;) { + if (state.tokens.next.id === "(string)") { + p = peek(0); + if (p.id === "(endline)") { + i = 1; + do { + pn = peek(i); + i = i + 1; + } while (pn.id === "(endline)"); + + if (pn.id !== ";") { + if (pn.id !== "(string)" && pn.id !== "(number)" && + pn.id !== "(regexp)" && pn.identifier !== true && + pn.id !== "}") { + break; + } + warning("W033", state.tokens.next); + } else { + p = pn; + } + } else if (p.id === "}") { + // Directive with no other statements, warn about missing semicolon + warning("W033", p); + } else if (p.id !== ";") { + break; + } + + indentation(); + advance(); + if (state.directive[state.tokens.curr.value]) { + warning("W034", state.tokens.curr, state.tokens.curr.value); + } + + if (state.tokens.curr.value === "use strict") { + if (!state.option["(explicitNewcap)"]) + state.option.newcap = true; + state.option.undef = true; + } + + // there's no directive negation, so always set to true + state.directive[state.tokens.curr.value] = true; + + if (p.id === ";") { + advance(";"); + } + continue; + } + break; + } + } + + + /* + * Parses a single block. A block is a sequence of statements wrapped in + * braces. + * + * ordinary - true for everything but function bodies and try blocks. + * stmt - true if block can be a single statement (e.g. in if/for/while). + * isfunc - true if block is a function body + * isfatarrow - true if its a body of a fat arrow function + * iscase - true if block is a switch case block + */ + function block(ordinary, stmt, isfunc, isfatarrow, iscase) { + var a, + b = inblock, + old_indent = indent, + m, + s = scope, + t, + line, + d; + + inblock = ordinary; + + if (!ordinary || !state.option.funcscope) + scope = Object.create(scope); + + nonadjacent(state.tokens.curr, state.tokens.next); + t = state.tokens.next; + + var metrics = funct["(metrics)"]; + metrics.nestedBlockDepth += 1; + metrics.verifyMaxNestedBlockDepthPerFunction(); + + if (state.tokens.next.id === "{") { + advance("{"); + + // create a new block scope + funct["(blockscope)"].stack(); + + line = state.tokens.curr.line; + if (state.tokens.next.id !== "}") { + indent += state.option.indent; + while (!ordinary && state.tokens.next.from > indent) { + indent += state.option.indent; + } + + if (isfunc) { + m = {}; + for (d in state.directive) { + if (_.has(state.directive, d)) { + m[d] = state.directive[d]; + } + } + directives(); + + if (state.option.strict && funct["(context)"]["(global)"]) { + if (!m["use strict"] && !state.directive["use strict"]) { + warning("E007"); + } + } + } + + a = statements(line); + + metrics.statementCount += a.length; + + if (isfunc) { + state.directive = m; + } + + indent -= state.option.indent; + if (line !== state.tokens.next.line) { + indentation(); + } + } else if (line !== state.tokens.next.line) { + indentation(); + } + advance("}", t); + + funct["(blockscope)"].unstack(); + + indent = old_indent; + } else if (!ordinary) { + if (isfunc) { + m = {}; + if (stmt && !isfatarrow && !state.option.inMoz(true)) { + error("W118", state.tokens.curr, "function closure expressions"); + } + + if (!stmt) { + for (d in state.directive) { + if (_.has(state.directive, d)) { + m[d] = state.directive[d]; + } + } + } + expression(10); + + if (state.option.strict && funct["(context)"]["(global)"]) { + if (!m["use strict"] && !state.directive["use strict"]) { + warning("E007"); + } + } + } else { + error("E021", state.tokens.next, "{", state.tokens.next.value); + } + } else { + + // check to avoid let declaration not within a block + funct["(nolet)"] = true; + + if (!stmt || state.option.curly) { + warning("W116", state.tokens.next, "{", state.tokens.next.value); + } + + noreach = true; + indent += state.option.indent; + // test indentation only if statement is in new line + a = [statement(state.tokens.next.line === state.tokens.curr.line)]; + indent -= state.option.indent; + noreach = false; + + delete funct["(nolet)"]; + } + // Don't clear and let it propagate out if it is "break", "return", or "throw" in switch case + if (!(iscase && ["break", "return", "throw"].indexOf(funct["(verb)"]) != -1)) { + funct["(verb)"] = null; + } + + if (!ordinary || !state.option.funcscope) scope = s; + inblock = b; + if (ordinary && state.option.noempty && (!a || a.length === 0)) { + warning("W035"); + } + metrics.nestedBlockDepth -= 1; + return a; + } + + + function countMember(m) { + if (membersOnly && typeof membersOnly[m] !== "boolean") { + warning("W036", state.tokens.curr, m); + } + if (typeof member[m] === "number") { + member[m] += 1; + } else { + member[m] = 1; + } + } + + + function note_implied(tkn) { + var name = tkn.value; + var desc = Object.getOwnPropertyDescriptor(implied, name); + + if (!desc) + implied[name] = [tkn.line]; + else + desc.value.push(tkn.line); + } + + + // Build the syntax table by declaring the syntactic elements of the language. + + type("(number)", function () { + return this; + }); + + type("(string)", function () { + return this; + }); + + state.syntax["(identifier)"] = { + type: "(identifier)", + lbp: 0, + identifier: true, + + nud: function () { + var v = this.value; + var s = scope[v]; + var f; + var block; + + if (typeof s === "function") { + // Protection against accidental inheritance. + s = undefined; + } else if (!funct["(blockscope)"].current.has(v) && typeof s === "boolean") { + f = funct; + funct = functions[0]; + addlabel(v, { type: "var" }); + s = funct; + funct = f; + } + + block = funct["(blockscope)"].getlabel(v); + + // The name is in scope and defined in the current function. + if (funct === s || block) { + // Change 'unused' to 'var', and reject labels. + // the name is in a block scope. + switch (block ? block[v]["(type)"] : funct[v]) { + case "unused": + if (block) block[v]["(type)"] = "var"; + else funct[v] = "var"; + break; + case "unction": + if (block) block[v]["(type)"] = "function"; + else funct[v] = "function"; + this["function"] = true; + break; + case "const": + setprop(funct, v, { unused: false }); + break; + case "function": + this["function"] = true; + break; + case "label": + warning("W037", state.tokens.curr, v); + break; + } + } else if (funct["(global)"]) { + // The name is not defined in the function. If we are in the global + // scope, then we have an undefined variable. + // + // Operators typeof and delete do not raise runtime errors even if + // the base object of a reference is null so no need to display warning + // if we're inside of typeof or delete. + + if (typeof predefined[v] !== "boolean") { + // Attempting to subscript a null reference will throw an + // error, even within the typeof and delete operators + if (!(anonname === "typeof" || anonname === "delete") || + (state.tokens.next && (state.tokens.next.value === "." || + state.tokens.next.value === "["))) { + + // if we're in a list comprehension, variables are declared + // locally and used before being defined. So we check + // the presence of the given variable in the comp array + // before declaring it undefined. + + if (!funct["(comparray)"].check(v)) { + isundef(funct, "W117", state.tokens.curr, v); + } + } + } + + note_implied(state.tokens.curr); + } else { + // If the name is already defined in the current + // function, but not as outer, then there is a scope error. + + switch (funct[v]) { + case "closure": + case "function": + case "var": + case "unused": + warning("W038", state.tokens.curr, v); + break; + case "label": + warning("W037", state.tokens.curr, v); + break; + case "outer": + case "global": + break; + default: + // If the name is defined in an outer function, make an outer entry, + // and if it was unused, make it var. + if (s === true) { + funct[v] = true; + } else if (s === null) { + warning("W039", state.tokens.curr, v); + note_implied(state.tokens.curr); + } else if (typeof s !== "object") { + // Operators typeof and delete do not raise runtime errors even + // if the base object of a reference is null so no need to + // + // display warning if we're inside of typeof or delete. + // Attempting to subscript a null reference will throw an + // error, even within the typeof and delete operators + if (!(anonname === "typeof" || anonname === "delete") || + (state.tokens.next && + (state.tokens.next.value === "." || state.tokens.next.value === "["))) { + + isundef(funct, "W117", state.tokens.curr, v); + } + funct[v] = true; + note_implied(state.tokens.curr); + } else { + switch (s[v]) { + case "function": + case "unction": + this["function"] = true; + s[v] = "closure"; + funct[v] = s["(global)"] ? "global" : "outer"; + break; + case "var": + case "unused": + s[v] = "closure"; + funct[v] = s["(global)"] ? "global" : "outer"; + break; + case "const": + setprop(s, v, { unused: false }); + break; + case "closure": + funct[v] = s["(global)"] ? "global" : "outer"; + break; + case "label": + warning("W037", state.tokens.curr, v); + } + } + } + } + return this; + }, + + led: function () { + error("E033", state.tokens.next, state.tokens.next.value); + } + }; + + type("(regexp)", function () { + return this; + }); + + // ECMAScript parser + + delim("(endline)"); + delim("(begin)"); + delim("(end)").reach = true; + delim("(error)").reach = true; + delim("}").reach = true; + delim(")"); + delim("]"); + delim("\"").reach = true; + delim("'").reach = true; + delim(";"); + delim(":").reach = true; + delim("#"); + + reserve("else"); + reserve("case").reach = true; + reserve("catch"); + reserve("default").reach = true; + reserve("finally"); + reservevar("arguments", function (x) { + if (state.directive["use strict"] && funct["(global)"]) { + warning("E008", x); + } + }); + reservevar("eval"); + reservevar("false"); + reservevar("Infinity"); + reservevar("null"); + reservevar("this", function (x) { + if (state.directive["use strict"] && !state.option.validthis && ((funct["(statement)"] && + funct["(name)"].charAt(0) > "Z") || funct["(global)"])) { + warning("W040", x); + } + }); + reservevar("true"); + reservevar("undefined"); + + assignop("=", "assign", 20); + assignop("+=", "assignadd", 20); + assignop("-=", "assignsub", 20); + assignop("*=", "assignmult", 20); + assignop("/=", "assigndiv", 20).nud = function () { + error("E014"); + }; + assignop("%=", "assignmod", 20); + + bitwiseassignop("&=", "assignbitand", 20); + bitwiseassignop("|=", "assignbitor", 20); + bitwiseassignop("^=", "assignbitxor", 20); + bitwiseassignop("<<=", "assignshiftleft", 20); + bitwiseassignop(">>=", "assignshiftright", 20); + bitwiseassignop(">>>=", "assignshiftrightunsigned", 20); + infix(",", function (left, that) { + var expr; + that.exprs = [left]; + if (!comma({peek: true})) { + return that; + } + while (true) { + if (!(expr = expression(10))) { + break; + } + that.exprs.push(expr); + if (state.tokens.next.value !== "," || !comma()) { + break; + } + } + return that; + }, 10, true); + + infix("?", function (left, that) { + increaseComplexityCount(); + that.left = left; + that.right = expression(10); + advance(":"); + that["else"] = expression(10); + return that; + }, 30); + + var orPrecendence = 40; + infix("||", function (left, that) { + increaseComplexityCount(); + that.left = left; + that.right = expression(orPrecendence); + return that; + }, orPrecendence); + infix("&&", "and", 50); + bitwise("|", "bitor", 70); + bitwise("^", "bitxor", 80); + bitwise("&", "bitand", 90); + relation("==", function (left, right) { + var eqnull = state.option.eqnull && (left.value === "null" || right.value === "null"); + + switch (true) { + case !eqnull && state.option.eqeqeq: + this.from = this.character; + warning("W116", this, "===", "=="); + break; + case isPoorRelation(left): + warning("W041", this, "===", left.value); + break; + case isPoorRelation(right): + warning("W041", this, "===", right.value); + break; + case isTypoTypeof(right, left): + warning("W122", this, right.value); + break; + case isTypoTypeof(left, right): + warning("W122", this, left.value); + break; + } + + return this; + }); + relation("===", function (left, right) { + if (isTypoTypeof(right, left)) { + warning("W122", this, right.value); + } else if (isTypoTypeof(left, right)) { + warning("W122", this, left.value); + } + return this; + }); + relation("!=", function (left, right) { + var eqnull = state.option.eqnull && + (left.value === "null" || right.value === "null"); + + if (!eqnull && state.option.eqeqeq) { + this.from = this.character; + warning("W116", this, "!==", "!="); + } else if (isPoorRelation(left)) { + warning("W041", this, "!==", left.value); + } else if (isPoorRelation(right)) { + warning("W041", this, "!==", right.value); + } else if (isTypoTypeof(right, left)) { + warning("W122", this, right.value); + } else if (isTypoTypeof(left, right)) { + warning("W122", this, left.value); + } + return this; + }); + relation("!==", function (left, right) { + if (isTypoTypeof(right, left)) { + warning("W122", this, right.value); + } else if (isTypoTypeof(left, right)) { + warning("W122", this, left.value); + } + return this; + }); + relation("<"); + relation(">"); + relation("<="); + relation(">="); + bitwise("<<", "shiftleft", 120); + bitwise(">>", "shiftright", 120); + bitwise(">>>", "shiftrightunsigned", 120); + infix("in", "in", 120); + infix("instanceof", "instanceof", 120); + infix("+", function (left, that) { + var right = expression(130); + if (left && right && left.id === "(string)" && right.id === "(string)") { + left.value += right.value; + left.character = right.character; + if (!state.option.scripturl && reg.javascriptURL.test(left.value)) { + warning("W050", left); + } + return left; + } + that.left = left; + that.right = right; + return that; + }, 130); + prefix("+", "num"); + prefix("+++", function () { + warning("W007"); + this.right = expression(150); + this.arity = "unary"; + return this; + }); + infix("+++", function (left) { + warning("W007"); + this.left = left; + this.right = expression(130); + return this; + }, 130); + infix("-", "sub", 130); + prefix("-", "neg"); + prefix("---", function () { + warning("W006"); + this.right = expression(150); + this.arity = "unary"; + return this; + }); + infix("---", function (left) { + warning("W006"); + this.left = left; + this.right = expression(130); + return this; + }, 130); + infix("*", "mult", 140); + infix("/", "div", 140); + infix("%", "mod", 140); + + suffix("++", "postinc"); + prefix("++", "preinc"); + state.syntax["++"].exps = true; + + suffix("--", "postdec"); + prefix("--", "predec"); + state.syntax["--"].exps = true; + prefix("delete", function () { + var p = expression(10); + if (!p || (p.id !== "." && p.id !== "[")) { + warning("W051"); + } + this.first = p; + return this; + }).exps = true; + + prefix("~", function () { + if (state.option.bitwise) { + warning("W052", this, "~"); + } + expression(150); + return this; + }); + + prefix("...", function () { + if (!state.option.inESNext()) { + warning("W104", this, "spread/rest operator"); + } + if (!state.tokens.next.identifier) { + error("E030", state.tokens.next, state.tokens.next.value); + } + expression(150); + return this; + }); + + prefix("!", function () { + this.right = expression(150); + this.arity = "unary"; + + if (!this.right) { // '!' followed by nothing? Give up. + quit("E041", this.line || 0); + } + + if (bang[this.right.id] === true) { + warning("W018", this, "!"); + } + return this; + }); + + prefix("typeof", "typeof"); + prefix("new", function () { + var c = expression(155), i; + if (c && c.id !== "function") { + if (c.identifier) { + c["new"] = true; + switch (c.value) { + case "Number": + case "String": + case "Boolean": + case "Math": + case "JSON": + warning("W053", state.tokens.prev, c.value); + break; + case "Function": + if (!state.option.evil) { + warning("W054"); + } + break; + case "Date": + case "RegExp": + case "this": + break; + default: + if (c.id !== "function") { + i = c.value.substr(0, 1); + if (state.option.newcap && (i < "A" || i > "Z") && !_.has(global, c.value)) { + warning("W055", state.tokens.curr); + } + } + } + } else { + if (c.id !== "." && c.id !== "[" && c.id !== "(") { + warning("W056", state.tokens.curr); + } + } + } else { + if (!state.option.supernew) + warning("W057", this); + } + adjacent(state.tokens.curr, state.tokens.next); + if (state.tokens.next.id !== "(" && !state.option.supernew) { + warning("W058", state.tokens.curr, state.tokens.curr.value); + } + this.first = c; + return this; + }); + state.syntax["new"].exps = true; + + prefix("void").exps = true; + + infix(".", function (left, that) { + adjacent(state.tokens.prev, state.tokens.curr); + nobreak(); + var m = identifier(false, true); + + if (typeof m === "string") { + countMember(m); + } + + that.left = left; + that.right = m; + + if (m && m === "hasOwnProperty" && state.tokens.next.value === "=") { + warning("W001"); + } + + if (left && left.value === "arguments" && (m === "callee" || m === "caller")) { + if (state.option.noarg) + warning("W059", left, m); + else if (state.directive["use strict"]) + error("E008"); + } else if (!state.option.evil && left && left.value === "document" && + (m === "write" || m === "writeln")) { + warning("W060", left); + } + + if (!state.option.evil && (m === "eval" || m === "execScript")) { + warning("W061"); + } + + return that; + }, 160, true); + + infix("(", function (left, that) { + if (state.tokens.prev.id !== "}" && state.tokens.prev.id !== ")") { + nobreak(state.tokens.prev, state.tokens.curr); + } + + nospace(); + if (state.option.immed && left && !left.immed && left.id === "function") { + warning("W062"); + } + + var n = 0; + var p = []; + + if (left) { + if (left.type === "(identifier)") { + if (left.value.match(/^[A-Z]([A-Z0-9_$]*[a-z][A-Za-z0-9_$]*)?$/)) { + if ("Number String Boolean Date Object".indexOf(left.value) === -1) { + if (left.value === "Math") { + warning("W063", left); + } else if (state.option.newcap) { + warning("W064", left); + } + } + } + } + } + + if (state.tokens.next.id !== ")") { + for (;;) { + p[p.length] = expression(10); + n += 1; + if (state.tokens.next.id !== ",") { + break; + } + comma(); + } + } + + advance(")"); + nospace(state.tokens.prev, state.tokens.curr); + + if (typeof left === "object") { + if (state.option.inES3() && left.value === "parseInt" && n === 1) { + warning("W065", state.tokens.curr); + } + if (!state.option.evil) { + if (left.value === "eval" || left.value === "Function" || + left.value === "execScript") { + warning("W061", left); + + if (p[0] && [0].id === "(string)") { + addInternalSrc(left, p[0].value); + } + } else if (p[0] && p[0].id === "(string)" && + (left.value === "setTimeout" || + left.value === "setInterval")) { + warning("W066", left); + addInternalSrc(left, p[0].value); + + // window.setTimeout/setInterval + } else if (p[0] && p[0].id === "(string)" && + left.value === "." && + left.left.value === "window" && + (left.right === "setTimeout" || + left.right === "setInterval")) { + warning("W066", left); + addInternalSrc(left, p[0].value); + } + } + if (!left.identifier && left.id !== "." && left.id !== "[" && + left.id !== "(" && left.id !== "&&" && left.id !== "||" && + left.id !== "?") { + warning("W067", left); + } + } + + that.left = left; + return that; + }, 155, true).exps = true; + + prefix("(", function () { + nospace(); + var bracket, brackets = []; + var pn, pn1, i = 0; + var ret; + var parens = 1; + + do { + pn = peek(i); + + if (pn.value === "(") { + parens += 1; + } else if (pn.value === ")") { + parens -= 1; + } + + i += 1; + pn1 = peek(i); + i += 1; + } while (!(parens === 0 && pn.value === ")") && + pn1.value !== "=>" && pn1.value !== ";" && pn1.type !== "(end)"); + + if (state.tokens.next.id === "function") { + state.tokens.next.immed = true; + } + + var exprs = []; + + if (state.tokens.next.id !== ")") { + for (;;) { + if (pn1.value === "=>" && _.contains(["{", "["], state.tokens.next.value)) { + bracket = state.tokens.next; + bracket.left = destructuringExpression(); + brackets.push(bracket); + for (var t in bracket.left) { + exprs.push(bracket.left[t].token); + } + } else { + exprs.push(expression(10)); + } + if (state.tokens.next.id !== ",") { + break; + } + comma(); + } + } + + advance(")", this); + nospace(state.tokens.prev, state.tokens.curr); + if (state.option.immed && exprs[0] && exprs[0].id === "function") { + if (state.tokens.next.id !== "(" && + (state.tokens.next.id !== "." || (peek().value !== "call" && peek().value !== "apply"))) { + warning("W068", this); + } + } + + if (state.tokens.next.value === "=>") { + return exprs; + } + if (!exprs.length) { + return; + } + if (exprs.length > 1) { + ret = Object.create(state.syntax[","]); + ret.exprs = exprs; + } else { + ret = exprs[0]; + } + if (ret) { + ret.paren = true; + } + return ret; + }); + + application("=>"); + + infix("[", function (left, that) { + nobreak(state.tokens.prev, state.tokens.curr); + nospace(); + var e = expression(10), s; + if (e && e.type === "(string)") { + if (!state.option.evil && (e.value === "eval" || e.value === "execScript")) { + warning("W061", that); + } + + countMember(e.value); + if (!state.option.sub && reg.identifier.test(e.value)) { + s = state.syntax[e.value]; + if (!s || !isReserved(s)) { + warning("W069", state.tokens.prev, e.value); + } + } + } + advance("]", that); + + if (e && e.value === "hasOwnProperty" && state.tokens.next.value === "=") { + warning("W001"); + } + + nospace(state.tokens.prev, state.tokens.curr); + that.left = left; + that.right = e; + return that; + }, 160, true); + + function comprehensiveArrayExpression() { + var res = {}; + res.exps = true; + funct["(comparray)"].stack(); + + // Handle reversed for expressions, used in spidermonkey + var reversed = false; + if (state.tokens.next.value !== "for") { + reversed = true; + if (!state.option.inMoz(true)) { + warning("W116", state.tokens.next, "for", state.tokens.next.value); + } + funct["(comparray)"].setState("use"); + res.right = expression(10); + } + + advance("for"); + if (state.tokens.next.value === "each") { + advance("each"); + if (!state.option.inMoz(true)) { + warning("W118", state.tokens.curr, "for each"); + } + } + advance("("); + funct["(comparray)"].setState("define"); + res.left = expression(130); + if (_.contains(["in", "of"], state.tokens.next.value)) { + advance(); + } else { + error("E045", state.tokens.curr); + } + funct["(comparray)"].setState("generate"); + expression(10); + + advance(")"); + if (state.tokens.next.value === "if") { + advance("if"); + advance("("); + funct["(comparray)"].setState("filter"); + res.filter = expression(10); + advance(")"); + } + + if (!reversed) { + funct["(comparray)"].setState("use"); + res.right = expression(10); + } + + advance("]"); + funct["(comparray)"].unstack(); + return res; + } + + prefix("[", function () { + var blocktype = lookupBlockType(true); + if (blocktype.isCompArray) { + if (!state.option.inESNext()) { + warning("W119", state.tokens.curr, "array comprehension"); + } + return comprehensiveArrayExpression(); + } else if (blocktype.isDestAssign && !state.option.inESNext()) { + warning("W104", state.tokens.curr, "destructuring assignment"); + } + var b = state.tokens.curr.line !== state.tokens.next.line; + this.first = []; + if (b) { + indent += state.option.indent; + if (state.tokens.next.from === indent + state.option.indent) { + indent += state.option.indent; + } + } + while (state.tokens.next.id !== "(end)") { + while (state.tokens.next.id === ",") { + if (!state.option.inES5()) + warning("W070"); + advance(","); + } + if (state.tokens.next.id === "]") { + break; + } + if (b && state.tokens.curr.line !== state.tokens.next.line) { + indentation(); + } + this.first.push(expression(10)); + if (state.tokens.next.id === ",") { + comma({ allowTrailing: true }); + if (state.tokens.next.id === "]" && !state.option.inES5(true)) { + warning("W070", state.tokens.curr); + break; + } + } else { + break; + } + } + if (b) { + indent -= state.option.indent; + indentation(); + } + advance("]", this); + return this; + }, 160); + + + function property_name() { + var id = optionalidentifier(false, true); + + if (!id) { + if (state.tokens.next.id === "(string)") { + id = state.tokens.next.value; + advance(); + } else if (state.tokens.next.id === "(number)") { + id = state.tokens.next.value.toString(); + advance(); + } + } + + if (id === "hasOwnProperty") { + warning("W001"); + } + + return id; + } + + function functionparams(parsed) { + var curr, next; + var params = []; + var ident; + var tokens = []; + var t; + var pastDefault = false; + + if (parsed) { + if (Array.isArray(parsed)) { + for (var i in parsed) { + curr = parsed[i]; + if (_.contains(["{", "["], curr.id)) { + for (t in curr.left) { + t = tokens[t]; + if (t && t.id) { + params.push(t.id); + addlabel(t.id, { type: "unused", token: t.token }); + } + } + } else if (curr.value === "...") { + if (!state.option.inESNext()) { + warning("W104", curr, "spread/rest operator"); + } + continue; + } else if (curr.value !== ",") { + params.push(curr.value); + addlabel(curr.value, { type: "unused", token: curr }); + } + } + return params; + } else { + if (parsed.identifier === true) { + addlabel(parsed.value, { type: "unused", token: parsed }); + return [parsed]; + } + } + } + + next = state.tokens.next; + + advance("("); + nospace(); + + if (state.tokens.next.id === ")") { + advance(")"); + return; + } + + for (;;) { + if (_.contains(["{", "["], state.tokens.next.id)) { + tokens = destructuringExpression(); + for (t in tokens) { + t = tokens[t]; + if (t.id) { + params.push(t.id); + addlabel(t.id, { type: "unused", token: t.token }); + } + } + } else if (state.tokens.next.value === "...") { + if (!state.option.inESNext()) { + warning("W104", state.tokens.next, "spread/rest operator"); + } + advance("..."); + nospace(); + ident = identifier(true); + params.push(ident); + addlabel(ident, { type: "unused", token: state.tokens.curr }); + } else { + ident = identifier(true); + params.push(ident); + addlabel(ident, { type: "unused", token: state.tokens.curr }); + } + + // it is a syntax error to have a regular argument after a default argument + if (pastDefault) { + if (state.tokens.next.id !== "=") { + error("E051", state.tokens.current); + } + } + if (state.tokens.next.id === "=") { + if (!state.option.inESNext()) { + warning("W119", state.tokens.next, "default parameters"); + } + advance("="); + pastDefault = true; + expression(10); + } + if (state.tokens.next.id === ",") { + comma(); + } else { + advance(")", next); + nospace(state.tokens.prev, state.tokens.curr); + return params; + } + } + } + + function setprop(funct, name, values) { + if (!funct["(properties)"][name]) { + funct["(properties)"][name] = { unused: false }; + } + + _.extend(funct["(properties)"][name], values); + } + + function getprop(funct, name, prop) { + if (!funct["(properties)"][name]) + return null; + + return funct["(properties)"][name][prop] || null; + } + + function functor(name, token, scope, overwrites) { + var funct = { + "(name)" : name, + "(breakage)" : 0, + "(loopage)" : 0, + "(scope)" : scope, + "(tokens)" : {}, + "(properties)": {}, + + "(catch)" : false, + "(global)" : false, + + "(line)" : null, + "(character)" : null, + "(metrics)" : null, + "(statement)" : null, + "(context)" : null, + "(blockscope)": null, + "(comparray)" : null, + "(generator)" : null, + "(params)" : null + }; + + if (token) { + _.extend(funct, { + "(line)" : token.line, + "(character)": token.character, + "(metrics)" : createMetrics(token) + }); + } + + _.extend(funct, overwrites); + + if (funct["(context)"]) { + funct["(blockscope)"] = funct["(context)"]["(blockscope)"]; + funct["(comparray)"] = funct["(context)"]["(comparray)"]; + } + + return funct; + } + + function doFunction(name, statement, generator, fatarrowparams) { + var f; + var oldOption = state.option; + var oldIgnored = state.ignored; + var oldScope = scope; + + state.option = Object.create(state.option); + state.ignored = Object.create(state.ignored); + scope = Object.create(scope); + + funct = functor(name || "\"" + anonname + "\"", state.tokens.next, scope, { + "(statement)": statement, + "(context)": funct, + "(generator)": generator ? true : null + }); + + f = funct; + state.tokens.curr.funct = funct; + + functions.push(funct); + + if (name) { + addlabel(name, { type: "function" }); + } + + funct["(params)"] = functionparams(fatarrowparams); + funct["(metrics)"].verifyMaxParametersPerFunction(funct["(params)"]); + + // So we parse fat-arrow functions after we encounter =>. So basically + // doFunction is called with the left side of => as its last argument. + // This means that the parser, at that point, had already added its + // arguments to the undefs array and here we undo that. + + JSHINT.undefs = _.filter(JSHINT.undefs, function (item) { + return !_.contains(_.union(fatarrowparams), item[2]); + }); + + block(false, true, true, fatarrowparams ? true : false); + + if (!state.option.noyield && generator && funct["(generator)"] !== "yielded") { + warning("W124", state.tokens.curr); + } + + funct["(metrics)"].verifyMaxStatementsPerFunction(); + funct["(metrics)"].verifyMaxComplexityPerFunction(); + funct["(unusedOption)"] = state.option.unused; + + scope = oldScope; + state.option = oldOption; + state.ignored = oldIgnored; + funct["(last)"] = state.tokens.curr.line; + funct["(lastcharacter)"] = state.tokens.curr.character; + + _.map(Object.keys(funct), function (key) { + if (key[0] === "(") return; + funct["(blockscope)"].unshadow(key); + }); + + funct = funct["(context)"]; + + return f; + } + + function createMetrics(functionStartToken) { + return { + statementCount: 0, + nestedBlockDepth: -1, + ComplexityCount: 1, + + verifyMaxStatementsPerFunction: function () { + if (state.option.maxstatements && + this.statementCount > state.option.maxstatements) { + warning("W071", functionStartToken, this.statementCount); + } + }, + + verifyMaxParametersPerFunction: function (params) { + params = params || []; + + if (state.option.maxparams && params.length > state.option.maxparams) { + warning("W072", functionStartToken, params.length); + } + }, + + verifyMaxNestedBlockDepthPerFunction: function () { + if (state.option.maxdepth && + this.nestedBlockDepth > 0 && + this.nestedBlockDepth === state.option.maxdepth + 1) { + warning("W073", null, this.nestedBlockDepth); + } + }, + + verifyMaxComplexityPerFunction: function () { + var max = state.option.maxcomplexity; + var cc = this.ComplexityCount; + if (max && cc > max) { + warning("W074", functionStartToken, cc); + } + } + }; + } + + function increaseComplexityCount() { + funct["(metrics)"].ComplexityCount += 1; + } + + // Parse assignments that were found instead of conditionals. + // For example: if (a = 1) { ... } + + function checkCondAssignment(expr) { + var id, paren; + if (expr) { + id = expr.id; + paren = expr.paren; + if (id === "," && (expr = expr.exprs[expr.exprs.length - 1])) { + id = expr.id; + paren = paren || expr.paren; + } + } + switch (id) { + case "=": + case "+=": + case "-=": + case "*=": + case "%=": + case "&=": + case "|=": + case "^=": + case "/=": + if (!paren && !state.option.boss) { + warning("W084"); + } + } + } + + + (function (x) { + x.nud = function (isclassdef) { + var b, f, i, p, t, g; + var props = {}; // All properties, including accessors + var tag = ""; + + function saveProperty(name, tkn) { + if (props[name] && _.has(props, name)) + warning("W075", state.tokens.next, i); + else + props[name] = {}; + + props[name].basic = true; + props[name].basictkn = tkn; + } + + function saveSetter(name, tkn) { + if (props[name] && _.has(props, name)) { + if (props[name].basic || props[name].setter) + warning("W075", state.tokens.next, i); + } else { + props[name] = {}; + } + + props[name].setter = true; + props[name].setterToken = tkn; + } + + function saveGetter(name) { + if (props[name] && _.has(props, name)) { + if (props[name].basic || props[name].getter) + warning("W075", state.tokens.next, i); + } else { + props[name] = {}; + } + + props[name].getter = true; + props[name].getterToken = state.tokens.curr; + } + + b = state.tokens.curr.line !== state.tokens.next.line; + if (b) { + indent += state.option.indent; + if (state.tokens.next.from === indent + state.option.indent) { + indent += state.option.indent; + } + } + + for (;;) { + if (state.tokens.next.id === "}") { + break; + } + + if (b) { + indentation(); + } + + if (isclassdef && state.tokens.next.value === "static") { + advance("static"); + tag = "static "; + } + + if (state.tokens.next.value === "get" && peek().id !== ":") { + advance("get"); + + if (!state.option.inES5(!isclassdef)) { + error("E034"); + } + + i = property_name(); + + // ES6 allows for get() {...} and set() {...} method + // definition shorthand syntax, so we don't produce an error + // if the esnext option is enabled. + if (!i && !state.option.inESNext()) { + error("E035"); + } + + // It is a Syntax Error if PropName of MethodDefinition is + // "constructor" and SpecialMethod of MethodDefinition is true. + if (isclassdef && i === "constructor") { + error("E049", state.tokens.next, "class getter method", i); + } + + // We don't want to save this getter unless it's an actual getter + // and not an ES6 concise method + if (i) { + saveGetter(tag + i); + } + + t = state.tokens.next; + adjacent(state.tokens.curr, state.tokens.next); + f = doFunction(); + p = f["(params)"]; + + // Don't warn about getter/setter pairs if this is an ES6 concise method + if (i && p) { + warning("W076", t, p[0], i); + } + + adjacent(state.tokens.curr, state.tokens.next); + } else if (state.tokens.next.value === "set" && peek().id !== ":") { + advance("set"); + + if (!state.option.inES5(!isclassdef)) { + error("E034"); + } + + i = property_name(); + + // ES6 allows for get() {...} and set() {...} method + // definition shorthand syntax, so we don't produce an error + // if the esnext option is enabled. + if (!i && !state.option.inESNext()) { + error("E035"); + } + + // It is a Syntax Error if PropName of MethodDefinition is + // "constructor" and SpecialMethod of MethodDefinition is true. + if (isclassdef && i === "constructor") { + error("E049", state.tokens.next, "class setter method", i); + } + + // We don't want to save this getter unless it's an actual getter + // and not an ES6 concise method + if (i) { + saveSetter(tag + i, state.tokens.next); + } + + t = state.tokens.next; + adjacent(state.tokens.curr, state.tokens.next); + f = doFunction(); + p = f["(params)"]; + + // Don't warn about getter/setter pairs if this is an ES6 concise method + if (i && (!p || p.length !== 1)) { + warning("W077", t, i); + } + } else { + g = false; + if (state.tokens.next.value === "*" && state.tokens.next.type === "(punctuator)") { + if (!state.option.inESNext()) { + warning("W104", state.tokens.next, "generator functions"); + } + advance("*"); + g = true; + } + i = property_name(); + saveProperty(tag + i, state.tokens.next); + + if (typeof i !== "string") { + break; + } + + if (state.tokens.next.value === "(") { + if (!state.option.inESNext()) { + warning("W104", state.tokens.curr, "concise methods"); + } + doFunction(i, undefined, g); + } else if (!isclassdef) { + advance(":"); + nonadjacent(state.tokens.curr, state.tokens.next); + expression(10); + } + } + // It is a Syntax Error if PropName of MethodDefinition is "prototype". + if (isclassdef && i === "prototype") { + error("E049", state.tokens.next, "class method", i); + } + + countMember(i); + if (isclassdef) { + tag = ""; + continue; + } + if (state.tokens.next.id === ",") { + comma({ allowTrailing: true, property: true }); + if (state.tokens.next.id === ",") { + warning("W070", state.tokens.curr); + } else if (state.tokens.next.id === "}" && !state.option.inES5(true)) { + warning("W070", state.tokens.curr); + } + } else { + break; + } + } + if (b) { + indent -= state.option.indent; + indentation(); + } + advance("}", this); + + // Check for lonely setters if in the ES5 mode. + if (state.option.inES5()) { + for (var name in props) { + if (_.has(props, name) && props[name].setter && !props[name].getter) { + warning("W078", props[name].setterToken); + } + } + } + return this; + }; + x.fud = function () { + error("E036", state.tokens.curr); + }; + }(delim("{"))); + + function destructuringExpression() { + var id, ids; + var identifiers = []; + if (!state.option.inESNext()) { + warning("W104", state.tokens.curr, "destructuring expression"); + } + var nextInnerDE = function () { + var ident; + if (_.contains(["[", "{"], state.tokens.next.value)) { + ids = destructuringExpression(); + for (var id in ids) { + id = ids[id]; + identifiers.push({ id: id.id, token: id.token }); + } + } else if (state.tokens.next.value === ",") { + identifiers.push({ id: null, token: state.tokens.curr }); + } else if (state.tokens.next.value === "(") { + advance("("); + nextInnerDE(); + advance(")"); + } else { + ident = identifier(); + if (ident) + identifiers.push({ id: ident, token: state.tokens.curr }); + } + }; + if (state.tokens.next.value === "[") { + advance("["); + nextInnerDE(); + while (state.tokens.next.value !== "]") { + advance(","); + nextInnerDE(); + } + advance("]"); + } else if (state.tokens.next.value === "{") { + advance("{"); + id = identifier(); + if (state.tokens.next.value === ":") { + advance(":"); + nextInnerDE(); + } else { + identifiers.push({ id: id, token: state.tokens.curr }); + } + while (state.tokens.next.value !== "}") { + advance(","); + id = identifier(); + if (state.tokens.next.value === ":") { + advance(":"); + nextInnerDE(); + } else { + identifiers.push({ id: id, token: state.tokens.curr }); + } + } + advance("}"); + } + return identifiers; + } + + function destructuringExpressionMatch(tokens, value) { + var first = value.first; + + if (!first) + return; + + _.zip(tokens, Array.isArray(first) ? first : [ first ]).forEach(function (val) { + var token = val[0]; + var value = val[1]; + + if (token && value) + token.first = value; + else if (token && token.first && !value) + warning("W080", token.first, token.first.value); + }); + } + + var conststatement = stmt("const", function (prefix) { + var tokens; + var value; + var lone; // State variable to know if it is a lone identifier, or a destructuring statement. + + if (!state.option.inESNext()) + warning("W104", state.tokens.curr, "const"); + + this.first = []; + for (;;) { + var names = []; + nonadjacent(state.tokens.curr, state.tokens.next); + if (_.contains(["{", "["], state.tokens.next.value)) { + tokens = destructuringExpression(); + lone = false; + } else { + tokens = [ { id: identifier(), token: state.tokens.curr } ]; + lone = true; + } + for (var t in tokens) { + if (tokens.hasOwnProperty(t)) { + t = tokens[t]; + if (funct[t.id] === "const") { + warning("E011", null, t.id); + } + if (funct["(global)"] && predefined[t.id] === false) { + warning("W079", t.token, t.id); + } + if (t.id) { + addlabel(t.id, { token: t.token, type: "const", unused: true }); + names.push(t.token); + } + } + } + if (prefix) { + break; + } + + this.first = this.first.concat(names); + + if (state.tokens.next.id !== "=") { + warning("E012", state.tokens.curr, state.tokens.curr.value); + } + + if (state.tokens.next.id === "=") { + nonadjacent(state.tokens.curr, state.tokens.next); + advance("="); + nonadjacent(state.tokens.curr, state.tokens.next); + if (state.tokens.next.id === "undefined") { + warning("W080", state.tokens.prev, state.tokens.prev.value); + } + if (peek(0).id === "=" && state.tokens.next.identifier) { + warning("W120", state.tokens.next, state.tokens.next.value); + } + value = expression(10); + if (lone) { + tokens[0].first = value; + } else { + destructuringExpressionMatch(names, value); + } + } + + if (state.tokens.next.id !== ",") { + break; + } + comma(); + } + return this; + }); + + conststatement.exps = true; + var varstatement = stmt("var", function (prefix) { + // JavaScript does not have block scope. It only has function scope. So, + // declaring a variable in a block can have unexpected consequences. + var tokens, lone, value; + + if (funct["(onevar)"] && state.option.onevar) { + warning("W081"); + } else if (!funct["(global)"]) { + funct["(onevar)"] = true; + } + + this.first = []; + for (;;) { + var names = []; + nonadjacent(state.tokens.curr, state.tokens.next); + if (_.contains(["{", "["], state.tokens.next.value)) { + tokens = destructuringExpression(); + lone = false; + } else { + tokens = [ { id: identifier(), token: state.tokens.curr } ]; + lone = true; + } + for (var t in tokens) { + if (tokens.hasOwnProperty(t)) { + t = tokens[t]; + if (state.option.inESNext() && funct[t.id] === "const") { + warning("E011", null, t.id); + } + if (funct["(global)"] && predefined[t.id] === false) { + warning("W079", t.token, t.id); + } + if (t.id) { + addlabel(t.id, { type: "unused", token: t.token }); + names.push(t.token); + } + } + } + if (prefix) { + break; + } + + this.first = this.first.concat(names); + + if (state.tokens.next.id === "=") { + nonadjacent(state.tokens.curr, state.tokens.next); + advance("="); + nonadjacent(state.tokens.curr, state.tokens.next); + if (state.tokens.next.id === "undefined") { + warning("W080", state.tokens.prev, state.tokens.prev.value); + } + if (peek(0).id === "=" && state.tokens.next.identifier) { + warning("W120", state.tokens.next, state.tokens.next.value); + } + value = expression(10); + if (lone) { + tokens[0].first = value; + } else { + destructuringExpressionMatch(names, value); + } + } + + if (state.tokens.next.id !== ",") { + break; + } + comma(); + } + return this; + }); + varstatement.exps = true; + + var letstatement = stmt("let", function (prefix) { + var tokens, lone, value, letblock; + + if (!state.option.inESNext()) { + warning("W104", state.tokens.curr, "let"); + } + + if (state.tokens.next.value === "(") { + if (!state.option.inMoz(true)) { + warning("W118", state.tokens.next, "let block"); + } + advance("("); + funct["(blockscope)"].stack(); + letblock = true; + } else if (funct["(nolet)"]) { + error("E048", state.tokens.curr); + } + + if (funct["(onevar)"] && state.option.onevar) { + warning("W081"); + } else if (!funct["(global)"]) { + funct["(onevar)"] = true; + } + + this.first = []; + for (;;) { + var names = []; + nonadjacent(state.tokens.curr, state.tokens.next); + if (_.contains(["{", "["], state.tokens.next.value)) { + tokens = destructuringExpression(); + lone = false; + } else { + tokens = [ { id: identifier(), token: state.tokens.curr.value } ]; + lone = true; + } + for (var t in tokens) { + if (tokens.hasOwnProperty(t)) { + t = tokens[t]; + if (state.option.inESNext() && funct[t.id] === "const") { + warning("E011", null, t.id); + } + if (funct["(global)"] && predefined[t.id] === false) { + warning("W079", t.token, t.id); + } + if (t.id && !funct["(nolet)"]) { + addlabel(t.id, { type: "unused", token: t.token, islet: true }); + names.push(t.token); + } + } + } + if (prefix) { + break; + } + + this.first = this.first.concat(names); + + if (state.tokens.next.id === "=") { + nonadjacent(state.tokens.curr, state.tokens.next); + advance("="); + nonadjacent(state.tokens.curr, state.tokens.next); + if (state.tokens.next.id === "undefined") { + warning("W080", state.tokens.prev, state.tokens.prev.value); + } + if (peek(0).id === "=" && state.tokens.next.identifier) { + warning("W120", state.tokens.next, state.tokens.next.value); + } + value = expression(10); + if (lone) { + tokens[0].first = value; + } else { + destructuringExpressionMatch(names, value); + } + } + + if (state.tokens.next.id !== ",") { + break; + } + comma(); + } + if (letblock) { + advance(")"); + block(true, true); + this.block = true; + funct["(blockscope)"].unstack(); + } + + return this; + }); + letstatement.exps = true; + + blockstmt("class", function () { + return classdef.call(this, true); + }); + + function classdef(stmt) { + /*jshint validthis:true */ + if (!state.option.inESNext()) { + warning("W104", state.tokens.curr, "class"); + } + if (stmt) { + // BindingIdentifier + this.name = identifier(); + addlabel(this.name, { type: "unused", token: state.tokens.curr }); + } else if (state.tokens.next.identifier && state.tokens.next.value !== "extends") { + // BindingIdentifier(opt) + this.name = identifier(); + } + classtail(this); + return this; + } + + function classtail(c) { + var strictness = state.directive["use strict"]; + + // ClassHeritage(opt) + if (state.tokens.next.value === "extends") { + advance("extends"); + c.heritage = expression(10); + } + + // A ClassBody is always strict code. + state.directive["use strict"] = true; + advance("{"); + // ClassBody(opt) + c.body = state.syntax["{"].nud(true); + state.directive["use strict"] = strictness; + } + + blockstmt("function", function () { + var generator = false; + if (state.tokens.next.value === "*") { + advance("*"); + if (state.option.inESNext(true)) { + generator = true; + } else { + warning("W119", state.tokens.curr, "function*"); + } + } + if (inblock) { + warning("W082", state.tokens.curr); + + } + var i = identifier(); + if (funct[i] === "const") { + warning("E011", null, i); + } + adjacent(state.tokens.curr, state.tokens.next); + addlabel(i, { type: "unction", token: state.tokens.curr }); + + doFunction(i, { statement: true }, generator); + if (state.tokens.next.id === "(" && state.tokens.next.line === state.tokens.curr.line) { + error("E039"); + } + return this; + }); + + prefix("function", function () { + var generator = false; + if (state.tokens.next.value === "*") { + if (!state.option.inESNext()) { + warning("W119", state.tokens.curr, "function*"); + } + advance("*"); + generator = true; + } + var i = optionalidentifier(); + if (i || state.option.gcl) { + adjacent(state.tokens.curr, state.tokens.next); + } else { + nonadjacent(state.tokens.curr, state.tokens.next); + } + doFunction(i, undefined, generator); + if (!state.option.loopfunc && funct["(loopage)"]) { + warning("W083"); + } + return this; + }); + + blockstmt("if", function () { + var t = state.tokens.next; + increaseComplexityCount(); + state.condition = true; + advance("("); + nonadjacent(this, t); + nospace(); + checkCondAssignment(expression(0)); + advance(")", t); + state.condition = false; + nospace(state.tokens.prev, state.tokens.curr); + block(true, true); + if (state.tokens.next.id === "else") { + nonadjacent(state.tokens.curr, state.tokens.next); + advance("else"); + if (state.tokens.next.id === "if" || state.tokens.next.id === "switch") { + statement(true); + } else { + block(true, true); + } + } + return this; + }); + + blockstmt("try", function () { + var b; + + function doCatch() { + var oldScope = scope; + var e; + + advance("catch"); + nonadjacent(state.tokens.curr, state.tokens.next); + advance("("); + + scope = Object.create(oldScope); + + e = state.tokens.next.value; + if (state.tokens.next.type !== "(identifier)") { + e = null; + warning("E030", state.tokens.next, e); + } + + advance(); + + funct = functor("(catch)", state.tokens.next, scope, { + "(context)" : funct, + "(breakage)" : funct["(breakage)"], + "(loopage)" : funct["(loopage)"], + "(statement)": false, + "(catch)" : true + }); + + if (e) { + addlabel(e, { type: "exception" }); + } + + if (state.tokens.next.value === "if") { + if (!state.option.inMoz(true)) { + warning("W118", state.tokens.curr, "catch filter"); + } + advance("if"); + expression(0); + } + + advance(")"); + + state.tokens.curr.funct = funct; + functions.push(funct); + + block(false); + + scope = oldScope; + + funct["(last)"] = state.tokens.curr.line; + funct["(lastcharacter)"] = state.tokens.curr.character; + funct = funct["(context)"]; + } + + block(true); + + while (state.tokens.next.id === "catch") { + increaseComplexityCount(); + if (b && (!state.option.inMoz(true))) { + warning("W118", state.tokens.next, "multiple catch blocks"); + } + doCatch(); + b = true; + } + + if (state.tokens.next.id === "finally") { + advance("finally"); + block(true); + return; + } + + if (!b) { + error("E021", state.tokens.next, "catch", state.tokens.next.value); + } + + return this; + }); + + blockstmt("while", function () { + var t = state.tokens.next; + funct["(breakage)"] += 1; + funct["(loopage)"] += 1; + increaseComplexityCount(); + advance("("); + nonadjacent(this, t); + nospace(); + checkCondAssignment(expression(0)); + advance(")", t); + nospace(state.tokens.prev, state.tokens.curr); + block(true, true); + funct["(breakage)"] -= 1; + funct["(loopage)"] -= 1; + return this; + }).labelled = true; + + blockstmt("with", function () { + var t = state.tokens.next; + if (state.directive["use strict"]) { + error("E010", state.tokens.curr); + } else if (!state.option.withstmt) { + warning("W085", state.tokens.curr); + } + + advance("("); + nonadjacent(this, t); + nospace(); + expression(0); + advance(")", t); + nospace(state.tokens.prev, state.tokens.curr); + block(true, true); + + return this; + }); + + blockstmt("switch", function () { + var t = state.tokens.next; + var g = false; + var noindent = false; + + funct["(breakage)"] += 1; + advance("("); + nonadjacent(this, t); + nospace(); + checkCondAssignment(expression(0)); + advance(")", t); + nospace(state.tokens.prev, state.tokens.curr); + nonadjacent(state.tokens.curr, state.tokens.next); + t = state.tokens.next; + advance("{"); + nonadjacent(state.tokens.curr, state.tokens.next); + + if (state.tokens.next.from === indent) + noindent = true; + + if (!noindent) + indent += state.option.indent; + + this.cases = []; + + for (;;) { + switch (state.tokens.next.id) { + case "case": + switch (funct["(verb)"]) { + case "yield": + case "break": + case "case": + case "continue": + case "return": + case "switch": + case "throw": + break; + default: + // You can tell JSHint that you don't use break intentionally by + // adding a comment /* falls through */ on a line just before + // the next `case`. + if (!reg.fallsThrough.test(state.lines[state.tokens.next.line - 2])) { + warning("W086", state.tokens.curr, "case"); + } + } + indentation(); + advance("case"); + this.cases.push(expression(20)); + increaseComplexityCount(); + g = true; + advance(":"); + funct["(verb)"] = "case"; + break; + case "default": + switch (funct["(verb)"]) { + case "yield": + case "break": + case "continue": + case "return": + case "throw": + break; + default: + // Do not display a warning if 'default' is the first statement or if + // there is a special /* falls through */ comment. + if (this.cases.length) { + if (!reg.fallsThrough.test(state.lines[state.tokens.next.line - 2])) { + warning("W086", state.tokens.curr, "default"); + } + } + } + indentation(); + advance("default"); + g = true; + advance(":"); + break; + case "}": + if (!noindent) + indent -= state.option.indent; + indentation(); + advance("}", t); + funct["(breakage)"] -= 1; + funct["(verb)"] = undefined; + return; + case "(end)": + error("E023", state.tokens.next, "}"); + return; + default: + indent += state.option.indent; + if (g) { + switch (state.tokens.curr.id) { + case ",": + error("E040"); + return; + case ":": + g = false; + statements(); + break; + default: + error("E025", state.tokens.curr); + return; + } + } else { + if (state.tokens.curr.id === ":") { + advance(":"); + error("E024", state.tokens.curr, ":"); + statements(); + } else { + error("E021", state.tokens.next, "case", state.tokens.next.value); + return; + } + } + indent -= state.option.indent; + } + } + }).labelled = true; + + stmt("debugger", function () { + if (!state.option.debug) { + warning("W087", this); + } + return this; + }).exps = true; + + (function () { + var x = stmt("do", function () { + funct["(breakage)"] += 1; + funct["(loopage)"] += 1; + increaseComplexityCount(); + + this.first = block(true, true); + advance("while"); + var t = state.tokens.next; + nonadjacent(state.tokens.curr, t); + advance("("); + nospace(); + checkCondAssignment(expression(0)); + advance(")", t); + nospace(state.tokens.prev, state.tokens.curr); + funct["(breakage)"] -= 1; + funct["(loopage)"] -= 1; + return this; + }); + x.labelled = true; + x.exps = true; + }()); + + blockstmt("for", function () { + var s, t = state.tokens.next; + var letscope = false; + var foreachtok = null; + + if (t.value === "each") { + foreachtok = t; + advance("each"); + if (!state.option.inMoz(true)) { + warning("W118", state.tokens.curr, "for each"); + } + } + + funct["(breakage)"] += 1; + funct["(loopage)"] += 1; + increaseComplexityCount(); + advance("("); + nonadjacent(this, t); + nospace(); + + // what kind of for(…) statement it is? for(…of…)? for(…in…)? for(…;…;…)? + var nextop; // contains the token of the "in" or "of" operator + var i = 0; + var inof = ["in", "of"]; + do { + nextop = peek(i); + ++i; + } while (!_.contains(inof, nextop.value) && nextop.value !== ";" && + nextop.type !== "(end)"); + + // if we're in a for (… in|of …) statement + if (_.contains(inof, nextop.value)) { + if (!state.option.inESNext() && nextop.value === "of") { + error("W104", nextop, "for of"); + } + if (state.tokens.next.id === "var") { + advance("var"); + state.syntax["var"].fud.call(state.syntax["var"].fud, true); + } else if (state.tokens.next.id === "let") { + advance("let"); + // create a new block scope + letscope = true; + funct["(blockscope)"].stack(); + state.syntax["let"].fud.call(state.syntax["let"].fud, true); + } else { + switch (funct[state.tokens.next.value]) { + case "unused": + funct[state.tokens.next.value] = "var"; + break; + case "var": + break; + default: + if (!funct["(blockscope)"].getlabel(state.tokens.next.value)) + warning("W088", state.tokens.next, state.tokens.next.value); + } + advance(); + } + advance(nextop.value); + expression(20); + advance(")", t); + s = block(true, true); + if (state.option.forin && s && (s.length > 1 || typeof s[0] !== "object" || + s[0].value !== "if")) { + warning("W089", this); + } + funct["(breakage)"] -= 1; + funct["(loopage)"] -= 1; + } else { + if (foreachtok) { + error("E045", foreachtok); + } + if (state.tokens.next.id !== ";") { + if (state.tokens.next.id === "var") { + advance("var"); + state.syntax["var"].fud.call(state.syntax["var"].fud); + } else if (state.tokens.next.id === "let") { + advance("let"); + // create a new block scope + letscope = true; + funct["(blockscope)"].stack(); + state.syntax["let"].fud.call(state.syntax["let"].fud); + } else { + for (;;) { + expression(0, "for"); + if (state.tokens.next.id !== ",") { + break; + } + comma(); + } + } + } + nolinebreak(state.tokens.curr); + advance(";"); + if (state.tokens.next.id !== ";") { + checkCondAssignment(expression(0)); + } + nolinebreak(state.tokens.curr); + advance(";"); + if (state.tokens.next.id === ";") { + error("E021", state.tokens.next, ")", ";"); + } + if (state.tokens.next.id !== ")") { + for (;;) { + expression(0, "for"); + if (state.tokens.next.id !== ",") { + break; + } + comma(); + } + } + advance(")", t); + nospace(state.tokens.prev, state.tokens.curr); + block(true, true); + funct["(breakage)"] -= 1; + funct["(loopage)"] -= 1; + + } + // unstack loop blockscope + if (letscope) { + funct["(blockscope)"].unstack(); + } + return this; + }).labelled = true; + + + stmt("break", function () { + var v = state.tokens.next.value; + + if (funct["(breakage)"] === 0) + warning("W052", state.tokens.next, this.value); + + if (!state.option.asi) + nolinebreak(this); + + if (state.tokens.next.id !== ";" && !state.tokens.next.reach) { + if (state.tokens.curr.line === state.tokens.next.line) { + if (funct[v] !== "label") { + warning("W090", state.tokens.next, v); + } else if (scope[v] !== funct) { + warning("W091", state.tokens.next, v); + } + this.first = state.tokens.next; + advance(); + } + } + reachable("break"); + return this; + }).exps = true; + + + stmt("continue", function () { + var v = state.tokens.next.value; + + if (funct["(breakage)"] === 0) + warning("W052", state.tokens.next, this.value); + + if (!state.option.asi) + nolinebreak(this); + + if (state.tokens.next.id !== ";" && !state.tokens.next.reach) { + if (state.tokens.curr.line === state.tokens.next.line) { + if (funct[v] !== "label") { + warning("W090", state.tokens.next, v); + } else if (scope[v] !== funct) { + warning("W091", state.tokens.next, v); + } + this.first = state.tokens.next; + advance(); + } + } else if (!funct["(loopage)"]) { + warning("W052", state.tokens.next, this.value); + } + reachable("continue"); + return this; + }).exps = true; + + + stmt("return", function () { + if (this.line === state.tokens.next.line) { + if (state.tokens.next.id !== ";" && !state.tokens.next.reach) { + nonadjacent(state.tokens.curr, state.tokens.next); + this.first = expression(0); + + if (this.first && + this.first.type === "(punctuator)" && this.first.value === "=" && + !this.first.paren && !state.option.boss) { + warningAt("W093", this.first.line, this.first.character); + } + } + } else { + if (state.tokens.next.type === "(punctuator)" && + ["[", "{", "+", "-"].indexOf(state.tokens.next.value) > -1) { + nolinebreak(this); // always warn (Line breaking error) + } + } + reachable("return"); + return this; + }).exps = true; + + (function (x) { + x.exps = true; + x.lbp = 25; + }(prefix("yield", function () { + var prev = state.tokens.prev; + if (state.option.inESNext(true) && !funct["(generator)"]) { + error("E046", state.tokens.curr, "yield"); + } else if (!state.option.inESNext()) { + warning("W104", state.tokens.curr, "yield"); + } + funct["(generator)"] = "yielded"; + if (this.line === state.tokens.next.line || !state.option.inMoz(true)) { + if (state.tokens.next.id !== ";" && !state.tokens.next.reach && state.tokens.next.nud) { + nobreaknonadjacent(state.tokens.curr, state.tokens.next); + this.first = expression(10); + + if (this.first.type === "(punctuator)" && this.first.value === "=" && + !this.first.paren && !state.option.boss) { + warningAt("W093", this.first.line, this.first.character); + } + } + + if (state.option.inMoz(true) && state.tokens.next.id !== ")" && + (prev.lbp > 30 || (!prev.assign && !isEndOfExpr()) || prev.id === "yield")) { + error("E050", this); + } + } else if (!state.option.asi) { + nolinebreak(this); // always warn (Line breaking error) + } + return this; + }))); + + + stmt("throw", function () { + nolinebreak(this); + nonadjacent(state.tokens.curr, state.tokens.next); + this.first = expression(20); + reachable("throw"); + return this; + }).exps = true; + + stmt("import", function () { + if (!state.option.inESNext()) { + warning("W119", state.tokens.curr, "import"); + } + + if (state.tokens.next.identifier) { + this.name = identifier(); + addlabel(this.name, { type: "unused", token: state.tokens.curr }); + } else { + advance("{"); + for (;;) { + var importName; + if (state.tokens.next.type === "default") { + importName = "default"; + advance("default"); + } else { + importName = identifier(); + } + if (state.tokens.next.value === "as") { + advance("as"); + importName = identifier(); + } + addlabel(importName, { type: "unused", token: state.tokens.curr }); + + if (state.tokens.next.value === ",") { + advance(","); + } else if (state.tokens.next.value === "}") { + advance("}"); + break; + } else { + error("E024", state.tokens.next, state.tokens.next.value); + break; + } + } + } + + advance("from"); + advance("(string)"); + return this; + }).exps = true; + + stmt("export", function () { + if (!state.option.inESNext()) { + warning("W119", state.tokens.curr, "export"); + } + + if (state.tokens.next.type === "default") { + advance("default"); + if (state.tokens.next.id === "function" || state.tokens.next.id === "class") { + this.block = true; + } + this.exportee = expression(10); + + return this; + } + + if (state.tokens.next.value === "{") { + advance("{"); + for (;;) { + identifier(); + + if (state.tokens.next.value === ",") { + advance(","); + } else if (state.tokens.next.value === "}") { + advance("}"); + break; + } else { + error("E024", state.tokens.next, state.tokens.next.value); + break; + } + } + return this; + } + + if (state.tokens.next.id === "var") { + advance("var"); + state.syntax["var"].fud.call(state.syntax["var"].fud); + } else if (state.tokens.next.id === "let") { + advance("let"); + state.syntax["let"].fud.call(state.syntax["let"].fud); + } else if (state.tokens.next.id === "const") { + advance("const"); + state.syntax["const"].fud.call(state.syntax["const"].fud); + } else if (state.tokens.next.id === "function") { + this.block = true; + advance("function"); + state.syntax["function"].fud(); + } else if (state.tokens.next.id === "class") { + this.block = true; + advance("class"); + state.syntax["class"].fud(); + } else { + error("E024", state.tokens.next, state.tokens.next.value); + } + + return this; + }).exps = true; + + // Future Reserved Words + + FutureReservedWord("abstract"); + FutureReservedWord("boolean"); + FutureReservedWord("byte"); + FutureReservedWord("char"); + FutureReservedWord("class", { es5: true, nud: classdef }); + FutureReservedWord("double"); + FutureReservedWord("enum", { es5: true }); + FutureReservedWord("export", { es5: true }); + FutureReservedWord("extends", { es5: true }); + FutureReservedWord("final"); + FutureReservedWord("float"); + FutureReservedWord("goto"); + FutureReservedWord("implements", { es5: true, strictOnly: true }); + FutureReservedWord("import", { es5: true }); + FutureReservedWord("int"); + FutureReservedWord("interface", { es5: true, strictOnly: true }); + FutureReservedWord("long"); + FutureReservedWord("native"); + FutureReservedWord("package", { es5: true, strictOnly: true }); + FutureReservedWord("private", { es5: true, strictOnly: true }); + FutureReservedWord("protected", { es5: true, strictOnly: true }); + FutureReservedWord("public", { es5: true, strictOnly: true }); + FutureReservedWord("short"); + FutureReservedWord("static", { es5: true, strictOnly: true }); + FutureReservedWord("super", { es5: true }); + FutureReservedWord("synchronized"); + FutureReservedWord("throws"); + FutureReservedWord("transient"); + FutureReservedWord("volatile"); + + // this function is used to determine wether a squarebracket or a curlybracket + // expression is a comprehension array, destructuring assignment or a json value. + + var lookupBlockType = function () { + var pn, pn1; + var i = -1; + var bracketStack = 0; + var ret = {}; + if (_.contains(["[", "{"], state.tokens.curr.value)) + bracketStack += 1; + do { + pn = (i === -1) ? state.tokens.next : peek(i); + pn1 = peek(i + 1); + i = i + 1; + if (_.contains(["[", "{"], pn.value)) { + bracketStack += 1; + } else if (_.contains(["]", "}"], pn.value)) { + bracketStack -= 1; + } + if (pn.identifier && pn.value === "for" && bracketStack === 1) { + ret.isCompArray = true; + ret.notJson = true; + break; + } + if (_.contains(["}", "]"], pn.value) && pn1.value === "=" && bracketStack === 0) { + ret.isDestAssign = true; + ret.notJson = true; + break; + } + if (pn.value === ";") { + ret.isBlock = true; + ret.notJson = true; + } + } while (bracketStack > 0 && pn.id !== "(end)" && i < 15); + return ret; + }; + + // Check whether this function has been reached for a destructuring assign with undeclared values + function destructuringAssignOrJsonValue() { + // lookup for the assignment (esnext only) + // if it has semicolons, it is a block, so go parse it as a block + // or it's not a block, but there are assignments, check for undeclared variables + + var block = lookupBlockType(); + if (block.notJson) { + if (!state.option.inESNext() && block.isDestAssign) { + warning("W104", state.tokens.curr, "destructuring assignment"); + } + statements(); + // otherwise parse json value + } else { + state.option.laxbreak = true; + state.jsonMode = true; + jsonValue(); + } + } + + // array comprehension parsing function + // parses and defines the three states of the list comprehension in order + // to avoid defining global variables, but keeping them to the list comprehension scope + // only. The order of the states are as follows: + // * "use" which will be the returned iterative part of the list comprehension + // * "define" which will define the variables local to the list comprehension + // * "filter" which will help filter out values + + var arrayComprehension = function () { + var CompArray = function () { + this.mode = "use"; + this.variables = []; + }; + var _carrays = []; + var _current; + function declare(v) { + var l = _current.variables.filter(function (elt) { + // if it has, change its undef state + if (elt.value === v) { + elt.undef = false; + return v; + } + }).length; + return l !== 0; + } + function use(v) { + var l = _current.variables.filter(function (elt) { + // and if it has been defined + if (elt.value === v && !elt.undef) { + if (elt.unused === true) { + elt.unused = false; + } + return v; + } + }).length; + // otherwise we warn about it + return (l === 0); + } + return {stack: function () { + _current = new CompArray(); + _carrays.push(_current); + }, + unstack: function () { + _current.variables.filter(function (v) { + if (v.unused) + warning("W098", v.token, v.value); + if (v.undef) + isundef(v.funct, "W117", v.token, v.value); + }); + _carrays.splice(-1, 1); + _current = _carrays[_carrays.length - 1]; + }, + setState: function (s) { + if (_.contains(["use", "define", "generate", "filter"], s)) + _current.mode = s; + }, + check: function (v) { + if (!_current) { + return; + } + // When we are in "use" state of the list comp, we enqueue that var + if (_current && _current.mode === "use") { + if (use(v)) { + _current.variables.push({ + funct: funct, + token: state.tokens.curr, + value: v, + undef: true, + unused: false + }); + } + return true; + // When we are in "define" state of the list comp, + } else if (_current && _current.mode === "define") { + // check if the variable has been used previously + if (!declare(v)) { + _current.variables.push({ + funct: funct, + token: state.tokens.curr, + value: v, + undef: false, + unused: true + }); + } + return true; + // When we are in the "generate" state of the list comp, + } else if (_current && _current.mode === "generate") { + isundef(funct, "W117", state.tokens.curr, v); + return true; + // When we are in "filter" state, + } else if (_current && _current.mode === "filter") { + // we check whether current variable has been declared + if (use(v)) { + // if not we warn about it + isundef(funct, "W117", state.tokens.curr, v); + } + return true; + } + return false; + } + }; + }; + + + // Parse JSON + + function jsonValue() { + + function jsonObject() { + var o = {}, t = state.tokens.next; + advance("{"); + if (state.tokens.next.id !== "}") { + for (;;) { + if (state.tokens.next.id === "(end)") { + error("E026", state.tokens.next, t.line); + } else if (state.tokens.next.id === "}") { + warning("W094", state.tokens.curr); + break; + } else if (state.tokens.next.id === ",") { + error("E028", state.tokens.next); + } else if (state.tokens.next.id !== "(string)") { + warning("W095", state.tokens.next, state.tokens.next.value); + } + if (o[state.tokens.next.value] === true) { + warning("W075", state.tokens.next, state.tokens.next.value); + } else if ((state.tokens.next.value === "__proto__" && + !state.option.proto) || (state.tokens.next.value === "__iterator__" && + !state.option.iterator)) { + warning("W096", state.tokens.next, state.tokens.next.value); + } else { + o[state.tokens.next.value] = true; + } + advance(); + advance(":"); + jsonValue(); + if (state.tokens.next.id !== ",") { + break; + } + advance(","); + } + } + advance("}"); + } + + function jsonArray() { + var t = state.tokens.next; + advance("["); + if (state.tokens.next.id !== "]") { + for (;;) { + if (state.tokens.next.id === "(end)") { + error("E027", state.tokens.next, t.line); + } else if (state.tokens.next.id === "]") { + warning("W094", state.tokens.curr); + break; + } else if (state.tokens.next.id === ",") { + error("E028", state.tokens.next); + } + jsonValue(); + if (state.tokens.next.id !== ",") { + break; + } + advance(","); + } + } + advance("]"); + } + + switch (state.tokens.next.id) { + case "{": + jsonObject(); + break; + case "[": + jsonArray(); + break; + case "true": + case "false": + case "null": + case "(number)": + case "(string)": + advance(); + break; + case "-": + advance("-"); + if (state.tokens.curr.character !== state.tokens.next.from) { + warning("W011", state.tokens.curr); + } + adjacent(state.tokens.curr, state.tokens.next); + advance("(number)"); + break; + default: + error("E003", state.tokens.next); + } + } + + var blockScope = function () { + var _current = {}; + var _variables = [_current]; + + function _checkBlockLabels() { + for (var t in _current) { + if (_current[t]["(type)"] === "unused") { + if (state.option.unused) { + var tkn = _current[t]["(token)"]; + var line = tkn.line; + var chr = tkn.character; + warningAt("W098", line, chr, t); + } + } + } + } + + return { + stack: function () { + _current = {}; + _variables.push(_current); + }, + + unstack: function () { + _checkBlockLabels(); + _variables.splice(_variables.length - 1, 1); + _current = _.last(_variables); + }, + + getlabel: function (l) { + for (var i = _variables.length - 1 ; i >= 0; --i) { + if (_.has(_variables[i], l) && !_variables[i][l]["(shadowed)"]) { + return _variables[i]; + } + } + }, + + shadow: function (name) { + for (var i = _variables.length - 1; i >= 0; i--) { + if (_.has(_variables[i], name)) { + _variables[i][name]["(shadowed)"] = true; + } + } + }, + + unshadow: function (name) { + for (var i = _variables.length - 1; i >= 0; i--) { + if (_.has(_variables[i], name)) { + _variables[i][name]["(shadowed)"] = false; + } + } + }, + + current: { + has: function (t) { + return _.has(_current, t); + }, + + add: function (t, type, tok) { + _current[t] = { "(type)" : type, "(token)": tok, "(shadowed)": false }; + } + } + }; + }; + + // The actual JSHINT function itself. + var itself = function (s, o, g) { + var i, k, x; + var optionKeys; + var newOptionObj = {}; + var newIgnoredObj = {}; + + o = _.clone(o); + state.reset(); + + if (o && o.scope) { + JSHINT.scope = o.scope; + } else { + JSHINT.errors = []; + JSHINT.undefs = []; + JSHINT.internals = []; + JSHINT.blacklist = {}; + JSHINT.scope = "(main)"; + } + + predefined = Object.create(null); + combine(predefined, vars.ecmaIdentifiers); + combine(predefined, vars.reservedVars); + + combine(predefined, g || {}); + + declared = Object.create(null); + exported = Object.create(null); + + function each(obj, cb) { + if (!obj) + return; + + if (!Array.isArray(obj) && typeof obj === "object") + obj = Object.keys(obj); + + obj.forEach(cb); + } + + if (o) { + each(o.predef || null, function (item) { + var slice, prop; + + if (item[0] === "-") { + slice = item.slice(1); + JSHINT.blacklist[slice] = slice; + } else { + prop = Object.getOwnPropertyDescriptor(o.predef, item); + predefined[item] = prop ? prop.value : false; + } + }); + + each(o.exported || null, function (item) { + exported[item] = true; + }); + + delete o.predef; + delete o.exported; + + optionKeys = Object.keys(o); + for (x = 0; x < optionKeys.length; x++) { + if (/^-W\d{3}$/g.test(optionKeys[x])) { + newIgnoredObj[optionKeys[x].slice(1)] = true; + } else { + newOptionObj[optionKeys[x]] = o[optionKeys[x]]; + + if (optionKeys[x] === "newcap" && o[optionKeys[x]] === false) + newOptionObj["(explicitNewcap)"] = true; + + if (optionKeys[x] === "indent") + newOptionObj["(explicitIndent)"] = o[optionKeys[x]] === false ? false : true; + } + } + } + + state.option = newOptionObj; + state.ignored = newIgnoredObj; + + state.option.indent = state.option.indent || 4; + state.option.maxerr = state.option.maxerr || 50; + + indent = 1; + global = Object.create(predefined); + scope = global; + + funct = functor("(global)", null, scope, { + "(global)" : true, + "(blockscope)": blockScope(), + "(comparray)" : arrayComprehension(), + "(metrics)" : createMetrics(state.tokens.next) + }); + + functions = [funct]; + urls = []; + stack = null; + member = {}; + membersOnly = null; + implied = {}; + inblock = false; + lookahead = []; + warnings = 0; + unuseds = []; + + if (!isString(s) && !Array.isArray(s)) { + errorAt("E004", 0); + return false; + } + + api = { + get isJSON() { + return state.jsonMode; + }, + + getOption: function (name) { + return state.option[name] || null; + }, + + getCache: function (name) { + return state.cache[name]; + }, + + setCache: function (name, value) { + state.cache[name] = value; + }, + + warn: function (code, data) { + warningAt.apply(null, [ code, data.line, data.char ].concat(data.data)); + }, + + on: function (names, listener) { + names.split(" ").forEach(function (name) { + emitter.on(name, listener); + }.bind(this)); + } + }; + + emitter.removeAllListeners(); + (extraModules || []).forEach(function (func) { + func(api); + }); + + state.tokens.prev = state.tokens.curr = state.tokens.next = state.syntax["(begin)"]; + + lex = new Lexer(s); + + lex.on("warning", function (ev) { + warningAt.apply(null, [ ev.code, ev.line, ev.character].concat(ev.data)); + }); + + lex.on("error", function (ev) { + errorAt.apply(null, [ ev.code, ev.line, ev.character ].concat(ev.data)); + }); + + lex.on("fatal", function (ev) { + quit("E041", ev.line, ev.from); + }); + + lex.on("Identifier", function (ev) { + emitter.emit("Identifier", ev); + }); + + lex.on("String", function (ev) { + emitter.emit("String", ev); + }); + + lex.on("Number", function (ev) { + emitter.emit("Number", ev); + }); + + lex.start(); + + // Check options + for (var name in o) { + if (_.has(o, name)) { + checkOption(name, state.tokens.curr); + } + } + + assume(); + + // combine the passed globals after we've assumed all our options + combine(predefined, g || {}); + + //reset values + comma.first = true; + + try { + advance(); + switch (state.tokens.next.id) { + case "{": + case "[": + destructuringAssignOrJsonValue(); + break; + default: + directives(); + + if (state.directive["use strict"]) { + if (!state.option.globalstrict && !(state.option.node || state.option.phantom)) { + warning("W097", state.tokens.prev); + } + } + + statements(); + } + advance((state.tokens.next && state.tokens.next.value !== ".") ? "(end)" : undefined); + funct["(blockscope)"].unstack(); + + var markDefined = function (name, context) { + do { + if (typeof context[name] === "string") { + // JSHINT marks unused variables as 'unused' and + // unused function declaration as 'unction'. This + // code changes such instances back 'var' and + // 'closure' so that the code in JSHINT.data() + // doesn't think they're unused. + + if (context[name] === "unused") + context[name] = "var"; + else if (context[name] === "unction") + context[name] = "closure"; + + return true; + } + + context = context["(context)"]; + } while (context); + + return false; + }; + + var clearImplied = function (name, line) { + if (!implied[name]) + return; + + var newImplied = []; + for (var i = 0; i < implied[name].length; i += 1) { + if (implied[name][i] !== line) + newImplied.push(implied[name][i]); + } + + if (newImplied.length === 0) + delete implied[name]; + else + implied[name] = newImplied; + }; + + var warnUnused = function (name, tkn, type, unused_opt) { + var line = tkn.line; + var chr = tkn.character; + + if (unused_opt === undefined) { + unused_opt = state.option.unused; + } + + if (unused_opt === true) { + unused_opt = "last-param"; + } + + var warnable_types = { + "vars": ["var"], + "last-param": ["var", "param"], + "strict": ["var", "param", "last-param"] + }; + + if (unused_opt) { + if (warnable_types[unused_opt] && warnable_types[unused_opt].indexOf(type) !== -1) { + warningAt("W098", line, chr, name); + } + } + + unuseds.push({ + name: name, + line: line, + character: chr + }); + }; + + var checkUnused = function (func, key) { + var type = func[key]; + var tkn = func["(tokens)"][key]; + + if (key.charAt(0) === "(") + return; + + if (type !== "unused" && type !== "unction" && type !== "const") + return; + + // Params are checked separately from other variables. + if (func["(params)"] && func["(params)"].indexOf(key) !== -1) + return; + + // Variable is in global scope and defined as exported. + if (func["(global)"] && _.has(exported, key)) + return; + + // Is this constant unused? + if (type === "const" && !getprop(func, key, "unused")) + return; + + warnUnused(key, tkn, "var"); + }; + + // Check queued 'x is not defined' instances to see if they're still undefined. + for (i = 0; i < JSHINT.undefs.length; i += 1) { + k = JSHINT.undefs[i].slice(0); + + if (markDefined(k[2].value, k[0])) { + clearImplied(k[2].value, k[2].line); + } else if (state.option.undef) { + warning.apply(warning, k.slice(1)); + } + } + + functions.forEach(function (func) { + if (func["(unusedOption)"] === false) { + return; + } + + for (var key in func) { + if (_.has(func, key)) { + checkUnused(func, key); + } + } + + if (!func["(params)"]) + return; + + var params = func["(params)"].slice(); + var param = params.pop(); + var type, unused_opt; + + while (param) { + type = func[param]; + unused_opt = func["(unusedOption)"] || state.option.unused; + unused_opt = unused_opt === true ? "last-param" : unused_opt; + + // 'undefined' is a special case for (function (window, undefined) { ... })(); + // patterns. + + if (param === "undefined") + return; + + if (type === "unused" || type === "unction") { + warnUnused(param, func["(tokens)"][param], "param", func["(unusedOption)"]); + } else if (unused_opt === "last-param") { + return; + } + + param = params.pop(); + } + }); + + for (var key in declared) { + if (_.has(declared, key) && !_.has(global, key) && !_.has(exported, key)) { + warnUnused(key, declared[key], "var"); + } + } + + } catch (err) { + if (err && err.name === "JSHintError") { + var nt = state.tokens.next || {}; + JSHINT.errors.push({ + scope : "(main)", + raw : err.raw, + code : err.code, + reason : err.message, + line : err.line || nt.line, + character : err.character || nt.from + }, null); + } else { + throw err; + } + } + + // Loop over the listed "internals", and check them as well. + + if (JSHINT.scope === "(main)") { + o = o || {}; + + for (i = 0; i < JSHINT.internals.length; i += 1) { + k = JSHINT.internals[i]; + o.scope = k.elem; + itself(k.value, o, g); + } + } + + return JSHINT.errors.length === 0; + }; + + // Modules. + itself.addModule = function (func) { + extraModules.push(func); + }; + + itself.addModule(style.register); + + // Data summary. + itself.data = function () { + var data = { + functions: [], + options: state.option + }; + + var implieds = []; + var members = []; + var fu, f, i, j, n, globals; + + if (itself.errors.length) { + data.errors = itself.errors; + } + + if (state.jsonMode) { + data.json = true; + } + + for (n in implied) { + if (_.has(implied, n)) { + implieds.push({ + name: n, + line: implied[n] + }); + } + } + + if (implieds.length > 0) { + data.implieds = implieds; + } + + if (urls.length > 0) { + data.urls = urls; + } + + globals = Object.keys(scope); + if (globals.length > 0) { + data.globals = globals; + } + + for (i = 1; i < functions.length; i += 1) { + f = functions[i]; + fu = {}; + + for (j = 0; j < functionicity.length; j += 1) { + fu[functionicity[j]] = []; + } + + for (j = 0; j < functionicity.length; j += 1) { + if (fu[functionicity[j]].length === 0) { + delete fu[functionicity[j]]; + } + } + + fu.name = f["(name)"]; + fu.param = f["(params)"]; + fu.line = f["(line)"]; + fu.character = f["(character)"]; + fu.last = f["(last)"]; + fu.lastcharacter = f["(lastcharacter)"]; + + fu.metrics = { + complexity: f["(metrics)"].ComplexityCount, + parameters: (f["(params)"] || []).length, + statements: f["(metrics)"].statementCount + }; + + data.functions.push(fu); + } + + if (unuseds.length > 0) { + data.unused = unuseds; + } + + members = []; + for (n in member) { + if (typeof member[n] === "number") { + data.member = member; + break; + } + } + + return data; + }; + + itself.jshint = itself; + + return itself; +}()); + +// Make JSHINT a Node module, if possible. +if (typeof exports === "object" && exports) { + exports.JSHINT = JSHINT; +} + +},{"./lex.js":14,"./messages.js":15,"./reg.js":16,"./state.js":17,"./style.js":18,"./vars.js":19,"console-browserify":10,"events":5,"underscore":11}],14:[function(require,module,exports){ +/* + * Lexical analysis and token construction. + */ + +"use strict"; + +var _ = require("underscore"); +var events = require("events"); +var reg = require("./reg.js"); +var state = require("./state.js").state; + +var unicodeData = require("../data/ascii-identifier-data.js"); +var asciiIdentifierStartTable = unicodeData.asciiIdentifierStartTable; +var asciiIdentifierPartTable = unicodeData.asciiIdentifierPartTable; +var nonAsciiIdentifierStartTable = require("../data/non-ascii-identifier-start.js"); +var nonAsciiIdentifierPartTable = require("../data/non-ascii-identifier-part-only.js"); + +// Some of these token types are from JavaScript Parser API +// while others are specific to JSHint parser. +// JS Parser API: https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API + +var Token = { + Identifier: 1, + Punctuator: 2, + NumericLiteral: 3, + StringLiteral: 4, + Comment: 5, + Keyword: 6, + NullLiteral: 7, + BooleanLiteral: 8, + RegExp: 9 +}; + +// Object that handles postponed lexing verifications that checks the parsed +// environment state. + +function asyncTrigger() { + var _checks = []; + + return { + push: function (fn) { + _checks.push(fn); + }, + + check: function () { + for (var check = 0; check < _checks.length; ++check) { + _checks[check](); + } + + _checks.splice(0, _checks.length); + } + }; +} + +/* + * Lexer for JSHint. + * + * This object does a char-by-char scan of the provided source code + * and produces a sequence of tokens. + * + * var lex = new Lexer("var i = 0;"); + * lex.start(); + * lex.token(); // returns the next token + * + * You have to use the token() method to move the lexer forward + * but you don't have to use its return value to get tokens. In addition + * to token() method returning the next token, the Lexer object also + * emits events. + * + * lex.on("Identifier", function (data) { + * if (data.name.indexOf("_") >= 0) { + * // Produce a warning. + * } + * }); + * + * Note that the token() method returns tokens in a JSLint-compatible + * format while the event emitter uses a slightly modified version of + * Mozilla's JavaScript Parser API. Eventually, we will move away from + * JSLint format. + */ +function Lexer(source) { + var lines = source; + + if (typeof lines === "string") { + lines = lines + .replace(/\r\n/g, "\n") + .replace(/\r/g, "\n") + .split("\n"); + } + + // If the first line is a shebang (#!), make it a blank and move on. + // Shebangs are used by Node scripts. + + if (lines[0] && lines[0].substr(0, 2) === "#!") { + if (lines[0].indexOf("node") !== -1) { + state.option.node = true; + } + lines[0] = ""; + } + + this.emitter = new events.EventEmitter(); + this.source = source; + this.setLines(lines); + this.prereg = true; + + this.line = 0; + this.char = 1; + this.from = 1; + this.input = ""; + this.inComment = false; + + for (var i = 0; i < state.option.indent; i += 1) { + state.tab += " "; + } +} + +Lexer.prototype = { + _lines: [], + + getLines: function () { + this._lines = state.lines; + return this._lines; + }, + + setLines: function (val) { + this._lines = val; + state.lines = this._lines; + }, + + /* + * Return the next i character without actually moving the + * char pointer. + */ + peek: function (i) { + return this.input.charAt(i || 0); + }, + + /* + * Move the char pointer forward i times. + */ + skip: function (i) { + i = i || 1; + this.char += i; + this.input = this.input.slice(i); + }, + + /* + * Subscribe to a token event. The API for this method is similar + * Underscore.js i.e. you can subscribe to multiple events with + * one call: + * + * lex.on("Identifier Number", function (data) { + * // ... + * }); + */ + on: function (names, listener) { + names.split(" ").forEach(function (name) { + this.emitter.on(name, listener); + }.bind(this)); + }, + + /* + * Trigger a token event. All arguments will be passed to each + * listener. + */ + trigger: function () { + this.emitter.emit.apply(this.emitter, Array.prototype.slice.call(arguments)); + }, + + /* + * Postpone a token event. the checking condition is set as + * last parameter, and the trigger function is called in a + * stored callback. To be later called using the check() function + * by the parser. This avoids parser's peek() to give the lexer + * a false context. + */ + triggerAsync: function (type, args, checks, fn) { + checks.push(function () { + if (fn()) { + this.trigger(type, args); + } + }.bind(this)); + }, + + /* + * Extract a punctuator out of the next sequence of characters + * or return 'null' if its not possible. + * + * This method's implementation was heavily influenced by the + * scanPunctuator function in the Esprima parser's source code. + */ + scanPunctuator: function () { + var ch1 = this.peek(); + var ch2, ch3, ch4; + + switch (ch1) { + // Most common single-character punctuators + case ".": + if ((/^[0-9]$/).test(this.peek(1))) { + return null; + } + if (this.peek(1) === "." && this.peek(2) === ".") { + return { + type: Token.Punctuator, + value: "..." + }; + } + /* falls through */ + case "(": + case ")": + case ";": + case ",": + case "{": + case "}": + case "[": + case "]": + case ":": + case "~": + case "?": + return { + type: Token.Punctuator, + value: ch1 + }; + + // A pound sign (for Node shebangs) + case "#": + return { + type: Token.Punctuator, + value: ch1 + }; + + // We're at the end of input + case "": + return null; + } + + // Peek more characters + + ch2 = this.peek(1); + ch3 = this.peek(2); + ch4 = this.peek(3); + + // 4-character punctuator: >>>= + + if (ch1 === ">" && ch2 === ">" && ch3 === ">" && ch4 === "=") { + return { + type: Token.Punctuator, + value: ">>>=" + }; + } + + // 3-character punctuators: === !== >>> <<= >>= + + if (ch1 === "=" && ch2 === "=" && ch3 === "=") { + return { + type: Token.Punctuator, + value: "===" + }; + } + + if (ch1 === "!" && ch2 === "=" && ch3 === "=") { + return { + type: Token.Punctuator, + value: "!==" + }; + } + + if (ch1 === ">" && ch2 === ">" && ch3 === ">") { + return { + type: Token.Punctuator, + value: ">>>" + }; + } + + if (ch1 === "<" && ch2 === "<" && ch3 === "=") { + return { + type: Token.Punctuator, + value: "<<=" + }; + } + + if (ch1 === ">" && ch2 === ">" && ch3 === "=") { + return { + type: Token.Punctuator, + value: ">>=" + }; + } + + // Fat arrow punctuator + if (ch1 === "=" && ch2 === ">") { + return { + type: Token.Punctuator, + value: ch1 + ch2 + }; + } + + // 2-character punctuators: <= >= == != ++ -- << >> && || + // += -= *= %= &= |= ^= (but not /=, see below) + if (ch1 === ch2 && ("+-<>&|".indexOf(ch1) >= 0)) { + return { + type: Token.Punctuator, + value: ch1 + ch2 + }; + } + + if ("<>=!+-*%&|^".indexOf(ch1) >= 0) { + if (ch2 === "=") { + return { + type: Token.Punctuator, + value: ch1 + ch2 + }; + } + + return { + type: Token.Punctuator, + value: ch1 + }; + } + + // Special case: /=. We need to make sure that this is an + // operator and not a regular expression. + + if (ch1 === "/") { + if (ch2 === "=" && /\/=(?!(\S*\/[gim]?))/.test(this.input)) { + // /= is not a part of a regular expression, return it as a + // punctuator. + return { + type: Token.Punctuator, + value: "/=" + }; + } + + return { + type: Token.Punctuator, + value: "/" + }; + } + + return null; + }, + + /* + * Extract a comment out of the next sequence of characters and/or + * lines or return 'null' if its not possible. Since comments can + * span across multiple lines this method has to move the char + * pointer. + * + * In addition to normal JavaScript comments (// and /*) this method + * also recognizes JSHint- and JSLint-specific comments such as + * /*jshint, /*jslint, /*globals and so on. + */ + scanComments: function () { + var ch1 = this.peek(); + var ch2 = this.peek(1); + var rest = this.input.substr(2); + var startLine = this.line; + var startChar = this.char; + + // Create a comment token object and make sure it + // has all the data JSHint needs to work with special + // comments. + + function commentToken(label, body, opt) { + var special = ["jshint", "jslint", "members", "member", "globals", "global", "exported"]; + var isSpecial = false; + var value = label + body; + var commentType = "plain"; + opt = opt || {}; + + if (opt.isMultiline) { + value += "*/"; + } + + special.forEach(function (str) { + if (isSpecial) { + return; + } + + // Don't recognize any special comments other than jshint for single-line + // comments. This introduced many problems with legit comments. + if (label === "//" && str !== "jshint") { + return; + } + + if (body.substr(0, str.length) === str) { + isSpecial = true; + label = label + str; + body = body.substr(str.length); + } + + if (!isSpecial && body.charAt(0) === " " && body.substr(1, str.length) === str) { + isSpecial = true; + label = label + " " + str; + body = body.substr(str.length + 1); + } + + if (!isSpecial) { + return; + } + + switch (str) { + case "member": + commentType = "members"; + break; + case "global": + commentType = "globals"; + break; + default: + commentType = str; + } + }); + + return { + type: Token.Comment, + commentType: commentType, + value: value, + body: body, + isSpecial: isSpecial, + isMultiline: opt.isMultiline || false, + isMalformed: opt.isMalformed || false + }; + } + + // End of unbegun comment. Raise an error and skip that input. + if (ch1 === "*" && ch2 === "/") { + this.trigger("error", { + code: "E018", + line: startLine, + character: startChar + }); + + this.skip(2); + return null; + } + + // Comments must start either with // or /* + if (ch1 !== "/" || (ch2 !== "*" && ch2 !== "/")) { + return null; + } + + // One-line comment + if (ch2 === "/") { + this.skip(this.input.length); // Skip to the EOL. + return commentToken("//", rest); + } + + var body = ""; + + /* Multi-line comment */ + if (ch2 === "*") { + this.inComment = true; + this.skip(2); + + while (this.peek() !== "*" || this.peek(1) !== "/") { + if (this.peek() === "") { // End of Line + body += "\n"; + + // If we hit EOF and our comment is still unclosed, + // trigger an error and end the comment implicitly. + if (!this.nextLine()) { + this.trigger("error", { + code: "E017", + line: startLine, + character: startChar + }); + + this.inComment = false; + return commentToken("/*", body, { + isMultiline: true, + isMalformed: true + }); + } + } else { + body += this.peek(); + this.skip(); + } + } + + this.skip(2); + this.inComment = false; + return commentToken("/*", body, { isMultiline: true }); + } + }, + + /* + * Extract a keyword out of the next sequence of characters or + * return 'null' if its not possible. + */ + scanKeyword: function () { + var result = /^[a-zA-Z_$][a-zA-Z0-9_$]*/.exec(this.input); + var keywords = [ + "if", "in", "do", "var", "for", "new", + "try", "let", "this", "else", "case", + "void", "with", "enum", "while", "break", + "catch", "throw", "const", "yield", "class", + "super", "return", "typeof", "delete", + "switch", "export", "import", "default", + "finally", "extends", "function", "continue", + "debugger", "instanceof" + ]; + + if (result && keywords.indexOf(result[0]) >= 0) { + return { + type: Token.Keyword, + value: result[0] + }; + } + + return null; + }, + + /* + * Extract a JavaScript identifier out of the next sequence of + * characters or return 'null' if its not possible. In addition, + * to Identifier this method can also produce BooleanLiteral + * (true/false) and NullLiteral (null). + */ + scanIdentifier: function () { + var id = ""; + var index = 0; + var type, char; + + function isNonAsciiIdentifierStart(code) { + return nonAsciiIdentifierStartTable.indexOf(code) > -1; + } + + function isNonAsciiIdentifierPart(code) { + return isNonAsciiIdentifierStart(code) || nonAsciiIdentifierPartTable.indexOf(code) > -1; + } + + function isHexDigit(str) { + return (/^[0-9a-fA-F]$/).test(str); + } + + var readUnicodeEscapeSequence = function () { + /*jshint validthis:true */ + index += 1; + + if (this.peek(index) !== "u") { + return null; + } + + var ch1 = this.peek(index + 1); + var ch2 = this.peek(index + 2); + var ch3 = this.peek(index + 3); + var ch4 = this.peek(index + 4); + var code; + + if (isHexDigit(ch1) && isHexDigit(ch2) && isHexDigit(ch3) && isHexDigit(ch4)) { + code = parseInt(ch1 + ch2 + ch3 + ch4, 16); + + if (asciiIdentifierPartTable[code] || isNonAsciiIdentifierPart(code)) { + index += 5; + return "\\u" + ch1 + ch2 + ch3 + ch4; + } + + return null; + } + + return null; + }.bind(this); + + var getIdentifierStart = function () { + /*jshint validthis:true */ + var chr = this.peek(index); + var code = chr.charCodeAt(0); + + if (code === 92) { + return readUnicodeEscapeSequence(); + } + + if (code < 128) { + if (asciiIdentifierStartTable[code]) { + index += 1; + return chr; + } + + return null; + } + + if (isNonAsciiIdentifierStart(code)) { + index += 1; + return chr; + } + + return null; + }.bind(this); + + var getIdentifierPart = function () { + /*jshint validthis:true */ + var chr = this.peek(index); + var code = chr.charCodeAt(0); + + if (code === 92) { + return readUnicodeEscapeSequence(); + } + + if (code < 128) { + if (asciiIdentifierPartTable[code]) { + index += 1; + return chr; + } + + return null; + } + + if (isNonAsciiIdentifierPart(code)) { + index += 1; + return chr; + } + + return null; + }.bind(this); + + char = getIdentifierStart(); + if (char === null) { + return null; + } + + id = char; + for (;;) { + char = getIdentifierPart(); + + if (char === null) { + break; + } + + id += char; + } + + switch (id) { + case "true": + case "false": + type = Token.BooleanLiteral; + break; + case "null": + type = Token.NullLiteral; + break; + default: + type = Token.Identifier; + } + + return { + type: type, + value: id + }; + }, + + /* + * Extract a numeric literal out of the next sequence of + * characters or return 'null' if its not possible. This method + * supports all numeric literals described in section 7.8.3 + * of the EcmaScript 5 specification. + * + * This method's implementation was heavily influenced by the + * scanNumericLiteral function in the Esprima parser's source code. + */ + scanNumericLiteral: function () { + var index = 0; + var value = ""; + var length = this.input.length; + var char = this.peek(index); + var bad; + + function isDecimalDigit(str) { + return (/^[0-9]$/).test(str); + } + + function isOctalDigit(str) { + return (/^[0-7]$/).test(str); + } + + function isHexDigit(str) { + return (/^[0-9a-fA-F]$/).test(str); + } + + function isIdentifierStart(ch) { + return (ch === "$") || (ch === "_") || (ch === "\\") || + (ch >= "a" && ch <= "z") || (ch >= "A" && ch <= "Z"); + } + + // Numbers must start either with a decimal digit or a point. + + if (char !== "." && !isDecimalDigit(char)) { + return null; + } + + if (char !== ".") { + value = this.peek(index); + index += 1; + char = this.peek(index); + + if (value === "0") { + // Base-16 numbers. + if (char === "x" || char === "X") { + index += 1; + value += char; + + while (index < length) { + char = this.peek(index); + if (!isHexDigit(char)) { + break; + } + value += char; + index += 1; + } + + if (value.length <= 2) { // 0x + return { + type: Token.NumericLiteral, + value: value, + isMalformed: true + }; + } + + if (index < length) { + char = this.peek(index); + if (isIdentifierStart(char)) { + return null; + } + } + + return { + type: Token.NumericLiteral, + value: value, + base: 16, + isMalformed: false + }; + } + + // Base-8 numbers. + if (isOctalDigit(char)) { + index += 1; + value += char; + bad = false; + + while (index < length) { + char = this.peek(index); + + // Numbers like '019' (note the 9) are not valid octals + // but we still parse them and mark as malformed. + + if (isDecimalDigit(char)) { + bad = true; + } else if (!isOctalDigit(char)) { + break; + } + value += char; + index += 1; + } + + if (index < length) { + char = this.peek(index); + if (isIdentifierStart(char)) { + return null; + } + } + + return { + type: Token.NumericLiteral, + value: value, + base: 8, + isMalformed: false + }; + } + + // Decimal numbers that start with '0' such as '09' are illegal + // but we still parse them and return as malformed. + + if (isDecimalDigit(char)) { + index += 1; + value += char; + } + } + + while (index < length) { + char = this.peek(index); + if (!isDecimalDigit(char)) { + break; + } + value += char; + index += 1; + } + } + + // Decimal digits. + + if (char === ".") { + value += char; + index += 1; + + while (index < length) { + char = this.peek(index); + if (!isDecimalDigit(char)) { + break; + } + value += char; + index += 1; + } + } + + // Exponent part. + + if (char === "e" || char === "E") { + value += char; + index += 1; + char = this.peek(index); + + if (char === "+" || char === "-") { + value += this.peek(index); + index += 1; + } + + char = this.peek(index); + if (isDecimalDigit(char)) { + value += char; + index += 1; + + while (index < length) { + char = this.peek(index); + if (!isDecimalDigit(char)) { + break; + } + value += char; + index += 1; + } + } else { + return null; + } + } + + if (index < length) { + char = this.peek(index); + if (isIdentifierStart(char)) { + return null; + } + } + + return { + type: Token.NumericLiteral, + value: value, + base: 10, + isMalformed: !isFinite(value) + }; + }, + + /* + * Extract a string out of the next sequence of characters and/or + * lines or return 'null' if its not possible. Since strings can + * span across multiple lines this method has to move the char + * pointer. + * + * This method recognizes pseudo-multiline JavaScript strings: + * + * var str = "hello\ + * world"; + */ + scanStringLiteral: function (checks) { + /*jshint loopfunc:true */ + var quote = this.peek(); + + // String must start with a quote. + if (quote !== "\"" && quote !== "'") { + return null; + } + + // In JSON strings must always use double quotes. + this.triggerAsync("warning", { + code: "W108", + line: this.line, + character: this.char // +1? + }, checks, function () { return state.jsonMode && quote !== "\""; }); + + var value = ""; + var startLine = this.line; + var startChar = this.char; + var allowNewLine = false; + + this.skip(); + + while (this.peek() !== quote) { + while (this.peek() === "") { // End Of Line + + // If an EOL is not preceded by a backslash, show a warning + // and proceed like it was a legit multi-line string where + // author simply forgot to escape the newline symbol. + // + // Another approach is to implicitly close a string on EOL + // but it generates too many false positives. + + if (!allowNewLine) { + this.trigger("warning", { + code: "W112", + line: this.line, + character: this.char + }); + } else { + allowNewLine = false; + + // Otherwise show a warning if multistr option was not set. + // For JSON, show warning no matter what. + + this.triggerAsync("warning", { + code: "W043", + line: this.line, + character: this.char + }, checks, function () { return !state.option.multistr; }); + + this.triggerAsync("warning", { + code: "W042", + line: this.line, + character: this.char + }, checks, function () { return state.jsonMode && state.option.multistr; }); + } + + // If we get an EOF inside of an unclosed string, show an + // error and implicitly close it at the EOF point. + + if (!this.nextLine()) { + this.trigger("error", { + code: "E029", + line: startLine, + character: startChar + }); + + return { + type: Token.StringLiteral, + value: value, + isUnclosed: true, + quote: quote + }; + } + } + + allowNewLine = false; + var char = this.peek(); + var jump = 1; // A length of a jump, after we're done + // parsing this character. + + if (char < " ") { + // Warn about a control character in a string. + this.trigger("warning", { + code: "W113", + line: this.line, + character: this.char, + data: [ "" ] + }); + } + + // Special treatment for some escaped characters. + + if (char === "\\") { + this.skip(); + char = this.peek(); + + switch (char) { + case "'": + this.triggerAsync("warning", { + code: "W114", + line: this.line, + character: this.char, + data: [ "\\'" ] + }, checks, function () {return state.jsonMode; }); + break; + case "b": + char = "\\b"; + break; + case "f": + char = "\\f"; + break; + case "n": + char = "\\n"; + break; + case "r": + char = "\\r"; + break; + case "t": + char = "\\t"; + break; + case "0": + char = "\\0"; + + // Octal literals fail in strict mode. + // Check if the number is between 00 and 07. + var n = parseInt(this.peek(1), 10); + this.triggerAsync("warning", { + code: "W115", + line: this.line, + character: this.char + }, checks, + function () { return n >= 0 && n <= 7 && state.directive["use strict"]; }); + break; + case "u": + char = String.fromCharCode(parseInt(this.input.substr(1, 4), 16)); + jump = 5; + break; + case "v": + this.triggerAsync("warning", { + code: "W114", + line: this.line, + character: this.char, + data: [ "\\v" ] + }, checks, function () { return state.jsonMode; }); + + char = "\v"; + break; + case "x": + var x = parseInt(this.input.substr(1, 2), 16); + + this.triggerAsync("warning", { + code: "W114", + line: this.line, + character: this.char, + data: [ "\\x-" ] + }, checks, function () { return state.jsonMode; }); + + char = String.fromCharCode(x); + jump = 3; + break; + case "\\": + char = "\\\\"; + break; + case "\"": + char = "\\\""; + break; + case "/": + break; + case "": + allowNewLine = true; + char = ""; + break; + case "!": + if (value.slice(value.length - 2) === "<") { + break; + } + + /*falls through */ + default: + // Weird escaping. + this.trigger("warning", { + code: "W044", + line: this.line, + character: this.char + }); + } + } + + value += char; + this.skip(jump); + } + + this.skip(); + return { + type: Token.StringLiteral, + value: value, + isUnclosed: false, + quote: quote + }; + }, + + /* + * Extract a regular expression out of the next sequence of + * characters and/or lines or return 'null' if its not possible. + * + * This method is platform dependent: it accepts almost any + * regular expression values but then tries to compile and run + * them using system's RegExp object. This means that there are + * rare edge cases where one JavaScript engine complains about + * your regular expression while others don't. + */ + scanRegExp: function () { + var index = 0; + var length = this.input.length; + var char = this.peek(); + var value = char; + var body = ""; + var flags = []; + var malformed = false; + var isCharSet = false; + var terminated; + + var scanUnexpectedChars = function () { + // Unexpected control character + if (char < " ") { + malformed = true; + this.trigger("warning", { + code: "W048", + line: this.line, + character: this.char + }); + } + + // Unexpected escaped character + if (char === "<") { + malformed = true; + this.trigger("warning", { + code: "W049", + line: this.line, + character: this.char, + data: [ char ] + }); + } + }.bind(this); + + // Regular expressions must start with '/' + if (!this.prereg || char !== "/") { + return null; + } + + index += 1; + terminated = false; + + // Try to get everything in between slashes. A couple of + // cases aside (see scanUnexpectedChars) we don't really + // care whether the resulting expression is valid or not. + // We will check that later using the RegExp object. + + while (index < length) { + char = this.peek(index); + value += char; + body += char; + + if (isCharSet) { + if (char === "]") { + if (this.peek(index - 1) !== "\\" || this.peek(index - 2) === "\\") { + isCharSet = false; + } + } + + if (char === "\\") { + index += 1; + char = this.peek(index); + body += char; + value += char; + + scanUnexpectedChars(); + } + + index += 1; + continue; + } + + if (char === "\\") { + index += 1; + char = this.peek(index); + body += char; + value += char; + + scanUnexpectedChars(); + + if (char === "/") { + index += 1; + continue; + } + + if (char === "[") { + index += 1; + continue; + } + } + + if (char === "[") { + isCharSet = true; + index += 1; + continue; + } + + if (char === "/") { + body = body.substr(0, body.length - 1); + terminated = true; + index += 1; + break; + } + + index += 1; + } + + // A regular expression that was never closed is an + // error from which we cannot recover. + + if (!terminated) { + this.trigger("error", { + code: "E015", + line: this.line, + character: this.from + }); + + return void this.trigger("fatal", { + line: this.line, + from: this.from + }); + } + + // Parse flags (if any). + + while (index < length) { + char = this.peek(index); + if (!/[gim]/.test(char)) { + break; + } + flags.push(char); + value += char; + index += 1; + } + + // Check regular expression for correctness. + + try { + new RegExp(body, flags.join("")); + } catch (err) { + malformed = true; + this.trigger("error", { + code: "E016", + line: this.line, + character: this.char, + data: [ err.message ] // Platform dependent! + }); + } + + return { + type: Token.RegExp, + value: value, + flags: flags, + isMalformed: malformed + }; + }, + + /* + * Scan for any occurence of mixed tabs and spaces. If smarttabs option + * is on, ignore tabs followed by spaces. + * + * Tabs followed by one space followed by a block comment are allowed. + */ + scanMixedSpacesAndTabs: function () { + var at, match; + + if (state.option.smarttabs) { + // Negative look-behind for "//" + match = this.input.match(/(\/\/|^\s?\*)? \t/); + at = match && !match[1] ? 0 : -1; + } else { + at = this.input.search(/ \t|\t [^\*]/); + } + + return at; + }, + + /* + * Scan for any occurence of non-breaking spaces. Non-breaking spaces + * can be mistakenly typed on OS X with option-space. Non UTF-8 web + * pages with non-breaking pages produce syntax errors. + */ + scanNonBreakingSpaces: function () { + return state.option.nonbsp ? + this.input.search(/(\u00A0)/) : -1; + }, + + /* + * Scan for characters that get silently deleted by one or more browsers. + */ + scanUnsafeChars: function () { + return this.input.search(reg.unsafeChars); + }, + + /* + * Produce the next raw token or return 'null' if no tokens can be matched. + * This method skips over all space characters. + */ + next: function (checks) { + this.from = this.char; + + // Move to the next non-space character. + var start; + if (/\s/.test(this.peek())) { + start = this.char; + + while (/\s/.test(this.peek())) { + this.from += 1; + this.skip(); + } + + if (this.peek() === "") { // EOL + if (!/^\s*$/.test(this.getLines()[this.line - 1]) && state.option.trailing) { + this.trigger("warning", { code: "W102", line: this.line, character: start }); + } + } + } + + // Methods that work with multi-line structures and move the + // character pointer. + + var match = this.scanComments() || + this.scanStringLiteral(checks); + + if (match) { + return match; + } + + // Methods that don't move the character pointer. + + match = + this.scanRegExp() || + this.scanPunctuator() || + this.scanKeyword() || + this.scanIdentifier() || + this.scanNumericLiteral(); + + if (match) { + this.skip(match.value.length); + return match; + } + + // No token could be matched, give up. + + return null; + }, + + /* + * Switch to the next line and reset all char pointers. Once + * switched, this method also checks for mixed spaces and tabs + * and other minor warnings. + */ + nextLine: function () { + var char; + + if (this.line >= this.getLines().length) { + return false; + } + + this.input = this.getLines()[this.line]; + this.line += 1; + this.char = 1; + this.from = 1; + + var inputTrimmed = this.input.trim(); + + var startsWith = function () { + return _.some(arguments, function (prefix) { + return inputTrimmed.indexOf(prefix) === 0; + }); + }; + + var endsWith = function () { + return _.some(arguments, function (suffix) { + return inputTrimmed.indexOf(suffix, inputTrimmed.length - suffix.length) !== -1; + }); + }; + + // If we are ignoring linter errors, replace the input with empty string + // if it doesn't already at least start or end a multi-line comment + if (state.ignoreLinterErrors === true) { + if (!startsWith("/*", "//") && !endsWith("*/")) { + this.input = ""; + } + } + + char = this.scanNonBreakingSpaces(); + if (char >= 0) { + this.trigger("warning", { code: "W125", line: this.line, character: char + 1 }); + } + + char = this.scanMixedSpacesAndTabs(); + if (char >= 0) { + this.trigger("warning", { code: "W099", line: this.line, character: char + 1 }); + } + + this.input = this.input.replace(/\t/g, state.tab); + char = this.scanUnsafeChars(); + + if (char >= 0) { + this.trigger("warning", { code: "W100", line: this.line, character: char }); + } + + // If there is a limit on line length, warn when lines get too + // long. + + if (state.option.maxlen && state.option.maxlen < this.input.length) { + var inComment = this.inComment || + startsWith.call(inputTrimmed, "//") || + startsWith.call(inputTrimmed, "/*"); + + var shouldTriggerError = !inComment || !reg.maxlenException.test(inputTrimmed); + + if (shouldTriggerError) { + this.trigger("warning", { code: "W101", line: this.line, character: this.input.length }); + } + } + + return true; + }, + + /* + * This is simply a synonym for nextLine() method with a friendlier + * public name. + */ + start: function () { + this.nextLine(); + }, + + /* + * Produce the next token. This function is called by advance() to get + * the next token. It retuns a token in a JSLint-compatible format. + */ + token: function () { + /*jshint loopfunc:true */ + var checks = asyncTrigger(); + var token; + + + function isReserved(token, isProperty) { + if (!token.reserved) { + return false; + } + var meta = token.meta; + + if (meta && meta.isFutureReservedWord && state.option.inES5()) { + // ES3 FutureReservedWord in an ES5 environment. + if (!meta.es5) { + return false; + } + + // Some ES5 FutureReservedWord identifiers are active only + // within a strict mode environment. + if (meta.strictOnly) { + if (!state.option.strict && !state.directive["use strict"]) { + return false; + } + } + + if (isProperty) { + return false; + } + } + + return true; + } + + // Produce a token object. + var create = function (type, value, isProperty) { + /*jshint validthis:true */ + var obj; + + if (type !== "(endline)" && type !== "(end)") { + this.prereg = false; + } + + if (type === "(punctuator)") { + switch (value) { + case ".": + case ")": + case "~": + case "#": + case "]": + this.prereg = false; + break; + default: + this.prereg = true; + } + + obj = Object.create(state.syntax[value] || state.syntax["(error)"]); + } + + if (type === "(identifier)") { + if (value === "return" || value === "case" || value === "typeof") { + this.prereg = true; + } + + if (_.has(state.syntax, value)) { + obj = Object.create(state.syntax[value] || state.syntax["(error)"]); + + // If this can't be a reserved keyword, reset the object. + if (!isReserved(obj, isProperty && type === "(identifier)")) { + obj = null; + } + } + } + + if (!obj) { + obj = Object.create(state.syntax[type]); + } + + obj.identifier = (type === "(identifier)"); + obj.type = obj.type || type; + obj.value = value; + obj.line = this.line; + obj.character = this.char; + obj.from = this.from; + + if (isProperty && obj.identifier) { + obj.isProperty = isProperty; + } + + obj.check = checks.check; + + return obj; + }.bind(this); + + for (;;) { + if (!this.input.length) { + return create(this.nextLine() ? "(endline)" : "(end)", ""); + } + + token = this.next(checks); + + if (!token) { + if (this.input.length) { + // Unexpected character. + this.trigger("error", { + code: "E024", + line: this.line, + character: this.char, + data: [ this.peek() ] + }); + + this.input = ""; + } + + continue; + } + + switch (token.type) { + case Token.StringLiteral: + this.triggerAsync("String", { + line: this.line, + char: this.char, + from: this.from, + value: token.value, + quote: token.quote + }, checks, function () { return true; }); + + return create("(string)", token.value); + case Token.Identifier: + this.trigger("Identifier", { + line: this.line, + char: this.char, + from: this.form, + name: token.value, + isProperty: state.tokens.curr.id === "." + }); + + /* falls through */ + case Token.Keyword: + case Token.NullLiteral: + case Token.BooleanLiteral: + return create("(identifier)", token.value, state.tokens.curr.id === "."); + + case Token.NumericLiteral: + if (token.isMalformed) { + this.trigger("warning", { + code: "W045", + line: this.line, + character: this.char, + data: [ token.value ] + }); + } + + this.triggerAsync("warning", { + code: "W114", + line: this.line, + character: this.char, + data: [ "0x-" ] + }, checks, function () { return token.base === 16 && state.jsonMode; }); + + this.triggerAsync("warning", { + code: "W115", + line: this.line, + character: this.char + }, checks, function () { + return state.directive["use strict"] && token.base === 8; + }); + + this.trigger("Number", { + line: this.line, + char: this.char, + from: this.from, + value: token.value, + base: token.base, + isMalformed: token.malformed + }); + + return create("(number)", token.value); + + case Token.RegExp: + return create("(regexp)", token.value); + + case Token.Comment: + state.tokens.curr.comment = true; + + if (token.isSpecial) { + return { + id: '(comment)', + value: token.value, + body: token.body, + type: token.commentType, + isSpecial: token.isSpecial, + line: this.line, + character: this.char, + from: this.from + }; + } + + break; + + case "": + break; + + default: + return create("(punctuator)", token.value); + } + } + } +}; + +exports.Lexer = Lexer; + +},{"../data/ascii-identifier-data.js":1,"../data/non-ascii-identifier-part-only.js":2,"../data/non-ascii-identifier-start.js":3,"./reg.js":16,"./state.js":17,"events":5,"underscore":11}],15:[function(require,module,exports){ +"use strict"; + +var _ = require("underscore"); + +var errors = { + // JSHint options + E001: "Bad option: '{a}'.", + E002: "Bad option value.", + + // JSHint input + E003: "Expected a JSON value.", + E004: "Input is neither a string nor an array of strings.", + E005: "Input is empty.", + E006: "Unexpected early end of program.", + + // Strict mode + E007: "Missing \"use strict\" statement.", + E008: "Strict violation.", + E009: "Option 'validthis' can't be used in a global scope.", + E010: "'with' is not allowed in strict mode.", + + // Constants + E011: "const '{a}' has already been declared.", + E012: "const '{a}' is initialized to 'undefined'.", + E013: "Attempting to override '{a}' which is a constant.", + + // Regular expressions + E014: "A regular expression literal can be confused with '/='.", + E015: "Unclosed regular expression.", + E016: "Invalid regular expression.", + + // Tokens + E017: "Unclosed comment.", + E018: "Unbegun comment.", + E019: "Unmatched '{a}'.", + E020: "Expected '{a}' to match '{b}' from line {c} and instead saw '{d}'.", + E021: "Expected '{a}' and instead saw '{b}'.", + E022: "Line breaking error '{a}'.", + E023: "Missing '{a}'.", + E024: "Unexpected '{a}'.", + E025: "Missing ':' on a case clause.", + E026: "Missing '}' to match '{' from line {a}.", + E027: "Missing ']' to match '[' from line {a}.", + E028: "Illegal comma.", + E029: "Unclosed string.", + + // Everything else + E030: "Expected an identifier and instead saw '{a}'.", + E031: "Bad assignment.", // FIXME: Rephrase + E032: "Expected a small integer or 'false' and instead saw '{a}'.", + E033: "Expected an operator and instead saw '{a}'.", + E034: "get/set are ES5 features.", + E035: "Missing property name.", + E036: "Expected to see a statement and instead saw a block.", + E037: null, + E038: null, + E039: "Function declarations are not invocable. Wrap the whole function invocation in parens.", + E040: "Each value should have its own case label.", + E041: "Unrecoverable syntax error.", + E042: "Stopping.", + E043: "Too many errors.", + E044: null, + E045: "Invalid for each loop.", + E046: "A yield statement shall be within a generator function (with syntax: `function*`)", + E047: null, // Vacant + E048: "Let declaration not directly within block.", + E049: "A {a} cannot be named '{b}'.", + E050: "Mozilla requires the yield expression to be parenthesized here.", + E051: "Regular parameters cannot come after default parameters." +}; + +var warnings = { + W001: "'hasOwnProperty' is a really bad name.", + W002: "Value of '{a}' may be overwritten in IE 8 and earlier.", + W003: "'{a}' was used before it was defined.", + W004: "'{a}' is already defined.", + W005: "A dot following a number can be confused with a decimal point.", + W006: "Confusing minuses.", + W007: "Confusing pluses.", + W008: "A leading decimal point can be confused with a dot: '{a}'.", + W009: "The array literal notation [] is preferable.", + W010: "The object literal notation {} is preferable.", + W011: "Unexpected space after '{a}'.", + W012: "Unexpected space before '{a}'.", + W013: "Missing space after '{a}'.", + W014: "Bad line breaking before '{a}'.", + W015: "Expected '{a}' to have an indentation at {b} instead at {c}.", + W016: "Unexpected use of '{a}'.", + W017: "Bad operand.", + W018: "Confusing use of '{a}'.", + W019: "Use the isNaN function to compare with NaN.", + W020: "Read only.", + W021: "'{a}' is a function.", + W022: "Do not assign to the exception parameter.", + W023: "Expected an identifier in an assignment and instead saw a function invocation.", + W024: "Expected an identifier and instead saw '{a}' (a reserved word).", + W025: "Missing name in function declaration.", + W026: "Inner functions should be listed at the top of the outer function.", + W027: "Unreachable '{a}' after '{b}'.", + W028: "Label '{a}' on {b} statement.", + W030: "Expected an assignment or function call and instead saw an expression.", + W031: "Do not use 'new' for side effects.", + W032: "Unnecessary semicolon.", + W033: "Missing semicolon.", + W034: "Unnecessary directive \"{a}\".", + W035: "Empty block.", + W036: "Unexpected /*member '{a}'.", + W037: "'{a}' is a statement label.", + W038: "'{a}' used out of scope.", + W039: "'{a}' is not allowed.", + W040: "Possible strict violation.", + W041: "Use '{a}' to compare with '{b}'.", + W042: "Avoid EOL escaping.", + W043: "Bad escaping of EOL. Use option multistr if needed.", + W044: "Bad or unnecessary escaping.", + W045: "Bad number '{a}'.", + W046: "Don't use extra leading zeros '{a}'.", + W047: "A trailing decimal point can be confused with a dot: '{a}'.", + W048: "Unexpected control character in regular expression.", + W049: "Unexpected escaped character '{a}' in regular expression.", + W050: "JavaScript URL.", + W051: "Variables should not be deleted.", + W052: "Unexpected '{a}'.", + W053: "Do not use {a} as a constructor.", + W054: "The Function constructor is a form of eval.", + W055: "A constructor name should start with an uppercase letter.", + W056: "Bad constructor.", + W057: "Weird construction. Is 'new' necessary?", + W058: "Missing '()' invoking a constructor.", + W059: "Avoid arguments.{a}.", + W060: "document.write can be a form of eval.", + W061: "eval can be harmful.", + W062: "Wrap an immediate function invocation in parens " + + "to assist the reader in understanding that the expression " + + "is the result of a function, and not the function itself.", + W063: "Math is not a function.", + W064: "Missing 'new' prefix when invoking a constructor.", + W065: "Missing radix parameter.", + W066: "Implied eval. Consider passing a function instead of a string.", + W067: "Bad invocation.", + W068: "Wrapping non-IIFE function literals in parens is unnecessary.", + W069: "['{a}'] is better written in dot notation.", + W070: "Extra comma. (it breaks older versions of IE)", + W071: "This function has too many statements. ({a})", + W072: "This function has too many parameters. ({a})", + W073: "Blocks are nested too deeply. ({a})", + W074: "This function's cyclomatic complexity is too high. ({a})", + W075: "Duplicate key '{a}'.", + W076: "Unexpected parameter '{a}' in get {b} function.", + W077: "Expected a single parameter in set {a} function.", + W078: "Setter is defined without getter.", + W079: "Redefinition of '{a}'.", + W080: "It's not necessary to initialize '{a}' to 'undefined'.", + W081: "Too many var statements.", + W082: "Function declarations should not be placed in blocks. " + + "Use a function expression or move the statement to the top of " + + "the outer function.", + W083: "Don't make functions within a loop.", + W084: "Expected a conditional expression and instead saw an assignment.", + W085: "Don't use 'with'.", + W086: "Expected a 'break' statement before '{a}'.", + W087: "Forgotten 'debugger' statement?", + W088: "Creating global 'for' variable. Should be 'for (var {a} ...'.", + W089: "The body of a for in should be wrapped in an if statement to filter " + + "unwanted properties from the prototype.", + W090: "'{a}' is not a statement label.", + W091: "'{a}' is out of scope.", + W093: "Did you mean to return a conditional instead of an assignment?", + W094: "Unexpected comma.", + W095: "Expected a string and instead saw {a}.", + W096: "The '{a}' key may produce unexpected results.", + W097: "Use the function form of \"use strict\".", + W098: "'{a}' is defined but never used.", + W099: "Mixed spaces and tabs.", + W100: "This character may get silently deleted by one or more browsers.", + W101: "Line is too long.", + W102: "Trailing whitespace.", + W103: "The '{a}' property is deprecated.", + W104: "'{a}' is available in ES6 (use esnext option) or Mozilla JS extensions (use moz).", + W105: "Unexpected {a} in '{b}'.", + W106: "Identifier '{a}' is not in camel case.", + W107: "Script URL.", + W108: "Strings must use doublequote.", + W109: "Strings must use singlequote.", + W110: "Mixed double and single quotes.", + W112: "Unclosed string.", + W113: "Control character in string: {a}.", + W114: "Avoid {a}.", + W115: "Octal literals are not allowed in strict mode.", + W116: "Expected '{a}' and instead saw '{b}'.", + W117: "'{a}' is not defined.", + W118: "'{a}' is only available in Mozilla JavaScript extensions (use moz option).", + W119: "'{a}' is only available in ES6 (use esnext option).", + W120: "You might be leaking a variable ({a}) here.", + W121: "Extending prototype of native object: '{a}'.", + W122: "Invalid typeof value '{a}'", + W123: "'{a}' is already defined in outer scope.", + W124: "A generator function shall contain a yield statement.", + W125: "This line contains non-breaking spaces: http://jshint.com/doc/options/#nonbsp" +}; + +var info = { + I001: "Comma warnings can be turned off with 'laxcomma'.", + I002: "Reserved words as properties can be used under the 'es5' option.", + I003: "ES5 option is now set per default" +}; + +exports.errors = {}; +exports.warnings = {}; +exports.info = {}; + +_.each(errors, function (desc, code) { + exports.errors[code] = { code: code, desc: desc }; +}); + +_.each(warnings, function (desc, code) { + exports.warnings[code] = { code: code, desc: desc }; +}); + +_.each(info, function (desc, code) { + exports.info[code] = { code: code, desc: desc }; +}); + +},{"underscore":11}],16:[function(require,module,exports){ +/* + * Regular expressions. Some of these are stupidly long. + */ + +/*jshint maxlen:1000 */ + +"use string"; + +// Unsafe comment or string (ax) +exports.unsafeString = + /@cc|<\/?|script|\]\s*\]|<\s*!|</i; + +// Unsafe characters that are silently deleted by one or more browsers (cx) +exports.unsafeChars = + /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/; + +// Characters in strings that need escaping (nx and nxg) +exports.needEsc = + /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/; + +exports.needEscGlobal = + /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + +// Star slash (lx) +exports.starSlash = /\*\//; + +// Identifier (ix) +exports.identifier = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/; + +// JavaScript URL (jx) +exports.javascriptURL = /^(?:javascript|jscript|ecmascript|vbscript|mocha|livescript)\s*:/i; + +// Catches /* falls through */ comments (ft) +exports.fallsThrough = /^\s*\/\*\s*falls?\sthrough\s*\*\/\s*$/; + +// very conservative rule (eg: only one space between the start of the comment and the first character) +// to relax the maxlen option +exports.maxlenException = /^(?:(?:\/\/|\/\*|\*) ?)?[^ ]+$/; + +},{}],17:[function(require,module,exports){ +"use strict"; + +var state = { + syntax: {}, + + reset: function () { + this.tokens = { + prev: null, + next: null, + curr: null + }; + + this.option = {}; + this.ignored = {}; + this.directive = {}; + this.jsonMode = false; + this.jsonWarnings = []; + this.lines = []; + this.tab = ""; + this.cache = {}; // Node.JS doesn't have Map. Sniff. + this.ignoreLinterErrors = false; // Blank out non-multi-line-commented + // lines when ignoring linter errors + } +}; + +exports.state = state; + +},{}],18:[function(require,module,exports){ +"use strict"; + +exports.register = function (linter) { + // Check for properties named __proto__. This special property was + // deprecated and then re-introduced for ES6. + + linter.on("Identifier", function style_scanProto(data) { + if (linter.getOption("proto")) { + return; + } + + if (data.name === "__proto__") { + linter.warn("W103", { + line: data.line, + char: data.char, + data: [ data.name ] + }); + } + }); + + // Check for properties named __iterator__. This is a special property + // available only in browsers with JavaScript 1.7 implementation. + + linter.on("Identifier", function style_scanIterator(data) { + if (linter.getOption("iterator")) { + return; + } + + if (data.name === "__iterator__") { + linter.warn("W104", { + line: data.line, + char: data.char, + data: [ data.name ] + }); + } + }); + + // Check for dangling underscores. + + linter.on("Identifier", function style_scanDangling(data) { + if (!linter.getOption("nomen")) { + return; + } + + // Underscore.js + if (data.name === "_") { + return; + } + + // In Node, __dirname and __filename should be ignored. + if (linter.getOption("node")) { + if (/^(__dirname|__filename)$/.test(data.name) && !data.isProperty) { + return; + } + } + + if (/^(_+.*|.*_+)$/.test(data.name)) { + linter.warn("W105", { + line: data.line, + char: data.from, + data: [ "dangling '_'", data.name ] + }); + } + }); + + // Check that all identifiers are using camelCase notation. + // Exceptions: names like MY_VAR and _myVar. + + linter.on("Identifier", function style_scanCamelCase(data) { + if (!linter.getOption("camelcase")) { + return; + } + + if (data.name.replace(/^_+|_+$/g, "").indexOf("_") > -1 && !data.name.match(/^[A-Z0-9_]*$/)) { + linter.warn("W106", { + line: data.line, + char: data.from, + data: [ data.name ] + }); + } + }); + + // Enforce consistency in style of quoting. + + linter.on("String", function style_scanQuotes(data) { + var quotmark = linter.getOption("quotmark"); + var code; + + if (!quotmark) { + return; + } + + // If quotmark is set to 'single' warn about all double-quotes. + + if (quotmark === "single" && data.quote !== "'") { + code = "W109"; + } + + // If quotmark is set to 'double' warn about all single-quotes. + + if (quotmark === "double" && data.quote !== "\"") { + code = "W108"; + } + + // If quotmark is set to true, remember the first quotation style + // and then warn about all others. + + if (quotmark === true) { + if (!linter.getCache("quotmark")) { + linter.setCache("quotmark", data.quote); + } + + if (linter.getCache("quotmark") !== data.quote) { + code = "W110"; + } + } + + if (code) { + linter.warn(code, { + line: data.line, + char: data.char, + }); + } + }); + + linter.on("Number", function style_scanNumbers(data) { + if (data.value.charAt(0) === ".") { + // Warn about a leading decimal point. + linter.warn("W008", { + line: data.line, + char: data.char, + data: [ data.value ] + }); + } + + if (data.value.substr(data.value.length - 1) === ".") { + // Warn about a trailing decimal point. + linter.warn("W047", { + line: data.line, + char: data.char, + data: [ data.value ] + }); + } + + if (/^00+/.test(data.value)) { + // Multiple leading zeroes. + linter.warn("W046", { + line: data.line, + char: data.char, + data: [ data.value ] + }); + } + }); + + // Warn about script URLs. + + linter.on("String", function style_scanJavaScriptURLs(data) { + var re = /^(?:javascript|jscript|ecmascript|vbscript|mocha|livescript)\s*:/i; + + if (linter.getOption("scripturl")) { + return; + } + + if (re.test(data.value)) { + linter.warn("W107", { + line: data.line, + char: data.char + }); + } + }); +}; +},{}],19:[function(require,module,exports){ +// jshint -W001 + +"use strict"; + +// Identifiers provided by the ECMAScript standard. + +exports.reservedVars = { + arguments : false, + NaN : false +}; + +exports.ecmaIdentifiers = { + Array : false, + Boolean : false, + Date : false, + decodeURI : false, + decodeURIComponent : false, + encodeURI : false, + encodeURIComponent : false, + Error : false, + "eval" : false, + EvalError : false, + Function : false, + hasOwnProperty : false, + isFinite : false, + isNaN : false, + JSON : false, + Math : false, + Map : false, + Number : false, + Object : false, + parseInt : false, + parseFloat : false, + RangeError : false, + ReferenceError : false, + RegExp : false, + Set : false, + String : false, + SyntaxError : false, + TypeError : false, + URIError : false, + WeakMap : false +}; + +// Global variables commonly provided by a web browser environment. + +exports.browser = { + Audio : false, + Blob : false, + addEventListener : false, + applicationCache : false, + atob : false, + blur : false, + btoa : false, + CanvasGradient : false, + CanvasPattern : false, + CanvasRenderingContext2D: false, + clearInterval : false, + clearTimeout : false, + close : false, + closed : false, + CustomEvent : false, + DOMParser : false, + defaultStatus : false, + document : false, + Element : false, + ElementTimeControl : false, + event : false, + FileReader : false, + FormData : false, + focus : false, + frames : false, + getComputedStyle : false, + HTMLElement : false, + HTMLAnchorElement : false, + HTMLBaseElement : false, + HTMLBlockquoteElement: false, + HTMLBodyElement : false, + HTMLBRElement : false, + HTMLButtonElement : false, + HTMLCanvasElement : false, + HTMLDirectoryElement : false, + HTMLDivElement : false, + HTMLDListElement : false, + HTMLFieldSetElement : false, + HTMLFontElement : false, + HTMLFormElement : false, + HTMLFrameElement : false, + HTMLFrameSetElement : false, + HTMLHeadElement : false, + HTMLHeadingElement : false, + HTMLHRElement : false, + HTMLHtmlElement : false, + HTMLIFrameElement : false, + HTMLImageElement : false, + HTMLInputElement : false, + HTMLIsIndexElement : false, + HTMLLabelElement : false, + HTMLLayerElement : false, + HTMLLegendElement : false, + HTMLLIElement : false, + HTMLLinkElement : false, + HTMLMapElement : false, + HTMLMenuElement : false, + HTMLMetaElement : false, + HTMLModElement : false, + HTMLObjectElement : false, + HTMLOListElement : false, + HTMLOptGroupElement : false, + HTMLOptionElement : false, + HTMLParagraphElement : false, + HTMLParamElement : false, + HTMLPreElement : false, + HTMLQuoteElement : false, + HTMLScriptElement : false, + HTMLSelectElement : false, + HTMLStyleElement : false, + HTMLTableCaptionElement: false, + HTMLTableCellElement : false, + HTMLTableColElement : false, + HTMLTableElement : false, + HTMLTableRowElement : false, + HTMLTableSectionElement: false, + HTMLTextAreaElement : false, + HTMLTitleElement : false, + HTMLUListElement : false, + HTMLVideoElement : false, + history : false, + Image : false, + length : false, + localStorage : false, + location : false, + matchMedia : false, + MessageChannel : false, + MessageEvent : false, + MessagePort : false, + MouseEvent : false, + moveBy : false, + moveTo : false, + MutationObserver : false, + name : false, + Node : false, + NodeFilter : false, + navigator : false, + onbeforeunload : true, + onblur : true, + onerror : true, + onfocus : true, + onload : true, + onresize : true, + onunload : true, + open : false, + openDatabase : false, + opener : false, + Option : false, + parent : false, + print : false, + removeEventListener : false, + resizeBy : false, + resizeTo : false, + screen : false, + scroll : false, + scrollBy : false, + scrollTo : false, + sessionStorage : false, + setInterval : false, + setTimeout : false, + SharedWorker : false, + status : false, + SVGAElement : false, + SVGAltGlyphDefElement: false, + SVGAltGlyphElement : false, + SVGAltGlyphItemElement: false, + SVGAngle : false, + SVGAnimateColorElement: false, + SVGAnimateElement : false, + SVGAnimateMotionElement: false, + SVGAnimateTransformElement: false, + SVGAnimatedAngle : false, + SVGAnimatedBoolean : false, + SVGAnimatedEnumeration: false, + SVGAnimatedInteger : false, + SVGAnimatedLength : false, + SVGAnimatedLengthList: false, + SVGAnimatedNumber : false, + SVGAnimatedNumberList: false, + SVGAnimatedPathData : false, + SVGAnimatedPoints : false, + SVGAnimatedPreserveAspectRatio: false, + SVGAnimatedRect : false, + SVGAnimatedString : false, + SVGAnimatedTransformList: false, + SVGAnimationElement : false, + SVGCSSRule : false, + SVGCircleElement : false, + SVGClipPathElement : false, + SVGColor : false, + SVGColorProfileElement: false, + SVGColorProfileRule : false, + SVGComponentTransferFunctionElement: false, + SVGCursorElement : false, + SVGDefsElement : false, + SVGDescElement : false, + SVGDocument : false, + SVGElement : false, + SVGElementInstance : false, + SVGElementInstanceList: false, + SVGEllipseElement : false, + SVGExternalResourcesRequired: false, + SVGFEBlendElement : false, + SVGFEColorMatrixElement: false, + SVGFEComponentTransferElement: false, + SVGFECompositeElement: false, + SVGFEConvolveMatrixElement: false, + SVGFEDiffuseLightingElement: false, + SVGFEDisplacementMapElement: false, + SVGFEDistantLightElement: false, + SVGFEFloodElement : false, + SVGFEFuncAElement : false, + SVGFEFuncBElement : false, + SVGFEFuncGElement : false, + SVGFEFuncRElement : false, + SVGFEGaussianBlurElement: false, + SVGFEImageElement : false, + SVGFEMergeElement : false, + SVGFEMergeNodeElement: false, + SVGFEMorphologyElement: false, + SVGFEOffsetElement : false, + SVGFEPointLightElement: false, + SVGFESpecularLightingElement: false, + SVGFESpotLightElement: false, + SVGFETileElement : false, + SVGFETurbulenceElement: false, + SVGFilterElement : false, + SVGFilterPrimitiveStandardAttributes: false, + SVGFitToViewBox : false, + SVGFontElement : false, + SVGFontFaceElement : false, + SVGFontFaceFormatElement: false, + SVGFontFaceNameElement: false, + SVGFontFaceSrcElement: false, + SVGFontFaceUriElement: false, + SVGForeignObjectElement: false, + SVGGElement : false, + SVGGlyphElement : false, + SVGGlyphRefElement : false, + SVGGradientElement : false, + SVGHKernElement : false, + SVGICCColor : false, + SVGImageElement : false, + SVGLangSpace : false, + SVGLength : false, + SVGLengthList : false, + SVGLineElement : false, + SVGLinearGradientElement: false, + SVGLocatable : false, + SVGMPathElement : false, + SVGMarkerElement : false, + SVGMaskElement : false, + SVGMatrix : false, + SVGMetadataElement : false, + SVGMissingGlyphElement: false, + SVGNumber : false, + SVGNumberList : false, + SVGPaint : false, + SVGPathElement : false, + SVGPathSeg : false, + SVGPathSegArcAbs : false, + SVGPathSegArcRel : false, + SVGPathSegClosePath : false, + SVGPathSegCurvetoCubicAbs: false, + SVGPathSegCurvetoCubicRel: false, + SVGPathSegCurvetoCubicSmoothAbs: false, + SVGPathSegCurvetoCubicSmoothRel: false, + SVGPathSegCurvetoQuadraticAbs: false, + SVGPathSegCurvetoQuadraticRel: false, + SVGPathSegCurvetoQuadraticSmoothAbs: false, + SVGPathSegCurvetoQuadraticSmoothRel: false, + SVGPathSegLinetoAbs : false, + SVGPathSegLinetoHorizontalAbs: false, + SVGPathSegLinetoHorizontalRel: false, + SVGPathSegLinetoRel : false, + SVGPathSegLinetoVerticalAbs: false, + SVGPathSegLinetoVerticalRel: false, + SVGPathSegList : false, + SVGPathSegMovetoAbs : false, + SVGPathSegMovetoRel : false, + SVGPatternElement : false, + SVGPoint : false, + SVGPointList : false, + SVGPolygonElement : false, + SVGPolylineElement : false, + SVGPreserveAspectRatio: false, + SVGRadialGradientElement: false, + SVGRect : false, + SVGRectElement : false, + SVGRenderingIntent : false, + SVGSVGElement : false, + SVGScriptElement : false, + SVGSetElement : false, + SVGStopElement : false, + SVGStringList : false, + SVGStylable : false, + SVGStyleElement : false, + SVGSwitchElement : false, + SVGSymbolElement : false, + SVGTRefElement : false, + SVGTSpanElement : false, + SVGTests : false, + SVGTextContentElement: false, + SVGTextElement : false, + SVGTextPathElement : false, + SVGTextPositioningElement: false, + SVGTitleElement : false, + SVGTransform : false, + SVGTransformList : false, + SVGTransformable : false, + SVGURIReference : false, + SVGUnitTypes : false, + SVGUseElement : false, + SVGVKernElement : false, + SVGViewElement : false, + SVGViewSpec : false, + SVGZoomAndPan : false, + TimeEvent : false, + top : false, + URL : false, + WebSocket : false, + window : false, + Worker : false, + XMLHttpRequest : false, + XMLSerializer : false, + XPathEvaluator : false, + XPathException : false, + XPathExpression : false, + XPathNamespace : false, + XPathNSResolver : false, + XPathResult : false +}; + +exports.devel = { + alert : false, + confirm: false, + console: false, + Debug : false, + opera : false, + prompt : false +}; + +exports.worker = { + importScripts: true, + postMessage : true, + self : true +}; + +// Widely adopted global names that are not part of ECMAScript standard +exports.nonstandard = { + escape : false, + unescape: false +}; + +// Globals provided by popular JavaScript environments. + +exports.couch = { + "require" : false, + respond : false, + getRow : false, + emit : false, + send : false, + start : false, + sum : false, + log : false, + exports : false, + module : false, + provides : false +}; + +exports.node = { + __filename : false, + __dirname : false, + GLOBAL : false, + global : false, + module : false, + require : false, + + // These globals are writeable because Node allows the following + // usage pattern: var Buffer = require("buffer").Buffer; + + Buffer : true, + console : true, + exports : true, + process : true, + setTimeout : true, + clearTimeout : true, + setInterval : true, + clearInterval : true, + setImmediate : true, // v0.9.1+ + clearImmediate: true // v0.9.1+ +}; + +exports.phantom = { + phantom : true, + require : true, + WebPage : true, + console : true, // in examples, but undocumented + exports : true // v1.7+ +}; + +exports.rhino = { + defineClass : false, + deserialize : false, + gc : false, + help : false, + importPackage: false, + "java" : false, + load : false, + loadClass : false, + print : false, + quit : false, + readFile : false, + readUrl : false, + runCommand : false, + seal : false, + serialize : false, + spawn : false, + sync : false, + toint32 : false, + version : false +}; + +exports.shelljs = { + target : false, + echo : false, + exit : false, + cd : false, + pwd : false, + ls : false, + find : false, + cp : false, + rm : false, + mv : false, + mkdir : false, + test : false, + cat : false, + sed : false, + grep : false, + which : false, + dirs : false, + pushd : false, + popd : false, + env : false, + exec : false, + chmod : false, + config : false, + error : false, + tempdir : false +}; + +exports.typed = { + ArrayBuffer : false, + ArrayBufferView : false, + DataView : false, + Float32Array : false, + Float64Array : false, + Int16Array : false, + Int32Array : false, + Int8Array : false, + Uint16Array : false, + Uint32Array : false, + Uint8Array : false, + Uint8ClampedArray : false +}; + +exports.wsh = { + ActiveXObject : true, + Enumerator : true, + GetObject : true, + ScriptEngine : true, + ScriptEngineBuildVersion : true, + ScriptEngineMajorVersion : true, + ScriptEngineMinorVersion : true, + VBArray : true, + WSH : true, + WScript : true, + XDomainRequest : true +}; + +// Globals provided by popular JavaScript libraries. + +exports.dojo = { + dojo : false, + dijit : false, + dojox : false, + define : false, + "require": false +}; + +exports.jquery = { + "$" : false, + jQuery : false +}; + +exports.mootools = { + "$" : false, + "$$" : false, + Asset : false, + Browser : false, + Chain : false, + Class : false, + Color : false, + Cookie : false, + Core : false, + Document : false, + DomReady : false, + DOMEvent : false, + DOMReady : false, + Drag : false, + Element : false, + Elements : false, + Event : false, + Events : false, + Fx : false, + Group : false, + Hash : false, + HtmlTable : false, + Iframe : false, + IframeShim : false, + InputValidator: false, + instanceOf : false, + Keyboard : false, + Locale : false, + Mask : false, + MooTools : false, + Native : false, + Options : false, + OverText : false, + Request : false, + Scroller : false, + Slick : false, + Slider : false, + Sortables : false, + Spinner : false, + Swiff : false, + Tips : false, + Type : false, + typeOf : false, + URI : false, + Window : false +}; + +exports.prototypejs = { + "$" : false, + "$$" : false, + "$A" : false, + "$F" : false, + "$H" : false, + "$R" : false, + "$break" : false, + "$continue" : false, + "$w" : false, + Abstract : false, + Ajax : false, + Class : false, + Enumerable : false, + Element : false, + Event : false, + Field : false, + Form : false, + Hash : false, + Insertion : false, + ObjectRange : false, + PeriodicalExecuter: false, + Position : false, + Prototype : false, + Selector : false, + Template : false, + Toggle : false, + Try : false, + Autocompleter : false, + Builder : false, + Control : false, + Draggable : false, + Draggables : false, + Droppables : false, + Effect : false, + Sortable : false, + SortableObserver : false, + Sound : false, + Scriptaculous : false +}; + +exports.yui = { + YUI : false, + Y : false, + YUI_config: false +}; + + +},{}]},{},["nr+AlQ"]) +JSHINT = require('jshint').JSHINT; +if (typeof exports === 'object' && exports) exports.JSHINT = JSHINT; +}()); \ No newline at end of file diff --git a/eslint/tests/bench/medium.js b/eslint/tests/bench/medium.js new file mode 100644 index 0000000..3edf835 --- /dev/null +++ b/eslint/tests/bench/medium.js @@ -0,0 +1,9111 @@ +/*! + * jQuery JavaScript Library v2.1.0 + * http://jquery.com/ + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * + * Copyright 2005, 2014 jQuery Foundation, Inc. and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2014-01-23T21:10Z + */ + +(function( global, factory ) { + + if ( typeof module === "object" && typeof module.exports === "object" ) { + // For CommonJS and CommonJS-like environments where a proper window is present, + // execute the factory and get jQuery + // For environments that do not inherently posses a window with a document + // (such as Node.js), expose a jQuery-making factory as module.exports + // This accentuates the need for the creation of a real window + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Can't do this because several apps including ASP.NET trace +// the stack via arguments.caller.callee and Firefox dies if +// you try to trace through "use strict" call chains. (#13335) +// Support: Firefox 18+ +// + +var arr = []; + +var slice = arr.slice; + +var concat = arr.concat; + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var trim = "".trim; + +var support = {}; + + + +var + // Use the correct document accordingly with window argument (sandbox) + document = window.document, + + version = "2.1.0", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }, + + // Matches dashed string for camelizing + rmsPrefix = /^-ms-/, + rdashAlpha = /-([\da-z])/gi, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return letter.toUpperCase(); + }; + +jQuery.fn = jQuery.prototype = { + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // Start with an empty selector + selector: "", + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num != null ? + + // Return a 'clean' array + ( num < 0 ? this[ num + this.length ] : this[ num ] ) : + + // Return just the object + slice.call( this ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + ret.context = this.context; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return jQuery.type(obj) === "function"; + }, + + isArray: Array.isArray, + + isWindow: function( obj ) { + return obj != null && obj === obj.window; + }, + + isNumeric: function( obj ) { + // parseFloat NaNs numeric-cast false positives (null|true|false|"") + // ...but misinterprets leading-number strings, particularly hex literals ("0x...") + // subtraction forces infinities to NaN + return obj - parseFloat( obj ) >= 0; + }, + + isPlainObject: function( obj ) { + // Not plain objects: + // - Any object or value whose internal [[Class]] property is not "[object Object]" + // - DOM nodes + // - window + if ( jQuery.type( obj ) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + // Support: Firefox <20 + // The try/catch suppresses exceptions thrown when attempting to access + // the "constructor" property of certain host objects, ie. |window.location| + // https://bugzilla.mozilla.org/show_bug.cgi?id=814622 + try { + if ( obj.constructor && + !Object.prototype.hasOwnProperty.call( obj.constructor.prototype, "isPrototypeOf" ) ) { + return false; + } + } catch ( e ) { + return false; + } + + // If the function hasn't returned already, we're confident that + // |obj| is a plain object, created by {} or constructed with new Object + return true; + }, + + isEmptyObject: function( obj ) { + var name; + for ( name in obj ) { + return false; + } + return true; + }, + + type: function( obj ) { + if ( obj == null ) { + return obj + ""; + } + // Support: Android < 4.0, iOS < 6 (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call(obj) ] || "object" : + typeof obj; + }, + + // Evaluates a script in a global context + globalEval: function( code ) { + var script, + indirect = eval; + + code = jQuery.trim( code ); + + if ( code ) { + // If the code includes a valid, prologue position + // strict mode pragma, execute code by injecting a + // script tag into the document. + if ( code.indexOf("use strict") === 1 ) { + script = document.createElement("script"); + script.text = code; + document.head.appendChild( script ).parentNode.removeChild( script ); + } else { + // Otherwise, avoid the DOM node creation, insertion + // and removal by using an indirect global eval + indirect( code ); + } + } + }, + + // Convert dashed to camelCase; used by the css and data modules + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + }, + + // args is for internal usage only + each: function( obj, callback, args ) { + var value, + i = 0, + length = obj.length, + isArray = isArraylike( obj ); + + if ( args ) { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } + } + + return obj; + }, + + trim: function( text ) { + return text == null ? "" : trim.call( text ); + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArraylike( Object(arr) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var value, + i = 0, + length = elems.length, + isArray = isArraylike( elems ), + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + var tmp, args, proxy; + + if ( typeof context === "string" ) { + tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + args = slice.call( arguments, 2 ); + proxy = function() { + return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || jQuery.guid++; + + return proxy; + }, + + now: Date.now, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +}); + +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +function isArraylike( obj ) { + var length = obj.length, + type = jQuery.type( obj ); + + if ( type === "function" || jQuery.isWindow( obj ) ) { + return false; + } + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v1.10.16 + * http://sizzlejs.com/ + * + * Copyright 2013 jQuery Foundation, Inc. and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2014-01-13 + */ +(function( window ) { + +var i, + support, + Expr, + getText, + isXML, + compile, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + -(new Date()), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // General-purpose constants + strundefined = typeof undefined, + MAX_NEGATIVE = 1 << 31, + + // Instance methods + hasOwn = ({}).hasOwnProperty, + arr = [], + pop = arr.pop, + push_native = arr.push, + push = arr.push, + slice = arr.slice, + // Use a stripped-down indexOf if we can't use a native one + indexOf = arr.indexOf || function( elem ) { + var i = 0, + len = this.length; + for ( ; i < len; i++ ) { + if ( this[i] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + // http://www.w3.org/TR/css3-syntax/#characters + characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", + + // Loosely modeled on CSS identifier characters + // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors + // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = characterEncoding.replace( "w", "w#" ), + + // Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace + + "*(?:([*^$|!~]?=)" + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]", + + // Prefer arguments quoted, + // then not containing pseudos/brackets, + // then attribute selectors/non-parenthetical expressions, + // then anything else + // These preferences are here to reduce the number of selectors + // needing tokenize in the PSEUDO preFilter + pseudos = ":(" + characterEncoding + ")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|" + attributes.replace( 3, 8 ) + ")*)|.*)\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), + + rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + characterEncoding + ")" ), + "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ), + "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + rescape = /'|\\/g, + + // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), + funescape = function( _, escaped, escapedWhitespace ) { + var high = "0x" + escaped - 0x10000; + // NaN means non-codepoint + // Support: Firefox + // Workaround erroneous numeric interpretation of +"0x" + return high !== high || escapedWhitespace ? + escaped : + high < 0 ? + // BMP codepoint + String.fromCharCode( high + 0x10000 ) : + // Supplemental Plane codepoint (surrogate pair) + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }; + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + (arr = slice.call( preferredDoc.childNodes )), + preferredDoc.childNodes + ); + // Support: Android<4.0 + // Detect silently failing push.apply + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + push_native.apply( target, slice.call(els) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + // Can't trust NodeList.length + while ( (target[j++] = els[i++]) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var match, elem, m, nodeType, + // QSA vars + i, groups, old, nid, newContext, newSelector; + + if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { + setDocument( context ); + } + + context = context || document; + results = results || []; + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) { + return []; + } + + if ( documentIsHTML && !seed ) { + + // Shortcuts + if ( (match = rquickExpr.exec( selector )) ) { + // Speed-up: Sizzle("#ID") + if ( (m = match[1]) ) { + if ( nodeType === 9 ) { + elem = context.getElementById( m ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document (jQuery #6963) + if ( elem && elem.parentNode ) { + // Handle the case where IE, Opera, and Webkit return items + // by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + } else { + // Context is not a document + if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) && + contains( context, elem ) && elem.id === m ) { + results.push( elem ); + return results; + } + } + + // Speed-up: Sizzle("TAG") + } else if ( match[2] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Speed-up: Sizzle(".CLASS") + } else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) { + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // QSA path + if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { + nid = old = expando; + newContext = context; + newSelector = nodeType === 9 && selector; + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + groups = tokenize( selector ); + + if ( (old = context.getAttribute("id")) ) { + nid = old.replace( rescape, "\\$&" ); + } else { + context.setAttribute( "id", nid ); + } + nid = "[id='" + nid + "'] "; + + i = groups.length; + while ( i-- ) { + groups[i] = nid + toSelector( groups[i] ); + } + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context; + newSelector = groups.join(","); + } + + if ( newSelector ) { + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch(qsaError) { + } finally { + if ( !old ) { + context.removeAttribute("id"); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {Function(string, Object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return (cache[ key + " " ] = value); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created div and expects a boolean result + */ +function assert( fn ) { + var div = document.createElement("div"); + + try { + return !!fn( div ); + } catch (e) { + return false; + } finally { + // Remove from its parent by default + if ( div.parentNode ) { + div.parentNode.removeChild( div ); + } + // release memory in IE + div = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {string} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split("|"), + i = attrs.length; + + while ( i-- ) { + Expr.attrHandle[ arr[i] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + ( ~b.sourceIndex || MAX_NEGATIVE ) - + ( ~a.sourceIndex || MAX_NEGATIVE ); + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( (cur = cur.nextSibling) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {string} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {string} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction(function( argument ) { + argument = +argument; + return markFunction(function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ (j = matchIndexes[i]) ] ) { + seed[j] = !(matches[j] = seed[j]); + } + } + }); + }); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== strundefined && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = elem && (elem.ownerDocument || elem).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, + doc = node ? node.ownerDocument || node : preferredDoc, + parent = doc.defaultView; + + // If no document and documentElement is available, return + if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Set our document + document = doc; + docElem = doc.documentElement; + + // Support tests + documentIsHTML = !isXML( doc ); + + // Support: IE>8 + // If iframe document is assigned to "document" variable and if iframe has been reloaded, + // IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936 + // IE6-8 do not support the defaultView property so parent will be undefined + if ( parent && parent !== parent.top ) { + // IE11 does not have attachEvent, so all must suffer + if ( parent.addEventListener ) { + parent.addEventListener( "unload", function() { + setDocument(); + }, false ); + } else if ( parent.attachEvent ) { + parent.attachEvent( "onunload", function() { + setDocument(); + }); + } + } + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties (excepting IE8 booleans) + support.attributes = assert(function( div ) { + div.className = "i"; + return !div.getAttribute("className"); + }); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert(function( div ) { + div.appendChild( doc.createComment("") ); + return !div.getElementsByTagName("*").length; + }); + + // Check if getElementsByClassName can be trusted + support.getElementsByClassName = rnative.test( doc.getElementsByClassName ) && assert(function( div ) { + div.innerHTML = "
"; + + // Support: Safari<4 + // Catch class over-caching + div.firstChild.className = "i"; + // Support: Opera<10 + // Catch gEBCN failure to find non-leading classes + return div.getElementsByClassName("i").length === 2; + }); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert(function( div ) { + docElem.appendChild( div ).id = expando; + return !doc.getElementsByName || !doc.getElementsByName( expando ).length; + }); + + // ID find and filter + if ( support.getById ) { + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== strundefined && documentIsHTML ) { + var m = context.getElementById( id ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [m] : []; + } + }; + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute("id") === attrId; + }; + }; + } else { + // Support: IE6/7 + // getElementById is not reliable as a find shortcut + delete Expr.find["ID"]; + + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id"); + return node && node.value === attrId; + }; + }; + } + + // Tag + Expr.find["TAG"] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== strundefined ) { + return context.getElementsByTagName( tag ); + } + } : + function( tag, context ) { + var elem, + tmp = [], + i = 0, + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( (elem = results[i++]) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== strundefined && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See http://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) { + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert(function( div ) { + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // http://bugs.jquery.com/ticket/12359 + div.innerHTML = ""; + + // Support: IE8, Opera 10-12 + // Nothing should be selected when empty strings follow ^= or $= or *= + if ( div.querySelectorAll("[t^='']").length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !div.querySelectorAll("[selected]").length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":checked").length ) { + rbuggyQSA.push(":checked"); + } + }); + + assert(function( div ) { + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = doc.createElement("input"); + input.setAttribute( "type", "hidden" ); + div.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( div.querySelectorAll("[name=d]").length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":enabled").length ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Opera 10-11 does not throw on post-comma invalid pseudos + div.querySelectorAll("*,:x"); + rbuggyQSA.push(",.*:"); + }); + } + + if ( (support.matchesSelector = rnative.test( (matches = docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector) )) ) { + + assert(function( div ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( div, "div" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( div, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + }); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully does not implement inclusive descendent + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } : + function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { + + // Choose the first element that is related to our preferred document + if ( a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { + return -1; + } + if ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + return a === doc ? -1 : + b === doc ? 1 : + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( (cur = cur.parentNode) ) { + ap.unshift( cur ); + } + cur = b; + while ( (cur = cur.parentNode) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[i] === bp[i] ) { + i++; + } + + return i ? + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[i], bp[i] ) : + + // Otherwise nodes in our document sort first + ap[i] === preferredDoc ? -1 : + bp[i] === preferredDoc ? 1 : + 0; + }; + + return doc; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + // Make sure that attribute selectors are quoted + expr = expr.replace( rattributeQuotes, "='$1']" ); + + if ( support.matchesSelector && documentIsHTML && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch(e) {} + } + + return Sizzle( expr, document, null, [elem] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + // Set document vars if needed + if ( ( context.ownerDocument || context ) !== document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && Object.prototype.hasOwnProperty.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + (val = elem.getAttributeNode(name)) && val.specified ? + val.value : + null; +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( (elem = results[i++]) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + // If no nodeType, this is expected to be an array + while ( (node = elem[i++]) ) { + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[1] = match[1].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[3] = ( match[4] || match[5] || "" ).replace( runescape, funescape ); + + if ( match[2] === "~=" ) { + match[3] = " " + match[3] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[1] = match[1].toLowerCase(); + + if ( match[1].slice( 0, 3 ) === "nth" ) { + // nth-* requires argument + if ( !match[3] ) { + Sizzle.error( match[0] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); + match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + + // other types prohibit arguments + } else if ( match[3] ) { + Sizzle.error( match[0] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[5] && match[2]; + + if ( matchExpr["CHILD"].test( match[0] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[3] && match[4] !== undefined ) { + match[2] = match[4]; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + // Get excess from tokenize (recursively) + (excess = tokenize( unquoted, true )) && + // advance to the next closing parenthesis + (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + + // excess is a negative index + match[0] = match[0].slice( 0, excess ); + match[2] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { return true; } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && + classCache( className, function( elem ) { + return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== strundefined && elem.getAttribute("class") || "" ); + }); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + }; + }, + + "CHILD": function( type, what, argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, context, xml ) { + var cache, outerCache, node, diff, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( (node = node[ dir ]) ) { + if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { + return false; + } + } + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + // Seek `elem` from a previously-cached index + outerCache = parent[ expando ] || (parent[ expando ] = {}); + cache = outerCache[ type ] || []; + nodeIndex = cache[0] === dirruns && cache[1]; + diff = cache[0] === dirruns && cache[2]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( (node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + (diff = nodeIndex = 0) || start.pop()) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + outerCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + // Use previously-cached element index if available + } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) { + diff = cache[1]; + + // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...) + } else { + // Use the same loop as above to seek `elem` from the start + while ( (node = ++nodeIndex && node && node[ dir ] || + (diff = nodeIndex = 0) || start.pop()) ) { + + if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { + // Cache the index of each encountered element + if ( useCache ) { + (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction(function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf.call( seed, matched[i] ); + seed[ idx ] = !( matches[ idx ] = matched[i] ); + } + }) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + // Potentially complex pseudos + "not": markFunction(function( selector ) { + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction(function( seed, matches, context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( (elem = unmatched[i]) ) { + seed[i] = !(matches[i] = elem); + } + } + }) : + function( elem, context, xml ) { + input[0] = elem; + matcher( input, null, xml, results ); + return !results.pop(); + }; + }), + + "has": markFunction(function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + }), + + "contains": markFunction(function( text ) { + return function( elem ) { + return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; + }; + }), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + // lang value must be a valid identifier + if ( !ridentifier.test(lang || "") ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( (elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); + return false; + }; + }), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + }, + + // Boolean properties + "enabled": function( elem ) { + return elem.disabled === false; + }, + + "disabled": function( elem ) { + return elem.disabled === true; + }, + + "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + }, + + "selected": function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos["empty"]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo(function() { + return [ 0 ]; + }), + + "last": createPositionalPseudo(function( matchIndexes, length ) { + return [ length - 1 ]; + }), + + "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + }), + + "even": createPositionalPseudo(function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "odd": createPositionalPseudo(function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }) + } +}; + +Expr.pseudos["nth"] = Expr.pseudos["eq"]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +function tokenize( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( match ) { + // Don't consume trailing commas as valid + soFar = soFar.slice( match[0].length ) || soFar; + } + groups.push( (tokens = []) ); + } + + matched = false; + + // Combinators + if ( (match = rcombinators.exec( soFar )) ) { + matched = match.shift(); + tokens.push({ + value: matched, + // Cast descendant combinators to space + type: match[0].replace( rtrim, " " ) + }); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || + (match = preFilters[ type ]( match ))) ) { + matched = match.shift(); + tokens.push({ + value: matched, + type: type, + matches: match + }); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +} + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[i].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + checkNonElements = base && dir === "parentNode", + doneName = done++; + + return combinator.first ? + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching + if ( xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || (elem[ expando ] = {}); + if ( (oldCache = outerCache[ dir ]) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return (newCache[ 2 ] = oldCache[ 2 ]); + } else { + // Reuse newcache so results back-propagate to previous elements + outerCache[ dir ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { + return true; + } + } + } + } + } + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[i]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[0]; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( (elem = unmatched[i]) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction(function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( (elem = temp[i]) ) { + matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) ) { + // Restore matcherIn since elem is not yet a final match + temp.push( (matcherIn[i] = elem) ); + } + } + postFinder( null, (matcherOut = []), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) && + (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) { + + seed[temp] = !(results[temp] = elem); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + }); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[0].type ], + implicitRelative = leadingRelative || Expr.relative[" "], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf.call( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + return ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + (checkContext = context).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + } ]; + + for ( ; i < len; i++ ) { + if ( (matcher = Expr.relative[ tokens[i].type ]) ) { + matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; + } else { + matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[j].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), + len = elems.length; + + if ( outermost ) { + outermostContext = context !== document && context; + } + + // Add elements passing elementMatchers directly to results + // Keep `i` a string if there are no elements so `matchedCount` will be "00" below + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && (elem = elems[i]) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + while ( (matcher = elementMatchers[j++]) ) { + if ( matcher( elem, context, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + // They will have gone through all possible matchers + if ( (elem = !matcher && elem) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // Apply set filters to unmatched elements + matchedCount += i; + if ( bySet && i !== matchedCount ) { + j = 0; + while ( (matcher = setMatchers[j++]) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !(unmatched[i] || setMatched[i]) ) { + setMatched[i] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + // Generate a function of recursive functions that can be used to check each element + if ( !group ) { + group = tokenize( selector ); + } + i = group.length; + while ( i-- ) { + cached = matcherFromTokens( group[i] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + } + return cached; +}; + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[i], results ); + } + return results; +} + +function select( selector, context, results, seed ) { + var i, tokens, token, type, find, + match = tokenize( selector ); + + if ( !seed ) { + // Try to minimize operations if there is only one group + if ( match.length === 1 ) { + + // Take a shortcut and set the context if the root selector is an ID + tokens = match[0] = match[0].slice( 0 ); + if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && + support.getById && context.nodeType === 9 && documentIsHTML && + Expr.relative[ tokens[1].type ] ) { + + context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; + if ( !context ) { + return results; + } + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[i]; + + // Abort if we hit a combinator + if ( Expr.relative[ (type = token.type) ] ) { + break; + } + if ( (find = Expr.find[ type ]) ) { + // Search, expanding context for leading sibling combinators + if ( (seed = find( + token.matches[0].replace( runescape, funescape ), + rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context + )) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + } + + // Compile and execute a filtering function + // Provide `match` to avoid retokenization if we modified the selector above + compile( selector, match )( + seed, + context, + !documentIsHTML, + results, + rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +} + +// One-time assignments + +// Sort stability +support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; + +// Support: Chrome<14 +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert(function( div1 ) { + // Should return 1, but returns 4 (following) + return div1.compareDocumentPosition( document.createElement("div") ) & 1; +}); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert(function( div ) { + div.innerHTML = ""; + return div.firstChild.getAttribute("href") === "#" ; +}) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + }); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert(function( div ) { + div.innerHTML = ""; + div.firstChild.setAttribute( "value", "" ); + return div.firstChild.getAttribute( "value" ) === ""; +}) ) { + addHandle( "value", function( elem, name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + }); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert(function( div ) { + return div.getAttribute("disabled") == null; +}) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + (val = elem.getAttributeNode( name )) && val.specified ? + val.value : + null; + } + }); +} + +return Sizzle; + +})( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.pseudos; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + + +var rneedsContext = jQuery.expr.match.needsContext; + +var rsingleTag = (/^<(\w+)\s*\/?>(?:<\/\1>|)$/); + + + +var risSimple = /^.[^:#\[\.,]*$/; + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + /* jshint -W018 */ + return !!qualifier.call( elem, i, elem ) !== not; + }); + + } + + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + }); + + } + + if ( typeof qualifier === "string" ) { + if ( risSimple.test( qualifier ) ) { + return jQuery.filter( qualifier, elements, not ); + } + + qualifier = jQuery.filter( qualifier, elements ); + } + + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) >= 0 ) !== not; + }); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 && elem.nodeType === 1 ? + jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] : + jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + })); +}; + +jQuery.fn.extend({ + find: function( selector ) { + var i, + len = this.length, + ret = [], + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter(function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + }) ); + } + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + // Needed because $( selector, context ) becomes $( context ).find( selector ) + ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret ); + ret.selector = this.selector ? this.selector + " " + selector : selector; + return ret; + }, + filter: function( selector ) { + return this.pushStack( winnow(this, selector || [], false) ); + }, + not: function( selector ) { + return this.pushStack( winnow(this, selector || [], true) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +}); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/, + + init = jQuery.fn.init = function( selector, context ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector[0] === "<" && selector[ selector.length - 1 ] === ">" && selector.length >= 3 ) { + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; + + // scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[1], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + // Properties of context are called as methods if possible + if ( jQuery.isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[2] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || rootjQuery ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return typeof rootjQuery.ready !== "undefined" ? + rootjQuery.ready( selector ) : + // Execute immediately if ready is not present + selector( jQuery ); + } + + if ( selector.selector !== undefined ) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.extend({ + dir: function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( (elem = elem[ dir ]) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; + }, + + sibling: function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; + } +}); + +jQuery.fn.extend({ + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter(function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ? + jQuery( selectors, context || this.context ) : + 0; + + for ( ; i < l; i++ ) { + for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) { + // Always skip document fragments + if ( cur.nodeType < 11 && (pos ? + pos.index(cur) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector(cur, selectors)) ) { + + matched.push( cur ); + break; + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.unique( matched ) : matched ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.unique( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter(selector) + ); + } +}); + +function sibling( cur, dir ) { + while ( (cur = cur[dir]) && cur.nodeType !== 1 ) {} + return cur; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return elem.contentDocument || jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.unique( matched ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +}); +var rnotwhite = (/\S+/g); + + + +// String to Object options format cache +var optionsCache = {}; + +// Convert String-formatted options into Object-formatted ones and store in cache +function createOptions( options ) { + var object = optionsCache[ options ] = {}; + jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) { + object[ flag ] = true; + }); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + ( optionsCache[ options ] || createOptions( options ) ) : + jQuery.extend( {}, options ); + + var // Last fire value (for non-forgettable lists) + memory, + // Flag to know if list was already fired + fired, + // Flag to know if list is currently firing + firing, + // First callback to fire (used internally by add and fireWith) + firingStart, + // End of the loop when firing + firingLength, + // Index of currently firing callback (modified by remove if needed) + firingIndex, + // Actual callback list + list = [], + // Stack of fire calls for repeatable lists + stack = !options.once && [], + // Fire callbacks + fire = function( data ) { + memory = options.memory && data; + fired = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + firing = true; + for ( ; list && firingIndex < firingLength; firingIndex++ ) { + if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { + memory = false; // To prevent further calls using add + break; + } + } + firing = false; + if ( list ) { + if ( stack ) { + if ( stack.length ) { + fire( stack.shift() ); + } + } else if ( memory ) { + list = []; + } else { + self.disable(); + } + } + }, + // Actual Callbacks object + self = { + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + // First, we save the current length + var start = list.length; + (function add( args ) { + jQuery.each( args, function( _, arg ) { + var type = jQuery.type( arg ); + if ( type === "function" ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && type !== "string" ) { + // Inspect recursively + add( arg ); + } + }); + })( arguments ); + // Do we need to add the callbacks to the + // current firing batch? + if ( firing ) { + firingLength = list.length; + // With memory, if we're not firing then + // we should call right away + } else if ( memory ) { + firingStart = start; + fire( memory ); + } + } + return this; + }, + // Remove a callback from the list + remove: function() { + if ( list ) { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + // Handle firing indexes + if ( firing ) { + if ( index <= firingLength ) { + firingLength--; + } + if ( index <= firingIndex ) { + firingIndex--; + } + } + } + }); + } + return this; + }, + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length ); + }, + // Remove all callbacks from the list + empty: function() { + list = []; + firingLength = 0; + return this; + }, + // Have the list do nothing anymore + disable: function() { + list = stack = memory = undefined; + return this; + }, + // Is it disabled? + disabled: function() { + return !list; + }, + // Lock the list in its current state + lock: function() { + stack = undefined; + if ( !memory ) { + self.disable(); + } + return this; + }, + // Is it locked? + locked: function() { + return !stack; + }, + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( list && ( !fired || stack ) ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + if ( firing ) { + stack.push( args ); + } else { + fire( args ); + } + } + return this; + }, + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +jQuery.extend({ + + Deferred: function( func ) { + var tuples = [ + // action, add listener, listener list, final state + [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], + [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], + [ "notify", "progress", jQuery.Callbacks("memory") ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + then: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + return jQuery.Deferred(function( newDefer ) { + jQuery.each( tuples, function( i, tuple ) { + var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; + // deferred[ done | fail | progress ] for forwarding actions to newDefer + deferred[ tuple[1] ](function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise() + .done( newDefer.resolve ) + .fail( newDefer.reject ) + .progress( newDefer.notify ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); + } + }); + }); + fns = null; + }).promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Keep pipe for back-compat + promise.pipe = promise.then; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 3 ]; + + // promise[ done | fail | progress ] = list.add + promise[ tuple[1] ] = list.add; + + // Handle state + if ( stateString ) { + list.add(function() { + // state = [ resolved | rejected ] + state = stateString; + + // [ reject_list | resolve_list ].disable; progress_list.lock + }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); + } + + // deferred[ resolve | reject | notify ] + deferred[ tuple[0] ] = function() { + deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments ); + return this; + }; + deferred[ tuple[0] + "With" ] = list.fireWith; + }); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( subordinate /* , ..., subordinateN */ ) { + var i = 0, + resolveValues = slice.call( arguments ), + length = resolveValues.length, + + // the count of uncompleted subordinates + remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, + + // the master Deferred. If resolveValues consist of only a single Deferred, just use that. + deferred = remaining === 1 ? subordinate : jQuery.Deferred(), + + // Update function for both resolve and progress values + updateFunc = function( i, contexts, values ) { + return function( value ) { + contexts[ i ] = this; + values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( values === progressValues ) { + deferred.notifyWith( contexts, values ); + } else if ( !( --remaining ) ) { + deferred.resolveWith( contexts, values ); + } + }; + }, + + progressValues, progressContexts, resolveContexts; + + // add listeners to Deferred subordinates; treat others as resolved + if ( length > 1 ) { + progressValues = new Array( length ); + progressContexts = new Array( length ); + resolveContexts = new Array( length ); + for ( ; i < length; i++ ) { + if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) { + resolveValues[ i ].promise() + .done( updateFunc( i, resolveContexts, resolveValues ) ) + .fail( deferred.reject ) + .progress( updateFunc( i, progressContexts, progressValues ) ); + } else { + --remaining; + } + } + } + + // if we're not waiting on anything, resolve the master + if ( !remaining ) { + deferred.resolveWith( resolveContexts, resolveValues ); + } + + return deferred.promise(); + } +}); + + +// The deferred used on DOM ready +var readyList; + +jQuery.fn.ready = function( fn ) { + // Add the callback + jQuery.ready.promise().done( fn ); + + return this; +}; + +jQuery.extend({ + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } + }, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger("ready").off("ready"); + } + } +}); + +/** + * The ready event handler and self cleanup method + */ +function completed() { + document.removeEventListener( "DOMContentLoaded", completed, false ); + window.removeEventListener( "load", completed, false ); + jQuery.ready(); +} + +jQuery.ready.promise = function( obj ) { + if ( !readyList ) { + + readyList = jQuery.Deferred(); + + // Catch cases where $(document).ready() is called after the browser event has already occurred. + // we once tried to use readyState "interactive" here, but it caused issues like the one + // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 + if ( document.readyState === "complete" ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + setTimeout( jQuery.ready ); + + } else { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed, false ); + } + } + return readyList.promise( obj ); +}; + +// Kick off the DOM ready check even if the user does not +jQuery.ready.promise(); + + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = jQuery.access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if ( jQuery.type( key ) === "object" ) { + chainable = true; + for ( i in key ) { + jQuery.access( elems, fn, i, key[i], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !jQuery.isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) ); + } + } + } + + return chainable ? + elems : + + // Gets + bulk ? + fn.call( elems ) : + len ? fn( elems[0], key ) : emptyGet; +}; + + +/** + * Determines whether an object can have data + */ +jQuery.acceptData = function( owner ) { + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + /* jshint -W018 */ + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + + +function Data() { + // Support: Android < 4, + // Old WebKit does not have Object.preventExtensions/freeze method, + // return new empty object instead with no [[set]] accessor + Object.defineProperty( this.cache = {}, 0, { + get: function() { + return {}; + } + }); + + this.expando = jQuery.expando + Math.random(); +} + +Data.uid = 1; +Data.accepts = jQuery.acceptData; + +Data.prototype = { + key: function( owner ) { + // We can accept data for non-element nodes in modern browsers, + // but we should not, see #8335. + // Always return the key for a frozen object. + if ( !Data.accepts( owner ) ) { + return 0; + } + + var descriptor = {}, + // Check if the owner object already has a cache key + unlock = owner[ this.expando ]; + + // If not, create one + if ( !unlock ) { + unlock = Data.uid++; + + // Secure it in a non-enumerable, non-writable property + try { + descriptor[ this.expando ] = { value: unlock }; + Object.defineProperties( owner, descriptor ); + + // Support: Android < 4 + // Fallback to a less secure definition + } catch ( e ) { + descriptor[ this.expando ] = unlock; + jQuery.extend( owner, descriptor ); + } + } + + // Ensure the cache object + if ( !this.cache[ unlock ] ) { + this.cache[ unlock ] = {}; + } + + return unlock; + }, + set: function( owner, data, value ) { + var prop, + // There may be an unlock assigned to this node, + // if there is no entry for this "owner", create one inline + // and set the unlock as though an owner entry had always existed + unlock = this.key( owner ), + cache = this.cache[ unlock ]; + + // Handle: [ owner, key, value ] args + if ( typeof data === "string" ) { + cache[ data ] = value; + + // Handle: [ owner, { properties } ] args + } else { + // Fresh assignments by object are shallow copied + if ( jQuery.isEmptyObject( cache ) ) { + jQuery.extend( this.cache[ unlock ], data ); + // Otherwise, copy the properties one-by-one to the cache object + } else { + for ( prop in data ) { + cache[ prop ] = data[ prop ]; + } + } + } + return cache; + }, + get: function( owner, key ) { + // Either a valid cache is found, or will be created. + // New caches will be created and the unlock returned, + // allowing direct access to the newly created + // empty data object. A valid owner object must be provided. + var cache = this.cache[ this.key( owner ) ]; + + return key === undefined ? + cache : cache[ key ]; + }, + access: function( owner, key, value ) { + var stored; + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ((key && typeof key === "string") && value === undefined) ) { + + stored = this.get( owner, key ); + + return stored !== undefined ? + stored : this.get( owner, jQuery.camelCase(key) ); + } + + // [*]When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, name, camel, + unlock = this.key( owner ), + cache = this.cache[ unlock ]; + + if ( key === undefined ) { + this.cache[ unlock ] = {}; + + } else { + // Support array or space separated string of keys + if ( jQuery.isArray( key ) ) { + // If "name" is an array of keys... + // When data is initially created, via ("key", "val") signature, + // keys will be converted to camelCase. + // Since there is no way to tell _how_ a key was added, remove + // both plain key and camelCase key. #12786 + // This will only penalize the array argument path. + name = key.concat( key.map( jQuery.camelCase ) ); + } else { + camel = jQuery.camelCase( key ); + // Try the string as a key before any manipulation + if ( key in cache ) { + name = [ key, camel ]; + } else { + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + name = camel; + name = name in cache ? + [ name ] : ( name.match( rnotwhite ) || [] ); + } + } + + i = name.length; + while ( i-- ) { + delete cache[ name[ i ] ]; + } + } + }, + hasData: function( owner ) { + return !jQuery.isEmptyObject( + this.cache[ owner[ this.expando ] ] || {} + ); + }, + discard: function( owner ) { + if ( owner[ this.expando ] ) { + delete this.cache[ owner[ this.expando ] ]; + } + } +}; +var data_priv = new Data(); + +var data_user = new Data(); + + + +/* + Implementation Summary + + 1. Enforce API surface and semantic compatibility with 1.9.x branch + 2. Improve the module's maintainability by reducing the storage + paths to a single mechanism. + 3. Use the same single mechanism to support "private" and "user" data. + 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) + 5. Avoid exposing implementation details on user objects (eg. expando properties) + 6. Provide a clear path for implementation upgrade to WeakMap in 2014 +*/ +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /([A-Z])/g; + +function dataAttr( elem, key, data ) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + // Only convert to a number if it doesn't change the string + +data + "" === data ? +data : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + data_user.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend({ + hasData: function( elem ) { + return data_user.hasData( elem ) || data_priv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return data_user.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + data_user.remove( elem, name ); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to data_priv methods, these can be deprecated. + _data: function( elem, name, data ) { + return data_priv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + data_priv.remove( elem, name ); + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = data_user.get( elem ); + + if ( elem.nodeType === 1 && !data_priv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + name = attrs[ i ].name; + + if ( name.indexOf( "data-" ) === 0 ) { + name = jQuery.camelCase( name.slice(5) ); + dataAttr( elem, name, data[ name ] ); + } + } + data_priv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each(function() { + data_user.set( this, key ); + }); + } + + return access( this, function( value ) { + var data, + camelKey = jQuery.camelCase( key ); + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { + // Attempt to get data from the cache + // with the key as-is + data = data_user.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to get data from the cache + // with the key camelized + data = data_user.get( elem, camelKey ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, camelKey, undefined ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each(function() { + // First, attempt to store a copy or reference of any + // data that might've been store with a camelCased key. + var data = data_user.get( this, camelKey ); + + // For HTML5 data-* attribute interop, we have to + // store property names with dashes in a camelCase form. + // This might not apply to all properties...* + data_user.set( this, camelKey, value ); + + // *... In the case of properties that might _actually_ + // have dashes, we need to also store a copy of that + // unchanged property. + if ( key.indexOf("-") !== -1 && data !== undefined ) { + data_user.set( this, key, value ); + } + }); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each(function() { + data_user.remove( this, key ); + }); + } +}); + + +jQuery.extend({ + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = data_priv.get( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || jQuery.isArray( data ) ) { + queue = data_priv.access( elem, type, jQuery.makeArray(data) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // not intended for public consumption - generates a queueHooks object, or returns the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return data_priv.get( elem, key ) || data_priv.access( elem, key, { + empty: jQuery.Callbacks("once memory").add(function() { + data_priv.remove( elem, [ type + "queue", key ] ); + }) + }); + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[0], type ); + } + + return data === undefined ? + this : + this.each(function() { + var queue = jQuery.queue( this, type, data ); + + // ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = data_priv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +}); +var pnum = (/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/).source; + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var isHidden = function( elem, el ) { + // isHidden might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem ); + }; + +var rcheckableType = (/^(?:checkbox|radio)$/i); + + + +(function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ); + + // #11217 - WebKit loses check when the name is after the checked attribute + div.innerHTML = ""; + + // Support: Safari 5.1, iOS 5.1, Android 4.x, Android 2.3 + // old WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Make sure textarea (and checkbox) defaultValue is properly cloned + // Support: IE9-IE11+ + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; +})(); +var strundefined = typeof undefined; + + + +support.focusinBubbles = "onfocusin" in window; + + +var + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|contextmenu)|click/, + rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)$/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = data_priv.get( elem ); + + // Don't attach events to noData or text/comment nodes (but allow plain objects) + if ( !elemData ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !(events = elemData.events) ) { + events = elemData.events = {}; + } + if ( !(eventHandle = elemData.handle) ) { + eventHandle = elemData.handle = function( e ) { + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== strundefined && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnotwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend({ + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join(".") + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !(handlers = events[ type ]) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = data_priv.hasData( elem ) && data_priv.get( elem ); + + if ( !elemData || !(events = elemData.events) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnotwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + delete elemData.handle; + data_priv.remove( elem, "events" ); + } + }, + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, + eventPath = [ elem || document ], + type = Object.prototype.hasOwnProperty.call( event, "type" ) ? event.type : event, + namespaces = Object.prototype.hasOwnProperty.call( event, "namespace" ) ? event.namespace.split(".") : []; + + cur = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf(".") >= 0 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf(":") < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join("."); + event.namespace_re = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === (elem.ownerDocument || document) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) { + + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( data_priv.get( cur, "events" ) || {} )[ event.type ] && data_priv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && jQuery.acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) && + jQuery.acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + elem[ type ](); + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + dispatch: function( event ) { + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( event ); + + var i, j, ret, matched, handleObj, + handlerQueue = [], + args = slice.call( arguments ), + handlers = ( data_priv.get( this, "events" ) || {} )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[0] = event; + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) { + + // Triggered event must either 1) have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). + if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) + .apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( (event.result = ret) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, matches, sel, handleObj, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + // Black-hole SVG instance trees (#13180) + // Avoid non-left-click bubbling in Firefox (#3861) + if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.disabled !== true || event.type !== "click" ) { + matches = []; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matches[ sel ] === undefined ) { + matches[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) >= 0 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matches[ sel ] ) { + matches.push( handleObj ); + } + } + if ( matches.length ) { + handlerQueue.push({ elem: cur, handlers: matches }); + } + } + } + } + + // Add the remaining (directly-bound) handlers + if ( delegateCount < handlers.length ) { + handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) }); + } + + return handlerQueue; + }, + + // Includes some event props shared by KeyEvent and MouseEvent + props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + + fixHooks: {}, + + keyHooks: { + props: "char charCode key keyCode".split(" "), + filter: function( event, original ) { + + // Add which for key events + if ( event.which == null ) { + event.which = original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + } + }, + + mouseHooks: { + props: "button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "), + filter: function( event, original ) { + var eventDoc, doc, body, + button = original.button; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && original.clientX != null ) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); + event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && button !== undefined ) { + event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); + } + + return event; + } + }, + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // Create a writable copy of the event object and normalize some properties + var i, prop, copy, + type = event.type, + originalEvent = event, + fixHook = this.fixHooks[ type ]; + + if ( !fixHook ) { + this.fixHooks[ type ] = fixHook = + rmouseEvent.test( type ) ? this.mouseHooks : + rkeyEvent.test( type ) ? this.keyHooks : + {}; + } + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + + event = new jQuery.Event( originalEvent ); + + i = copy.length; + while ( i-- ) { + prop = copy[ i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Support: Cordova 2.5 (WebKit) (#13255) + // All events should have a target; Cordova deviceready doesn't + if ( !event.target ) { + event.target = document; + } + + // Support: Safari 6.0+, Chrome < 28 + // Target should not be a text node (#504, #13143) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + return fixHook.filter ? fixHook.filter( event, originalEvent ) : event; + }, + + special: { + load: { + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + focus: { + // Fire native event if possible so blur/focus sequence is correct + trigger: function() { + if ( this !== safeActiveElement() && this.focus ) { + this.focus(); + return false; + } + }, + delegateType: "focusin" + }, + blur: { + trigger: function() { + if ( this === safeActiveElement() && this.blur ) { + this.blur(); + return false; + } + }, + delegateType: "focusout" + }, + click: { + // For checkbox, fire native event so checked state will be right + trigger: function() { + if ( this.type === "checkbox" && this.click && jQuery.nodeName( this, "input" ) ) { + this.click(); + return false; + } + }, + + // For cross-browser consistency, don't fire native .click() on links + _default: function( event ) { + return jQuery.nodeName( event.target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined ) { + event.originalEvent.returnValue = event.result; + } + } + } + }, + + simulate: function( type, elem, event, bubble ) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true, + originalEvent: {} + } + ); + if ( bubble ) { + jQuery.event.trigger( e, null, elem ); + } else { + jQuery.event.dispatch.call( elem, e ); + } + if ( e.isDefaultPrevented() ) { + event.preventDefault(); + } + } +}; + +jQuery.removeEvent = function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } +}; + +jQuery.Event = function( src, props ) { + // Allow instantiation without the 'new' keyword + if ( !(this instanceof jQuery.Event) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + // Support: Android < 4.0 + src.defaultPrevented === undefined && + src.getPreventDefault && src.getPreventDefault() ? + returnTrue : + returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && e.preventDefault ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && e.stopPropagation ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + } +}; + +// Create mouseenter/leave events using mouseover/out and event-time checks +// Support: Chrome 15+ +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || (related !== target && !jQuery.contains( target, related )) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +}); + +// Create "bubbling" focus and blur events +// Support: Firefox, Chrome, Safari +if ( !support.focusinBubbles ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + var doc = this.ownerDocument || this, + attaches = data_priv.access( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + data_priv.access( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this, + attaches = data_priv.access( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + data_priv.remove( doc, fix ); + + } else { + data_priv.access( doc, fix, attaches ); + } + } + }; + }); +} + +jQuery.fn.extend({ + + on: function( types, selector, data, fn, /*INTERNAL*/ one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + this.on( type, selector, data, types[ type ], one ); + } + return this; + } + + if ( data == null && fn == null ) { + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return this; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return this.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + }); + }, + one: function( types, selector, data, fn ) { + return this.on( types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each(function() { + jQuery.event.remove( this, types, fn, selector ); + }); + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + triggerHandler: function( type, data ) { + var elem = this[0]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +}); + + +var + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, + rtagName = /<([\w:]+)/, + rhtml = /<|&#?\w+;/, + rnoInnerhtml = /<(?:script|style|link)/i, + // checked="checked" or checked + rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, + rscriptType = /^$|\/(?:java|ecma)script/i, + rscriptTypeMasked = /^true\/(.*)/, + rcleanScript = /^\s*\s*$/g, + + // We have to close these tags to support XHTML (#13200) + wrapMap = { + + // Support: IE 9 + option: [ 1, "" ], + + thead: [ 1, "", "
" ], + col: [ 2, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + _default: [ 0, "", "" ] + }; + +// Support: IE 9 +wrapMap.optgroup = wrapMap.option; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// Support: 1.x compatibility +// Manipulating tables requires a tbody +function manipulationTarget( elem, content ) { + return jQuery.nodeName( elem, "table" ) && + jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ? + + elem.getElementsByTagName("tbody")[0] || + elem.appendChild( elem.ownerDocument.createElement("tbody") ) : + elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = (elem.getAttribute("type") !== null) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + var match = rscriptTypeMasked.exec( elem.type ); + + if ( match ) { + elem.type = match[ 1 ]; + } else { + elem.removeAttribute("type"); + } + + return elem; +} + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + data_priv.set( + elems[ i ], "globalEval", !refElements || data_priv.get( refElements[ i ], "globalEval" ) + ); + } +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( data_priv.hasData( src ) ) { + pdataOld = data_priv.access( src ); + pdataCur = data_priv.set( dest, pdataOld ); + events = pdataOld.events; + + if ( events ) { + delete pdataCur.handle; + pdataCur.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( data_user.hasData( src ) ) { + udataOld = data_user.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + data_user.set( dest, udataCur ); + } +} + +function getAll( context, tag ) { + var ret = context.getElementsByTagName ? context.getElementsByTagName( tag || "*" ) : + context.querySelectorAll ? context.querySelectorAll( tag || "*" ) : + []; + + return tag === undefined || tag && jQuery.nodeName( context, tag ) ? + jQuery.merge( [ context ], ret ) : + ret; +} + +// Support: IE >= 9 +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +jQuery.extend({ + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = jQuery.contains( elem.ownerDocument, elem ); + + // Support: IE >= 9 + // Fix Cloning issues + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + buildFragment: function( elems, context, scripts, selection ) { + var elem, tmp, tag, wrap, contains, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( jQuery.type( elem ) === "object" ) { + // Support: QtWebKit + // jQuery.merge because push.apply(_, arraylike) throws + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement("div") ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + elem.replace( rxhtmlTag, "<$1>" ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: QtWebKit + // jQuery.merge because push.apply(_, arraylike) throws + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Fixes #12346 + // Support: Webkit, IE + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( (elem = nodes[ i++ ]) ) { + + // #4087 - If origin and destination elements are the same, and this is + // that element, do not do anything + if ( selection && jQuery.inArray( elem, selection ) !== -1 ) { + continue; + } + + contains = jQuery.contains( elem.ownerDocument, elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( contains ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( (elem = tmp[ j++ ]) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; + }, + + cleanData: function( elems ) { + var data, elem, events, type, key, j, + special = jQuery.event.special, + i = 0; + + for ( ; (elem = elems[ i ]) !== undefined; i++ ) { + if ( jQuery.acceptData( elem ) ) { + key = elem[ data_priv.expando ]; + + if ( key && (data = data_priv.cache[ key ]) ) { + events = Object.keys( data.events || {} ); + if ( events.length ) { + for ( j = 0; (type = events[j]) !== undefined; j++ ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + if ( data_priv.cache[ key ] ) { + // Discard any remaining `private` data + delete data_priv.cache[ key ]; + } + } + } + // Discard any remaining `user` data + delete data_user.cache[ elem[ data_user.expando ] ]; + } + } +}); + +jQuery.fn.extend({ + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each(function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + }); + }, null, value, arguments.length ); + }, + + append: function() { + return this.domManip( arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + }); + }, + + prepend: function() { + return this.domManip( arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + }); + }, + + before: function() { + return this.domManip( arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + }); + }, + + after: function() { + return this.domManip( arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + }); + }, + + remove: function( selector, keepData /* Internal Use Only */ ) { + var elem, + elems = selector ? jQuery.filter( selector, this ) : this, + i = 0; + + for ( ; (elem = elems[i]) != null; i++ ) { + if ( !keepData && elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem ) ); + } + + if ( elem.parentNode ) { + if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) { + setGlobalEval( getAll( elem, "script" ) ); + } + elem.parentNode.removeChild( elem ); + } + } + + return this; + }, + + empty: function() { + var elem, + i = 0; + + for ( ; (elem = this[i]) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map(function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + }); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = value.replace( rxhtmlTag, "<$1>" ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var arg = arguments[ 0 ]; + + // Make the changes, replacing each context element with the new content + this.domManip( arguments, function( elem ) { + arg = this.parentNode; + + jQuery.cleanData( getAll( this ) ); + + if ( arg ) { + arg.replaceChild( elem, this ); + } + }); + + // Force removal if there was no new content (e.g., from empty arguments) + return arg && (arg.length || arg.nodeType) ? this : this.remove(); + }, + + detach: function( selector ) { + return this.remove( selector, true ); + }, + + domManip: function( args, callback ) { + + // Flatten any nested arrays + args = concat.apply( [], args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = this.length, + set = this, + iNoClone = l - 1, + value = args[ 0 ], + isFunction = jQuery.isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( isFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return this.each(function( index ) { + var self = set.eq( index ); + if ( isFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + self.domManip( args, callback ); + }); + } + + if ( l ) { + fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + if ( first ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + // Support: QtWebKit + // jQuery.merge because push.apply(_, arraylike) throws + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( this[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !data_priv.access( node, "globalEval" ) && jQuery.contains( doc, node ) ) { + + if ( node.src ) { + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl ) { + jQuery._evalUrl( node.src ); + } + } else { + jQuery.globalEval( node.textContent.replace( rcleanScript, "" ) ); + } + } + } + } + } + } + + return this; + } +}); + +jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: QtWebKit + // .get() because push.apply(_, arraylike) throws + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +}); + + +var iframe, + elemdisplay = {}; + +/** + * Retrieve the actual display of a element + * @param {string} name nodeName of the element + * @param {Object} doc Document object + */ +// Called only from within defaultDisplay +function actualDisplay( name, doc ) { + var elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ), + + // getDefaultComputedStyle might be reliably used only on attached element + display = window.getDefaultComputedStyle ? + + // Use of this method is a temporary fix (more like optmization) until something better comes along, + // since it was removed from specification and supported only in FF + window.getDefaultComputedStyle( elem[ 0 ] ).display : jQuery.css( elem[ 0 ], "display" ); + + // We don't have any data stored on the element, + // so use "detach" method as fast way to get rid of the element + elem.detach(); + + return display; +} + +/** + * Try to determine the default display value of an element + * @param {string} nodeName + */ +function defaultDisplay( nodeName ) { + var doc = document, + display = elemdisplay[ nodeName ]; + + if ( !display ) { + display = actualDisplay( nodeName, doc ); + + // If the simple way fails, read from inside an iframe + if ( display === "none" || !display ) { + + // Use the already-created iframe if possible + iframe = (iframe || jQuery( "