UNPKG

5.04 kBJavaScriptView Raw
1'use strict';
2
3const LazyResult = require('postcss/lib/lazy-result').default;
4const path = require('path');
5const { default: postcss } = require('postcss');
6const { promises: fs } = require('fs');
7
8/** @typedef {import('postcss').Result} Result */
9/** @typedef {import('postcss').Syntax} Syntax */
10/** @typedef {import('stylelint').CustomSyntax} CustomSyntax */
11/** @typedef {import('stylelint').GetPostcssOptions} GetPostcssOptions */
12/** @typedef {import('stylelint').InternalApi} StylelintInternalApi */
13
14const postcssProcessor = postcss();
15
16/**
17 * @param {StylelintInternalApi} stylelint
18 * @param {GetPostcssOptions} options
19 *
20 * @returns {Promise<Result>}
21 */
22module.exports = async function getPostcssResult(stylelint, options = {}) {
23 const cached = options.filePath ? stylelint._postcssResultCache.get(options.filePath) : undefined;
24
25 if (cached) {
26 return cached;
27 }
28
29 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} */
50 let getCode;
51
52 if (options.code !== undefined) {
53 getCode = options.code;
54 } else if (options.filePath) {
55 getCode = await fs.readFile(options.filePath, 'utf8');
56 }
57
58 if (getCode === undefined) {
59 return Promise.reject(new Error('code or filePath required'));
60 }
61
62 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 }
69
70 const sourceName = options.code ? options.codeFilename : options.filePath;
71
72 for (const codeProcessor of options.codeProcessors) {
73 getCode = codeProcessor(getCode, sourceName);
74 }
75 }
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;
84};
85
86/**
87 * @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);
96 } catch (error) {
97 if (
98 error &&
99 typeof error === 'object' &&
100 // @ts-expect-error -- TS2571: Object is of type 'unknown'.
101 error.code === 'MODULE_NOT_FOUND' &&
102 // @ts-expect-error -- TS2571: Object is of type 'unknown'.
103 error.message.includes(customSyntax)
104 ) {
105 throw new Error(
106 `Cannot resolve custom syntax module "${customSyntax}". Check that module "${customSyntax}" is available and spelled correctly.\n\nCaused by: ${error}`,
107 );
108 }
109
110 throw error;
111 }
112
113 /*
114 * PostCSS allows for syntaxes that only contain a parser, however,
115 * it then expects the syntax to be set as the `parse` option.
116 */
117 if (!resolved.parse) {
118 resolved = {
119 parse: resolved,
120 stringify: postcss.stringify,
121 };
122 }
123
124 return resolved;
125 }
126
127 if (typeof customSyntax === 'object') {
128 if (typeof customSyntax.parse === 'function') {
129 resolved = { ...customSyntax };
130 } else {
131 throw new TypeError(
132 `An object provided to the "customSyntax" option must have a "parse" property. Ensure the "parse" property exists and its value is a function.`,
133 );
134 }
135
136 return resolved;
137 }
138
139 throw new Error(`Custom syntax must be a string or a Syntax object`);
140}
141
142/** @type {{ [key: string]: string }} */
143const previouslyInferredExtensions = {
144 html: 'postcss-html',
145 js: '@stylelint/postcss-css-in-js',
146 jsx: '@stylelint/postcss-css-in-js',
147 less: 'postcss-less',
148 md: 'postcss-markdown',
149 sass: 'postcss-sass',
150 sss: 'sugarss',
151 scss: 'postcss-scss',
152 svelte: 'postcss-html',
153 ts: '@stylelint/postcss-css-in-js',
154 tsx: '@stylelint/postcss-css-in-js',
155 vue: 'postcss-html',
156 xml: 'postcss-html',
157 xst: 'postcss-html',
158};
159
160/**
161 * @param {StylelintInternalApi} stylelint
162 * @param {string|undefined} filePath
163 * @returns {Syntax}
164 */
165function cssSyntax(stylelint, filePath) {
166 const fileExtension = filePath ? path.extname(filePath).slice(1).toLowerCase() : '';
167 const extensions = ['css', 'pcss', 'postcss'];
168
169 if (previouslyInferredExtensions[fileExtension]) {
170 console.warn(
171 `${filePath}: When linting something other than CSS, you should install an appropriate syntax, e.g. "${previouslyInferredExtensions[fileExtension]}", and use the "customSyntax" option`,
172 );
173 }
174
175 return {
176 parse:
177 stylelint._options.fix && extensions.includes(fileExtension)
178 ? require('postcss-safe-parser')
179 : postcss.parse,
180 stringify: postcss.stringify,
181 };
182}