getPostcssResult.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  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. const getModulePath = require('./utils/getModulePath');
  7. /** @typedef {import('postcss').Result} Result */
  8. /** @typedef {import('postcss').Syntax} Syntax */
  9. /** @typedef {import('stylelint').CustomSyntax} CustomSyntax */
  10. /** @typedef {import('stylelint').GetPostcssOptions} GetPostcssOptions */
  11. /** @typedef {import('stylelint').InternalApi} StylelintInternalApi */
  12. const postcssProcessor = postcss();
  13. /**
  14. * @param {StylelintInternalApi} stylelint
  15. * @param {GetPostcssOptions} options
  16. *
  17. * @returns {Promise<Result>}
  18. */
  19. module.exports = async function getPostcssResult(stylelint, options = {}) {
  20. const cached = options.filePath ? stylelint._postcssResultCache.get(options.filePath) : undefined;
  21. if (cached) {
  22. return cached;
  23. }
  24. const syntax = options.customSyntax
  25. ? getCustomSyntax(options.customSyntax, stylelint._options.configBasedir)
  26. : cssSyntax(stylelint, options.filePath);
  27. const postcssOptions = {
  28. from: options.filePath,
  29. syntax,
  30. };
  31. /** @type {string | undefined} */
  32. let getCode;
  33. if (options.code !== undefined) {
  34. getCode = options.code;
  35. } else if (options.filePath) {
  36. getCode = await fs.readFile(options.filePath, 'utf8');
  37. }
  38. if (getCode === undefined) {
  39. return Promise.reject(new Error('code or filePath required'));
  40. }
  41. const postcssResult = await new LazyResult(postcssProcessor, getCode, postcssOptions);
  42. if (options.filePath) {
  43. stylelint._postcssResultCache.set(options.filePath, postcssResult);
  44. }
  45. return postcssResult;
  46. };
  47. /**
  48. * @param {CustomSyntax} customSyntax
  49. * @param {string | undefined} basedir
  50. * @returns {Syntax}
  51. */
  52. function getCustomSyntax(customSyntax, basedir) {
  53. if (typeof customSyntax === 'string') {
  54. const customSyntaxLookup = basedir ? getModulePath(basedir, customSyntax) : customSyntax;
  55. let resolved;
  56. try {
  57. resolved = require(customSyntaxLookup);
  58. } catch (error) {
  59. if (
  60. error &&
  61. typeof error === 'object' &&
  62. 'code' in error &&
  63. error.code === '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 {StylelintInternalApi} stylelint
  98. * @param {string|undefined} filePath
  99. * @returns {Syntax}
  100. */
  101. function cssSyntax(stylelint, filePath) {
  102. const fileExtension = filePath ? path.extname(filePath).slice(1).toLowerCase() : '';
  103. const extensions = ['css', 'pcss', 'postcss'];
  104. if (fileExtension && !extensions.includes(fileExtension)) {
  105. console.warn(
  106. `${filePath}: you should use the "customSyntax" option when linting something other than CSS`,
  107. );
  108. }
  109. return {
  110. parse:
  111. stylelint._options.fix && extensions.includes(fileExtension)
  112. ? require('postcss-safe-parser')
  113. : postcss.parse,
  114. stringify: postcss.stringify,
  115. };
  116. }