augmentConfig.cjs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537
  1. // NOTICE: This file is generated by Rollup. To modify it,
  2. // please instead edit the ESM counterpart and rebuild with Rollup (npm run build).
  3. 'use strict';
  4. const path = require('node:path');
  5. const globjoin = require('globjoin');
  6. const micromatch = require('micromatch');
  7. const normalizePath = require('normalize-path');
  8. const validateTypes = require('./utils/validateTypes.cjs');
  9. const errors = require('./utils/errors.cjs');
  10. const dynamicImport = require('./utils/dynamicImport.cjs');
  11. const emitWarning = require('./utils/emitWarning.cjs');
  12. const getModulePath = require('./utils/getModulePath.cjs');
  13. const normalizeAllRuleSettings = require('./normalizeAllRuleSettings.cjs');
  14. /** @import {Config as StylelintConfig, CosmiconfigResult as StylelintCosmiconfigResult, InternalApi as StylelintInternalApi} from 'stylelint' */
  15. /**
  16. * @param {string} glob
  17. * @param {string} basedir
  18. * @returns {string}
  19. */
  20. function absolutizeGlob(glob, basedir) {
  21. const result = path.isAbsolute(glob.replace(/^!/, '')) ? glob : globjoin(basedir, glob);
  22. // Glob patterns for micromatch should be in POSIX-style
  23. return normalizePath(result);
  24. }
  25. /**
  26. * - Merges config and stylelint options
  27. * - Makes all paths absolute
  28. * - Merges extends
  29. * @param {StylelintInternalApi} stylelint
  30. * @param {StylelintConfig} config
  31. * @param {string} configDir
  32. * @param {boolean} allowOverrides
  33. * @param {string} rootConfigDir
  34. * @param {string} [filePath]
  35. * @returns {Promise<StylelintConfig>}
  36. */
  37. async function augmentConfigBasic(
  38. stylelint,
  39. config,
  40. configDir,
  41. allowOverrides,
  42. rootConfigDir,
  43. filePath,
  44. ) {
  45. let augmentedConfig = config;
  46. if (allowOverrides) {
  47. augmentedConfig = addOptions(stylelint, augmentedConfig);
  48. }
  49. if (filePath) {
  50. augmentedConfig = applyOverrides(augmentedConfig, rootConfigDir, filePath);
  51. }
  52. augmentedConfig = await extendConfig(
  53. stylelint,
  54. augmentedConfig,
  55. configDir,
  56. rootConfigDir,
  57. filePath,
  58. );
  59. const cwd = stylelint._options.cwd;
  60. return absolutizePaths(augmentedConfig, configDir, cwd);
  61. }
  62. /**
  63. * Extended configs need to be run through augmentConfigBasic
  64. * but do not need the full treatment. Things like pluginFunctions
  65. * will be resolved and added by the parent config.
  66. * @param {string} cwd
  67. * @returns {(cosmiconfigResult?: StylelintCosmiconfigResult) => Promise<StylelintCosmiconfigResult>}
  68. */
  69. function augmentConfigExtended(cwd) {
  70. return async (cosmiconfigResult) => {
  71. if (!cosmiconfigResult) {
  72. return null;
  73. }
  74. const configDir = path.dirname(cosmiconfigResult.filepath || '');
  75. const { config } = cosmiconfigResult;
  76. const augmentedConfig = absolutizePaths(config, configDir, cwd);
  77. return {
  78. config: augmentedConfig,
  79. filepath: cosmiconfigResult.filepath,
  80. };
  81. };
  82. }
  83. /**
  84. * @param {StylelintInternalApi} stylelint
  85. * @param {string} [filePath]
  86. * @param {StylelintCosmiconfigResult} [cosmiconfigResult]
  87. * @returns {Promise<StylelintCosmiconfigResult>}
  88. */
  89. async function augmentConfigFull(stylelint, filePath, cosmiconfigResult) {
  90. if (!cosmiconfigResult) {
  91. return null;
  92. }
  93. const config = cosmiconfigResult.config;
  94. const filepath = cosmiconfigResult.filepath;
  95. const configDir = stylelint._options.configBasedir || path.dirname(filepath || '');
  96. let augmentedConfig = await augmentConfigBasic(
  97. stylelint,
  98. config,
  99. configDir,
  100. true,
  101. configDir,
  102. filePath,
  103. );
  104. augmentedConfig = await addPluginFunctions(augmentedConfig, stylelint._options);
  105. augmentedConfig = await addProcessorFunctions(augmentedConfig);
  106. if (!augmentedConfig.rules) {
  107. throw new errors.ConfigurationError(
  108. 'No rules found within configuration. Have you provided a "rules" property?',
  109. );
  110. }
  111. augmentedConfig = await normalizeAllRuleSettings(augmentedConfig);
  112. return {
  113. config: augmentedConfig,
  114. filepath: cosmiconfigResult.filepath,
  115. };
  116. }
  117. /**
  118. * Make all paths in the config absolute.
  119. *
  120. * @param {StylelintConfig} config
  121. * @param {string} configDir
  122. * @param {string} cwd
  123. * @returns {StylelintConfig}
  124. */
  125. function absolutizePaths(config, configDir, cwd) {
  126. if (config.ignoreFiles) {
  127. config.ignoreFiles = [config.ignoreFiles].flat().map((glob) => absolutizeGlob(glob, configDir));
  128. }
  129. /** @type {<T>(lookup: T) => (string | T)} */
  130. const toAbsolutePath = (lookup) => {
  131. if (typeof lookup === 'string') {
  132. return getModulePath(configDir, lookup, cwd);
  133. }
  134. return lookup;
  135. };
  136. if (config.plugins) {
  137. config.plugins = [config.plugins].flat().map(toAbsolutePath);
  138. }
  139. if (config.processors) {
  140. config.processors = config.processors.map(toAbsolutePath);
  141. }
  142. return config;
  143. }
  144. /**
  145. * @param {StylelintInternalApi} stylelint
  146. * @param {StylelintConfig} config
  147. * @param {string} configDir
  148. * @param {string} rootConfigDir
  149. * @param {string} [filePath]
  150. * @returns {Promise<StylelintConfig>}
  151. */
  152. async function extendConfig(stylelint, config, configDir, rootConfigDir, filePath) {
  153. if (config.extends === undefined) {
  154. return config;
  155. }
  156. const { extends: configExtends, ...originalWithoutExtends } = config;
  157. const normalizedExtends = [configExtends].flat();
  158. let resultConfig = originalWithoutExtends;
  159. for (const extendLookup of normalizedExtends) {
  160. let extendResult;
  161. if (typeof extendLookup === 'string') {
  162. extendResult = await loadExtendedConfig(stylelint, configDir, extendLookup);
  163. } else if (typeof extendLookup === 'object' && extendLookup !== null) {
  164. extendResult = { config: extendLookup };
  165. }
  166. if (extendResult) {
  167. let extendResultConfig = extendResult.config;
  168. const extendConfigDir = path.dirname(extendResult.filepath || '');
  169. extendResultConfig = await augmentConfigBasic(
  170. stylelint,
  171. extendResultConfig,
  172. extendConfigDir,
  173. false,
  174. rootConfigDir,
  175. filePath,
  176. );
  177. resultConfig = mergeConfigs(resultConfig, extendResultConfig);
  178. }
  179. }
  180. return mergeConfigs(resultConfig, originalWithoutExtends);
  181. }
  182. /**
  183. * @param {StylelintInternalApi} stylelint
  184. * @param {string} configDir
  185. * @param {string} extendLookup
  186. * @returns {Promise<StylelintCosmiconfigResult>}
  187. */
  188. function loadExtendedConfig(stylelint, configDir, extendLookup) {
  189. const extendPath = getModulePath(configDir, extendLookup, stylelint._options.cwd);
  190. return stylelint._extendExplorer.load(extendPath);
  191. }
  192. /**
  193. * When merging configs (via extends)
  194. * - plugin, extends, overrides arrays are joined
  195. * - rules are merged via Object.assign, so there is no attempt made to
  196. * merge any given rule's settings. If b contains the same rule as a,
  197. * b's rule settings will override a's rule settings entirely.
  198. * - Everything else is merged via Object.assign
  199. * @param {StylelintConfig} a
  200. * @param {StylelintConfig} b
  201. * @returns {StylelintConfig}
  202. */
  203. function mergeConfigs(a, b) {
  204. /** @type {Pick<StylelintConfig, 'plugins'>} */
  205. const pluginMerger = {};
  206. if (a.plugins || b.plugins) {
  207. pluginMerger.plugins = [];
  208. if (a.plugins) {
  209. pluginMerger.plugins = pluginMerger.plugins.concat(a.plugins);
  210. }
  211. if (b.plugins) {
  212. pluginMerger.plugins = [...new Set(pluginMerger.plugins.concat(b.plugins))];
  213. }
  214. }
  215. /** @type {Pick<StylelintConfig, 'processors'>} */
  216. const processorMerger = {};
  217. if (a.processors || b.processors) {
  218. processorMerger.processors = [];
  219. if (a.processors) {
  220. processorMerger.processors = processorMerger.processors.concat(a.processors);
  221. }
  222. if (b.processors) {
  223. processorMerger.processors = [...new Set(processorMerger.processors.concat(b.processors))];
  224. }
  225. }
  226. /** @type {Pick<StylelintConfig, 'overrides'>} */
  227. const overridesMerger = {};
  228. if (a.overrides || b.overrides) {
  229. overridesMerger.overrides = [];
  230. if (a.overrides) {
  231. overridesMerger.overrides = overridesMerger.overrides.concat(a.overrides);
  232. }
  233. if (b.overrides) {
  234. overridesMerger.overrides = [...new Set(overridesMerger.overrides.concat(b.overrides))];
  235. }
  236. }
  237. /** @type {Pick<StylelintConfig, 'extends'>} */
  238. const extendsMerger = {};
  239. if (a.extends || b.extends) {
  240. extendsMerger.extends = [];
  241. if (a.extends) {
  242. extendsMerger.extends = extendsMerger.extends.concat(a.extends);
  243. }
  244. if (b.extends) {
  245. extendsMerger.extends = extendsMerger.extends.concat(b.extends);
  246. }
  247. // Remove duplicates from the array, the last item takes precedence
  248. extendsMerger.extends = extendsMerger.extends.filter(
  249. (item, index, arr) => arr.lastIndexOf(item) === index,
  250. );
  251. }
  252. const rulesMerger = {};
  253. if (a.rules || b.rules) {
  254. rulesMerger.rules = { ...a.rules, ...b.rules };
  255. }
  256. const result = {
  257. ...a,
  258. ...b,
  259. ...extendsMerger,
  260. ...pluginMerger,
  261. ...processorMerger,
  262. ...overridesMerger,
  263. ...rulesMerger,
  264. };
  265. return result;
  266. }
  267. /**
  268. * @param {StylelintConfig} config
  269. * @param {import('stylelint').LinterOptions} options
  270. * @returns {Promise<StylelintConfig>}
  271. */
  272. async function addPluginFunctions(config, { quietDeprecationWarnings }) {
  273. if (!config.plugins) {
  274. return config;
  275. }
  276. const normalizedPlugins = [config.plugins].flat();
  277. /** @type {StylelintConfig['pluginFunctions']} */
  278. const pluginFunctions = {};
  279. for (const pluginLookup of normalizedPlugins) {
  280. let pluginImport;
  281. if (typeof pluginLookup === 'string') {
  282. pluginImport = await dynamicImport(pluginLookup);
  283. // NOTE: This '.cjs' check is limited. Some CommonJS plugins may have the '.js' extension.
  284. if (!quietDeprecationWarnings && pluginLookup.endsWith('.cjs')) {
  285. emitWarning.emitDeprecationWarning(
  286. `CommonJS plugins are deprecated ("${pluginLookup}").`,
  287. 'COMMONJS_PLUGINS',
  288. 'See https://stylelint.io/migration-guide/to-16',
  289. );
  290. }
  291. } else {
  292. pluginImport = pluginLookup;
  293. }
  294. // Handle either ES6 or CommonJS modules
  295. pluginImport = pluginImport.default || pluginImport;
  296. // A plugin can export either a single rule definition
  297. // or an array of them
  298. const normalizedPluginImport = [pluginImport].flat();
  299. for (const pluginRuleDefinition of normalizedPluginImport) {
  300. if (!pluginRuleDefinition.ruleName) {
  301. throw new errors.ConfigurationError(
  302. `stylelint requires plugins to expose a ruleName. The plugin "${pluginLookup}" is not doing this, so will not work with stylelint. Please file an issue with the plugin.`,
  303. );
  304. }
  305. if (!pluginRuleDefinition.ruleName.includes('/')) {
  306. throw new errors.ConfigurationError(
  307. `stylelint requires plugin rules to be namespaced, i.e. only \`plugin-namespace/plugin-rule-name\` plugin rule names are supported. The plugin rule "${pluginRuleDefinition.ruleName}" does not do this, so will not work. Please file an issue with the plugin.`,
  308. );
  309. }
  310. pluginFunctions[pluginRuleDefinition.ruleName] = pluginRuleDefinition.rule;
  311. }
  312. }
  313. config.pluginFunctions = pluginFunctions;
  314. return config;
  315. }
  316. /**
  317. * @param {StylelintConfig} config
  318. * @returns {Promise<StylelintConfig>}
  319. */
  320. async function addProcessorFunctions(config) {
  321. if (!config.processors) {
  322. return config;
  323. }
  324. const processorPromises = config.processors.map(async (processorLookup) => {
  325. let processor = await dynamicImport(processorLookup);
  326. processor = processor.default ?? processor;
  327. if (!validateTypes.isFunction(processor)) {
  328. throw new errors.ConfigurationError(`The processor "${processorLookup}" must be a function`);
  329. }
  330. const { name, postprocess } = processor();
  331. if (!validateTypes.isString(name) || !name) {
  332. throw new errors.ConfigurationError(
  333. `The processor "${processorLookup}" must return an object with the "name" property`,
  334. );
  335. }
  336. if (!validateTypes.isFunction(postprocess)) {
  337. throw new errors.ConfigurationError(
  338. `The processor "${processorLookup}" must return an object with the "postprocess" property`,
  339. );
  340. }
  341. return { name, postprocess };
  342. });
  343. /** @type {StylelintConfig['_processorFunctions']} */
  344. const processorFunctions = new Map();
  345. (await Promise.all(processorPromises)).forEach(({ name, postprocess }) => {
  346. if (name) {
  347. processorFunctions.set(name, postprocess);
  348. }
  349. });
  350. config._processorFunctions = processorFunctions;
  351. return config;
  352. }
  353. /**
  354. * @param {StylelintConfig} fullConfig
  355. * @param {string} rootConfigDir
  356. * @param {string} filePath
  357. * @returns {StylelintConfig}
  358. */
  359. function applyOverrides(fullConfig, rootConfigDir, filePath) {
  360. let { overrides, ...config } = fullConfig;
  361. if (!overrides) {
  362. return config;
  363. }
  364. if (!Array.isArray(overrides)) {
  365. throw new TypeError(
  366. 'The `overrides` configuration property should be an array, e.g. { "overrides": [{ "files": "*.css", "rules": {} }] }.',
  367. );
  368. }
  369. /** @type {(glob: string) => boolean} */
  370. const nonegateGlob = (glob) => !glob.startsWith('!');
  371. for (const override of overrides) {
  372. const { files, ...configOverrides } = override;
  373. if (!files) {
  374. throw new Error(
  375. 'Every object in the `overrides` configuration property should have a `files` property with globs, e.g. { "overrides": [{ "files": "*.css", "rules": {} }] }.',
  376. );
  377. }
  378. const fileList = [files].flat();
  379. const absoluteGlobs = fileList.map((glob) => absolutizeGlob(glob, rootConfigDir));
  380. if (
  381. micromatch.isMatch(filePath, absoluteGlobs, { dot: true }) ||
  382. // E.g. `*.css` matches any CSS files in any directories.
  383. micromatch.isMatch(filePath, fileList.filter(nonegateGlob), { dot: true, basename: true })
  384. ) {
  385. config = mergeConfigs(config, configOverrides);
  386. }
  387. }
  388. return config;
  389. }
  390. /**
  391. * Add options to the config
  392. *
  393. * @param {StylelintInternalApi} stylelint
  394. * @param {StylelintConfig} config
  395. *
  396. * @returns {StylelintConfig}
  397. */
  398. function addOptions(stylelint, config) {
  399. const augmentedConfig = {
  400. ...config,
  401. };
  402. const subset = /** @type {const} */ ([
  403. 'customSyntax',
  404. 'fix',
  405. 'computeEditInfo',
  406. 'ignoreDisables',
  407. 'quiet',
  408. 'reportDescriptionlessDisables',
  409. 'reportInvalidScopeDisables',
  410. 'reportNeedlessDisables',
  411. 'reportUnscopedDisables',
  412. 'validate',
  413. ]);
  414. /** @type {Partial<StylelintConfig>} */
  415. const options = {
  416. ...stylelint._options,
  417. // Override fix to match Config type.
  418. fix: stylelint._options.fix ? Boolean(stylelint._options.fix) : undefined,
  419. };
  420. /**
  421. * @template T
  422. * @param {T extends typeof subset[number] ? T : never} key
  423. */
  424. const addOption = (key) => {
  425. const value = options[key];
  426. if (value) {
  427. augmentedConfig[key] = value;
  428. }
  429. };
  430. subset.forEach((key) => addOption(key));
  431. return augmentedConfig;
  432. }
  433. exports.applyOverrides = applyOverrides;
  434. exports.augmentConfigExtended = augmentConfigExtended;
  435. exports.augmentConfigFull = augmentConfigFull;