eslint 7.0.0 → 7.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +3 -4
  3. package/lib/init/config-initializer.js +1 -0
  4. package/lib/linter/linter.js +2 -1
  5. package/lib/rules/accessor-pairs.js +1 -1
  6. package/lib/rules/arrow-parens.js +19 -3
  7. package/lib/rules/block-spacing.js +19 -2
  8. package/lib/rules/callback-return.js +1 -1
  9. package/lib/rules/global-require.js +1 -1
  10. package/lib/rules/handle-callback-err.js +1 -1
  11. package/lib/rules/index.js +1 -0
  12. package/lib/rules/linebreak-style.js +8 -2
  13. package/lib/rules/max-lines-per-function.js +1 -1
  14. package/lib/rules/no-buffer-constructor.js +1 -1
  15. package/lib/rules/no-loss-of-precision.js +198 -0
  16. package/lib/rules/no-mixed-requires.js +1 -1
  17. package/lib/rules/no-new-func.js +22 -19
  18. package/lib/rules/no-new-require.js +1 -1
  19. package/lib/rules/no-new-symbol.js +2 -1
  20. package/lib/rules/no-path-concat.js +1 -1
  21. package/lib/rules/no-process-env.js +1 -1
  22. package/lib/rules/no-process-exit.js +1 -1
  23. package/lib/rules/no-restricted-modules.js +1 -1
  24. package/lib/rules/no-sync.js +1 -1
  25. package/lib/rules/one-var-declaration-per-line.js +1 -1
  26. package/lib/rules/padded-blocks.js +17 -4
  27. package/lib/rules/rest-spread-spacing.js +3 -6
  28. package/lib/rules/semi-spacing.js +32 -8
  29. package/lib/rules/utils/ast-utils.js +51 -6
  30. package/lib/source-code/source-code.js +1 -0
  31. package/messages/extend-config-missing.txt +1 -1
  32. package/messages/no-config-found.txt +1 -1
  33. package/messages/plugin-conflict.txt +1 -1
  34. package/messages/plugin-missing.txt +1 -1
  35. package/messages/whitespace-found.txt +1 -1
  36. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,32 @@
1
+ v7.1.0 - May 22, 2020
2
+
3
+ * [`a93083a`](https://github.com/eslint/eslint/commit/a93083af89c6f9714dcdd4a7f27c8655a0b0dba6) Fix: astUtils.getNextLocation returns invalid location after CRLF (#13275) (Milos Djermanovic)
4
+ * [`df01af1`](https://github.com/eslint/eslint/commit/df01af184d93b3d64b37cee786cad59bd0d7aacb) Update: padded-blocks loc position changes (refs #12334) (#13328) (Anix)
5
+ * [`bd3f092`](https://github.com/eslint/eslint/commit/bd3f092efa579944f75bfbc277b35f85e6d966ed) Fix: max-lines-per-function flagging arrow IIFEs (fixes #13332) (#13336) (cherryblossom000)
6
+ * [`25462b2`](https://github.com/eslint/eslint/commit/25462b23eac4ed1ded97eeae6187b5d8baa58e78) Update: block-spacing changed loc for extra (refs #12334) (#13314) (Anix)
7
+ * [`de0aab9`](https://github.com/eslint/eslint/commit/de0aab95005f172db72196fc3fd18e91ee9a5880) Fix: report end loc in one-var-declaration-per-line (refs #12334) (#13326) (YeonJuan)
8
+ * [`1710296`](https://github.com/eslint/eslint/commit/1710296082083602a904b080908657bb431fb56c) Fix: no-new-symbol false positive with Symbol as an argument (#13337) (Milos Djermanovic)
9
+ * [`cc01451`](https://github.com/eslint/eslint/commit/cc014514c29626e556acb0a528e3478b3725e284) Fix: arrow-parens no reporting for comments inside (fixes #12995) (#13312) (Anix)
10
+ * [`a195141`](https://github.com/eslint/eslint/commit/a19514193a42f4f00732559ff828b33a6ec9d7c5) Update: reporting location for semi-spacing (refs #12334) (#13285) (Anix)
11
+ * [`e3e4c41`](https://github.com/eslint/eslint/commit/e3e4c41ab625a5af8d4614d1c6d32c656f104f6b) Fix: fix false positives of no-new-func (#13333) (Pig Fang)
12
+ * [`611c676`](https://github.com/eslint/eslint/commit/611c676dfd671013d81810724f184e2a9c5ad5d7) Docs: Update new rules policies (#13343) (Nicholas C. Zakas)
13
+ * [`3a5fbb3`](https://github.com/eslint/eslint/commit/3a5fbb3d634be688615950c0a5fa8aead6ff08b5) Chore: correct fileoverview doc in accessor-pairs (#13335) (YeonJuan)
14
+ * [`b0a6b81`](https://github.com/eslint/eslint/commit/b0a6b8134e3b399beeb69432a02232a1037f7c46) Update: Improve report location for rest-spread-spacing (refs #12334) (#13313) (Milos Djermanovic)
15
+ * [`68c8ee3`](https://github.com/eslint/eslint/commit/68c8ee3ab70187972aef4c4e866bcf29da70a207) Fix: Stop path analyzer on unknown nodes (#13305) (Ilya Volodin)
16
+ * [`89e1081`](https://github.com/eslint/eslint/commit/89e10811c4df666216aae58bff5f855cd9df738b) Update: Improve report location for linebreak-style (refs #12334) (#13317) (Milos Djermanovic)
17
+ * [`0891379`](https://github.com/eslint/eslint/commit/08913798b4ec420b261b8fbc470504f9f248c840) Docs: Document the "correct" way to build an array with values (#13246) (Ed S)
18
+ * [`88127d7`](https://github.com/eslint/eslint/commit/88127d74d56b88cc5a0758856995716050021131) Chore: remove checkbox from PR template prerequesites (#13330) (Kai Cataldo)
19
+ * [`c636d57`](https://github.com/eslint/eslint/commit/c636d5708c461a8ff1ea55e5df56d4f76f9c4044) New: no-loss-of-precision (fixes #11279) (#12747) (jmoore914)
20
+ * [`72a4e10`](https://github.com/eslint/eslint/commit/72a4e1044592057c4a3f39dbb1dbe61b00ea8af6) Chore: Mark SourceCode getComments() method as deprecated (fixes #13293) (#13296) (SuperOleg39)
21
+ * [`7f14846`](https://github.com/eslint/eslint/commit/7f1484690665b4f4b9cd9680ca8bb7b5cf56e48a) Docs: fix broken link in Node.js API docs (#13307) (Kai Cataldo)
22
+ * [`02aeba1`](https://github.com/eslint/eslint/commit/02aeba19afb301140514097235a9f2a00a9acb2a) Sponsors: Sync README with website (ESLint Jenkins)
23
+ * [`1f17533`](https://github.com/eslint/eslint/commit/1f175338cba29960aab67a540f516051f9d428c8) Docs: Gitter -> Discord URL (refs #13039) (#13308) (Nicholas C. Zakas)
24
+ * [`82a448a`](https://github.com/eslint/eslint/commit/82a448a7deff24e9207f60dfe77622c00102bd99) Docs: improve documentation of no-return-await (#13215) (Linus Unnebäck)
25
+ * [`742941d`](https://github.com/eslint/eslint/commit/742941d7fdc3fd79ff8c5d2588413e0d3a5a525b) Update: added typescript-eslint/recommended configs for init (#13235) (Anix)
26
+ * [`3d03df0`](https://github.com/eslint/eslint/commit/3d03df08c8000403a85baffe2a000287f3335114) Sponsors: Sync README with website (ESLint Jenkins)
27
+ * [`f44a6b4`](https://github.com/eslint/eslint/commit/f44a6b4fd92602af8e2c75d5852f796ec064aa8e) Chore: fix invalid syntax in require-await tests (#13277) (Milos Djermanovic)
28
+ * [`2c778fb`](https://github.com/eslint/eslint/commit/2c778fb6e31b7943bb27a47a6e15dcbfd8336f39) Fix: remove custom plugins from replacedBy metadata (#13274) (Kai Cataldo)
29
+ * [`0db3b1d`](https://github.com/eslint/eslint/commit/0db3b1d5cc5e4e1de21462679581b7a4d89ff36e) Sponsors: Sync README with website (ESLint Jenkins)
1
30
  v7.0.0 - May 8, 2020
2
31
 
3
32
  * [`b98d8bd`](https://github.com/eslint/eslint/commit/b98d8bda4630fe8278c5aa2b6650630770568fe5) Upgrade: eslint-release@2.0.0 (#13271) (Kai Cataldo)
package/README.md CHANGED
@@ -5,7 +5,6 @@
5
5
  <br />
6
6
  [![Open Collective Backers](https://img.shields.io/opencollective/backers/eslint)](https://opencollective.com/eslint)
7
7
  [![Open Collective Sponsors](https://img.shields.io/opencollective/sponsors/eslint)](https://opencollective.com/eslint)
8
- [![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)
9
8
  [![Follow us on Twitter](https://img.shields.io/twitter/follow/geteslint?label=Follow&style=social)](https://twitter.com/intent/user?screen_name=geteslint)
10
9
 
11
10
  # ESLint
@@ -18,7 +17,7 @@
18
17
  [Code of Conduct](https://js.foundation/community/code-of-conduct) |
19
18
  [Twitter](https://twitter.com/geteslint) |
20
19
  [Mailing List](https://groups.google.com/group/eslint) |
21
- [Chat Room](https://gitter.im/eslint/eslint)
20
+ [Chat Room](https://eslint.org/chat)
22
21
 
23
22
  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:
24
23
 
@@ -134,7 +133,7 @@ Once a language feature has been adopted into the ECMAScript standard (stage 4 a
134
133
 
135
134
  ### Where to ask for help?
136
135
 
137
- Join our [Mailing List](https://groups.google.com/group/eslint) or [Chatroom](https://gitter.im/eslint/eslint).
136
+ Join our [Mailing List](https://groups.google.com/group/eslint) or [Chatroom](https://eslint.org/chat).
138
137
 
139
138
  ## <a name="releases"></a>Releases
140
139
 
@@ -252,7 +251,7 @@ The following companies, organizations, and individuals support ESLint's ongoing
252
251
  <h3>Gold Sponsors</h3>
253
252
  <p><a href="https://www.shopify.com"><img src="https://images.opencollective.com/shopify/e780cd4/logo.png" alt="Shopify" height="96"></a> <a href="https://www.salesforce.com"><img src="https://images.opencollective.com/salesforce/ca8f997/logo.png" alt="Salesforce" height="96"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="96"></a></p><h3>Silver Sponsors</h3>
254
253
  <p><a href="https://www.ampproject.org/"><img src="https://images.opencollective.com/amp/c8a3b25/logo.png" alt="AMP Project" height="64"></a></p><h3>Bronze Sponsors</h3>
255
- <p><a href="https://www.casinotop.com/"><img src="https://images.opencollective.com/casinotop-com/10fd95b/logo.png" alt="CasinoTop.com" height="32"></a> <a href="https://www.casinotopp.net/"><img src="https://images.opencollective.com/casino-topp/1dd399a/logo.png" alt="Casino Topp" height="32"></a> <a href="https://writersperhour.com/urgent-essay-writing-service"><img src="https://images.opencollective.com/writersperhour/5787d4b/logo.png" alt="Writers Per Hour" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="https://cooltechzone.com/netflix-vpn"><img src="https://images.opencollective.com/vpn-netflix/4850160/logo.png" alt="vpn netflix" height="32"></a> <a href="https://www.kasinot.fi"><img src="https://images.opencollective.com/kasinot-fi/e09aa2e/logo.png" alt="Kasinot.fi" height="32"></a> <a href="https://www.pelisivut.com"><img src="https://images.opencollective.com/pelisivut/04f08f2/logo.png" alt="Pelisivut" height="32"></a> <a href="https://www.nettikasinot.org"><img src="https://images.opencollective.com/nettikasinot-org/bbd887f/logo.png" alt="Nettikasinot.org" height="32"></a> <a href="https://www.bonus.com.de/freispiele"><img src="https://images.opencollective.com/bonusfinder-deutschland/646169e/logo.png" alt="BonusFinder Deutschland" height="32"></a> <a href="https://medium.com/@niksundin/best-web-design-companies-1872e445775f"><img src="https://images.opencollective.com/top-web-design-agencies/d92d747/logo.png" alt="Top Web Design Agencies" height="32"></a> <a href="https://www.bugsnag.com/platforms?utm_source=Open Collective&utm_medium=Website&utm_content=open-source&utm_campaign=2019-community&utm_term="><img src="https://images.opencollective.com/bugsnag-stability-monitoring/c2cef36/logo.png" alt="Bugsnag Stability Monitoring" height="32"></a> <a href="https://mixpanel.com"><img src="https://images.opencollective.com/mixpanel/cd682f7/logo.png" alt="Mixpanel" height="32"></a> <a href="https://www.vpsserver.com"><img src="https://images.opencollective.com/vpsservercom/logo.png" alt="VPS Server" height="32"></a> <a href="https://icons8.com"><img src="https://images.opencollective.com/icons8/0b37d14/logo.png" alt="Free Icons by Icons8" height="32"></a> <a href="https://uxplanet.org/top-ui-ux-design-agencies-user-experience-firms-8c54697e290"><img src="https://images.opencollective.com/ui-ux-design-agencies/cae5dfe/logo.png" alt="UI UX Design Agencies" height="32"></a> <a href="https://clay.global"><img src="https://images.opencollective.com/clayglobal/2468f34/logo.png" alt="clay" height="32"></a> <a href="https://discordapp.com"><img src="https://images.opencollective.com/discordapp/7e3d9a9/logo.png" alt="Discord" height="32"></a> <a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://tekhattan.com"><img src="https://images.opencollective.com/tekhattan/bc73c28/logo.png" alt="TekHattan" height="32"></a> <a href="https://www.marfeel.com/"><img src="https://images.opencollective.com/marfeel/4b88e30/logo.png" alt="Marfeel" height="32"></a> <a href="http://www.firesticktricks.com"><img src="https://images.opencollective.com/fire-stick-tricks/b8fbe2c/logo.png" alt="Fire Stick Tricks" height="32"></a></p>
254
+ <p><a href="https://bruce.agency"><img src="https://images.opencollective.com/brucemade/0c70c59/logo.png" alt="Bruce" height="32"></a> <a href="https://edubirdie.com/"><img src="https://images.opencollective.com/edubirdie2/b1d51ab/logo.png" alt="EduBirdie" height="32"></a> <a href="https://www.casinotop.com/"><img src="https://images.opencollective.com/casinotop-com/10fd95b/logo.png" alt="CasinoTop.com" height="32"></a> <a href="https://www.casinotopp.net/"><img src="https://images.opencollective.com/casino-topp/1dd399a/logo.png" alt="Casino Topp" height="32"></a> <a href="https://writersperhour.com/urgent-essay-writing-service"><img src="https://images.opencollective.com/writersperhour/5787d4b/logo.png" alt="Writers Per Hour" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="https://cooltechzone.com/netflix-vpn"><img src="https://images.opencollective.com/vpn-netflix/4850160/logo.png" alt="vpn netflix" height="32"></a> <a href="https://www.kasinot.fi"><img src="https://images.opencollective.com/kasinot-fi/e09aa2e/logo.png" alt="Kasinot.fi" height="32"></a> <a href="https://www.pelisivut.com"><img src="https://images.opencollective.com/pelisivut/04f08f2/logo.png" alt="Pelisivut" height="32"></a> <a href="https://www.nettikasinot.org"><img src="https://images.opencollective.com/nettikasinot-org/bbd887f/logo.png" alt="Nettikasinot.org" height="32"></a> <a href="https://www.bonus.com.de/freispiele"><img src="https://images.opencollective.com/bonusfinder-deutschland/646169e/logo.png" alt="BonusFinder Deutschland" height="32"></a> <a href="https://www.bugsnag.com/platforms?utm_source=Open Collective&utm_medium=Website&utm_content=open-source&utm_campaign=2019-community&utm_term="><img src="https://images.opencollective.com/bugsnag-stability-monitoring/c2cef36/logo.png" alt="Bugsnag Stability Monitoring" height="32"></a> <a href="https://mixpanel.com"><img src="https://images.opencollective.com/mixpanel/cd682f7/logo.png" alt="Mixpanel" height="32"></a> <a href="https://www.vpsserver.com"><img src="https://images.opencollective.com/vpsservercom/logo.png" alt="VPS Server" height="32"></a> <a href="https://icons8.com"><img src="https://images.opencollective.com/icons8/0b37d14/logo.png" alt="Free Icons by Icons8" height="32"></a> <a href="https://discordapp.com"><img src="https://images.opencollective.com/discordapp/7e3d9a9/logo.png" alt="Discord" height="32"></a> <a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://tekhattan.com"><img src="https://images.opencollective.com/tekhattan/bc73c28/logo.png" alt="TekHattan" height="32"></a> <a href="https://www.marfeel.com/"><img src="https://images.opencollective.com/marfeel/4b88e30/logo.png" alt="Marfeel" height="32"></a> <a href="http://www.firesticktricks.com"><img src="https://images.opencollective.com/fire-stick-tricks/b8fbe2c/logo.png" alt="Fire Stick Tricks" height="32"></a></p>
256
255
  <!--sponsorsend-->
257
256
 
258
257
  ## <a name="technology-sponsors"></a>Technology Sponsors
@@ -326,6 +326,7 @@ function processAnswers(answers) {
326
326
  }
327
327
  if (answers.typescript && config.extends.includes("eslint:recommended")) {
328
328
  config.extends.push("plugin:@typescript-eslint/eslint-recommended");
329
+ config.extends.push("plugin:@typescript-eslint/recommended");
329
330
  }
330
331
 
331
332
  // normalize extends
@@ -938,7 +938,8 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserOptions, parser
938
938
  });
939
939
  });
940
940
 
941
- const eventGenerator = new CodePathAnalyzer(new NodeEventGenerator(emitter));
941
+ // only run code path analyzer if the top level node is "Program", skip otherwise
942
+ const eventGenerator = nodeQueue[0].node.type === "Program" ? new CodePathAnalyzer(new NodeEventGenerator(emitter)) : new NodeEventGenerator(emitter);
942
943
 
943
944
  nodeQueue.forEach(traversalInfo => {
944
945
  currentNode = traversalInfo.node;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @fileoverview Rule to flag wrapping non-iife in parens
2
+ * @fileoverview Rule to enforce getter and setter pairs in objects and classes.
3
3
  * @author Gyandeep Singh
4
4
  */
5
5
 
@@ -105,10 +105,27 @@ module.exports = {
105
105
  ], `${shouldAddSpaceForAsync ? " " : ""}${paramToken.value}`);
106
106
  }
107
107
 
108
+ /**
109
+ * Checks whether there are comments inside the params or not.
110
+ * @returns {boolean} `true` if there are comments inside of parens, else `false`
111
+ */
112
+ function hasCommentsInParens() {
113
+ if (astUtils.isOpeningParenToken(firstTokenOfParam)) {
114
+ const closingParenToken = sourceCode.getTokenAfter(node.params[0], astUtils.isClosingParenToken);
115
+
116
+ return closingParenToken && sourceCode.commentsExistBetween(firstTokenOfParam, closingParenToken);
117
+ }
118
+ return false;
119
+
120
+ }
121
+
122
+ if (hasCommentsInParens()) {
123
+ return;
124
+ }
125
+
108
126
  // "as-needed", { "requireForBlockBody": true }: x => x
109
127
  if (
110
128
  requireForBlockBody &&
111
- node.params.length === 1 &&
112
129
  node.params[0].type === "Identifier" &&
113
130
  !node.params[0].typeAnnotation &&
114
131
  node.body.type !== "BlockStatement" &&
@@ -144,7 +161,6 @@ module.exports = {
144
161
 
145
162
  // "as-needed": x => x
146
163
  if (asNeeded &&
147
- node.params.length === 1 &&
148
164
  node.params[0].type === "Identifier" &&
149
165
  !node.params[0].typeAnnotation &&
150
166
  !node.returnType
@@ -178,7 +194,7 @@ module.exports = {
178
194
  }
179
195
 
180
196
  return {
181
- ArrowFunctionExpression: parens
197
+ "ArrowFunctionExpression[params.length=1]": parens
182
198
  };
183
199
  }
184
200
  };
@@ -102,9 +102,18 @@ module.exports = {
102
102
 
103
103
  // Check.
104
104
  if (!isValid(openBrace, firstToken)) {
105
+ let loc = openBrace.loc;
106
+
107
+ if (messageId === "extra") {
108
+ loc = {
109
+ start: openBrace.loc.end,
110
+ end: firstToken.loc.start
111
+ };
112
+ }
113
+
105
114
  context.report({
106
115
  node,
107
- loc: openBrace.loc.start,
116
+ loc,
108
117
  messageId,
109
118
  data: {
110
119
  location: "after",
@@ -120,9 +129,17 @@ module.exports = {
120
129
  });
121
130
  }
122
131
  if (!isValid(lastToken, closeBrace)) {
132
+ let loc = closeBrace.loc;
133
+
134
+ if (messageId === "extra") {
135
+ loc = {
136
+ start: lastToken.loc.end,
137
+ end: closeBrace.loc.start
138
+ };
139
+ }
123
140
  context.report({
124
141
  node,
125
- loc: closeBrace.loc.start,
142
+ loc,
126
143
  messageId,
127
144
  data: {
128
145
  location: "before",
@@ -12,7 +12,7 @@ module.exports = {
12
12
  meta: {
13
13
  deprecated: true,
14
14
 
15
- replacedBy: ["node/callback-return"],
15
+ replacedBy: [],
16
16
 
17
17
  type: "suggestion",
18
18
 
@@ -50,7 +50,7 @@ module.exports = {
50
50
  meta: {
51
51
  deprecated: true,
52
52
 
53
- replacedBy: ["node/global-require"],
53
+ replacedBy: [],
54
54
 
55
55
  type: "suggestion",
56
56
 
@@ -13,7 +13,7 @@ module.exports = {
13
13
  meta: {
14
14
  deprecated: true,
15
15
 
16
- replacedBy: ["node/handle-callback-err"],
16
+ replacedBy: [],
17
17
 
18
18
  type: "suggestion",
19
19
 
@@ -148,6 +148,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
148
148
  "no-lone-blocks": () => require("./no-lone-blocks"),
149
149
  "no-lonely-if": () => require("./no-lonely-if"),
150
150
  "no-loop-func": () => require("./no-loop-func"),
151
+ "no-loss-of-precision": () => require("./no-loss-of-precision"),
151
152
  "no-magic-numbers": () => require("./no-magic-numbers"),
152
153
  "no-misleading-character-class": () => require("./no-misleading-character-class"),
153
154
  "no-mixed-operators": () => require("./no-mixed-operators"),
@@ -86,8 +86,14 @@ module.exports = {
86
86
  context.report({
87
87
  node,
88
88
  loc: {
89
- line: i,
90
- column: sourceCode.lines[i - 1].length
89
+ start: {
90
+ line: i,
91
+ column: sourceCode.lines[i - 1].length
92
+ },
93
+ end: {
94
+ line: i + 1,
95
+ column: 0
96
+ }
91
97
  },
92
98
  messageId: expectedLF ? "expectedLF" : "expectedCRLF",
93
99
  fix: createFix(range, expectedLFChars)
@@ -134,7 +134,7 @@ module.exports = {
134
134
  * @returns {boolean} True if it's an IIFE
135
135
  */
136
136
  function isIIFE(node) {
137
- return node.type === "FunctionExpression" && node.parent && node.parent.type === "CallExpression" && node.parent.callee === node;
137
+ return (node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") && node.parent && node.parent.type === "CallExpression" && node.parent.callee === node;
138
138
  }
139
139
 
140
140
  /**
@@ -12,7 +12,7 @@ module.exports = {
12
12
  meta: {
13
13
  deprecated: true,
14
14
 
15
- replacedBy: ["node/no-deprecated-api"],
15
+ replacedBy: [],
16
16
 
17
17
  type: "problem",
18
18
 
@@ -0,0 +1,198 @@
1
+ /**
2
+ * @fileoverview Rule to flag numbers that will lose significant figure precision at runtime
3
+ * @author Jacob Moore
4
+ */
5
+
6
+ "use strict";
7
+
8
+ //------------------------------------------------------------------------------
9
+ // Rule Definition
10
+ //------------------------------------------------------------------------------
11
+
12
+ module.exports = {
13
+ meta: {
14
+ type: "problem",
15
+
16
+ docs: {
17
+ description: "disallow literal numbers that lose precision",
18
+ category: "Possible Errors",
19
+ recommended: false,
20
+ url: "https://eslint.org/docs/rules/no-loss-of-precision"
21
+ },
22
+ schema: [],
23
+ messages: {
24
+ noLossOfPrecision: "This number literal will lose precision at runtime."
25
+ }
26
+ },
27
+
28
+ create(context) {
29
+
30
+ /**
31
+ * Returns whether the node is number literal
32
+ * @param {Node} node the node literal being evaluated
33
+ * @returns {boolean} true if the node is a number literal
34
+ */
35
+ function isNumber(node) {
36
+ return typeof node.value === "number";
37
+ }
38
+
39
+
40
+ /**
41
+ * Checks whether the number is base ten
42
+ * @param {ASTNode} node the node being evaluated
43
+ * @returns {boolean} true if the node is in base ten
44
+ */
45
+ function isBaseTen(node) {
46
+ const prefixes = ["0x", "0X", "0b", "0B", "0o", "0O"];
47
+
48
+ return prefixes.every(prefix => !node.raw.startsWith(prefix)) &&
49
+ !/^0[0-7]+$/u.test(node.raw);
50
+ }
51
+
52
+ /**
53
+ * Checks that the user-intended non-base ten number equals the actual number after is has been converted to the Number type
54
+ * @param {Node} node the node being evaluated
55
+ * @returns {boolean} true if they do not match
56
+ */
57
+ function notBaseTenLosesPrecision(node) {
58
+ const rawString = node.raw.toUpperCase();
59
+ let base = 0;
60
+
61
+ if (rawString.startsWith("0B")) {
62
+ base = 2;
63
+ } else if (rawString.startsWith("0X")) {
64
+ base = 16;
65
+ } else {
66
+ base = 8;
67
+ }
68
+
69
+ return !rawString.endsWith(node.value.toString(base).toUpperCase());
70
+ }
71
+
72
+ /**
73
+ * Adds a decimal point to the numeric string at index 1
74
+ * @param {string} stringNumber the numeric string without any decimal point
75
+ * @returns {string} the numeric string with a decimal point in the proper place
76
+ */
77
+ function addDecimalPointToNumber(stringNumber) {
78
+ return `${stringNumber.slice(0, 1)}.${stringNumber.slice(1)}`;
79
+ }
80
+
81
+ /**
82
+ * Returns the number stripped of leading zeros
83
+ * @param {string} numberAsString the string representation of the number
84
+ * @returns {string} the stripped string
85
+ */
86
+ function removeLeadingZeros(numberAsString) {
87
+ return numberAsString.replace(/^0*/u, "");
88
+ }
89
+
90
+ /**
91
+ * Returns the number stripped of trailing zeros
92
+ * @param {string} numberAsString the string representation of the number
93
+ * @returns {string} the stripped string
94
+ */
95
+ function removeTrailingZeros(numberAsString) {
96
+ return numberAsString.replace(/0*$/u, "");
97
+ }
98
+
99
+ /**
100
+ * Converts an integer to to an object containing the the integer's coefficient and order of magnitude
101
+ * @param {string} stringInteger the string representation of the integer being converted
102
+ * @returns {Object} the object containing the the integer's coefficient and order of magnitude
103
+ */
104
+ function normalizeInteger(stringInteger) {
105
+ const significantDigits = removeTrailingZeros(removeLeadingZeros(stringInteger));
106
+
107
+ return {
108
+ magnitude: stringInteger.startsWith("0") ? stringInteger.length - 2 : stringInteger.length - 1,
109
+ coefficient: addDecimalPointToNumber(significantDigits)
110
+ };
111
+ }
112
+
113
+ /**
114
+ *
115
+ * Converts a float to to an object containing the the floats's coefficient and order of magnitude
116
+ * @param {string} stringFloat the string representation of the float being converted
117
+ * @returns {Object} the object containing the the integer's coefficient and order of magnitude
118
+ */
119
+ function normalizeFloat(stringFloat) {
120
+ const trimmedFloat = removeLeadingZeros(stringFloat);
121
+
122
+ if (trimmedFloat.startsWith(".")) {
123
+ const decimalDigits = trimmedFloat.split(".").pop();
124
+ const significantDigits = removeLeadingZeros(decimalDigits);
125
+
126
+ return {
127
+ magnitude: significantDigits.length - decimalDigits.length - 1,
128
+ coefficient: addDecimalPointToNumber(significantDigits)
129
+ };
130
+
131
+ }
132
+ return {
133
+ magnitude: trimmedFloat.indexOf(".") - 1,
134
+ coefficient: addDecimalPointToNumber(trimmedFloat.replace(".", ""))
135
+
136
+ };
137
+ }
138
+
139
+
140
+ /**
141
+ * Converts a base ten number to proper scientific notation
142
+ * @param {string} stringNumber the string representation of the base ten number to be converted
143
+ * @returns {string} the number converted to scientific notation
144
+ */
145
+ function convertNumberToScientificNotation(stringNumber) {
146
+ const splitNumber = stringNumber.replace("E", "e").split("e");
147
+ const originalCoefficient = splitNumber[0];
148
+ const normalizedNumber = stringNumber.includes(".") ? normalizeFloat(originalCoefficient)
149
+ : normalizeInteger(originalCoefficient);
150
+ const normalizedCoefficient = normalizedNumber.coefficient;
151
+ const magnitude = splitNumber.length > 1 ? (parseInt(splitNumber[1], 10) + normalizedNumber.magnitude)
152
+ : normalizedNumber.magnitude;
153
+
154
+ return `${normalizedCoefficient}e${magnitude}`;
155
+
156
+ }
157
+
158
+ /**
159
+ * Checks that the user-intended base ten number equals the actual number after is has been converted to the Number type
160
+ * @param {Node} node the node being evaluated
161
+ * @returns {boolean} true if they do not match
162
+ */
163
+ function baseTenLosesPrecision(node) {
164
+ const normalizedRawNumber = convertNumberToScientificNotation(node.raw);
165
+ const requestedPrecision = normalizedRawNumber.split("e")[0].replace(".", "").length;
166
+
167
+ if (requestedPrecision > 100) {
168
+ return true;
169
+ }
170
+ const storedNumber = node.value.toPrecision(requestedPrecision);
171
+ const normalizedStoredNumber = convertNumberToScientificNotation(storedNumber);
172
+
173
+ return normalizedRawNumber !== normalizedStoredNumber;
174
+ }
175
+
176
+
177
+ /**
178
+ * Checks that the user-intended number equals the actual number after is has been converted to the Number type
179
+ * @param {Node} node the node being evaluated
180
+ * @returns {boolean} true if they do not match
181
+ */
182
+ function losesPrecision(node) {
183
+ return isBaseTen(node) ? baseTenLosesPrecision(node) : notBaseTenLosesPrecision(node);
184
+ }
185
+
186
+
187
+ return {
188
+ Literal(node) {
189
+ if (node.value && isNumber(node) && losesPrecision(node)) {
190
+ context.report({
191
+ messageId: "noLossOfPrecision",
192
+ node
193
+ });
194
+ }
195
+ }
196
+ };
197
+ }
198
+ };
@@ -13,7 +13,7 @@ module.exports = {
13
13
  meta: {
14
14
  deprecated: true,
15
15
 
16
- replacedBy: ["node/no-mixed-requires"],
16
+ replacedBy: [],
17
17
 
18
18
  type: "suggestion",
19
19
 
@@ -29,26 +29,29 @@ module.exports = {
29
29
 
30
30
  create(context) {
31
31
 
32
- //--------------------------------------------------------------------------
33
- // Helpers
34
- //--------------------------------------------------------------------------
35
-
36
- /**
37
- * Reports a node.
38
- * @param {ASTNode} node The node to report
39
- * @returns {void}
40
- * @private
41
- */
42
- function report(node) {
43
- context.report({
44
- node,
45
- messageId: "noFunctionConstructor"
46
- });
47
- }
48
-
49
32
  return {
50
- "NewExpression[callee.name = 'Function']": report,
51
- "CallExpression[callee.name = 'Function']": report
33
+ "Program:exit"() {
34
+ const globalScope = context.getScope();
35
+ const variable = globalScope.set.get("Function");
36
+
37
+ if (variable && variable.defs.length === 0) {
38
+ variable.references.forEach(ref => {
39
+ const node = ref.identifier;
40
+ const { parent } = node;
41
+
42
+ if (
43
+ parent &&
44
+ (parent.type === "NewExpression" || parent.type === "CallExpression") &&
45
+ node === parent.callee
46
+ ) {
47
+ context.report({
48
+ node: parent,
49
+ messageId: "noFunctionConstructor"
50
+ });
51
+ }
52
+ });
53
+ }
54
+ }
52
55
  };
53
56
 
54
57
  }
@@ -13,7 +13,7 @@ module.exports = {
13
13
  meta: {
14
14
  deprecated: true,
15
15
 
16
- replacedBy: ["node/no-new-require"],
16
+ replacedBy: [],
17
17
 
18
18
  type: "suggestion",
19
19
 
@@ -37,8 +37,9 @@ module.exports = {
37
37
  if (variable && variable.defs.length === 0) {
38
38
  variable.references.forEach(ref => {
39
39
  const node = ref.identifier;
40
+ const parent = node.parent;
40
41
 
41
- if (node.parent && node.parent.type === "NewExpression") {
42
+ if (parent && parent.type === "NewExpression" && parent.callee === node) {
42
43
  context.report({
43
44
  node,
44
45
  messageId: "noNewSymbol"
@@ -12,7 +12,7 @@ module.exports = {
12
12
  meta: {
13
13
  deprecated: true,
14
14
 
15
- replacedBy: ["node/no-path-concat"],
15
+ replacedBy: [],
16
16
 
17
17
  type: "suggestion",
18
18
 
@@ -12,7 +12,7 @@ module.exports = {
12
12
  meta: {
13
13
  deprecated: true,
14
14
 
15
- replacedBy: ["node/no-process-env"],
15
+ replacedBy: [],
16
16
 
17
17
  type: "suggestion",
18
18
 
@@ -12,7 +12,7 @@ module.exports = {
12
12
  meta: {
13
13
  deprecated: true,
14
14
 
15
- replacedBy: ["node/no-process-exit"],
15
+ replacedBy: [],
16
16
 
17
17
  type: "suggestion",
18
18
 
@@ -42,7 +42,7 @@ module.exports = {
42
42
  meta: {
43
43
  deprecated: true,
44
44
 
45
- replacedBy: ["node/no-restricted-require"],
45
+ replacedBy: [],
46
46
 
47
47
  type: "suggestion",
48
48
 
@@ -15,7 +15,7 @@ module.exports = {
15
15
  meta: {
16
16
  deprecated: true,
17
17
 
18
- replacedBy: ["node/no-sync"],
18
+ replacedBy: [],
19
19
 
20
20
  type: "suggestion",
21
21
 
@@ -71,7 +71,7 @@ module.exports = {
71
71
  context.report({
72
72
  node,
73
73
  messageId: "expectVarOnNewline",
74
- loc: current.loc.start,
74
+ loc: current.loc,
75
75
  fix: fixer => fixer.insertTextBefore(current, "\n")
76
76
  });
77
77
  }
@@ -203,10 +203,14 @@ module.exports = {
203
203
  }
204
204
 
205
205
  if (requirePaddingFor(node)) {
206
+
206
207
  if (!blockHasTopPadding) {
207
208
  context.report({
208
209
  node,
209
- loc: { line: tokenBeforeFirst.loc.start.line, column: tokenBeforeFirst.loc.start.column },
210
+ loc: {
211
+ start: tokenBeforeFirst.loc.start,
212
+ end: firstBlockToken.loc.start
213
+ },
210
214
  fix(fixer) {
211
215
  return fixer.insertTextAfter(tokenBeforeFirst, "\n");
212
216
  },
@@ -216,7 +220,10 @@ module.exports = {
216
220
  if (!blockHasBottomPadding) {
217
221
  context.report({
218
222
  node,
219
- loc: { line: tokenAfterLast.loc.end.line, column: tokenAfterLast.loc.end.column - 1 },
223
+ loc: {
224
+ end: tokenAfterLast.loc.start,
225
+ start: lastBlockToken.loc.end
226
+ },
220
227
  fix(fixer) {
221
228
  return fixer.insertTextBefore(tokenAfterLast, "\n");
222
229
  },
@@ -228,7 +235,10 @@ module.exports = {
228
235
 
229
236
  context.report({
230
237
  node,
231
- loc: { line: tokenBeforeFirst.loc.start.line, column: tokenBeforeFirst.loc.start.column },
238
+ loc: {
239
+ start: tokenBeforeFirst.loc.start,
240
+ end: firstBlockToken.loc.start
241
+ },
232
242
  fix(fixer) {
233
243
  return fixer.replaceTextRange([tokenBeforeFirst.range[1], firstBlockToken.range[0] - firstBlockToken.loc.start.column], "\n");
234
244
  },
@@ -240,7 +250,10 @@ module.exports = {
240
250
 
241
251
  context.report({
242
252
  node,
243
- loc: { line: tokenAfterLast.loc.end.line, column: tokenAfterLast.loc.end.column - 1 },
253
+ loc: {
254
+ end: tokenAfterLast.loc.start,
255
+ start: lastBlockToken.loc.end
256
+ },
244
257
  messageId: "neverPadBlock",
245
258
  fix(fixer) {
246
259
  return fixer.replaceTextRange([lastBlockToken.range[1], tokenAfterLast.range[0] - tokenAfterLast.loc.start.column], "\n");
@@ -79,10 +79,7 @@ module.exports = {
79
79
  if (alwaysSpace && !hasWhitespace) {
80
80
  context.report({
81
81
  node,
82
- loc: {
83
- line: operator.loc.end.line,
84
- column: operator.loc.end.column
85
- },
82
+ loc: operator.loc,
86
83
  messageId: "expectedWhitespace",
87
84
  data: {
88
85
  type
@@ -95,8 +92,8 @@ module.exports = {
95
92
  context.report({
96
93
  node,
97
94
  loc: {
98
- line: operator.loc.end.line,
99
- column: operator.loc.end.column
95
+ start: operator.loc.end,
96
+ end: nextToken.loc.start
100
97
  },
101
98
  messageId: "unexpectedWhitespace",
102
99
  data: {
@@ -117,6 +117,18 @@ module.exports = {
117
117
  }
118
118
 
119
119
  /**
120
+ * Report location example :
121
+ *
122
+ * for unexpected space `before`
123
+ *
124
+ * var a = 'b' ;
125
+ * ^^^
126
+ *
127
+ * for unexpected space `after`
128
+ *
129
+ * var a = 'b'; c = 10;
130
+ * ^^
131
+ *
120
132
  * Reports if the given token has invalid spacing.
121
133
  * @param {Token} token The semicolon token to check.
122
134
  * @param {ASTNode} node The corresponding node of the token.
@@ -124,16 +136,19 @@ module.exports = {
124
136
  */
125
137
  function checkSemicolonSpacing(token, node) {
126
138
  if (astUtils.isSemicolonToken(token)) {
127
- const location = token.loc.start;
128
-
129
139
  if (hasLeadingSpace(token)) {
130
140
  if (!requireSpaceBefore) {
141
+ const tokenBefore = sourceCode.getTokenBefore(token);
142
+ const loc = {
143
+ start: tokenBefore.loc.end,
144
+ end: token.loc.start
145
+ };
146
+
131
147
  context.report({
132
148
  node,
133
- loc: location,
149
+ loc,
134
150
  messageId: "unexpectedWhitespaceBefore",
135
151
  fix(fixer) {
136
- const tokenBefore = sourceCode.getTokenBefore(token);
137
152
 
138
153
  return fixer.removeRange([tokenBefore.range[1], token.range[0]]);
139
154
  }
@@ -141,9 +156,11 @@ module.exports = {
141
156
  }
142
157
  } else {
143
158
  if (requireSpaceBefore) {
159
+ const loc = token.loc;
160
+
144
161
  context.report({
145
162
  node,
146
- loc: location,
163
+ loc,
147
164
  messageId: "missingWhitespaceBefore",
148
165
  fix(fixer) {
149
166
  return fixer.insertTextBefore(token, " ");
@@ -155,12 +172,17 @@ module.exports = {
155
172
  if (!isFirstTokenInCurrentLine(token) && !isLastTokenInCurrentLine(token) && !isBeforeClosingParen(token)) {
156
173
  if (hasTrailingSpace(token)) {
157
174
  if (!requireSpaceAfter) {
175
+ const tokenAfter = sourceCode.getTokenAfter(token);
176
+ const loc = {
177
+ start: token.loc.end,
178
+ end: tokenAfter.loc.start
179
+ };
180
+
158
181
  context.report({
159
182
  node,
160
- loc: location,
183
+ loc,
161
184
  messageId: "unexpectedWhitespaceAfter",
162
185
  fix(fixer) {
163
- const tokenAfter = sourceCode.getTokenAfter(token);
164
186
 
165
187
  return fixer.removeRange([token.range[1], tokenAfter.range[0]]);
166
188
  }
@@ -168,9 +190,11 @@ module.exports = {
168
190
  }
169
191
  } else {
170
192
  if (requireSpaceAfter) {
193
+ const loc = token.loc;
194
+
171
195
  context.report({
172
196
  node,
173
- loc: location,
197
+ loc,
174
198
  messageId: "missingWhitespaceAfter",
175
199
  fix(fixer) {
176
200
  return fixer.insertTextAfter(token, " ");
@@ -1243,19 +1243,64 @@ module.exports = {
1243
1243
 
1244
1244
  /**
1245
1245
  * Gets next location when the result is not out of bound, otherwise returns null.
1246
+ *
1247
+ * Assumptions:
1248
+ *
1249
+ * - The given location represents a valid location in the given source code.
1250
+ * - Columns are 0-based.
1251
+ * - Lines are 1-based.
1252
+ * - Column immediately after the last character in a line (not incl. linebreaks) is considered to be a valid location.
1253
+ * - If the source code ends with a linebreak, `sourceCode.lines` array will have an extra element (empty string) at the end.
1254
+ * The start (column 0) of that extra line is considered to be a valid location.
1255
+ *
1256
+ * Examples of successive locations (line, column):
1257
+ *
1258
+ * code: foo
1259
+ * locations: (1, 0) -> (1, 1) -> (1, 2) -> (1, 3) -> null
1260
+ *
1261
+ * code: foo<LF>
1262
+ * locations: (1, 0) -> (1, 1) -> (1, 2) -> (1, 3) -> (2, 0) -> null
1263
+ *
1264
+ * code: foo<CR><LF>
1265
+ * locations: (1, 0) -> (1, 1) -> (1, 2) -> (1, 3) -> (2, 0) -> null
1266
+ *
1267
+ * code: a<LF>b
1268
+ * locations: (1, 0) -> (1, 1) -> (2, 0) -> (2, 1) -> null
1269
+ *
1270
+ * code: a<LF>b<LF>
1271
+ * locations: (1, 0) -> (1, 1) -> (2, 0) -> (2, 1) -> (3, 0) -> null
1272
+ *
1273
+ * code: a<CR><LF>b<CR><LF>
1274
+ * locations: (1, 0) -> (1, 1) -> (2, 0) -> (2, 1) -> (3, 0) -> null
1275
+ *
1276
+ * code: a<LF><LF>
1277
+ * locations: (1, 0) -> (1, 1) -> (2, 0) -> (3, 0) -> null
1278
+ *
1279
+ * code: <LF>
1280
+ * locations: (1, 0) -> (2, 0) -> null
1281
+ *
1282
+ * code:
1283
+ * locations: (1, 0) -> null
1246
1284
  * @param {SourceCode} sourceCode The sourceCode
1247
1285
  * @param {{line: number, column: number}} location The location
1248
1286
  * @returns {{line: number, column: number} | null} Next location
1249
1287
  */
1250
- getNextLocation(sourceCode, location) {
1251
- const index = sourceCode.getIndexFromLoc(location);
1288
+ getNextLocation(sourceCode, { line, column }) {
1289
+ if (column < sourceCode.lines[line - 1].length) {
1290
+ return {
1291
+ line,
1292
+ column: column + 1
1293
+ };
1294
+ }
1252
1295
 
1253
- // Avoid out of bound location
1254
- if (index + 1 > sourceCode.text.length) {
1255
- return null;
1296
+ if (line < sourceCode.lines.length) {
1297
+ return {
1298
+ line: line + 1,
1299
+ column: 0
1300
+ };
1256
1301
  }
1257
1302
 
1258
- return sourceCode.getLocFromIndex(index + 1);
1303
+ return null;
1259
1304
  },
1260
1305
 
1261
1306
  /**
@@ -305,6 +305,7 @@ class SourceCode extends TokenStore {
305
305
  * @returns {Object} An object containing a leading and trailing array
306
306
  * of comments indexed by their position.
307
307
  * @public
308
+ * @deprecated replaced by getCommentsBefore(), getCommentsAfter(), and getCommentsInside().
308
309
  */
309
310
  getComments(node) {
310
311
  if (this._commentCache.has(node)) {
@@ -2,4 +2,4 @@ ESLint couldn't find the config "<%- configName %>" to extend from. Please check
2
2
 
3
3
  The config "<%- configName %>" was referenced from the config file in "<%- importerName %>".
4
4
 
5
- If you still have problems, please stop by https://gitter.im/eslint/eslint to chat with the team.
5
+ If you still have problems, please stop by https://eslint.org/chat to chat with the team.
@@ -4,4 +4,4 @@ ESLint couldn't find a configuration file. To set up a configuration file for th
4
4
 
5
5
  ESLint looked for configuration files in <%= directoryPath %> and its ancestors. If it found none, it then looked in your home directory.
6
6
 
7
- 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
7
+ If you think you already have a configuration file or if you need more help, please stop by the ESLint chat room: https://eslint.org/chat
@@ -4,4 +4,4 @@ ESLint couldn't determine the plugin "<%- pluginId %>" uniquely.
4
4
 
5
5
  Please remove the "plugins" setting from either config or remove either plugin installation.
6
6
 
7
- If you still can't figure out the problem, please stop by https://gitter.im/eslint/eslint to chat with the team.
7
+ If you still can't figure out the problem, please stop by https://eslint.org/chat to chat with the team.
@@ -8,4 +8,4 @@ It's likely that the plugin isn't installed correctly. Try reinstalling by runni
8
8
 
9
9
  The plugin "<%- pluginName %>" was referenced from the config file in "<%- importerName %>".
10
10
 
11
- If you still can't figure out the problem, please stop by https://gitter.im/eslint/eslint to chat with the team.
11
+ If you still can't figure out the problem, please stop by https://eslint.org/chat to chat with the team.
@@ -1,3 +1,3 @@
1
1
  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.
2
2
 
3
- If you still can't figure out the problem, please stop by https://gitter.im/eslint/eslint to chat with the team.
3
+ If you still can't figure out the problem, please stop by https://eslint.org/chat to chat with the team.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint",
3
- "version": "7.0.0",
3
+ "version": "7.1.0",
4
4
  "author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
5
5
  "description": "An AST-based pattern checker for JavaScript.",
6
6
  "bin": {