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");
16 const DEFAULT_IGNORE_FILENAME = ".stylelintignore";
17 const FILE_NOT_FOUND_ERROR_CODE = "ENOENT";
18 const ALWAYS_IGNORED_GLOBS = ["**/node_modules/**", "**/bower_components/**"];
20 /*::type CssSyntaxErrorT = {
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;
55 const startTime = Date.now();
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)
62 : path.resolve(process.cwd(), ignoreFilePath);
65 ignoreText = fs.readFileSync(absoluteIgnoreFilePath, "utf8");
67 if (readError.code !== FILE_NOT_FOUND_ERROR_CODE) throw readError;
69 const ignorePattern = options.ignorePattern || [];
70 const ignorer = ignore()
74 const isValidCode = typeof code === "string";
75 if ((!files && !isValidCode) || (files && (code || isValidCode))) {
77 "You must pass stylelint a `files` glob or a `code` string, though not both"
81 let formatterFunction;
82 if (typeof formatter === "string") {
83 formatterFunction = formatters[formatter];
84 if (formatterFunction === undefined) {
85 return Promise.reject(
87 "You must use a valid formatter option: 'json', 'string', 'verbose', or a function"
91 } else if (typeof formatter === "function") {
92 formatterFunction = formatter;
94 formatterFunction = formatters.json;
97 const stylelint = createStylelint({
103 reportNeedlessDisables,
110 const absoluteCodeFilename =
111 codeFilename !== undefined && !path.isAbsolute(codeFilename)
112 ? path.join(process.cwd(), codeFilename)
117 codeFilename: absoluteCodeFilename
119 .then(postcssResult => {
120 // Check for file existence
121 return new Promise((resolve, reject) => {
122 if (!absoluteCodeFilename) {
127 fs.stat(absoluteCodeFilename, err => {
136 return stylelint._createStylelintResult(
142 return stylelint._createStylelintResult(postcssResult);
146 .then(stylelintResult => {
147 const returnValue = prepareReturnValue([stylelintResult]);
148 const postcssResult = stylelintResult._postcssResult;
149 // if file is ignored, return nothing
151 absoluteCodeFilename &&
152 !ignorer.filter(path.relative(process.cwd(), absoluteCodeFilename))
155 returnValue.output = "";
159 !postcssResult.stylelint.ignored
161 // If we're fixing, the output should be the fixed code
162 returnValue.output = postcssResult.root.toString(
163 postcssResult.opts.syntax
170 let fileList = files;
171 if (typeof fileList === "string") {
172 fileList = [fileList];
174 if (!options.disableDefaultIgnores) {
175 fileList = fileList.concat(ALWAYS_IGNORED_GLOBS.map(glob => "!" + glob));
179 const stylelintVersion = pkg.version;
180 const hashOfConfig = hash(`${stylelintVersion}_${JSON.stringify(config)}`);
181 fileCache = new FileCache(cacheLocation, hashOfConfig);
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
189 return globby(fileList)
191 // The ignorer filter needs to check paths relative to cwd
192 filePaths = ignorer.filter(
193 filePaths.map(p => path.relative(process.cwd(), p))
196 if (!filePaths.length) {
197 return Promise.all([]);
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;
208 absoluteFilePaths = absoluteFilePaths.filter(
209 fileCache.hasFileChanged.bind(fileCache)
213 const getStylelintResults = absoluteFilePaths.map(absoluteFilepath => {
214 debug(`Processing ${absoluteFilepath}`);
217 filePath: absoluteFilepath
219 .then(postcssResult => {
220 if (postcssResult.stylelint.stylelintError && useCache) {
224 } contains linting errors and will not be cached.`
226 fileCache.removeEntry(absoluteFilepath);
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
235 fixFile = pify(fs.writeFile)(absoluteFilepath, fixedCss);
238 return fixFile.then(() =>
239 stylelint._createStylelintResult(postcssResult, absoluteFilepath)
245 return Promise.all(getStylelintResults);
247 .then(stylelintResults => {
249 fileCache.reconcile();
251 return prepareReturnValue(stylelintResults);
254 function prepareReturnValue(
255 stylelintResults /*: Array<stylelint$result>*/
256 ) /*: stylelint$standaloneReturnValue*/ {
257 const errored = stylelintResults.some(
258 result => result.errored || result.parseErrors.length > 0
260 const returnValue /*: stylelint$standaloneReturnValue*/ = {
262 output: formatterFunction(stylelintResults),
263 results: stylelintResults
265 if (reportNeedlessDisables) {
266 returnValue.needlessDisables = needlessDisables(stylelintResults);
268 debug(`Linting complete in ${Date.now() - startTime}ms`);
273 function handleError(error /*: Object*/) {
274 if (error.name === "CssSyntaxError") {
275 return convertCssSyntaxErrorToResult(error);
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") {
293 source: error.file || "<input css 1>",
295 invalidOptionWarnings: [],
301 column: error.column,
304 text: error.reason + " (" + error.name + ")"