You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
182 lines
4.6 KiB
182 lines
4.6 KiB
'use strict'; |
|
|
|
const fs = require('fs'); |
|
const LazyResult = require('postcss/lib/lazy-result'); |
|
const postcss = require('postcss'); |
|
const syntaxes = require('./syntaxes'); |
|
|
|
/** @typedef {import('postcss').Result} Result */ |
|
/** @typedef {import('postcss').Syntax} Syntax */ |
|
/** @typedef {import('stylelint').CustomSyntax} CustomSyntax */ |
|
/** @typedef {import('stylelint').GetPostcssOptions} GetPostcssOptions */ |
|
/** @typedef {import('stylelint').StylelintInternalApi} StylelintInternalApi */ |
|
|
|
const postcssProcessor = postcss(); |
|
|
|
/** |
|
* @param {StylelintInternalApi} stylelint |
|
* @param {GetPostcssOptions} options |
|
* |
|
* @returns {Promise<Result>} |
|
*/ |
|
module.exports = function (stylelint, options = {}) { |
|
const cached = options.filePath ? stylelint._postcssResultCache.get(options.filePath) : undefined; |
|
|
|
if (cached) return Promise.resolve(cached); |
|
|
|
/** @type {Promise<string> | undefined} */ |
|
let getCode; |
|
|
|
if (options.code !== undefined) { |
|
getCode = Promise.resolve(options.code); |
|
} else if (options.filePath) { |
|
getCode = readFile(options.filePath); |
|
} |
|
|
|
if (!getCode) { |
|
throw new Error('code or filePath required'); |
|
} |
|
|
|
return getCode |
|
.then((code) => { |
|
/** @type {Syntax | null} */ |
|
let syntax = null; |
|
|
|
if (stylelint._options.customSyntax) { |
|
syntax = getCustomSyntax(stylelint._options.customSyntax); |
|
} else if (stylelint._options.syntax) { |
|
if (stylelint._options.syntax === 'css') { |
|
syntax = cssSyntax(stylelint); |
|
} else { |
|
const keys = Object.keys(syntaxes); |
|
|
|
if (!keys.includes(stylelint._options.syntax)) { |
|
throw new Error( |
|
`You must use a valid syntax option, either: css, ${keys |
|
.slice(0, -1) |
|
.join(', ')} or ${keys.slice(-1)}`, |
|
); |
|
} |
|
|
|
syntax = syntaxes[stylelint._options.syntax]; |
|
} |
|
} else if (!(options.codeProcessors && options.codeProcessors.length)) { |
|
const autoSyntax = require('postcss-syntax'); |
|
|
|
// TODO: investigate why lazy import HTML syntax causes |
|
// JS files with the word "html" to throw TypeError |
|
// https://github.com/stylelint/stylelint/issues/4793 |
|
const { html, ...rest } = syntaxes; |
|
|
|
syntax = autoSyntax({ |
|
css: cssSyntax(stylelint), |
|
jsx: syntaxes['css-in-js'], |
|
...rest, |
|
}); |
|
} |
|
|
|
const postcssOptions = { |
|
from: options.filePath, |
|
syntax, |
|
}; |
|
|
|
const source = options.code ? options.codeFilename : options.filePath; |
|
let preProcessedCode = code; |
|
|
|
if (options.codeProcessors && options.codeProcessors.length) { |
|
if (stylelint._options.fix) { |
|
console.warn( |
|
'Autofix is incompatible with processors and will be disabled. Are you sure you need a processor?', |
|
); |
|
stylelint._options.fix = false; |
|
} |
|
|
|
options.codeProcessors.forEach((codeProcessor) => { |
|
preProcessedCode = codeProcessor(preProcessedCode, source); |
|
}); |
|
} |
|
|
|
const result = new LazyResult(postcssProcessor, preProcessedCode, postcssOptions); |
|
|
|
return result; |
|
}) |
|
.then((postcssResult) => { |
|
if (options.filePath) { |
|
stylelint._postcssResultCache.set(options.filePath, postcssResult); |
|
} |
|
|
|
return postcssResult; |
|
}); |
|
}; |
|
|
|
/** |
|
* @param {CustomSyntax} customSyntax |
|
* @returns {Syntax} |
|
*/ |
|
function getCustomSyntax(customSyntax) { |
|
let resolved; |
|
|
|
if (typeof customSyntax === 'string') { |
|
try { |
|
resolved = require(customSyntax); |
|
} catch { |
|
throw new Error( |
|
`Cannot resolve custom syntax module ${customSyntax}. Check that module ${customSyntax} is available and spelled correctly.`, |
|
); |
|
} |
|
|
|
/* |
|
* PostCSS allows for syntaxes that only contain a parser, however, |
|
* it then expects the syntax to be set as the `parse` option. |
|
*/ |
|
if (!resolved.parse) { |
|
resolved = { |
|
parse: resolved, |
|
stringify: postcss.stringify, |
|
}; |
|
} |
|
|
|
return resolved; |
|
} |
|
|
|
if (typeof customSyntax === 'object') { |
|
if (typeof customSyntax.parse === 'function') { |
|
resolved = { ...customSyntax }; |
|
} else { |
|
throw new TypeError( |
|
`An object provided to the "customSyntax" option must have a "parse" property. Ensure the "parse" property exists and its value is a function.`, |
|
); |
|
} |
|
|
|
return resolved; |
|
} |
|
|
|
throw new Error(`Custom syntax must be a string or a Syntax object`); |
|
} |
|
|
|
/** |
|
* @param {string} filePath |
|
* @returns {Promise<string>} |
|
*/ |
|
function readFile(filePath) { |
|
return new Promise((resolve, reject) => { |
|
fs.readFile(filePath, 'utf8', (err, content) => { |
|
if (err) { |
|
return reject(err); |
|
} |
|
|
|
resolve(content); |
|
}); |
|
}); |
|
} |
|
|
|
/** |
|
* @param {StylelintInternalApi} stylelint |
|
* @returns {Syntax} |
|
*/ |
|
function cssSyntax(stylelint) { |
|
return { |
|
parse: stylelint._options.fix ? require('postcss-safe-parser') : postcss.parse, |
|
stringify: postcss.stringify, |
|
}; |
|
}
|
|
|