Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 1 | 'use strict'; |
| 2 | |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 3 | const createStylelint = require('./createStylelint'); |
| 4 | const createStylelintResult = require('./createStylelintResult'); |
| 5 | const debug = require('debug')('stylelint:standalone'); |
Tim van der Lippe | efb716a | 2020-12-01 12:54:04 +0000 | [diff] [blame] | 6 | const fastGlob = require('fast-glob'); |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 7 | const FileCache = require('./utils/FileCache'); |
| 8 | const filterFilePaths = require('./utils/filterFilePaths'); |
| 9 | const formatters = require('./formatters'); |
| 10 | const fs = require('fs'); |
| 11 | const getFormatterOptionsText = require('./utils/getFormatterOptionsText'); |
| 12 | const globby = require('globby'); |
| 13 | const hash = require('./utils/hash'); |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame^] | 14 | const isPathNotFoundError = require('./utils/isPathNotFoundError'); |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 15 | const NoFilesFoundError = require('./utils/noFilesFoundError'); |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame^] | 16 | const normalizePath = require('normalize-path'); |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 17 | const path = require('path'); |
| 18 | const pkg = require('../package.json'); |
Tim van der Lippe | efb716a | 2020-12-01 12:54:04 +0000 | [diff] [blame] | 19 | const prepareReturnValue = require('./prepareReturnValue'); |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 20 | const { default: ignore } = require('ignore'); |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame^] | 21 | |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 22 | const DEFAULT_IGNORE_FILENAME = '.stylelintignore'; |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 23 | const ALWAYS_IGNORED_GLOBS = ['**/node_modules/**']; |
| 24 | const writeFileAtomic = require('write-file-atomic'); |
| 25 | |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame^] | 26 | /** @typedef {import('stylelint').LinterOptions} LinterOptions */ |
| 27 | /** @typedef {import('stylelint').LinterResult} LinterResult */ |
| 28 | /** @typedef {import('stylelint').LintResult} StylelintResult */ |
Tim van der Lippe | efb716a | 2020-12-01 12:54:04 +0000 | [diff] [blame] | 29 | /** @typedef {import('stylelint').Formatter} Formatter */ |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame^] | 30 | /** @typedef {import('stylelint').FormatterType} FormatterType */ |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 31 | |
| 32 | /** |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame^] | 33 | * |
| 34 | * @param {LinterOptions} options |
| 35 | * @returns {Promise<LinterResult>} |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 36 | */ |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame^] | 37 | async function standalone({ |
| 38 | allowEmptyInput = false, |
| 39 | cache: useCache = false, |
| 40 | cacheLocation, |
| 41 | code, |
| 42 | codeFilename, |
| 43 | config, |
| 44 | configBasedir, |
| 45 | configFile, |
| 46 | customSyntax, |
| 47 | disableDefaultIgnores, |
| 48 | files, |
| 49 | fix, |
| 50 | formatter, |
| 51 | globbyOptions, |
| 52 | ignoreDisables, |
| 53 | ignorePath = DEFAULT_IGNORE_FILENAME, |
| 54 | ignorePattern = [], |
| 55 | maxWarnings, |
| 56 | quiet, |
| 57 | reportDescriptionlessDisables, |
| 58 | reportInvalidScopeDisables, |
| 59 | reportNeedlessDisables, |
| 60 | syntax, |
| 61 | }) { |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 62 | /** @type {FileCache} */ |
| 63 | let fileCache; |
| 64 | const startTime = Date.now(); |
| 65 | |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame^] | 66 | const isValidCode = typeof code === 'string'; |
| 67 | |
| 68 | if ((!files && !isValidCode) || (files && (code || isValidCode))) { |
| 69 | return Promise.reject( |
| 70 | new Error('You must pass stylelint a `files` glob or a `code` string, though not both'), |
| 71 | ); |
| 72 | } |
| 73 | |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 74 | // The ignorer will be used to filter file paths after the glob is checked, |
| 75 | // before any files are actually read |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame^] | 76 | const absoluteIgnoreFilePath = path.isAbsolute(ignorePath) |
| 77 | ? ignorePath |
| 78 | : path.resolve(process.cwd(), ignorePath); |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 79 | let ignoreText = ''; |
| 80 | |
| 81 | try { |
| 82 | ignoreText = fs.readFileSync(absoluteIgnoreFilePath, 'utf8'); |
| 83 | } catch (readError) { |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame^] | 84 | if (!isPathNotFoundError(readError)) { |
| 85 | return Promise.reject(readError); |
| 86 | } |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 87 | } |
| 88 | |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 89 | const ignorer = ignore().add(ignoreText).add(ignorePattern); |
| 90 | |
Tim van der Lippe | efb716a | 2020-12-01 12:54:04 +0000 | [diff] [blame] | 91 | /** @type {Formatter} */ |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 92 | let formatterFunction; |
| 93 | |
Tim van der Lippe | efb716a | 2020-12-01 12:54:04 +0000 | [diff] [blame] | 94 | try { |
| 95 | formatterFunction = getFormatterFunction(formatter); |
| 96 | } catch (error) { |
| 97 | return Promise.reject(error); |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 98 | } |
| 99 | |
| 100 | const stylelint = createStylelint({ |
| 101 | config, |
| 102 | configFile, |
| 103 | configBasedir, |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 104 | ignoreDisables, |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame^] | 105 | ignorePath, |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 106 | reportNeedlessDisables, |
| 107 | reportInvalidScopeDisables, |
Tim van der Lippe | efb716a | 2020-12-01 12:54:04 +0000 | [diff] [blame] | 108 | reportDescriptionlessDisables, |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 109 | syntax, |
| 110 | customSyntax, |
| 111 | fix, |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame^] | 112 | quiet, |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 113 | }); |
| 114 | |
| 115 | if (!files) { |
| 116 | const absoluteCodeFilename = |
| 117 | codeFilename !== undefined && !path.isAbsolute(codeFilename) |
| 118 | ? path.join(process.cwd(), codeFilename) |
| 119 | : codeFilename; |
| 120 | |
| 121 | // if file is ignored, return nothing |
| 122 | if ( |
| 123 | absoluteCodeFilename && |
| 124 | !filterFilePaths(ignorer, [path.relative(process.cwd(), absoluteCodeFilename)]).length |
| 125 | ) { |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame^] | 126 | return prepareReturnValue([], maxWarnings, formatterFunction); |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 127 | } |
| 128 | |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame^] | 129 | let stylelintResult; |
| 130 | |
| 131 | try { |
| 132 | const postcssResult = await stylelint._lintSource({ |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 133 | code, |
| 134 | codeFilename: absoluteCodeFilename, |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 135 | }); |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame^] | 136 | |
| 137 | stylelintResult = await stylelint._createStylelintResult(postcssResult, absoluteCodeFilename); |
| 138 | } catch (error) { |
| 139 | stylelintResult = await handleError(stylelint, error); |
| 140 | } |
| 141 | |
| 142 | const postcssResult = stylelintResult._postcssResult; |
| 143 | const returnValue = prepareReturnValue([stylelintResult], maxWarnings, formatterFunction); |
| 144 | |
| 145 | if ( |
| 146 | fix && |
| 147 | postcssResult && |
| 148 | !postcssResult.stylelint.ignored && |
| 149 | !postcssResult.stylelint.ruleDisableFix |
| 150 | ) { |
| 151 | returnValue.output = |
| 152 | !postcssResult.stylelint.disableWritingFix && postcssResult.opts |
| 153 | ? // If we're fixing, the output should be the fixed code |
| 154 | postcssResult.root.toString(postcssResult.opts.syntax) |
| 155 | : // If the writing of the fix is disabled, the input code is returned as-is |
| 156 | code; |
| 157 | } |
| 158 | |
| 159 | return returnValue; |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 160 | } |
| 161 | |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame^] | 162 | let fileList = [files].flat().map((entry) => { |
| 163 | const cwd = (globbyOptions && globbyOptions.cwd) || process.cwd(); |
Tim van der Lippe | efb716a | 2020-12-01 12:54:04 +0000 | [diff] [blame] | 164 | const absolutePath = !path.isAbsolute(entry) ? path.join(cwd, entry) : path.normalize(entry); |
| 165 | |
| 166 | if (fs.existsSync(absolutePath)) { |
| 167 | // This path points to a file. Return an escaped path to avoid globbing |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame^] | 168 | return fastGlob.escapePath(normalizePath(entry)); |
Tim van der Lippe | efb716a | 2020-12-01 12:54:04 +0000 | [diff] [blame] | 169 | } |
| 170 | |
| 171 | return entry; |
| 172 | }); |
| 173 | |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame^] | 174 | if (!disableDefaultIgnores) { |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 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 | |
| 182 | fileCache = new FileCache(cacheLocation, hashOfConfig); |
| 183 | } else { |
| 184 | // No need to calculate hash here, we just want to delete cache file. |
| 185 | fileCache = new FileCache(cacheLocation); |
| 186 | // Remove cache file if cache option is disabled |
| 187 | fileCache.destroy(); |
| 188 | } |
| 189 | |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame^] | 190 | let filePaths = await globby(fileList, globbyOptions); |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 191 | |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame^] | 192 | // The ignorer filter needs to check paths relative to cwd |
| 193 | filePaths = filterFilePaths( |
| 194 | ignorer, |
| 195 | filePaths.map((p) => path.relative(process.cwd(), p)), |
| 196 | ); |
| 197 | |
| 198 | let stylelintResults; |
| 199 | |
| 200 | if (filePaths.length) { |
| 201 | const cwd = (globbyOptions && globbyOptions.cwd) || process.cwd(); |
| 202 | let absoluteFilePaths = filePaths.map((filePath) => { |
| 203 | const absoluteFilepath = !path.isAbsolute(filePath) |
| 204 | ? path.join(cwd, filePath) |
| 205 | : path.normalize(filePath); |
| 206 | |
| 207 | return absoluteFilepath; |
| 208 | }); |
| 209 | |
| 210 | if (useCache) { |
| 211 | absoluteFilePaths = absoluteFilePaths.filter(fileCache.hasFileChanged.bind(fileCache)); |
| 212 | } |
| 213 | |
| 214 | const getStylelintResults = absoluteFilePaths.map(async (absoluteFilepath) => { |
| 215 | debug(`Processing ${absoluteFilepath}`); |
| 216 | |
| 217 | try { |
| 218 | const postcssResult = await stylelint._lintSource({ |
| 219 | filePath: absoluteFilepath, |
| 220 | }); |
| 221 | |
| 222 | if (postcssResult.stylelint.stylelintError && useCache) { |
| 223 | debug(`${absoluteFilepath} contains linting errors and will not be cached.`); |
| 224 | fileCache.removeEntry(absoluteFilepath); |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 225 | } |
| 226 | |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame^] | 227 | /** |
| 228 | * If we're fixing, save the file with changed code |
| 229 | */ |
| 230 | if ( |
| 231 | postcssResult.root && |
| 232 | postcssResult.opts && |
| 233 | !postcssResult.stylelint.ignored && |
| 234 | fix && |
| 235 | !postcssResult.stylelint.disableWritingFix |
| 236 | ) { |
| 237 | const fixedCss = postcssResult.root.toString(postcssResult.opts.syntax); |
| 238 | |
| 239 | if ( |
| 240 | postcssResult.root && |
| 241 | postcssResult.root.source && |
| 242 | postcssResult.root.source.input.css !== fixedCss |
| 243 | ) { |
| 244 | await writeFileAtomic(absoluteFilepath, fixedCss); |
| 245 | } |
| 246 | } |
| 247 | |
| 248 | return stylelint._createStylelintResult(postcssResult, absoluteFilepath); |
| 249 | } catch (error) { |
| 250 | // On any error, we should not cache the lint result |
| 251 | fileCache.removeEntry(absoluteFilepath); |
| 252 | |
| 253 | return handleError(stylelint, error, absoluteFilepath); |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 254 | } |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 255 | }); |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame^] | 256 | |
| 257 | stylelintResults = await Promise.all(getStylelintResults); |
| 258 | } else if (allowEmptyInput) { |
| 259 | stylelintResults = await Promise.all([]); |
| 260 | } else { |
| 261 | stylelintResults = await Promise.reject(new NoFilesFoundError(fileList)); |
| 262 | } |
| 263 | |
| 264 | if (useCache) { |
| 265 | fileCache.reconcile(); |
| 266 | } |
| 267 | |
| 268 | const result = prepareReturnValue(stylelintResults, maxWarnings, formatterFunction); |
| 269 | |
| 270 | debug(`Linting complete in ${Date.now() - startTime}ms`); |
| 271 | |
| 272 | return result; |
| 273 | } |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 274 | |
Tim van der Lippe | efb716a | 2020-12-01 12:54:04 +0000 | [diff] [blame] | 275 | /** |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame^] | 276 | * @param {FormatterType | Formatter | undefined} selected |
Tim van der Lippe | efb716a | 2020-12-01 12:54:04 +0000 | [diff] [blame] | 277 | * @returns {Formatter} |
| 278 | */ |
| 279 | function getFormatterFunction(selected) { |
| 280 | /** @type {Formatter} */ |
| 281 | let formatterFunction; |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 282 | |
Tim van der Lippe | efb716a | 2020-12-01 12:54:04 +0000 | [diff] [blame] | 283 | if (typeof selected === 'string') { |
| 284 | formatterFunction = formatters[selected]; |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 285 | |
Tim van der Lippe | efb716a | 2020-12-01 12:54:04 +0000 | [diff] [blame] | 286 | if (formatterFunction === undefined) { |
| 287 | throw new Error( |
| 288 | `You must use a valid formatter option: ${getFormatterOptionsText()} or a function`, |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 289 | ); |
| 290 | } |
Tim van der Lippe | efb716a | 2020-12-01 12:54:04 +0000 | [diff] [blame] | 291 | } else if (typeof selected === 'function') { |
| 292 | formatterFunction = selected; |
| 293 | } else { |
| 294 | formatterFunction = formatters.json; |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 295 | } |
Tim van der Lippe | efb716a | 2020-12-01 12:54:04 +0000 | [diff] [blame] | 296 | |
| 297 | return formatterFunction; |
| 298 | } |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 299 | |
| 300 | /** |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame^] | 301 | * @param {import('stylelint').InternalApi} stylelint |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 302 | * @param {any} error |
| 303 | * @param {string} [filePath] |
| 304 | * @return {Promise<StylelintResult>} |
| 305 | */ |
| 306 | function handleError(stylelint, error, filePath = undefined) { |
| 307 | if (error.name === 'CssSyntaxError') { |
| 308 | return createStylelintResult(stylelint, undefined, filePath, error); |
| 309 | } |
| 310 | |
| 311 | throw error; |
| 312 | } |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame^] | 313 | |
| 314 | module.exports = /** @type {typeof import('stylelint').lint} */ (standalone); |