css-import-lexer.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. "use strict";
  2. /**
  3. * @license
  4. * Copyright Google LLC All Rights Reserved.
  5. *
  6. * Use of this source code is governed by an MIT-style license that can be
  7. * found in the LICENSE file at https://angular.dev/license
  8. */
  9. Object.defineProperty(exports, "__esModule", { value: true });
  10. exports.findImports = findImports;
  11. /**
  12. * Determines if a unicode code point is a CSS whitespace character.
  13. * @param code The unicode code point to test.
  14. * @returns true, if the code point is CSS whitespace; false, otherwise.
  15. */
  16. function isWhitespace(code) {
  17. // Based on https://www.w3.org/TR/css-syntax-3/#whitespace
  18. switch (code) {
  19. case 0x0009: // tab
  20. case 0x0020: // space
  21. case 0x000a: // line feed
  22. case 0x000c: // form feed
  23. case 0x000d: // carriage return
  24. return true;
  25. default:
  26. return false;
  27. }
  28. }
  29. /**
  30. * Scans a CSS or Sass file and locates all valid import/use directive values as defined by the
  31. * syntax specification.
  32. * @param contents A string containing a CSS or Sass file to scan.
  33. * @returns An iterable that yields each CSS directive value found.
  34. */
  35. function* findImports(contents, sass) {
  36. yield* find(contents, '@import ');
  37. if (sass) {
  38. for (const result of find(contents, '@use ')) {
  39. yield { ...result, fromUse: true };
  40. }
  41. }
  42. }
  43. /**
  44. * Scans a CSS or Sass file and locates all valid function/directive values as defined by the
  45. * syntax specification.
  46. * @param contents A string containing a CSS or Sass file to scan.
  47. * @param prefix The prefix to start a valid segment.
  48. * @returns An iterable that yields each CSS url function value found.
  49. */
  50. function* find(contents, prefix) {
  51. let pos = 0;
  52. let width = 1;
  53. let current = -1;
  54. const next = () => {
  55. pos += width;
  56. current = contents.codePointAt(pos) ?? -1;
  57. width = current > 0xffff ? 2 : 1;
  58. return current;
  59. };
  60. // Based on https://www.w3.org/TR/css-syntax-3/#consume-ident-like-token
  61. while ((pos = contents.indexOf(prefix, pos)) !== -1) {
  62. // Set to position of the last character in prefix
  63. pos += prefix.length - 1;
  64. width = 1;
  65. // Consume all leading whitespace
  66. while (isWhitespace(next())) {
  67. /* empty */
  68. }
  69. // Initialize URL state
  70. const url = { start: pos, end: -1, specifier: '' };
  71. let complete = false;
  72. // If " or ', then consume the value as a string
  73. if (current === 0x0022 || current === 0x0027) {
  74. const ending = current;
  75. // Based on https://www.w3.org/TR/css-syntax-3/#consume-string-token
  76. while (!complete) {
  77. switch (next()) {
  78. case -1: // EOF
  79. return;
  80. case 0x000a: // line feed
  81. case 0x000c: // form feed
  82. case 0x000d: // carriage return
  83. // Invalid
  84. complete = true;
  85. break;
  86. case 0x005c: // \ -- character escape
  87. // If not EOF or newline, add the character after the escape
  88. switch (next()) {
  89. case -1:
  90. return;
  91. case 0x000a: // line feed
  92. case 0x000c: // form feed
  93. case 0x000d: // carriage return
  94. // Skip when inside a string
  95. break;
  96. default:
  97. // TODO: Handle hex escape codes
  98. url.specifier += String.fromCodePoint(current);
  99. break;
  100. }
  101. break;
  102. case ending:
  103. // Full string position should include the quotes for replacement
  104. url.end = pos + 1;
  105. complete = true;
  106. yield url;
  107. break;
  108. default:
  109. url.specifier += String.fromCodePoint(current);
  110. break;
  111. }
  112. }
  113. next();
  114. continue;
  115. }
  116. }
  117. }