UNPKG

4.75 kBJavaScriptView Raw
1'use strict';
2
3const fs = require('fs');
4const LazyResult = require('postcss/lib/lazy-result');
5const postcss = require('postcss');
6const syntaxes = require('./syntaxes');
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').StylelintInternalApi} StylelintInternalApi */
13
14const postcssProcessor = postcss();
15
16/**
17 * @param {StylelintInternalApi} stylelint
18 * @param {GetPostcssOptions} options
19 *
20 * @returns {Promise<Result>}
21 */
22module.exports = function (stylelint, options = {}) {
23 const cached = options.filePath ? stylelint._postcssResultCache.get(options.filePath) : undefined;
24
25 if (cached) return Promise.resolve(cached);
26
27 /** @type {Promise<string> | undefined} */
28 let getCode;
29
30 if (options.code !== undefined) {
31 getCode = Promise.resolve(options.code);
32 } else if (options.filePath) {
33 getCode = readFile(options.filePath);
34 }
35
36 if (!getCode) {
37 throw new Error('code or filePath required');
38 }
39
40 return getCode
41 .then((code) => {
42 /** @type {Syntax | null} */
43 let syntax = null;
44
45 if (stylelint._options.customSyntax) {
46 syntax = getCustomSyntax(stylelint._options.customSyntax);
47 } else if (stylelint._options.syntax) {
48 if (stylelint._options.syntax === 'css') {
49 syntax = cssSyntax(stylelint);
50 } else {
51 const keys = Object.keys(syntaxes);
52
53 if (!keys.includes(stylelint._options.syntax)) {
54 throw new Error(
55 `You must use a valid syntax option, either: css, ${keys
56 .slice(0, -1)
57 .join(', ')} or ${keys.slice(-1)}`,
58 );
59 }
60
61 syntax = syntaxes[stylelint._options.syntax];
62 }
63 } else if (!(options.codeProcessors && options.codeProcessors.length)) {
64 const autoSyntax = require('postcss-syntax');
65
66 // TODO: investigate why lazy import HTML syntax causes
67 // JS files with the word "html" to throw TypeError
68 // https://github.com/stylelint/stylelint/issues/4793
69 const { html, ...rest } = syntaxes;
70
71 syntax = autoSyntax({
72 css: cssSyntax(stylelint),
73 jsx: syntaxes['css-in-js'],
74 ...rest,
75 });
76 }
77
78 const postcssOptions = {
79 from: options.filePath,
80 syntax,
81 };
82
83 const source = options.code ? options.codeFilename : options.filePath;
84 let preProcessedCode = code;
85
86 if (options.codeProcessors && options.codeProcessors.length) {
87 if (stylelint._options.fix) {
88 // eslint-disable-next-line no-console
89 console.warn(
90 'Autofix is incompatible with processors and will be disabled. Are you sure you need a processor?',
91 );
92 stylelint._options.fix = false;
93 }
94
95 options.codeProcessors.forEach((codeProcessor) => {
96 preProcessedCode = codeProcessor(preProcessedCode, source);
97 });
98 }
99
100 const result = new LazyResult(postcssProcessor, preProcessedCode, postcssOptions);
101
102 return result;
103 })
104 .then((postcssResult) => {
105 if (options.filePath) {
106 stylelint._postcssResultCache.set(options.filePath, postcssResult);
107 }
108
109 return postcssResult;
110 });
111};
112
113/**
114 * @param {CustomSyntax} customSyntax
115 * @returns {Syntax}
116 */
117function getCustomSyntax(customSyntax) {
118 let resolved;
119
120 if (typeof customSyntax === 'string') {
121 try {
122 resolved = require(customSyntax);
123 } catch (error) {
124 throw new Error(
125 `Cannot resolve custom syntax module ${customSyntax}. Check that module ${customSyntax} is available and spelled correctly.`,
126 );
127 }
128
129 /*
130 * PostCSS allows for syntaxes that only contain a parser, however,
131 * it then expects the syntax to be set as the `parse` option.
132 */
133 if (!resolved.parse) {
134 resolved = {
135 parse: resolved,
136 stringify: postcss.stringify,
137 };
138 }
139
140 return resolved;
141 }
142
143 if (typeof customSyntax === 'object') {
144 if (typeof customSyntax.parse === 'function') {
145 resolved = { ...customSyntax };
146 } else {
147 throw new Error(
148 `An object provided to the "customSyntax" option must have a "parse" property. Ensure the "parse" property exists and its value is a function.`,
149 );
150 }
151
152 return resolved;
153 }
154
155 throw new Error(`Custom syntax must be a string or a Syntax object`);
156}
157
158/**
159 * @param {string} filePath
160 * @returns {Promise<string>}
161 */
162function readFile(filePath) {
163 return new Promise((resolve, reject) => {
164 fs.readFile(filePath, 'utf8', (err, content) => {
165 if (err) {
166 return reject(err);
167 }
168
169 resolve(content);
170 });
171 });
172}
173
174/**
175 * @param {StylelintInternalApi} stylelint
176 * @returns {Syntax}
177 */
178function cssSyntax(stylelint) {
179 return {
180 parse: stylelint._options.fix ? require('postcss-safe-parser') : postcss.parse,
181 stringify: postcss.stringify,
182 };
183}