index.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. 'use strict';
  2. const atRuleParamIndex = require('../../utils/atRuleParamIndex');
  3. const declarationValueIndex = require('../../utils/declarationValueIndex');
  4. const isStandardSyntaxSelector = require('../../utils/isStandardSyntaxSelector');
  5. const parseSelector = require('../../utils/parseSelector');
  6. const report = require('../../utils/report');
  7. const ruleMessages = require('../../utils/ruleMessages');
  8. const validateOptions = require('../../utils/validateOptions');
  9. const valueParser = require('postcss-value-parser');
  10. const ruleName = 'string-no-newline';
  11. const reNewLine = /\r?\n/;
  12. const messages = ruleMessages(ruleName, {
  13. rejected: 'Unexpected newline in string',
  14. });
  15. const meta = {
  16. url: 'https://stylelint.io/user-guide/rules/string-no-newline',
  17. };
  18. /** @type {import('stylelint').Rule} */
  19. const rule = (primary) => {
  20. return (root, result) => {
  21. const validOptions = validateOptions(result, ruleName, { actual: primary });
  22. if (!validOptions) {
  23. return;
  24. }
  25. root.walk((node) => {
  26. switch (node.type) {
  27. case 'atrule':
  28. checkDeclOrAtRule(node, node.params, atRuleParamIndex);
  29. break;
  30. case 'decl':
  31. checkDeclOrAtRule(node, node.value, declarationValueIndex);
  32. break;
  33. case 'rule':
  34. checkRule(node);
  35. break;
  36. }
  37. });
  38. /**
  39. * @param {import('postcss').Rule} ruleNode
  40. * @returns {void}
  41. */
  42. function checkRule(ruleNode) {
  43. // Get out quickly if there are no new line
  44. if (!reNewLine.test(ruleNode.selector)) {
  45. return;
  46. }
  47. if (!isStandardSyntaxSelector(ruleNode.selector)) {
  48. return;
  49. }
  50. parseSelector(ruleNode.selector, result, ruleNode, (selectorTree) => {
  51. selectorTree.walkAttributes((attributeNode) => {
  52. const { value, quoteMark } = attributeNode;
  53. if (!value || !reNewLine.test(value)) {
  54. return;
  55. }
  56. const openIndex = [
  57. // length of our attribute
  58. attributeNode.attribute,
  59. // length of our operator , ie '='
  60. attributeNode.operator || '',
  61. ].reduce(
  62. (index, str) => index + str.length,
  63. // index of the start of our attribute node in our source
  64. // plus 1 for the opening quotation mark
  65. attributeNode.sourceIndex + 1,
  66. );
  67. const valueLength = value.length + (quoteMark || '').length * 2;
  68. report({
  69. message: messages.rejected,
  70. node: ruleNode,
  71. index: openIndex,
  72. endIndex: openIndex + valueLength,
  73. result,
  74. ruleName,
  75. });
  76. });
  77. });
  78. }
  79. /**
  80. * @template {import('postcss').AtRule | import('postcss').Declaration} T
  81. * @param {T} node
  82. * @param {string} value
  83. * @param {(node: T) => number} getIndex
  84. * @returns {void}
  85. */
  86. function checkDeclOrAtRule(node, value, getIndex) {
  87. // Get out quickly if there are no new line
  88. if (!reNewLine.test(value)) {
  89. return;
  90. }
  91. valueParser(value).walk((valueNode) => {
  92. if (valueNode.type !== 'string') {
  93. return;
  94. }
  95. if (!reNewLine.test(valueNode.value)) {
  96. return;
  97. }
  98. const nodeIndex = getIndex(node);
  99. report({
  100. message: messages.rejected,
  101. node,
  102. index: nodeIndex + valueNode.sourceIndex,
  103. endIndex: nodeIndex + valueNode.sourceEndIndex,
  104. result,
  105. ruleName,
  106. });
  107. });
  108. }
  109. };
  110. };
  111. rule.ruleName = ruleName;
  112. rule.messages = messages;
  113. rule.meta = meta;
  114. module.exports = rule;