.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / stylelint / lib / standalone.js
1 /* @flow */
2 "use strict";
3 const createStylelint = require("./createStylelint");
4 const debug = require("debug")("stylelint:standalone");
5 const FileCache = require("./utils/FileCache");
6 const formatters /*: Object*/ = require("./formatters");
7 const fs = require("fs");
8 const globby /*: Function*/ = require("globby");
9 const hash = require("./utils/hash");
10 const ignore = require("ignore");
11 const needlessDisables /*: Function*/ = require("./needlessDisables");
12 const path = require("path");
13 const pify = require("pify");
14 const pkg = require("../package.json");
15
16 const DEFAULT_IGNORE_FILENAME = ".stylelintignore";
17 const FILE_NOT_FOUND_ERROR_CODE = "ENOENT";
18 const ALWAYS_IGNORED_GLOBS = ["**/node_modules/**", "**/bower_components/**"];
19
20 /*::type CssSyntaxErrorT = {
21   column: number;
22   file?: string;
23   input: {
24     column: number;
25     file?: string;
26     line: number;
27     source: string;
28   };
29   line: number;
30   message: string;
31   name: string;
32   reason: string;
33   source: string;
34 }*/
35
36 module.exports = function(
37   options /*: stylelint$standaloneOptions */
38 ) /*: Promise<stylelint$standaloneReturnValue>*/ {
39   const cacheLocation = options.cacheLocation;
40   const code = options.code;
41   const codeFilename = options.codeFilename;
42   const config = options.config;
43   const configBasedir = options.configBasedir;
44   const configFile = options.configFile;
45   const configOverrides = options.configOverrides;
46   const customSyntax = options.customSyntax;
47   const files = options.files;
48   const fix = options.fix;
49   const formatter = options.formatter;
50   const ignoreDisables = options.ignoreDisables;
51   const reportNeedlessDisables = options.reportNeedlessDisables;
52   const syntax = options.syntax;
53   const useCache = options.cache || false;
54   let fileCache;
55   const startTime = Date.now();
56
57   // The ignorer will be used to filter file paths after the glob is checked,
58   // before any files are actually read
59   const ignoreFilePath = options.ignorePath || DEFAULT_IGNORE_FILENAME;
60   const absoluteIgnoreFilePath = path.isAbsolute(ignoreFilePath)
61     ? ignoreFilePath
62     : path.resolve(process.cwd(), ignoreFilePath);
63   let ignoreText = "";
64   try {
65     ignoreText = fs.readFileSync(absoluteIgnoreFilePath, "utf8");
66   } catch (readError) {
67     if (readError.code !== FILE_NOT_FOUND_ERROR_CODE) throw readError;
68   }
69   const ignorePattern = options.ignorePattern || [];
70   const ignorer = ignore()
71     .add(ignoreText)
72     .add(ignorePattern);
73
74   const isValidCode = typeof code === "string";
75   if ((!files && !isValidCode) || (files && (code || isValidCode))) {
76     throw new Error(
77       "You must pass stylelint a `files` glob or a `code` string, though not both"
78     );
79   }
80
81   let formatterFunction;
82   if (typeof formatter === "string") {
83     formatterFunction = formatters[formatter];
84     if (formatterFunction === undefined) {
85       return Promise.reject(
86         new Error(
87           "You must use a valid formatter option: 'json', 'string', 'verbose', or a function"
88         )
89       );
90     }
91   } else if (typeof formatter === "function") {
92     formatterFunction = formatter;
93   } else {
94     formatterFunction = formatters.json;
95   }
96
97   const stylelint = createStylelint({
98     config,
99     configFile,
100     configBasedir,
101     configOverrides,
102     ignoreDisables,
103     reportNeedlessDisables,
104     syntax,
105     customSyntax,
106     fix
107   });
108
109   if (!files) {
110     const absoluteCodeFilename =
111       codeFilename !== undefined && !path.isAbsolute(codeFilename)
112         ? path.join(process.cwd(), codeFilename)
113         : codeFilename;
114     return stylelint
115       ._lintSource({
116         code,
117         codeFilename: absoluteCodeFilename
118       })
119       .then(postcssResult => {
120         // Check for file existence
121         return new Promise((resolve, reject) => {
122           if (!absoluteCodeFilename) {
123             reject();
124             return;
125           }
126
127           fs.stat(absoluteCodeFilename, err => {
128             if (err) {
129               reject();
130             } else {
131               resolve();
132             }
133           });
134         })
135           .then(() => {
136             return stylelint._createStylelintResult(
137               postcssResult,
138               absoluteCodeFilename
139             );
140           })
141           .catch(() => {
142             return stylelint._createStylelintResult(postcssResult);
143           });
144       })
145       .catch(handleError)
146       .then(stylelintResult => {
147         const returnValue = prepareReturnValue([stylelintResult]);
148         const postcssResult = stylelintResult._postcssResult;
149         // if file is ignored, return nothing
150         if (
151           absoluteCodeFilename &&
152           !ignorer.filter(path.relative(process.cwd(), absoluteCodeFilename))
153             .length
154         ) {
155           returnValue.output = "";
156         } else if (
157           options.fix &&
158           postcssResult &&
159           !postcssResult.stylelint.ignored
160         ) {
161           // If we're fixing, the output should be the fixed code
162           returnValue.output = postcssResult.root.toString(
163             postcssResult.opts.syntax
164           );
165         }
166         return returnValue;
167       });
168   }
169
170   let fileList = files;
171   if (typeof fileList === "string") {
172     fileList = [fileList];
173   }
174   if (!options.disableDefaultIgnores) {
175     fileList = fileList.concat(ALWAYS_IGNORED_GLOBS.map(glob => "!" + glob));
176   }
177
178   if (useCache) {
179     const stylelintVersion = pkg.version;
180     const hashOfConfig = hash(`${stylelintVersion}_${JSON.stringify(config)}`);
181     fileCache = new FileCache(cacheLocation, hashOfConfig);
182   } else {
183     // No need to calculate hash here, we just want to delete cache file.
184     fileCache = new FileCache(cacheLocation);
185     // Remove cache file if cache option is disabled
186     fileCache.destroy();
187   }
188
189   return globby(fileList)
190     .then(filePaths => {
191       // The ignorer filter needs to check paths relative to cwd
192       filePaths = ignorer.filter(
193         filePaths.map(p => path.relative(process.cwd(), p))
194       );
195
196       if (!filePaths.length) {
197         return Promise.all([]);
198       }
199
200       let absoluteFilePaths = filePaths.map(filePath => {
201         const absoluteFilepath = !path.isAbsolute(filePath)
202           ? path.join(process.cwd(), filePath)
203           : path.normalize(filePath);
204         return absoluteFilepath;
205       });
206
207       if (useCache) {
208         absoluteFilePaths = absoluteFilePaths.filter(
209           fileCache.hasFileChanged.bind(fileCache)
210         );
211       }
212
213       const getStylelintResults = absoluteFilePaths.map(absoluteFilepath => {
214         debug(`Processing ${absoluteFilepath}`);
215         return stylelint
216           ._lintSource({
217             filePath: absoluteFilepath
218           })
219           .then(postcssResult => {
220             if (postcssResult.stylelint.stylelintError && useCache) {
221               debug(
222                 `${
223                   absoluteFilepath
224                 } contains linting errors and will not be cached.`
225               );
226               fileCache.removeEntry(absoluteFilepath);
227             }
228
229             // If we're fixing, save the file with changed code
230             let fixFile = Promise.resolve();
231             if (!postcssResult.stylelint.ignored && options.fix) {
232               const fixedCss = postcssResult.root.toString(
233                 postcssResult.opts.syntax
234               );
235               fixFile = pify(fs.writeFile)(absoluteFilepath, fixedCss);
236             }
237
238             return fixFile.then(() =>
239               stylelint._createStylelintResult(postcssResult, absoluteFilepath)
240             );
241           })
242           .catch(handleError);
243       });
244
245       return Promise.all(getStylelintResults);
246     })
247     .then(stylelintResults => {
248       if (useCache) {
249         fileCache.reconcile();
250       }
251       return prepareReturnValue(stylelintResults);
252     });
253
254   function prepareReturnValue(
255     stylelintResults /*: Array<stylelint$result>*/
256   ) /*: stylelint$standaloneReturnValue*/ {
257     const errored = stylelintResults.some(
258       result => result.errored || result.parseErrors.length > 0
259     );
260     const returnValue /*: stylelint$standaloneReturnValue*/ = {
261       errored,
262       output: formatterFunction(stylelintResults),
263       results: stylelintResults
264     };
265     if (reportNeedlessDisables) {
266       returnValue.needlessDisables = needlessDisables(stylelintResults);
267     }
268     debug(`Linting complete in ${Date.now() - startTime}ms`);
269     return returnValue;
270   }
271 };
272
273 function handleError(error /*: Object*/) {
274   if (error.name === "CssSyntaxError") {
275     return convertCssSyntaxErrorToResult(error);
276   } else {
277     throw error;
278   }
279 }
280
281 // By converting syntax errors to stylelint results,
282 // we can control their appearance in the formatted output
283 // and other tools like editor plugins can decide how to
284 // present them, as well
285 function convertCssSyntaxErrorToResult(
286   error /*: CssSyntaxErrorT*/
287 ) /*: stylelint$result*/ {
288   if (error.name !== "CssSyntaxError") {
289     throw error;
290   }
291
292   return {
293     source: error.file || "<input css 1>",
294     deprecations: [],
295     invalidOptionWarnings: [],
296     parseErrors: [],
297     errored: true,
298     warnings: [
299       {
300         line: error.line,
301         column: error.column,
302         rule: error.name,
303         severity: "error",
304         text: error.reason + " (" + error.name + ")"
305       }
306     ]
307   };
308 }