assignDisabledRanges.cjs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. // NOTICE: This file is generated by Rollup. To modify it,
  2. // please instead edit the ESM counterpart and rebuild with Rollup (npm run build).
  3. 'use strict';
  4. const constants = require('./constants.cjs');
  5. const configurationComment = require('./utils/configurationComment.cjs');
  6. const validateTypes = require('./utils/validateTypes.cjs');
  7. const typeGuards = require('./utils/typeGuards.cjs');
  8. const isStandardSyntaxComment = require('./utils/isStandardSyntaxComment.cjs');
  9. const cssTokenizer = require('@csstools/css-tokenizer');
  10. /** @import {Node as PostcssNode, Comment as PostcssComment, Document as PostcssDocument, Root as PostcssRoot, Source as PostcssSource} from 'postcss' */
  11. /** @import {DisabledRange, DisabledRangeObject, PostcssResult} from 'stylelint' */
  12. /** @typedef {Pick<PostcssSource, 'start' | 'end'>} Source */
  13. /**
  14. * @param {PostcssNode} node
  15. * @param {number} start
  16. * @param {boolean} strictStart
  17. * @param {string|undefined} description
  18. * @param {number} [end]
  19. * @param {boolean} [strictEnd]
  20. * @returns {DisabledRange}
  21. */
  22. function createDisableRange(node, start, strictStart, description, end, strictEnd) {
  23. return {
  24. node,
  25. start,
  26. end: end || undefined,
  27. strictStart,
  28. strictEnd: typeof strictEnd === 'boolean' ? strictEnd : undefined,
  29. description,
  30. };
  31. }
  32. /**
  33. * Run it like a PostCSS plugin
  34. * @param {PostcssRoot | PostcssDocument} root
  35. * @param {PostcssResult} result
  36. * @returns {PostcssResult}
  37. */
  38. function assignDisabledRanges(root, result) {
  39. /**
  40. * Most of the functions below work via side effects mutating this object
  41. * @type {DisabledRangeObject}
  42. */
  43. const disabledRanges = result.stylelint.disabledRanges;
  44. /** @type {DisabledRange[]} */
  45. const disabledRangesAll = [];
  46. disabledRanges[constants.RULE_NAME_ALL] = disabledRangesAll;
  47. // Work around postcss/postcss-scss#109 by merging adjacent `//` comments
  48. // into a single node before passing to `checkComment`.
  49. /** @type {PostcssComment?} */
  50. let inlineEnd;
  51. const configurationComment$1 = result.stylelint.config?.configurationComment;
  52. root.walk((node) => {
  53. if (typeGuards.isComment(node)) {
  54. if (inlineEnd) {
  55. // Ignore comments already processed by grouping with a previous one.
  56. if (inlineEnd === node) inlineEnd = null;
  57. return;
  58. }
  59. const nextComment = node.next();
  60. // If any of these conditions are not met, do not merge comments.
  61. if (
  62. !(
  63. !isStandardSyntaxComment(node) &&
  64. configurationComment.isConfigurationComment(node.text, configurationComment$1) &&
  65. nextComment &&
  66. typeGuards.isComment(nextComment) &&
  67. (node.text.includes('--') || nextComment.text.startsWith('--'))
  68. )
  69. ) {
  70. checkComment(node, node.source, node.text);
  71. return;
  72. }
  73. let lastLine = node.source?.end?.line ?? 0;
  74. const fullComment = node.clone();
  75. let current = nextComment;
  76. while (
  77. !isStandardSyntaxComment(current) &&
  78. !configurationComment.isConfigurationComment(current.text, configurationComment$1)
  79. ) {
  80. const currentLine = current.source?.end?.line ?? 0;
  81. if (lastLine + 1 !== currentLine) break;
  82. fullComment.text += `\n${current.text}`;
  83. if (fullComment.source && current.source) {
  84. fullComment.source.end = current.source.end;
  85. }
  86. inlineEnd = current;
  87. const next = current.next();
  88. if (!next || !typeGuards.isComment(next)) break;
  89. current = next;
  90. lastLine = currentLine;
  91. }
  92. checkComment(fullComment, fullComment.source, fullComment.text);
  93. }
  94. if (typeGuards.isRule(node)) {
  95. let offset = 0;
  96. const selector = node.raws?.selector?.raw;
  97. checkCommentsInNode(node, selector, offset);
  98. offset += selector?.length ?? node.selector.length;
  99. const between = node.raws?.between;
  100. checkCommentsInNode(node, between, offset);
  101. }
  102. if (typeGuards.isAtRule(node)) {
  103. let offset = node.name.length + 1; // `@` + name
  104. const afterName = node.raws?.afterName;
  105. checkCommentsInNode(node, afterName, offset);
  106. offset += afterName?.length ?? 0;
  107. const params = node.raws?.params?.raw;
  108. checkCommentsInNode(node, params, offset);
  109. offset += params?.length ?? node.params.length;
  110. const between = node.raws?.between;
  111. checkCommentsInNode(node, between, offset);
  112. }
  113. if (typeGuards.isDeclaration(node)) {
  114. let offset = node.prop.length;
  115. const between = node.raws?.between;
  116. checkCommentsInNode(node, between, offset);
  117. offset += between?.length ?? 0;
  118. const value = node.raws?.value?.raw;
  119. checkCommentsInNode(node, value, offset);
  120. }
  121. });
  122. return result;
  123. /**
  124. * @param {PostcssNode} node
  125. * @param {Source} source
  126. * @param {string} text
  127. */
  128. function processDisableLineCommand(node, source, text) {
  129. if (source.start) {
  130. const line = source.start.line;
  131. const description = getDescription(text);
  132. for (const ruleName of getCommandRules(configurationComment.DISABLE_LINE_COMMAND, text)) {
  133. disableLine(node, line, ruleName, description);
  134. }
  135. }
  136. }
  137. /**
  138. * @param {PostcssNode} node
  139. * @param {Source} source
  140. * @param {string} text
  141. */
  142. function processDisableNextLineCommand(node, source, text) {
  143. if (source.end) {
  144. const line = source.end.line;
  145. const description = getDescription(text);
  146. for (const ruleName of getCommandRules(configurationComment.DISABLE_NEXT_LINE_COMMAND, text)) {
  147. disableLine(node, line + 1, ruleName, description);
  148. }
  149. }
  150. }
  151. /**
  152. * @param {PostcssNode} node
  153. * @param {number} line
  154. * @param {string} ruleName
  155. * @param {string|undefined} description
  156. */
  157. function disableLine(node, line, ruleName, description) {
  158. if (ruleIsDisabled(constants.RULE_NAME_ALL)) {
  159. throw node.error('All rules have already been disabled', {
  160. plugin: 'stylelint',
  161. });
  162. }
  163. if (ruleName === constants.RULE_NAME_ALL) {
  164. for (const disabledRuleName of Object.keys(disabledRanges)) {
  165. if (ruleIsDisabled(disabledRuleName)) continue;
  166. const strict = disabledRuleName === constants.RULE_NAME_ALL;
  167. startDisabledRange(node, line, disabledRuleName, strict, description);
  168. endDisabledRange(line, disabledRuleName, strict);
  169. }
  170. } else {
  171. if (ruleIsDisabled(ruleName)) {
  172. throw node.error(`"${ruleName}" has already been disabled`, {
  173. plugin: 'stylelint',
  174. });
  175. }
  176. startDisabledRange(node, line, ruleName, true, description);
  177. endDisabledRange(line, ruleName, true);
  178. }
  179. }
  180. /**
  181. * @param {PostcssNode} node
  182. * @param {Source} source
  183. * @param {string} text
  184. */
  185. function processDisableCommand(node, source, text) {
  186. const description = getDescription(text);
  187. for (const ruleToDisable of getCommandRules(configurationComment.DISABLE_COMMAND, text)) {
  188. const isAllRules = ruleToDisable === constants.RULE_NAME_ALL;
  189. if (ruleIsDisabled(ruleToDisable)) {
  190. throw node.error(
  191. isAllRules
  192. ? 'All rules have already been disabled'
  193. : `"${ruleToDisable}" has already been disabled`,
  194. {
  195. plugin: 'stylelint',
  196. },
  197. );
  198. }
  199. if (source.start) {
  200. const line = source.start.line;
  201. if (isAllRules) {
  202. for (const ruleName of Object.keys(disabledRanges)) {
  203. startDisabledRange(node, line, ruleName, ruleName === constants.RULE_NAME_ALL, description);
  204. }
  205. } else {
  206. startDisabledRange(node, line, ruleToDisable, true, description);
  207. }
  208. }
  209. }
  210. }
  211. /**
  212. * @param {PostcssNode} node
  213. * @param {Source} source
  214. * @param {string} text
  215. */
  216. function processEnableCommand(node, source, text) {
  217. for (const ruleToEnable of getCommandRules(configurationComment.ENABLE_COMMAND, text)) {
  218. // need fallback if endLine will be undefined
  219. const endLine = source.end?.line;
  220. validateTypes.assertNumber(endLine);
  221. if (ruleToEnable === constants.RULE_NAME_ALL) {
  222. if (
  223. Object.values(disabledRanges).every((ranges) => {
  224. if (ranges.length === 0) return true;
  225. const lastRange = ranges[ranges.length - 1];
  226. return lastRange && typeof lastRange.end === 'number';
  227. })
  228. ) {
  229. throw node.error('No rules have been disabled', {
  230. plugin: 'stylelint',
  231. });
  232. }
  233. for (const [ruleName, ranges] of Object.entries(disabledRanges)) {
  234. const lastRange = ranges[ranges.length - 1];
  235. if (!lastRange || !lastRange.end) {
  236. endDisabledRange(endLine, ruleName, ruleName === constants.RULE_NAME_ALL);
  237. }
  238. }
  239. continue;
  240. }
  241. if (ruleIsDisabled(constants.RULE_NAME_ALL) && disabledRanges[ruleToEnable] === undefined) {
  242. // Get a starting point from the where all rules were disabled
  243. disabledRanges[ruleToEnable] = disabledRangesAll.map(({ start, end, description }) =>
  244. createDisableRange(node, start, false, description, end, false),
  245. );
  246. endDisabledRange(endLine, ruleToEnable, true);
  247. continue;
  248. }
  249. if (ruleIsDisabled(ruleToEnable)) {
  250. endDisabledRange(endLine, ruleToEnable, true);
  251. continue;
  252. }
  253. throw node.error(`"${ruleToEnable}" has not been disabled`, {
  254. plugin: 'stylelint',
  255. });
  256. }
  257. }
  258. /**
  259. * @param {PostcssNode} node
  260. * @param {string | undefined} part
  261. * @param {number} offset
  262. */
  263. function checkCommentsInNode(node, part, offset) {
  264. if (!(part && part.includes('/*') && part.includes('*/'))) return;
  265. cssTokenizer.tokenize({ css: part }).forEach((token) => {
  266. if (!cssTokenizer.isTokenComment(token)) return;
  267. const [, text, start, end] = token;
  268. const source = node.rangeBy({
  269. index: start + offset,
  270. endIndex: end + 1 + offset,
  271. });
  272. checkComment(node, source, text.slice(2, -2).trim());
  273. });
  274. }
  275. /**
  276. * @param {PostcssNode} node
  277. * @param {Source | undefined} source
  278. * @param {string} text
  279. */
  280. function checkComment(node, source, text) {
  281. if (!source) return;
  282. // Ignore comments that are not relevant commands
  283. if (!configurationComment.isConfigurationComment(text, configurationComment$1)) {
  284. return;
  285. }
  286. switch (configurationComment.extractConfigurationComment(text, configurationComment$1)) {
  287. case configurationComment.DISABLE_LINE_COMMAND:
  288. processDisableLineCommand(node, source, text);
  289. break;
  290. case configurationComment.DISABLE_NEXT_LINE_COMMAND:
  291. processDisableNextLineCommand(node, source, text);
  292. break;
  293. case configurationComment.DISABLE_COMMAND:
  294. processDisableCommand(node, source, text);
  295. break;
  296. case configurationComment.ENABLE_COMMAND:
  297. processEnableCommand(node, source, text);
  298. break;
  299. }
  300. }
  301. /**
  302. * @param {string} command
  303. * @param {string} fullText
  304. * @returns {string[]}
  305. */
  306. function getCommandRules(command, fullText) {
  307. // Allow for description (f.e. /* stylelint-disable a, b -- Description */).
  308. const fullCommand = configurationComment.getConfigurationComment(command, configurationComment$1);
  309. const rulesText = fullText.slice(fullCommand.length).split(/\s-{2,}\s/u)[0];
  310. validateTypes.assertString(rulesText);
  311. const rules = rulesText
  312. .trim()
  313. .split(',')
  314. .filter(Boolean)
  315. .map((r) => r.trim());
  316. if (rules.length === 0) {
  317. return [constants.RULE_NAME_ALL];
  318. }
  319. return rules;
  320. }
  321. /**
  322. * @param {string} fullText
  323. * @returns {string|undefined}
  324. */
  325. function getDescription(fullText) {
  326. const descriptionStart = fullText.indexOf('--');
  327. if (descriptionStart === -1) return;
  328. return fullText.slice(descriptionStart + 2).trim();
  329. }
  330. /**
  331. * @param {PostcssNode} node
  332. * @param {number} line
  333. * @param {string} ruleName
  334. * @param {boolean} strict
  335. * @param {string|undefined} description
  336. */
  337. function startDisabledRange(node, line, ruleName, strict, description) {
  338. const rangeObj = createDisableRange(node, line, strict, description);
  339. ensureRuleRanges(ruleName);
  340. const range = disabledRanges[ruleName];
  341. validateTypes.assert(range);
  342. range.push(rangeObj);
  343. }
  344. /**
  345. * @param {number} line
  346. * @param {string} ruleName
  347. * @param {boolean} strict
  348. */
  349. function endDisabledRange(line, ruleName, strict) {
  350. const ranges = disabledRanges[ruleName];
  351. const lastRangeForRule = ranges ? ranges[ranges.length - 1] : null;
  352. if (!lastRangeForRule) {
  353. return;
  354. }
  355. // Add an `end` prop to the last range of that rule
  356. lastRangeForRule.end = line;
  357. lastRangeForRule.strictEnd = strict;
  358. }
  359. /**
  360. * @param {string} ruleName
  361. */
  362. function ensureRuleRanges(ruleName) {
  363. if (!disabledRanges[ruleName]) {
  364. disabledRanges[ruleName] = disabledRangesAll.map(({ node, start, end, description }) =>
  365. createDisableRange(node, start, false, description, end, false),
  366. );
  367. }
  368. }
  369. /**
  370. * @param {string} ruleName
  371. * @returns {boolean}
  372. */
  373. function ruleIsDisabled(ruleName) {
  374. const ranges = disabledRanges[ruleName];
  375. if (!ranges) return false;
  376. const lastRange = ranges[ranges.length - 1];
  377. if (!lastRange) return false;
  378. if (!lastRange.end) return true;
  379. return false;
  380. }
  381. }
  382. module.exports = assignDisabledRanges;