UNPKG

6.68 kBJavaScriptView Raw
1/* @flow */
2"use strict";
3
4const _ = require("lodash");
5const assignDisabledRanges = require("./assignDisabledRanges");
6const configurationError = require("./utils/configurationError");
7const getOsEol = require("./utils/getOsEol");
8const path = require("path");
9const requireRule = require("./requireRule");
10const rulesOrder = require("./rules");
11
12/*:: type postcssResultT = {
13 processor: {
14 version: string,
15 plugins: Array<Object>,
16 },
17 messages: Array<any>,
18 root: {
19 raws: {
20 semicolon?: boolean,
21 after: string,
22 },
23 type: string,
24 nodes: Array<Object>,
25 source: {
26 input: Object,
27 start: Object,
28 },
29 lastEach?: number,
30 indexes?: Object,
31 toResult: Function,
32 },
33 opts: {
34 configFile?: string,
35 defaultSeverity?: string,
36 ignoreFiles?: Array<string>,
37 rules?: Object,
38 from?: ?string,
39 syntax?: ?{
40 parse: Function,
41 stringify: Function,
42 },
43 },
44 css: ?string,
45 map: ?any,
46 lastPlugin?: {
47 postcssPlugin: string,
48 postcssVersion: string,
49 },
50 stylelint: {
51 customMessages: Object,
52 ignored?: boolean,
53 ignoreDisables?: boolean,
54 ruleSeverities: Object,
55 quiet?: boolean,
56 },
57}*/
58
59/*:: type emptyPostcssResultT = {
60 root: {
61 source: {
62 input: {
63 file: ?string,
64 },
65 },
66 },
67 messages: Array<any>,
68 stylelint: Object,
69}*/
70
71// Run stylelint on a PostCSS Result, either one that is provided
72// or one that we create
73module.exports = function lintSource(
74 stylelint /*: stylelint$internalApi*/,
75 options /*: {
76 code?: string,
77 codeFilename?: string, // Must be an absolute file path
78 filePath?: string, // Must be an absolute file path
79 existingPostcssResult?: Object,
80 }*/
81) /*: Promise<Object>*/ {
82 options = options || {};
83
84 if (
85 !options.filePath &&
86 options.code === undefined &&
87 !options.existingPostcssResult
88 ) {
89 return Promise.reject(
90 new Error("You must provide filePath, code, or existingPostcssResult")
91 );
92 }
93
94 const isCodeNotFile = options.code !== undefined;
95
96 const inputFilePath = isCodeNotFile ? options.codeFilename : options.filePath;
97
98 if (inputFilePath !== undefined && !path.isAbsolute(inputFilePath)) {
99 if (isCodeNotFile) {
100 return Promise.reject(new Error("codeFilename must be an absolute path"));
101 } else {
102 return Promise.reject(new Error("filePath must be an absolute path"));
103 }
104 }
105
106 const getIsIgnored = stylelint.isPathIgnored(inputFilePath).catch(err => {
107 if (isCodeNotFile && err.code === "ENOENT") return false;
108
109 throw err;
110 });
111
112 return getIsIgnored.then(isIgnored => {
113 if (isIgnored) {
114 const postcssResult /*: Object*/ =
115 options.existingPostcssResult ||
116 createEmptyPostcssResult(inputFilePath);
117
118 postcssResult.stylelint = postcssResult.stylelint || {};
119 postcssResult.stylelint.ignored = true;
120 postcssResult.standaloneIgnored = true; // TODO: remove need for this
121
122 return postcssResult;
123 }
124
125 const configSearchPath = stylelint._options.configFile || inputFilePath;
126
127 const getConfig = stylelint
128 .getConfigForFile(configSearchPath)
129 .catch(err => {
130 if (isCodeNotFile && err.code === "ENOENT")
131 return stylelint.getConfigForFile(process.cwd());
132
133 throw err;
134 });
135
136 return getConfig.then(result => {
137 const config /*: stylelint$config*/ = result.config;
138 const existingPostcssResult = options.existingPostcssResult;
139
140 if (existingPostcssResult) {
141 return lintPostcssResult(stylelint, existingPostcssResult, config).then(
142 () => existingPostcssResult
143 );
144 }
145
146 return stylelint
147 ._getPostcssResult({
148 code: options.code,
149 codeFilename: options.codeFilename,
150 filePath: inputFilePath,
151 codeProcessors: config.codeProcessors
152 })
153 .then(postcssResult => {
154 return lintPostcssResult(stylelint, postcssResult, config).then(
155 () => postcssResult
156 );
157 });
158 });
159 });
160};
161
162function lintPostcssResult(
163 stylelint /*: stylelint$internalApi*/,
164 postcssResult /*: postcssResultT*/,
165 config /*: stylelint$config*/
166) /*: Promise<Array<*>>*/ {
167 postcssResult.stylelint = postcssResult.stylelint || {};
168 postcssResult.stylelint.ruleSeverities = {};
169 postcssResult.stylelint.customMessages = {};
170 postcssResult.stylelint.quiet = config.quiet;
171
172 const postcssDoc = postcssResult.root;
173 const newlineMatch = postcssDoc.source.input.css.match(/\r?\n/);
174 const newline = newlineMatch ? newlineMatch[0] : getOsEol();
175
176 assignDisabledRanges(postcssDoc, postcssResult);
177
178 if (
179 stylelint._options.reportNeedlessDisables ||
180 stylelint._options.ignoreDisables
181 ) {
182 postcssResult.stylelint.ignoreDisables = true;
183 }
184
185 const postcssRoots =
186 postcssDoc.constructor.name === "Document"
187 ? postcssDoc.nodes
188 : [postcssDoc];
189
190 // Promises for the rules. Although the rule code runs synchronously now,
191 // the use of Promises makes it compatible with the possibility of async
192 // rules down the line.
193 const performRules = [];
194
195 const rules = config.rules
196 ? Object.keys(config.rules).sort(
197 (a, b) => rulesOrder.indexOf(a) - rulesOrder.indexOf(b)
198 )
199 : [];
200
201 rules.forEach(ruleName => {
202 const ruleFunction =
203 requireRule(ruleName) || _.get(config, ["pluginFunctions", ruleName]);
204
205 if (ruleFunction === undefined) {
206 throw configurationError(`Undefined rule ${ruleName}`);
207 }
208
209 const ruleSettings = _.get(config, ["rules", ruleName]);
210
211 if (ruleSettings === null || ruleSettings[0] === null) {
212 return;
213 }
214
215 const primaryOption = ruleSettings[0];
216 const secondaryOptions = ruleSettings[1];
217
218 // Log the rule's severity in the PostCSS result
219 const defaultSeverity = config.defaultSeverity || "error";
220
221 postcssResult.stylelint.ruleSeverities[ruleName] = _.get(
222 secondaryOptions,
223 "severity",
224 defaultSeverity
225 );
226 postcssResult.stylelint.customMessages[ruleName] = _.get(
227 secondaryOptions,
228 "message"
229 );
230
231 performRules.push(
232 Promise.all(
233 postcssRoots.map(postcssRoot =>
234 ruleFunction(primaryOption, secondaryOptions, {
235 fix: stylelint._options.fix,
236 newline
237 })(postcssRoot, postcssResult)
238 )
239 )
240 );
241 });
242
243 return Promise.all(performRules);
244}
245
246function createEmptyPostcssResult(
247 filePath /*:: ?: string*/
248) /*: emptyPostcssResultT*/ {
249 return {
250 root: {
251 source: {
252 input: { file: filePath }
253 }
254 },
255 messages: [],
256 stylelint: { stylelintError: null }
257 };
258}