@atlaskit/eslint-plugin-ui-styling-standard
Version:
The eslint plugin to enforce and educate on Atlassian's UI Styling Standard
259 lines (254 loc) • 9.27 kB
JavaScript
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
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 _regeneratorRuntime from "@babel/runtime/regenerator";
import { isNesting, isPseudo } from 'postcss-selector-parser';
import { allowedPseudos } from './constants';
import { getSourceLocationFromRange } from './get-source-location-from-range';
import { getRangeFromNode } from './source-position-utils';
export function lintSelector(args) {
checkNoAmbiguousPseudos(args);
var pseudos = [];
var nestings = [];
args.selector.walk(function (node) {
if (isPseudo(node)) {
pseudos.push(node);
} else if (isNesting(node)) {
nestings.push(node);
}
});
checkNoRestrictedPseudos(args, pseudos);
checkNoLegacyPseudoElementSyntax(args, pseudos);
checkNoIncreasedSpecificity(args, nestings);
}
var legacyPseudoElements = new Set([':after', ':before', ':first-letter', ':first-line']);
/**
* Ensures the use of a nesting selector `&` with pseudo-selectors.
*/
function checkNoAmbiguousPseudos(_ref) {
var context = _ref.context,
sourceNode = _ref.sourceNode,
selector = _ref.selector,
config = _ref.config,
isXcssCall = _ref.isXcssCall;
if (isXcssCall) {
/**
* The `xcss` API does not currently support a leading `&`.
*
* This may change in the future, but we are ignoring `xcss` calls for now.
*/
return;
}
if (selector.first.type !== 'pseudo') {
return;
}
var range = getRangeFromNode({
node: selector.first,
sourceNode: sourceNode
});
var loc = getSourceLocationFromRange(context, range);
if (config.shouldAlwaysInsertNestingSelectorForAmbiguousPseudos) {
context.report({
loc: loc,
messageId: 'no-ambiguous-pseudos',
fix: /*#__PURE__*/_regeneratorRuntime.mark(function fix(fixer) {
return _regeneratorRuntime.wrap(function (_context) {
while (1) switch (_context.prev = _context.next) {
case 0:
_context.next = 1;
return fixer.insertTextBeforeRange(range, '&');
case 1:
case "end":
return _context.stop();
}
}, fix);
})
});
} else {
context.report({
loc: loc,
messageId: 'no-ambiguous-pseudos',
suggest: [{
messageId: 'insert-nesting-selector',
fix: /*#__PURE__*/_regeneratorRuntime.mark(function fix(fixer) {
return _regeneratorRuntime.wrap(function (_context2) {
while (1) switch (_context2.prev = _context2.next) {
case 0:
_context2.next = 1;
return fixer.insertTextBeforeRange(range, '&');
case 1:
case "end":
return _context2.stop();
}
}, fix);
})
}]
});
}
}
function checkNoRestrictedPseudos(_ref2, pseudos) {
var context = _ref2.context,
sourceNode = _ref2.sourceNode;
var _iterator = _createForOfIteratorHelper(pseudos),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var pseudo = _step.value;
if (allowedPseudos.has(pseudo.value) || isInAllowedPseudoChain(pseudo)) {
continue;
}
/**
* If someone has written `:after` it is allowed,
* they've just used the legacy syntax.
*
* This will be caught by the `no-legacy-pseudo-element-syntax` check.
*/
if (legacyPseudoElements.has(pseudo.value) && allowedPseudos.has(":".concat(pseudo.value))) {
continue;
}
if (!allowedPseudos.has(pseudo.value)) {
var range = getRangeFromNode({
node: pseudo,
sourceNode: sourceNode
});
var loc = getSourceLocationFromRange(context, range);
context.report({
loc: loc,
messageId: 'no-restricted-pseudos',
data: {
pseudo: pseudo.value
}
});
}
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
}
/**
* The allowlist includes chained pseudos such as `:focus:not(:focus-visible)` whose parts are not
* all allowed on their own, so a pseudo is also allowed when a run of adjacent pseudos containing
* it matches an allowlisted chain.
*/
function isInAllowedPseudoChain(pseudo) {
var chain = [pseudo];
for (var node = pseudo.prev(); isPseudo(node); node = node.prev()) {
chain.unshift(node);
}
for (var _node = pseudo.next(); isPseudo(_node); _node = _node.next()) {
chain.push(_node);
}
var index = chain.indexOf(pseudo);
for (var start = 0; start <= index; start++) {
for (var end = index + 1; end <= chain.length; end++) {
var text = chain.slice(start, end).map(function (node) {
return String(node).replace(/\s+/g, '');
}).join('');
if (allowedPseudos.has(text)) {
return true;
}
}
}
return false;
}
/**
* The original four pseudo-elements have a legacy single colon syntax.
*
* https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-elements
*/
function checkNoLegacyPseudoElementSyntax(_ref3, pseudos) {
var context = _ref3.context,
sourceNode = _ref3.sourceNode;
var _iterator2 = _createForOfIteratorHelper(pseudos),
_step2;
try {
var _loop = function _loop() {
var pseudo = _step2.value;
if (legacyPseudoElements.has(pseudo.value)) {
var range = getRangeFromNode({
node: pseudo,
sourceNode: sourceNode
});
var loc = getSourceLocationFromRange(context, range);
context.report({
loc: loc,
messageId: 'no-legacy-pseudo-element-syntax',
fix: /*#__PURE__*/_regeneratorRuntime.mark(function fix(fixer) {
return _regeneratorRuntime.wrap(function (_context3) {
while (1) switch (_context3.prev = _context3.next) {
case 0:
_context3.next = 1;
return fixer.insertTextBeforeRange(range, ':');
case 1:
case "end":
return _context3.stop();
}
}, fix);
})
});
}
};
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
_loop();
}
} catch (err) {
_iterator2.e(err);
} finally {
_iterator2.f();
}
}
function checkNoIncreasedSpecificity(_ref4, nestings) {
var context = _ref4.context,
sourceNode = _ref4.sourceNode;
var _iterator3 = _createForOfIteratorHelper(nestings),
_step3;
try {
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
var nesting = _step3.value;
/**
* If it's not the start of a chain, then it's already been reported.
*/
if (isNesting(nesting.prev())) {
continue;
}
var next = nesting.next();
/**
* If it is followed by something other than another nesting selector,
* then it isn't just there to increase specificity — it is actually necessary.
*/
if (next && !isNesting(next)) {
continue;
}
var lastNesting = nesting;
while (isNesting(next)) {
lastNesting = next;
next = next.next();
}
var _getRangeFromNode = getRangeFromNode({
node: nesting,
sourceNode: sourceNode
}),
_getRangeFromNode2 = _slicedToArray(_getRangeFromNode, 1),
start = _getRangeFromNode2[0];
var _getRangeFromNode3 = getRangeFromNode({
node: lastNesting,
sourceNode: sourceNode
}),
_getRangeFromNode4 = _slicedToArray(_getRangeFromNode3, 2),
_ = _getRangeFromNode4[0],
end = _getRangeFromNode4[1];
var loc = getSourceLocationFromRange(context, [start, end]);
context.report({
loc: loc,
messageId: 'no-increased-specificity'
});
}
} catch (err) {
_iterator3.e(err);
} finally {
_iterator3.f();
}
}