@atlaskit/eslint-plugin-design-system
Version:
The essential plugin for use with the Atlassian Design System.
279 lines (276 loc) • 11.9 kB
JavaScript
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
import { createLintRule } from '../utils/create-lint-rule';
var EASING_KEYWORDS = ['ease', 'ease-in', 'ease-out', 'ease-in-out', 'linear', 'step-start', 'step-end'];
var KEYWORD_VALUES = ['none', 'all', 'inherit', 'initial', 'unset'];
var isDuration = function isDuration(token) {
return /^(?:0|\d+(?:\.\d+)?m?s)$/.test(token);
};
var isEasing = function isEasing(token) {
return EASING_KEYWORDS.includes(token) || token.startsWith('cubic-bezier(') || token.startsWith('steps(');
};
/**
* Tokenizes a CSS shorthand value string, respecting function boundaries.
* e.g. 'opacity 200ms cubic-bezier(0.4, 0, 0, 1) 0ms' →
* ['opacity', '200ms', 'cubic-bezier(0.4, 0, 0, 1)', '0ms']
* Splits on whitespace only when not inside parentheses.
*/
var tokenizeShorthand = function tokenizeShorthand(value) {
var tokens = [];
var depth = 0;
var current = '';
for (var i = 0; i < value.length; i++) {
var ch = value[i];
if (ch === '(') {
depth++;
current += ch;
} else if (ch === ')') {
depth--;
current += ch;
} else if (/\s/.test(ch) && depth === 0) {
if (current.length > 0) {
tokens.push(current);
current = '';
}
} else {
current += ch;
}
}
if (current.length > 0) {
tokens.push(current);
}
return tokens;
};
// Splits on top-level commas (outside function parens) — preserves cubic-bezier(...) commas.
var splitOnTopLevelCommas = function splitOnTopLevelCommas(value) {
var parts = [];
var depth = 0;
var current = '';
var _iterator = _createForOfIteratorHelper(value),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var ch = _step.value;
if (ch === '(') {
depth++;
current += ch;
} else if (ch === ')') {
depth--;
current += ch;
} else if (ch === ',' && depth === 0) {
parts.push(current.trim());
current = '';
} else {
current += ch;
}
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
if (current.trim().length > 0) {
parts.push(current.trim());
}
return parts;
};
var parseTransition = function parseTransition(value) {
var parts = tokenizeShorthand(value.trim());
var result = {};
var durationCount = 0;
var _iterator2 = _createForOfIteratorHelper(parts),
_step2;
try {
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
var part = _step2.value;
if (isDuration(part)) {
if (durationCount === 0) {
result.transitionDuration = part;
} else {
result.transitionDelay = part;
}
durationCount++;
} else if (isEasing(part)) {
result.transitionTimingFunction = part;
} else {
result.transitionProperty = part;
}
}
} catch (err) {
_iterator2.e(err);
} finally {
_iterator2.f();
}
return result;
};
var parseAnimation = function parseAnimation(value) {
var parts = tokenizeShorthand(value.trim());
var result = {};
var durationCount = 0;
var _iterator3 = _createForOfIteratorHelper(parts),
_step3;
try {
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
var part = _step3.value;
if (isDuration(part)) {
if (durationCount === 0) {
result.animationDuration = part;
} else {
result.animationDelay = part;
}
durationCount++;
} else if (isEasing(part)) {
result.animationTimingFunction = part;
} else if (part === 'infinite' || /^\d+(\.\d+)?$/.test(part)) {
result.animationIterationCount = part;
} else if (['normal', 'reverse', 'alternate', 'alternate-reverse'].includes(part)) {
result.animationDirection = part;
} else if (['none', 'forwards', 'backwards', 'both'].includes(part)) {
result.animationFillMode = part;
} else if (['running', 'paused'].includes(part)) {
result.animationPlayState = part;
} else {
result.animationName = part;
}
}
} catch (err) {
_iterator3.e(err);
} finally {
_iterator3.f();
}
return result;
};
// Combine sub-property values across comma-separated transitions/animations.
// If no segment explicitly set this sub-property, omit it entirely.
// Otherwise, fill missing slots with the CSS spec default to preserve positional alignment.
var combineSubPropertyValues = function combineSubPropertyValues(segments, subProperty, defaultValue) {
if (segments.every(function (s) {
return s[subProperty] === undefined;
})) {
return undefined;
}
return segments.map(function (s) {
var _s$subProperty;
return (_s$subProperty = s[subProperty]) !== null && _s$subProperty !== void 0 ? _s$subProperty : defaultValue;
}).join(', ');
};
var buildTransitionFix = function buildTransitionFix(segments, indent) {
var lines = [];
var property = combineSubPropertyValues(segments, 'transitionProperty', 'all');
var duration = combineSubPropertyValues(segments, 'transitionDuration', '0s');
var timing = combineSubPropertyValues(segments, 'transitionTimingFunction', 'ease');
var delay = combineSubPropertyValues(segments, 'transitionDelay', '0s');
if (property !== undefined) lines.push("transitionProperty: '".concat(property, "'"));
if (duration !== undefined) lines.push("transitionDuration: '".concat(duration, "'"));
if (timing !== undefined) lines.push("transitionTimingFunction: '".concat(timing, "'"));
if (delay !== undefined) lines.push("transitionDelay: '".concat(delay, "'"));
return lines.join(",\n".concat(indent));
};
var buildAnimationFix = function buildAnimationFix(segments, indent) {
var lines = [];
var name = combineSubPropertyValues(segments, 'animationName', 'none');
var duration = combineSubPropertyValues(segments, 'animationDuration', '0s');
var timing = combineSubPropertyValues(segments, 'animationTimingFunction', 'ease');
var delay = combineSubPropertyValues(segments, 'animationDelay', '0s');
var iter = combineSubPropertyValues(segments, 'animationIterationCount', '1');
var direction = combineSubPropertyValues(segments, 'animationDirection', 'normal');
var fill = combineSubPropertyValues(segments, 'animationFillMode', 'none');
var playState = combineSubPropertyValues(segments, 'animationPlayState', 'running');
if (name !== undefined) lines.push("animationName: '".concat(name, "'"));
if (duration !== undefined) lines.push("animationDuration: '".concat(duration, "'"));
if (timing !== undefined) lines.push("animationTimingFunction: '".concat(timing, "'"));
if (delay !== undefined) lines.push("animationDelay: '".concat(delay, "'"));
if (iter !== undefined) lines.push("animationIterationCount: '".concat(iter, "'"));
if (direction !== undefined) lines.push("animationDirection: '".concat(direction, "'"));
if (fill !== undefined) lines.push("animationFillMode: '".concat(fill, "'"));
if (playState !== undefined) lines.push("animationPlayState: '".concat(playState, "'"));
return lines.join(",\n".concat(indent));
};
var TRANSITION_SUB_PROPERTIES = ['transitionProperty', 'transitionDuration', 'transitionTimingFunction', 'transitionDelay'];
var ANIMATION_SUB_PROPERTIES = ['animationName', 'animationDuration', 'animationTimingFunction', 'animationDelay', 'animationIterationCount', 'animationDirection', 'animationFillMode', 'animationPlayState'];
var executeExpandTransitionRule = function executeExpandTransitionRule(context, node, property) {
var _context$sourceCode, _node$loc;
if (node.value.type === 'CallExpression') {
return;
}
if (node.value.type === 'TemplateLiteral') {
return;
}
if (node.value.type !== 'Literal' || typeof node.value.value !== 'string') {
return;
}
var rawValue = node.value.value;
if (KEYWORD_VALUES.includes(rawValue)) {
return;
}
var subProperties = property === 'transition' ? TRANSITION_SUB_PROPERTIES : ANIMATION_SUB_PROPERTIES;
// Extract leading whitespace to preserve indentation style (tabs vs spaces)
var sourceCode = (_context$sourceCode = context.sourceCode) !== null && _context$sourceCode !== void 0 ? _context$sourceCode : context.getSourceCode();
var nodeStart = (_node$loc = node.loc) === null || _node$loc === void 0 ? void 0 : _node$loc.start;
var indent = '\t';
if (nodeStart) {
var _sourceCode$lines;
var lineText = (_sourceCode$lines = sourceCode.lines[nodeStart.line - 1]) !== null && _sourceCode$lines !== void 0 ? _sourceCode$lines : '';
var leadingWhitespace = lineText.match(/^(\s*)/);
if (leadingWhitespace) {
indent = leadingWhitespace[1];
}
}
var segmentStrings = splitOnTopLevelCommas(rawValue);
if (property === 'transition') {
var segments = segmentStrings.map(parseTransition);
var fixText = buildTransitionFix(segments, indent);
context.report({
node: node,
messageId: 'expandTransitionShorthand',
data: {
property: property,
subProperties: subProperties.join(', ')
},
fix: function fix(fixer) {
return fixer.replaceText(node, fixText);
}
});
} else {
var _segments = segmentStrings.map(parseAnimation);
var _fixText = buildAnimationFix(_segments, indent);
context.report({
node: node,
messageId: 'expandTransitionShorthand',
data: {
property: property,
subProperties: subProperties.join(', ')
},
fix: function fix(fixer) {
return fixer.replaceText(node, _fixText);
}
});
}
};
var rule = createLintRule({
meta: {
name: 'expand-motion-shorthand',
type: 'suggestion',
fixable: 'code',
docs: {
description: 'Expands `transition` and `animation` CSS shorthand properties into their individual sub-properties, so individual values can be replaced with motion tokens.',
recommended: false,
severity: 'warn'
},
messages: {
expandTransitionShorthand: "Use {{ subProperties }} instead of the '{{ property }}' shorthand so that individual values can be replaced with motion tokens."
}
},
create: function create(context) {
return {
'Property[key.name="transition"]': function PropertyKeyNameTransition(node) {
executeExpandTransitionRule(context, node, 'transition');
},
'Property[key.name="animation"]': function PropertyKeyNameAnimation(node) {
executeExpandTransitionRule(context, node, 'animation');
}
};
}
});
export default rule;