enableDisableRules.js 6.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. "use strict";
  2. /**
  3. * @license
  4. * Copyright 2014 Palantir Technologies, Inc.
  5. *
  6. * Licensed under the Apache License, Version 2.0 (the "License");
  7. * you may not use this file except in compliance with the License.
  8. * You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. Object.defineProperty(exports, "__esModule", { value: true });
  19. // tslint:disable object-literal-sort-keys
  20. var utils = require("tsutils");
  21. var ts = require("typescript");
  22. /**
  23. * regex is: start of string followed by any amount of whitespace
  24. * followed by tslint and colon
  25. * followed by either "enable" or "disable"
  26. * followed optionally by -line or -next-line
  27. * followed by either colon, whitespace or end of string
  28. */
  29. exports.ENABLE_DISABLE_REGEX = /^\s*tslint:(enable|disable)(?:-(line|next-line))?(:|\s|$)/;
  30. function removeDisabledFailures(sourceFile, failures) {
  31. if (failures.length === 0) {
  32. // Usually there won't be failures anyway, so no need to look for "tslint:disable".
  33. return failures;
  34. }
  35. var failingRules = new Set(failures.map(function (f) { return f.getRuleName(); }));
  36. var map = getDisableMap(sourceFile, failingRules);
  37. return failures.filter(function (failure) {
  38. var disabledIntervals = map.get(failure.getRuleName());
  39. return disabledIntervals === undefined || !disabledIntervals.some(function (_a) {
  40. var pos = _a.pos, end = _a.end;
  41. var failPos = failure.getStartPosition().getPosition();
  42. var failEnd = failure.getEndPosition().getPosition();
  43. return failEnd >= pos && (end === -1 || failPos < end);
  44. });
  45. });
  46. }
  47. exports.removeDisabledFailures = removeDisabledFailures;
  48. /**
  49. * The map will have an array of TextRange for each disable of a rule in a file.
  50. * (It will have no entry if the rule is never disabled, meaning all arrays are non-empty.)
  51. */
  52. function getDisableMap(sourceFile, failingRules) {
  53. var map = new Map();
  54. utils.forEachComment(sourceFile, function (fullText, comment) {
  55. var commentText = comment.kind === ts.SyntaxKind.SingleLineCommentTrivia
  56. ? fullText.substring(comment.pos + 2, comment.end)
  57. : fullText.substring(comment.pos + 2, comment.end - 2);
  58. var parsed = parseComment(commentText);
  59. if (parsed !== undefined) {
  60. var rulesList = parsed.rulesList, isEnabled = parsed.isEnabled, modifier = parsed.modifier;
  61. var switchRange = getSwitchRange(modifier, comment, sourceFile);
  62. if (switchRange !== undefined) {
  63. var rulesToSwitch = rulesList === "all" ? Array.from(failingRules) : rulesList.filter(function (r) { return failingRules.has(r); });
  64. for (var _i = 0, rulesToSwitch_1 = rulesToSwitch; _i < rulesToSwitch_1.length; _i++) {
  65. var ruleToSwitch = rulesToSwitch_1[_i];
  66. switchRuleState(ruleToSwitch, isEnabled, switchRange.pos, switchRange.end);
  67. }
  68. }
  69. }
  70. });
  71. return map;
  72. function switchRuleState(ruleName, isEnable, start, end) {
  73. var disableRanges = map.get(ruleName);
  74. if (isEnable) {
  75. if (disableRanges !== undefined) {
  76. var lastDisable = disableRanges[disableRanges.length - 1];
  77. if (lastDisable.end === -1) {
  78. lastDisable.end = start;
  79. if (end !== -1) {
  80. // Disable it again after the enable range is over.
  81. disableRanges.push({ pos: end, end: -1 });
  82. }
  83. }
  84. }
  85. }
  86. else { // disable
  87. if (disableRanges === undefined) {
  88. map.set(ruleName, [{ pos: start, end: end }]);
  89. }
  90. else if (disableRanges[disableRanges.length - 1].end !== -1) {
  91. disableRanges.push({ pos: start, end: end });
  92. }
  93. }
  94. }
  95. }
  96. /** End will be -1 to indicate no end. */
  97. function getSwitchRange(modifier, range, sourceFile) {
  98. var lineStarts = sourceFile.getLineStarts();
  99. switch (modifier) {
  100. case "line":
  101. return {
  102. // start at the beginning of the line where comment starts
  103. pos: getStartOfLinePosition(range.pos),
  104. // end at the beginning of the line following the comment
  105. end: getStartOfLinePosition(range.end, 1),
  106. };
  107. case "next-line":
  108. // start at the beginning of the line following the comment
  109. var pos = getStartOfLinePosition(range.end, 1);
  110. if (pos === -1) {
  111. // no need to switch anything, there is no next line
  112. return undefined;
  113. }
  114. // end at the beginning of the line following the next line
  115. return { pos: pos, end: getStartOfLinePosition(range.end, 2) };
  116. default:
  117. // switch rule for the rest of the file
  118. // start at the current position, but skip end position
  119. return { pos: range.pos, end: -1 };
  120. }
  121. /** Returns -1 for last line. */
  122. function getStartOfLinePosition(position, lineOffset) {
  123. if (lineOffset === void 0) { lineOffset = 0; }
  124. var line = ts.getLineAndCharacterOfPosition(sourceFile, position).line + lineOffset;
  125. return line >= lineStarts.length ? -1 : lineStarts[line];
  126. }
  127. }
  128. function parseComment(commentText) {
  129. var match = exports.ENABLE_DISABLE_REGEX.exec(commentText);
  130. if (match === null) {
  131. return undefined;
  132. }
  133. // remove everything matched by the previous regex to get only the specified rules
  134. // split at whitespaces
  135. // filter empty items coming from whitespaces at start, at end or empty list
  136. var rulesList = splitOnSpaces(commentText.substr(match[0].length));
  137. if (rulesList.length === 0 && match[3] === ":") {
  138. // nothing to do here: an explicit separator was specified but no rules to switch
  139. return undefined;
  140. }
  141. if (rulesList.length === 0 ||
  142. rulesList.indexOf("all") !== -1) {
  143. // if list is empty we default to all enabled rules
  144. // if `all` is specified we ignore the other rules and take all enabled rules
  145. rulesList = "all";
  146. }
  147. return { rulesList: rulesList, isEnabled: match[1] === "enable", modifier: match[2] };
  148. }
  149. function splitOnSpaces(str) {
  150. return str.split(/\s+/).filter(function (s) { return s !== ""; });
  151. }