blob: 273dae48d661c8ebff12b386b9730f1992770293 [file] [log] [blame]
Mathias Bynens79e2cf02020-05-29 16:46:17 +02001'use strict';
2
Tim van der Lippe16b82282021-11-08 13:50:26 +00003const LazyResult = require('postcss/lib/lazy-result').default;
4const path = require('path');
5const { default: postcss } = require('postcss');
6const { promises: fs } = require('fs');
Mathias Bynens79e2cf02020-05-29 16:46:17 +02007
Tim van der Lippeefb716a2020-12-01 12:54:04 +00008/** @typedef {import('postcss').Result} Result */
9/** @typedef {import('postcss').Syntax} Syntax */
10/** @typedef {import('stylelint').CustomSyntax} CustomSyntax */
11/** @typedef {import('stylelint').GetPostcssOptions} GetPostcssOptions */
Tim van der Lippe16b82282021-11-08 13:50:26 +000012/** @typedef {import('stylelint').InternalApi} StylelintInternalApi */
Mathias Bynens79e2cf02020-05-29 16:46:17 +020013
14const postcssProcessor = postcss();
15
16/**
17 * @param {StylelintInternalApi} stylelint
Tim van der Lippeefb716a2020-12-01 12:54:04 +000018 * @param {GetPostcssOptions} options
Mathias Bynens79e2cf02020-05-29 16:46:17 +020019 *
Tim van der Lippeefb716a2020-12-01 12:54:04 +000020 * @returns {Promise<Result>}
Mathias Bynens79e2cf02020-05-29 16:46:17 +020021 */
Tim van der Lippe16b82282021-11-08 13:50:26 +000022module.exports = async function getPostcssResult(stylelint, options = {}) {
Mathias Bynens79e2cf02020-05-29 16:46:17 +020023 const cached = options.filePath ? stylelint._postcssResultCache.get(options.filePath) : undefined;
24
Tim van der Lippe16b82282021-11-08 13:50:26 +000025 if (cached) {
26 return cached;
27 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +020028
Tim van der Lippe16b82282021-11-08 13:50:26 +000029 if (stylelint._options.syntax) {
30 let error = 'The "syntax" option is no longer available. ';
31
32 error +=
33 stylelint._options.syntax === 'css'
34 ? 'You can remove the "--syntax" CLI flag as stylelint will now parse files as CSS by default'
35 : `You should install an appropriate syntax, e.g. postcss-scss, and use the "customSyntax" option`;
36
37 return Promise.reject(new Error(error));
38 }
39
40 const syntax = options.customSyntax
41 ? getCustomSyntax(options.customSyntax)
42 : cssSyntax(stylelint, options.filePath);
43
44 const postcssOptions = {
45 from: options.filePath,
46 syntax,
47 };
48
49 /** @type {string | undefined} */
Mathias Bynens79e2cf02020-05-29 16:46:17 +020050 let getCode;
51
52 if (options.code !== undefined) {
Tim van der Lippe16b82282021-11-08 13:50:26 +000053 getCode = options.code;
Mathias Bynens79e2cf02020-05-29 16:46:17 +020054 } else if (options.filePath) {
Tim van der Lippe16b82282021-11-08 13:50:26 +000055 getCode = await fs.readFile(options.filePath, 'utf8');
Mathias Bynens79e2cf02020-05-29 16:46:17 +020056 }
57
Tim van der Lippe16b82282021-11-08 13:50:26 +000058 if (getCode === undefined) {
59 return Promise.reject(new Error('code or filePath required'));
Mathias Bynens79e2cf02020-05-29 16:46:17 +020060 }
61
Tim van der Lippe16b82282021-11-08 13:50:26 +000062 if (options.codeProcessors && options.codeProcessors.length) {
63 if (stylelint._options.fix) {
64 console.warn(
65 'Autofix is incompatible with processors and will be disabled. Are you sure you need a processor?',
66 );
67 stylelint._options.fix = false;
68 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +020069
Tim van der Lippe16b82282021-11-08 13:50:26 +000070 const sourceName = options.code ? options.codeFilename : options.filePath;
Mathias Bynens79e2cf02020-05-29 16:46:17 +020071
Tim van der Lippe16b82282021-11-08 13:50:26 +000072 options.codeProcessors.forEach((codeProcessor) => {
73 getCode = codeProcessor(getCode, sourceName);
Mathias Bynens79e2cf02020-05-29 16:46:17 +020074 });
Tim van der Lippe16b82282021-11-08 13:50:26 +000075 }
76
77 const postcssResult = await new LazyResult(postcssProcessor, getCode, postcssOptions);
78
79 if (options.filePath) {
80 stylelint._postcssResultCache.set(options.filePath, postcssResult);
81 }
82
83 return postcssResult;
Mathias Bynens79e2cf02020-05-29 16:46:17 +020084};
85
86/**
Tim van der Lippeefb716a2020-12-01 12:54:04 +000087 * @param {CustomSyntax} customSyntax
88 * @returns {Syntax}
89 */
90function getCustomSyntax(customSyntax) {
91 let resolved;
92
93 if (typeof customSyntax === 'string') {
94 try {
95 resolved = require(customSyntax);
Tim van der Lippe16b82282021-11-08 13:50:26 +000096 } catch (error) {
97 // @ts-expect-error -- TS2571: Object is of type 'unknown'.
98 if (error && typeof error === 'object' && error.code === 'MODULE_NOT_FOUND') {
99 throw new Error(
100 `Cannot resolve custom syntax module "${customSyntax}". Check that module "${customSyntax}" is available and spelled correctly.`,
101 );
102 }
103
104 throw error;
Tim van der Lippeefb716a2020-12-01 12:54:04 +0000105 }
106
107 /*
108 * PostCSS allows for syntaxes that only contain a parser, however,
109 * it then expects the syntax to be set as the `parse` option.
110 */
111 if (!resolved.parse) {
112 resolved = {
113 parse: resolved,
114 stringify: postcss.stringify,
115 };
116 }
117
118 return resolved;
119 }
120
121 if (typeof customSyntax === 'object') {
122 if (typeof customSyntax.parse === 'function') {
123 resolved = { ...customSyntax };
124 } else {
Tim van der Lippecc71b282021-02-12 15:51:14 +0000125 throw new TypeError(
Tim van der Lippeefb716a2020-12-01 12:54:04 +0000126 `An object provided to the "customSyntax" option must have a "parse" property. Ensure the "parse" property exists and its value is a function.`,
127 );
128 }
129
130 return resolved;
131 }
132
133 throw new Error(`Custom syntax must be a string or a Syntax object`);
134}
135
Tim van der Lippe16b82282021-11-08 13:50:26 +0000136/** @type {{ [key: string]: string }} */
137const previouslyInferredExtensions = {
138 html: 'postcss-html',
139 js: '@stylelint/postcss-css-in-js',
140 jsx: '@stylelint/postcss-css-in-js',
141 less: 'postcss-less',
142 md: 'postcss-markdown',
143 sass: 'postcss-sass',
144 sss: 'sugarss',
145 scss: 'postcss-scss',
146 svelte: 'postcss-html',
147 ts: '@stylelint/postcss-css-in-js',
148 tsx: '@stylelint/postcss-css-in-js',
149 vue: 'postcss-html',
150 xml: 'postcss-html',
151 xst: 'postcss-html',
152};
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200153
154/**
155 * @param {StylelintInternalApi} stylelint
Tim van der Lippe16b82282021-11-08 13:50:26 +0000156 * @param {string|undefined} filePath
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200157 * @returns {Syntax}
158 */
Tim van der Lippe16b82282021-11-08 13:50:26 +0000159function cssSyntax(stylelint, filePath) {
160 const fileExtension = filePath ? path.extname(filePath).slice(1).toLowerCase() : '';
161 const extensions = ['css', 'pcss', 'postcss'];
162
163 if (previouslyInferredExtensions[fileExtension]) {
164 console.warn(
165 `${filePath}: When linting something other than CSS, you should install an appropriate syntax, e.g. "${previouslyInferredExtensions[fileExtension]}", and use the "customSyntax" option`,
166 );
167 }
168
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200169 return {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000170 parse:
171 stylelint._options.fix && extensions.includes(fileExtension)
172 ? require('postcss-safe-parser')
173 : postcss.parse,
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200174 stringify: postcss.stringify,
175 };
176}