UNPKG

@atlaskit/stylelint-design-system

Version:

Stylelint plugin for use with the Atlassian Design System.

122 lines (121 loc) • 4.54 kB
import valueParser from 'postcss-value-parser'; import stylelint from 'stylelint'; import renameMapping from '@atlaskit/tokens/rename-mapping'; import { getCSSCustomProperty } from '@atlaskit/tokens/utils/get-css-custom-property'; import { getDefaultTokenValue } from '../../utils/get-default-token-value'; import { isFunction } from '../../utils/is-function'; import { isToken } from '../../utils/is-token'; import { isVar } from '../../utils/is-var'; import { isWord } from '../../utils/is-word'; export const ruleName = 'design-system/no-unsafe-design-token-usage'; export const messages = stylelint.utils.ruleMessages(ruleName, { invalidToken: name => `The token '${name}' does not exist. You can find the design tokens reference at <https://atlaskit.atlassian.com/packages/design-system/tokens/docs/tokens-reference>.`, tokenRemoved: (name, replacement) => `The token '${name}' has been deleted. Please use ${replacement} instead.`, missingFallback: 'Token usage is missing a fallback.', hasFallback: 'Token usage has a fallback.' }); const isInvalidToken = node => isWord(node) && node.value.startsWith('--ds-') && !isToken(node); const isDeletedToken = node => isWord(node) && node.value.startsWith('--ds-') && renameMapping.filter(({ state }) => state === 'deleted').some(token => getCSSCustomProperty(token.path) === node.value); const ruleBase = (isEnabled, flags = {}, context) => { return (root, result) => { const validOptions = stylelint.utils.validateOptions(result, ruleName, { actual: isEnabled, possible: [true, false] }, { actual: flags, possible: { shouldEnsureFallbackUsage: [true, false], fallbackUsage: ['forced', 'optional', 'none'] }, optional: true }); if (!validOptions || !isEnabled) { return; } const { shouldEnsureFallbackUsage, fallbackUsage = 'optional' } = flags; let fallbackUsageStrategy = fallbackUsage; if (shouldEnsureFallbackUsage) { fallbackUsageStrategy = 'forced'; } else if (shouldEnsureFallbackUsage === false) { fallbackUsageStrategy = 'none'; } root.walkDecls(decl => { const parsedValue = valueParser(decl.value); parsedValue.walk(node => { if (!isFunction(node) || !isVar(node)) { return; } const [head, ...tail] = node.nodes; if (!head) { return; } if (isDeletedToken(head)) { const tokenMeta = renameMapping.find(({ path }) => getCSSCustomProperty(path) === head.value); if (!tokenMeta) { return; } if (tokenMeta.replacement) { const replacement = getCSSCustomProperty(tokenMeta.replacement); if (context.fix) { decl.value = decl.value.replace(head.value, replacement); return; } else { return stylelint.utils.report({ message: messages.tokenRemoved(head.value, replacement), node: decl, word: node.value, result, ruleName }); } } } if (isInvalidToken(head)) { return stylelint.utils.report({ message: messages.invalidToken(head.value), node: decl, word: node.value, result, ruleName }); } const isTokenUsage = isToken(head); if (!isTokenUsage) { return; } const hasFallback = tail.some(node => !isToken(node)); if (fallbackUsageStrategy === 'optional' || fallbackUsageStrategy === 'forced' && hasFallback || fallbackUsageStrategy === 'none' && !hasFallback) { return; } if (context.fix && !hasFallback) { const defaultFallback = getDefaultTokenValue(head); if (defaultFallback) { decl.value = decl.value.replace(head.value, `${head.value}, ${defaultFallback}`); return; } } stylelint.utils.report({ message: hasFallback ? messages.hasFallback : messages.missingFallback, node: decl, word: node.value, result, ruleName }); }); }); }; }; const rule = Object.assign(ruleBase, { ruleName: ruleName, messages: messages }); const plugin = stylelint.createPlugin(ruleName, rule); // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports export default plugin;