getPostcssResult.mjs 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. import { readFile } from 'node:fs/promises';
  2. import postcss from 'postcss';
  3. import dynamicImport from './utils/dynamicImport.mjs';
  4. import getModulePath from './utils/getModulePath.mjs';
  5. import normalizeFixMode from './utils/normalizeFixMode.mjs';
  6. /** @import {Result, Syntax} from 'postcss' */
  7. /** @import {CustomSyntax, GetPostcssOptions, InternalApi as StylelintInternalApi} from 'stylelint' */
  8. const postcssProcessor = postcss();
  9. /**
  10. * @param {StylelintInternalApi} stylelint
  11. * @param {GetPostcssOptions} options
  12. *
  13. * @returns {Promise<Result>}
  14. */
  15. export default async function getPostcssResult(stylelint, { customSyntax, filePath, code } = {}) {
  16. const cached = filePath ? stylelint._postcssResultCache.get(filePath) : undefined;
  17. if (cached) {
  18. return cached;
  19. }
  20. const fix = normalizeFixMode(stylelint._options.fix);
  21. const syntax = await (customSyntax
  22. ? getCustomSyntax(customSyntax, stylelint._options.configBasedir)
  23. : cssSyntax(fix === 'lax'));
  24. const postcssOptions = {
  25. from: filePath,
  26. syntax,
  27. };
  28. /** @type {string | undefined} */
  29. let getCode;
  30. if (code !== undefined) {
  31. getCode = code;
  32. } else if (filePath) {
  33. getCode = await readFile(filePath, 'utf8');
  34. }
  35. if (getCode === undefined) {
  36. throw new Error('code or filePath required');
  37. }
  38. const postcssResult = await postcssProcessor.process(getCode, postcssOptions).async();
  39. if (filePath) {
  40. stylelint._postcssResultCache.set(filePath, postcssResult);
  41. }
  42. return postcssResult;
  43. }
  44. /**
  45. * @param {CustomSyntax} customSyntax
  46. * @param {string | undefined} basedir
  47. * @returns {Promise<Syntax>}
  48. */
  49. async function getCustomSyntax(customSyntax, basedir) {
  50. if (typeof customSyntax === 'string') {
  51. const customSyntaxLookup = basedir ? getModulePath(basedir, customSyntax) : customSyntax;
  52. let resolved;
  53. try {
  54. resolved = await dynamicImport(customSyntaxLookup);
  55. resolved = resolved.default ?? resolved;
  56. } catch (error) {
  57. if (
  58. error &&
  59. typeof error === 'object' &&
  60. 'code' in error &&
  61. // TODO: Remove 'MODULE_NOT_FOUND' when we drop the CommonJS support.
  62. // See https://nodejs.org/api/errors.html#module_not_found
  63. (error.code === 'MODULE_NOT_FOUND' || error.code === 'ERR_MODULE_NOT_FOUND') &&
  64. 'message' in error &&
  65. typeof error.message === 'string' &&
  66. error.message.includes(customSyntax)
  67. ) {
  68. throw new Error(
  69. `Cannot resolve custom syntax module "${customSyntax}". Check that module "${customSyntax}" is available and spelled correctly.\n\nCaused by: ${error}`,
  70. );
  71. }
  72. throw error;
  73. }
  74. /*
  75. * PostCSS allows for syntaxes that only contain a parser, however,
  76. * it then expects the syntax to be set as the `parse` option.
  77. */
  78. if (!resolved.parse) {
  79. resolved = {
  80. parse: resolved,
  81. stringify: postcss.stringify,
  82. };
  83. }
  84. return resolved;
  85. }
  86. if (typeof customSyntax === 'object') {
  87. if (typeof customSyntax.parse === 'function') {
  88. return { ...customSyntax };
  89. }
  90. throw new TypeError(
  91. 'An object provided to the "customSyntax" option must have a "parse" property. Ensure the "parse" property exists and its value is a function.',
  92. );
  93. }
  94. throw new Error('Custom syntax must be a string or a Syntax object');
  95. }
  96. /**
  97. * @param {boolean} fix
  98. * @returns {Promise<Syntax>}
  99. */
  100. async function cssSyntax(fix) {
  101. const parse = await (fix ? import('postcss-safe-parser').then((m) => m.default) : postcss.parse);
  102. return { parse, stringify: postcss.stringify };
  103. }