eslint 4.4.1 → 4.5.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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ v4.5.0 - August 18, 2017
2
+
3
+ * decdd2c Update: allow arbitrary nodes to be ignored in `indent` (fixes #8594) (#9105) (Teddy Katz)
4
+ * 79062f3 Update: fix indentation of multiline `new.target` expressions (#9116) (Teddy Katz)
5
+ * d00e24f Upgrade: `chalk` to 2.x release (#9115) (Stephen Edgar)
6
+ * 6ef734a Docs: add missing word in processor documentation (#9106) (Teddy Katz)
7
+ * a4f53ba Fix: Include files with no messages in junit results (#9093) (#9094) (Sean DuBois)
8
+ * 1d6a9c0 Chore: enable eslint-plugin/test-case-shorthand-strings (#9067) (薛定谔的猫)
9
+ * f8add8f Fix: don't autofix with linter.verifyAndFix when `fix: false` is used (#9098) (Teddy Katz)
10
+ * 77bcee4 Docs: update instructions for adding TSC members (#9086) (Teddy Katz)
11
+ * bd09cd5 Update: avoid requiring NaN spaces of indentation (fixes #9083) (#9085) (Teddy Katz)
12
+ * c93a853 Chore: Remove extra space in blogpost template (#9088) (Kai Cataldo)
13
+
1
14
  v4.4.1 - August 7, 2017
2
15
 
3
16
  * ec93614 Fix: no-multi-spaces to avoid reporting consecutive tabs (fixes #9079) (#9087) (Teddy Katz)
@@ -39,10 +39,7 @@ module.exports = function(results) {
39
39
 
40
40
  const messages = result.messages;
41
41
 
42
- if (messages.length) {
43
- output += `<testsuite package="org.eslint" time="0" tests="${messages.length}" errors="${messages.length}" name="${result.filePath}">\n`;
44
- }
45
-
42
+ output += `<testsuite package="org.eslint" time="0" tests="${messages.length}" errors="${messages.length}" name="${result.filePath}">\n`;
46
43
  messages.forEach(message => {
47
44
  const type = message.fatal ? "error" : "failure";
48
45
 
@@ -57,10 +54,7 @@ module.exports = function(results) {
57
54
  output += `</${type}>`;
58
55
  output += "</testcase>\n";
59
56
  });
60
-
61
- if (messages.length) {
62
- output += "</testsuite>\n";
63
- }
57
+ output += "</testsuite>\n";
64
58
 
65
59
  });
66
60
 
@@ -5,6 +5,7 @@
5
5
  "use strict";
6
6
 
7
7
  const chalk = require("chalk"),
8
+ stripAnsi = require("strip-ansi"),
8
9
  table = require("text-table");
9
10
 
10
11
  //------------------------------------------------------------------------------
@@ -71,7 +72,7 @@ module.exports = function(results) {
71
72
  {
72
73
  align: ["", "r", "l"],
73
74
  stringLength(str) {
74
- return chalk.stripColor(str).length;
75
+ return stripAnsi(str).length;
75
76
  }
76
77
  }
77
78
  ).split("\n").map(el => el.replace(/(\d+)\s+(\d+)/, (m, p1, p2) => chalk.dim(`${p1}:${p2}`))).join("\n")}\n\n`;
package/lib/linter.js CHANGED
@@ -1210,7 +1210,7 @@ class Linter extends EventEmitter {
1210
1210
  fixed = false,
1211
1211
  passNumber = 0;
1212
1212
  const debugTextDescription = options && options.filename || `${text.slice(0, 10)}...`;
1213
- const shouldFix = options && options.fix || true;
1213
+ const shouldFix = options && typeof options.fix !== "undefined" ? options.fix : true;
1214
1214
 
1215
1215
  /**
1216
1216
  * This loop continues until one of the following is true:
@@ -581,6 +581,15 @@ module.exports = {
581
581
  ImportDeclaration: ELEMENT_LIST_SCHEMA,
582
582
  flatTernaryExpressions: {
583
583
  type: "boolean"
584
+ },
585
+ ignoredNodes: {
586
+ type: "array",
587
+ items: {
588
+ type: "string",
589
+ not: {
590
+ pattern: ":exit$"
591
+ }
592
+ }
584
593
  }
585
594
  },
586
595
  additionalProperties: false
@@ -618,7 +627,8 @@ module.exports = {
618
627
  ArrayExpression: 1,
619
628
  ObjectExpression: 1,
620
629
  ImportDeclaration: 1,
621
- flatTernaryExpressions: false
630
+ flatTernaryExpressions: false,
631
+ ignoredNodes: []
622
632
  };
623
633
 
624
634
  if (context.options.length) {
@@ -783,7 +793,7 @@ module.exports = {
783
793
  offsets.setDesiredOffsets(
784
794
  [startToken.range[1], endToken.range[0]],
785
795
  startToken,
786
- offset === "first" ? 1 : offset
796
+ typeof offset === "number" ? offset : 1
787
797
  );
788
798
  offsets.setDesiredOffset(endToken, startToken, 0);
789
799
 
@@ -914,7 +924,7 @@ module.exports = {
914
924
  * @param {ASTNode} node Unknown Node
915
925
  * @returns {void}
916
926
  */
917
- function ignoreUnknownNode(node) {
927
+ function ignoreNode(node) {
918
928
  const unknownNodeTokens = new Set(sourceCode.getTokens(node, { includeComments: true }));
919
929
 
920
930
  unknownNodeTokens.forEach(token => {
@@ -930,19 +940,6 @@ module.exports = {
930
940
  });
931
941
  }
932
942
 
933
- /**
934
- * Ignore node if it is unknown
935
- * @param {ASTNode} node Node
936
- * @returns {void}
937
- */
938
- function checkForUnknownNode(node) {
939
- const isNodeUnknown = !(KNOWN_NODES.has(node.type));
940
-
941
- if (isNodeUnknown) {
942
- ignoreUnknownNode(node);
943
- }
944
- }
945
-
946
943
  /**
947
944
  * Check whether the given token is the first token of a statement.
948
945
  * @param {Token} token The token to check.
@@ -960,7 +957,7 @@ module.exports = {
960
957
  return !node || node.range[0] === token.range[0];
961
958
  }
962
959
 
963
- return {
960
+ const baseOffsetListeners = {
964
961
  "ArrayExpression, ArrayPattern"(node) {
965
962
  const openingBracket = sourceCode.getFirstToken(node);
966
963
  const closingBracket = sourceCode.getTokenAfter(lodash.findLast(node.elements) || openingBracket, astUtils.isClosingBracketToken);
@@ -1178,14 +1175,15 @@ module.exports = {
1178
1175
  }
1179
1176
  },
1180
1177
 
1181
- "MemberExpression, JSXMemberExpression"(node) {
1182
- const firstNonObjectToken = sourceCode.getFirstTokenBetween(node.object, node.property, astUtils.isNotClosingParenToken);
1178
+ "MemberExpression, JSXMemberExpression, MetaProperty"(node) {
1179
+ const object = node.type === "MetaProperty" ? node.meta : node.object;
1180
+ const firstNonObjectToken = sourceCode.getFirstTokenBetween(object, node.property, astUtils.isNotClosingParenToken);
1183
1181
  const secondNonObjectToken = sourceCode.getTokenAfter(firstNonObjectToken);
1184
1182
 
1185
- const objectParenCount = sourceCode.getTokensBetween(node.object, node.property, { filter: astUtils.isClosingParenToken }).length;
1183
+ const objectParenCount = sourceCode.getTokensBetween(object, node.property, { filter: astUtils.isClosingParenToken }).length;
1186
1184
  const firstObjectToken = objectParenCount
1187
- ? sourceCode.getTokenBefore(node.object, { skip: objectParenCount - 1 })
1188
- : sourceCode.getFirstToken(node.object);
1185
+ ? sourceCode.getTokenBefore(object, { skip: objectParenCount - 1 })
1186
+ : sourceCode.getFirstToken(object);
1189
1187
  const lastObjectToken = sourceCode.getTokenBefore(firstNonObjectToken);
1190
1188
  const firstPropertyToken = node.computed ? firstNonObjectToken : secondNonObjectToken;
1191
1189
 
@@ -1333,8 +1331,6 @@ module.exports = {
1333
1331
  }
1334
1332
  },
1335
1333
 
1336
- "*:exit": checkForUnknownNode,
1337
-
1338
1334
  "JSXAttribute[value]"(node) {
1339
1335
  const equalsToken = sourceCode.getFirstTokenBetween(node.name, node.value, token => token.type === "Punctuator" && token.value === "=");
1340
1336
 
@@ -1378,62 +1374,130 @@ module.exports = {
1378
1374
  1
1379
1375
  );
1380
1376
  offsets.setDesiredOffset(closingCurly, openingCurly, 0);
1381
- },
1377
+ }
1378
+ };
1382
1379
 
1383
- "Program:exit"() {
1384
- addParensIndent(sourceCode.ast.tokens);
1380
+ const listenerCallQueue = [];
1385
1381
 
1386
- /*
1387
- * Create a Map from (tokenOrComment) => (precedingToken).
1388
- * This is necessary because sourceCode.getTokenBefore does not handle a comment as an argument correctly.
1389
- */
1390
- const precedingTokens = sourceCode.ast.comments.reduce((commentMap, comment) => {
1391
- const tokenOrCommentBefore = sourceCode.getTokenBefore(comment, { includeComments: true });
1382
+ /*
1383
+ * To ignore the indentation of a node:
1384
+ * 1. Don't call the node's listener when entering it (if it has a listener)
1385
+ * 2. Call `ignoreNode` on the node sometime after exiting it and before validating offsets.
1386
+ */
1387
+ const offsetListeners = lodash.mapValues(
1388
+ baseOffsetListeners,
1389
+
1390
+ /*
1391
+ * Offset listener calls are deferred until traversal is finished, and are called as
1392
+ * part of the final `Program:exit` listener. This is necessary because a node might
1393
+ * be matched by multiple selectors.
1394
+ *
1395
+ * Example: Suppose there is an offset listener for `Identifier`, and the user has
1396
+ * specified in configuration that `MemberExpression > Identifier` should be ignored.
1397
+ * Due to selector specificity rules, the `Identifier` listener will get called first. However,
1398
+ * if a given Identifier node is supposed to be ignored, then the `Identifier` offset listener
1399
+ * should not have been called at all. Without doing extra selector matching, we don't know
1400
+ * whether the Identifier matches the `MemberExpression > Identifier` selector until the
1401
+ * `MemberExpression > Identifier` listener is called.
1402
+ *
1403
+ * To avoid this, the `Identifier` listener isn't called until traversal finishes and all
1404
+ * ignored nodes are known.
1405
+ */
1406
+ listener =>
1407
+ node =>
1408
+ listenerCallQueue.push({ listener, node })
1409
+ );
1392
1410
 
1393
- return commentMap.set(comment, commentMap.has(tokenOrCommentBefore) ? commentMap.get(tokenOrCommentBefore) : tokenOrCommentBefore);
1394
- }, new WeakMap());
1411
+ // For each ignored node selector, set up a listener to collect it into the `ignoredNodes` set.
1412
+ const ignoredNodes = new Set();
1413
+ const addToIgnoredNodes = ignoredNodes.add.bind(ignoredNodes);
1395
1414
 
1396
- sourceCode.lines.forEach((line, lineIndex) => {
1397
- const lineNumber = lineIndex + 1;
1415
+ const ignoredNodeListeners = options.ignoredNodes.reduce(
1416
+ (listeners, ignoredSelector) => Object.assign(listeners, { [ignoredSelector]: addToIgnoredNodes }),
1417
+ {}
1418
+ );
1398
1419
 
1399
- if (!tokenInfo.firstTokensByLineNumber.has(lineNumber)) {
1420
+ /*
1421
+ * Join the listeners, and add a listener to verify that all tokens actually have the correct indentation
1422
+ * at the end.
1423
+ *
1424
+ * Using Object.assign will cause some offset listeners to be overwritten if the same selector also appears
1425
+ * in `ignoredNodeListeners`. This isn't a problem because all of the matching nodes will be ignored,
1426
+ * so those listeners wouldn't be called anyway.
1427
+ */
1428
+ return Object.assign(
1429
+ offsetListeners,
1430
+ ignoredNodeListeners,
1431
+ {
1432
+ "*:exit"(node) {
1400
1433
 
1401
- // Don't check indentation on blank lines
1402
- return;
1434
+ // If a node's type is nonstandard, we can't tell how its children should be offset, so ignore it.
1435
+ if (!KNOWN_NODES.has(node.type)) {
1436
+ ignoredNodes.add(node);
1403
1437
  }
1438
+ },
1439
+ "Program:exit"() {
1404
1440
 
1405
- const firstTokenOfLine = tokenInfo.firstTokensByLineNumber.get(lineNumber);
1441
+ // Invoke the queued offset listeners for the nodes that aren't ignored.
1442
+ listenerCallQueue
1443
+ .filter(nodeInfo => !ignoredNodes.has(nodeInfo.node))
1444
+ .forEach(nodeInfo => nodeInfo.listener(nodeInfo.node));
1406
1445
 
1407
- if (firstTokenOfLine.loc.start.line !== lineNumber) {
1446
+ // Update the offsets for ignored nodes to prevent their child tokens from being reported.
1447
+ ignoredNodes.forEach(ignoreNode);
1408
1448
 
1409
- // Don't check the indentation of multi-line tokens (e.g. template literals or block comments) twice.
1410
- return;
1411
- }
1449
+ addParensIndent(sourceCode.ast.tokens);
1412
1450
 
1413
- // If the token matches the expected expected indentation, don't report it.
1414
- if (validateTokenIndent(firstTokenOfLine, offsets.getDesiredIndent(firstTokenOfLine))) {
1415
- return;
1416
- }
1451
+ /*
1452
+ * Create a Map from (tokenOrComment) => (precedingToken).
1453
+ * This is necessary because sourceCode.getTokenBefore does not handle a comment as an argument correctly.
1454
+ */
1455
+ const precedingTokens = sourceCode.ast.comments.reduce((commentMap, comment) => {
1456
+ const tokenOrCommentBefore = sourceCode.getTokenBefore(comment, { includeComments: true });
1457
+
1458
+ return commentMap.set(comment, commentMap.has(tokenOrCommentBefore) ? commentMap.get(tokenOrCommentBefore) : tokenOrCommentBefore);
1459
+ }, new WeakMap());
1417
1460
 
1418
- if (astUtils.isCommentToken(firstTokenOfLine)) {
1419
- const tokenBefore = precedingTokens.get(firstTokenOfLine);
1420
- const tokenAfter = tokenBefore ? sourceCode.getTokenAfter(tokenBefore) : sourceCode.ast.tokens[0];
1461
+ sourceCode.lines.forEach((line, lineIndex) => {
1462
+ const lineNumber = lineIndex + 1;
1421
1463
 
1422
- // If a comment matches the expected indentation of the token immediately before or after, don't report it.
1423
- if (
1424
- tokenBefore && validateTokenIndent(firstTokenOfLine, offsets.getDesiredIndent(tokenBefore)) ||
1425
- tokenAfter && validateTokenIndent(firstTokenOfLine, offsets.getDesiredIndent(tokenAfter))
1426
- ) {
1464
+ if (!tokenInfo.firstTokensByLineNumber.has(lineNumber)) {
1465
+
1466
+ // Don't check indentation on blank lines
1427
1467
  return;
1428
1468
  }
1429
- }
1430
1469
 
1431
- // Otherwise, report the token/comment.
1432
- report(firstTokenOfLine, offsets.getDesiredIndent(firstTokenOfLine));
1433
- });
1434
- }
1470
+ const firstTokenOfLine = tokenInfo.firstTokensByLineNumber.get(lineNumber);
1435
1471
 
1436
- };
1472
+ if (firstTokenOfLine.loc.start.line !== lineNumber) {
1473
+
1474
+ // Don't check the indentation of multi-line tokens (e.g. template literals or block comments) twice.
1475
+ return;
1476
+ }
1477
+
1478
+ // If the token matches the expected expected indentation, don't report it.
1479
+ if (validateTokenIndent(firstTokenOfLine, offsets.getDesiredIndent(firstTokenOfLine))) {
1480
+ return;
1481
+ }
1482
+
1483
+ if (astUtils.isCommentToken(firstTokenOfLine)) {
1484
+ const tokenBefore = precedingTokens.get(firstTokenOfLine);
1485
+ const tokenAfter = tokenBefore ? sourceCode.getTokenAfter(tokenBefore) : sourceCode.ast.tokens[0];
1437
1486
 
1487
+ // If a comment matches the expected indentation of the token immediately before or after, don't report it.
1488
+ if (
1489
+ tokenBefore && validateTokenIndent(firstTokenOfLine, offsets.getDesiredIndent(tokenBefore)) ||
1490
+ tokenAfter && validateTokenIndent(firstTokenOfLine, offsets.getDesiredIndent(tokenAfter))
1491
+ ) {
1492
+ return;
1493
+ }
1494
+ }
1495
+
1496
+ // Otherwise, report the token/comment.
1497
+ report(firstTokenOfLine, offsets.getDesiredIndent(firstTokenOfLine));
1498
+ });
1499
+ }
1500
+ }
1501
+ );
1438
1502
  }
1439
1503
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint",
3
- "version": "4.4.1",
3
+ "version": "4.5.0",
4
4
  "author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
5
5
  "description": "An AST-based pattern checker for JavaScript.",
6
6
  "bin": {
@@ -37,7 +37,7 @@
37
37
  "dependencies": {
38
38
  "ajv": "^5.2.0",
39
39
  "babel-code-frame": "^6.22.0",
40
- "chalk": "^1.1.3",
40
+ "chalk": "^2.1.0",
41
41
  "concat-stream": "^1.6.0",
42
42
  "cross-spawn": "^5.1.0",
43
43
  "debug": "^2.6.8",
@@ -68,6 +68,7 @@
68
68
  "progress": "^2.0.0",
69
69
  "require-uncached": "^1.0.3",
70
70
  "semver": "^5.3.0",
71
+ "strip-ansi": "^4.0.0",
71
72
  "strip-json-comments": "~2.0.1",
72
73
  "table": "^4.0.1",
73
74
  "text-table": "~0.2.0"