getPostcssResult.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. 'use strict';
  2. const LazyResult = require('postcss/lib/lazy-result').default;
  3. const path = require('path');
  4. const { default: postcss } = require('postcss');
  5. const { promises: fs } = require('fs');
  6. /** @typedef {import('postcss').Result} Result */
  7. /** @typedef {import('postcss').Syntax} Syntax */
  8. /** @typedef {import('stylelint').CustomSyntax} CustomSyntax */
  9. /** @typedef {import('stylelint').GetPostcssOptions} GetPostcssOptions */
  10. /** @typedef {import('stylelint').InternalApi} StylelintInternalApi */
  11. const postcssProcessor = postcss();
  12. /**
  13. * @param {StylelintInternalApi} stylelint
  14. * @param {GetPostcssOptions} options
  15. *
  16. * @returns {Promise<Result>}
  17. */
  18. module.exports = async function getPostcssResult(stylelint, options = {}) {
  19. const cached = options.filePath ? stylelint._postcssResultCache.get(options.filePath) : undefined;
  20. if (cached) {
  21. return cached;
  22. }
  23. if (stylelint._options.syntax) {
  24. let error = 'The "syntax" option is no longer available. ';
  25. error +=
  26. stylelint._options.syntax === 'css'
  27. ? 'You can remove the "--syntax" CLI flag as stylelint will now parse files as CSS by default'
  28. : `You should install an appropriate syntax, e.g. postcss-scss, and use the "customSyntax" option`;
  29. return Promise.reject(new Error(error));
  30. }
  31. const syntax = options.customSyntax
  32. ? getCustomSyntax(options.customSyntax)
  33. : cssSyntax(stylelint, options.filePath);
  34. const postcssOptions = {
  35. from: options.filePath,
  36. syntax,
  37. };
  38. /** @type {string | undefined} */
  39. let getCode;
  40. if (options.code !== undefined) {
  41. getCode = options.code;
  42. } else if (options.filePath) {
  43. getCode = await fs.readFile(options.filePath, 'utf8');
  44. }
  45. if (getCode === undefined) {
  46. return Promise.reject(new Error('code or filePath required'));
  47. }
  48. if (options.codeProcessors && options.codeProcessors.length) {
  49. if (stylelint._options.fix) {
  50. console.warn(
  51. 'Autofix is incompatible with processors and will be disabled. Are you sure you need a processor?',
  52. );
  53. stylelint._options.fix = false;
  54. }
  55. const sourceName = options.code ? options.codeFilename : options.filePath;
  56. for (const codeProcessor of options.codeProcessors) {
  57. getCode = codeProcessor(getCode, sourceName);
  58. }
  59. }
  60. const postcssResult = await new LazyResult(postcssProcessor, getCode, postcssOptions);
  61. if (options.filePath) {
  62. stylelint._postcssResultCache.set(options.filePath, postcssResult);
  63. }
  64. return postcssResult;
  65. };
  66. /**
  67. * @param {CustomSyntax} customSyntax
  68. * @returns {Syntax}
  69. */
  70. function getCustomSyntax(customSyntax) {
  71. let resolved;
  72. if (typeof customSyntax === 'string') {
  73. try {
  74. resolved = require(customSyntax);
  75. } catch (error) {
  76. if (
  77. error &&
  78. typeof error === 'object' &&
  79. // @ts-expect-error -- TS2571: Object is of type 'unknown'.
  80. error.code === 'MODULE_NOT_FOUND' &&
  81. // @ts-expect-error -- TS2571: Object is of type 'unknown'.
  82. error.message.includes(customSyntax)
  83. ) {
  84. throw new Error(
  85. `Cannot resolve custom syntax module "${customSyntax}". Check that module "${customSyntax}" is available and spelled correctly.\n\nCaused by: ${error}`,
  86. );
  87. }
  88. throw error;
  89. }
  90. /*
  91. * PostCSS allows for syntaxes that only contain a parser, however,
  92. * it then expects the syntax to be set as the `parse` option.
  93. */
  94. if (!resolved.parse) {
  95. resolved = {
  96. parse: resolved,
  97. stringify: postcss.stringify,
  98. };
  99. }
  100. return resolved;
  101. }
  102. if (typeof customSyntax === 'object') {
  103. if (typeof customSyntax.parse === 'function') {
  104. resolved = { ...customSyntax };
  105. } else {
  106. throw new TypeError(
  107. `An object provided to the "customSyntax" option must have a "parse" property. Ensure the "parse" property exists and its value is a function.`,
  108. );
  109. }
  110. return resolved;
  111. }
  112. throw new Error(`Custom syntax must be a string or a Syntax object`);
  113. }
  114. /** @type {{ [key: string]: string }} */
  115. const previouslyInferredExtensions = {
  116. html: 'postcss-html',
  117. js: '@stylelint/postcss-css-in-js',
  118. jsx: '@stylelint/postcss-css-in-js',
  119. less: 'postcss-less',
  120. md: 'postcss-markdown',
  121. sass: 'postcss-sass',
  122. sss: 'sugarss',
  123. scss: 'postcss-scss',
  124. svelte: 'postcss-html',
  125. ts: '@stylelint/postcss-css-in-js',
  126. tsx: '@stylelint/postcss-css-in-js',
  127. vue: 'postcss-html',
  128. xml: 'postcss-html',
  129. xst: 'postcss-html',
  130. };
  131. /**
  132. * @param {StylelintInternalApi} stylelint
  133. * @param {string|undefined} filePath
  134. * @returns {Syntax}
  135. */
  136. function cssSyntax(stylelint, filePath) {
  137. const fileExtension = filePath ? path.extname(filePath).slice(1).toLowerCase() : '';
  138. const extensions = ['css', 'pcss', 'postcss'];
  139. if (previouslyInferredExtensions[fileExtension]) {
  140. console.warn(
  141. `${filePath}: When linting something other than CSS, you should install an appropriate syntax, e.g. "${previouslyInferredExtensions[fileExtension]}", and use the "customSyntax" option`,
  142. );
  143. }
  144. return {
  145. parse:
  146. stylelint._options.fix && extensions.includes(fileExtension)
  147. ? require('postcss-safe-parser')
  148. : postcss.parse,
  149. stringify: postcss.stringify,
  150. };
  151. }