UNPKG

@shopify/stylelint-polaris

Version:

Polaris Design System Stylelint tooling

164 lines (143 loc) 4.33 kB
const stylelint = require('stylelint'); const mediaParser = require('postcss-media-query-parser').default; const { hasScssInterpolation, scssInterpolationRegExp, scssInterpolationExpression, isString, isRegExp, matchesStringOrRegExp, isBoolean, } = require('../../utils'); const ruleName = 'polaris/media-query-allowed-list'; const messages = stylelint.utils.ruleMessages(ruleName, { /** * @type {stylelint.RuleMessageFunc} */ rejected: (invalidMedia) => `Unexpected media query "${invalidMedia}"`, }); /** @typedef {(string | RegExp)[]} AllowedPatterns */ /** * @typedef {Object} PrimaryOptions * @property {AllowedPatterns} allowedMediaTypes * @property {AllowedPatterns} allowedMediaFeatureNames * @property {AllowedPatterns} allowedScssInterpolations * @property {boolean} disabled */ const {rule} = stylelint.createPlugin( ruleName, /** @param {PrimaryOptions} primary */ (primary) => { return (root, result) => { const validOptions = stylelint.utils.validateOptions( result, ruleName, { actual: primary.allowedMediaTypes, possible: [isString, isRegExp], optional: true, }, { actual: primary.allowedMediaFeatureNames, possible: [isString, isRegExp], optional: true, }, { actual: primary.allowedScssInterpolations, possible: [isString, isRegExp], optional: true, }, { actual: primary.disabled, possible: [isBoolean], optional: true, }, ); if (!validOptions) { throw new Error( `Invalid options were provided to the [${ruleName}] stylelint plugin.\n`, ); } const { allowedMediaTypes = [], allowedMediaFeatureNames = [], allowedScssInterpolations = [], disabled, } = primary; if (disabled) { return; } // Pass `primary.allowedMediaFeatureNames` to the // built-in `media-feature-name-allowed-list` rule stylelint.utils.checkAgainstRule( { ruleName: 'media-feature-name-allowed-list', ruleSettings: allowedMediaFeatureNames, root, }, (warning) => { if ( // The built-in `media-feature-name-allowed-list` rule // doesn't handle this case: allowedMediaFeatureNames.includes('-ms-high-contrast') && warning.text.includes('-ms-high-contrast') ) { return; } stylelint.utils.report({ message: messages.rejected(warning.node.params), ruleName, result, node: warning.node, line: warning.line, column: warning.column, endLine: warning.endLine, endColumn: warning.endColumn, }); }, ); root.walkAtRules('media', (atRule) => { const media = atRule.params; if (hasScssInterpolation(media)) { const scssInterpolations = [ ...media.matchAll(new RegExp(scssInterpolationRegExp, 'g')), ].map((match) => match[0]); for (const scssInterpolation of scssInterpolations) { const expression = scssInterpolationExpression(scssInterpolation); if (matchesStringOrRegExp(expression, allowedScssInterpolations)) { return; } stylelint.utils.report({ message: messages.rejected(media), node: atRule, result, ruleName, }); } } mediaParser(media).walk('media-type', (node) => { const mediaType = node.value; if ( // Ignore SCSS interpolated media-types as they are handled // in the above scssInterpolationRegExp check hasScssInterpolation(mediaType) || matchesStringOrRegExp(mediaType, allowedMediaTypes) ) { return; } stylelint.utils.report({ message: messages.rejected(media), node: atRule, result, ruleName, }); }); }); }; }, ); module.exports = { rule, ruleName, messages, };