@atlaskit/eslint-plugin-ui-styling-standard
Version:
The eslint plugin to enforce and educate on Atlassian's UI Styling Standard
183 lines (182 loc) • 9.07 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _estraverse = _interopRequireDefault(require("estraverse"));
var _contextCompat = require("@atlaskit/eslint-utils/context-compat");
var _isSupportedImport = require("@atlaskit/eslint-utils/is-supported-import");
var _createRule = require("../utils/create-rule");
var schema = [{
type: 'object',
properties: {
importSources: {
type: 'array',
items: {
type: 'string'
},
uniqueItems: true
}
}
}];
var unwrapTypeScriptExpression = function unwrapTypeScriptExpression(node) {
var expression = node;
while (expression.type === 'TSAsExpression' || expression.type === 'TSTypeAssertion' || expression.type === 'TSNonNullExpression' || expression.type === 'TSSatisfiesExpression') {
expression = expression.expression;
}
return expression;
};
var isStaticContainerKey = function isStaticContainerKey(node) {
var _expression$expressio;
var expression = unwrapTypeScriptExpression(node);
if (expression.type === 'Literal') {
return typeof expression.value === 'string';
}
return expression.type === 'TemplateLiteral' && ((_expression$expressio = expression.expressions) === null || _expression$expressio === void 0 ? void 0 : _expression$expressio.length) === 0;
};
var getStaticString = function getStaticString(node) {
var _expression$expressio2;
if (!node) {
return undefined;
}
var expression = unwrapTypeScriptExpression(node);
if (expression.type === 'Literal') {
var value = expression.value;
return typeof value === 'string' ? value : undefined;
}
if (expression.type === 'TemplateLiteral' && ((_expression$expressio2 = expression.expressions) === null || _expression$expressio2 === void 0 ? void 0 : _expression$expressio2.length) === 0) {
var _expression$quasis;
return (_expression$quasis = expression.quasis) === null || _expression$quasis === void 0 || (_expression$quasis = _expression$quasis[0]) === null || _expression$quasis === void 0 || (_expression$quasis = _expression$quasis.value) === null || _expression$quasis === void 0 ? void 0 : _expression$quasis.raw;
}
return undefined;
};
var getPropertyName = function getPropertyName(node) {
var expression = unwrapTypeScriptExpression(node);
return expression.type === 'Identifier' ? expression.name : getStaticString(expression);
};
var isDimensionQuery = function isDimensionQuery(query) {
// Policy source: https://hello.atlassian.net/wiki/spaces/~7012119e15ecf78c74278a34bd6681745f7bb/pages/2595609668/What+would+it+take+to+get+container+queries+working
var condition = query.replace(/^@container\s+[-_a-zA-Z][-_a-zA-Z0-9]*\s*/, '');
return !condition.includes('style(') && /\b(?:width|height|min-width|max-width|min-height|max-height)\b/i.test(condition);
};
var getContainerName = function getContainerName(query) {
var match = query.match(/^@container\s+([-_a-zA-Z][-_a-zA-Z0-9]*)\b/);
return match === null || match === void 0 ? void 0 : match[1];
};
var isContainerDeclaration = function isContainerDeclaration(node) {
return getPropertyName(node) === 'containerName';
};
var isImportedContainerType = function isImportedContainerType(node, context) {
var expression = node;
var _loop = function _loop() {
var _typeAnnotation$typeN;
var _expression = expression,
typeAnnotation = _expression.typeAnnotation;
if ((typeAnnotation === null || typeAnnotation === void 0 ? void 0 : typeAnnotation.type) === 'TSTypeReference' && ((_typeAnnotation$typeN = typeAnnotation.typeName) === null || _typeAnnotation$typeN === void 0 ? void 0 : _typeAnnotation$typeN.type) === 'Identifier') {
var typeName = typeAnnotation.typeName.name;
var isImported = (0, _contextCompat.getSourceCode)(context).ast.body.some(function (statement) {
return statement.type === 'ImportDeclaration' && statement.source.value === '@atlaskit/css/at-rules/container' && statement.specifiers.some(function (specifier) {
return specifier.type === 'ImportDefaultSpecifier' && specifier.local.name === typeName;
});
});
if (isImported) {
return {
v: true
};
}
}
expression = expression.expression;
},
_ret;
while (expression.type === 'TSSatisfiesExpression') {
_ret = _loop();
if (_ret) return _ret.v;
}
return false;
};
var isContainerQuery = function isContainerQuery(node) {
var _expression$quasis2;
var expression = unwrapTypeScriptExpression(node);
if (expression.type === 'Literal' && typeof expression.value === 'string') {
return expression.value.includes('@container');
}
return expression.type === 'TemplateLiteral' && Boolean((_expression$quasis2 = expression.quasis) === null || _expression$quasis2 === void 0 ? void 0 : _expression$quasis2.some(function (quasi) {
var _quasi$value;
return (_quasi$value = quasi.value) === null || _quasi$value === void 0 || (_quasi$value = _quasi$value.raw) === null || _quasi$value === void 0 ? void 0 : _quasi$value.includes('@container');
}));
};
var rule = (0, _createRule.createLintRule)({
meta: {
name: 'no-container-queries',
docs: {
description: 'Prevents usage of @container query within css styling',
recommended: true,
severity: 'error'
},
messages: {
'no-container-queries': "Use a named container declared in this file and a width/height condition; type the static query key with satisfies and the default Container import from @atlaskit/css/at-rules/container.",
'no-container-style-queries': "Replace the style query with an explicit prop and local cssMap variant, or use a width/height-based query when size is the intended condition.",
'no-unbound-container-queries': "Name the container in the same file and reference it in the query, for example containerName: \"card\" with \"@container card (min-width: 30rem)\".",
'no-mismatched-container-name': "Use the same container name in this @container query and the containerName declaration in this file; rename the mismatched declaration or query.",
'no-non-dimension-container-queries': "Use a width/height condition for this named local container. Represent other conditions as explicit props selecting local styles."
},
type: 'problem',
schema: schema
},
create: function create(context) {
var importSources = (0, _isSupportedImport.getImportSources)(context);
var containerNames = new Set();
return {
Program: function Program(node) {
_estraverse.default.traverse(node, {
enter: function enter(child) {
if (child.type !== 'Property') {
return;
}
var property = child;
if (isContainerDeclaration(property.key)) {
var id = getStaticString(property.value);
if (id) {
containerNames.add(id);
}
}
},
fallback: 'iteration'
});
},
CallExpression: function CallExpression(node) {
var _getScope = (0, _contextCompat.getScope)(context, node),
references = _getScope.references;
if ((0, _isSupportedImport.isCss)(node.callee, references, importSources) || (0, _isSupportedImport.isStyled)(node.callee, references, importSources) || (0, _isSupportedImport.isKeyframes)(node.callee, references, importSources) || (0, _isSupportedImport.isCssMap)(node.callee, references, importSources) || (0, _isSupportedImport.isXcss)(node.callee, references, importSources)) {
_estraverse.default.traverse(node, {
enter: function enter(node) {
if (node.type !== 'Property') {
return;
}
var key = node.key;
if (!isContainerQuery(key)) {
return;
}
var query = getStaticString(key);
var queryName = query ? getContainerName(query) : undefined;
var reportMessageId = !query ? 'no-container-queries' : query.includes('style(') ? 'no-container-style-queries' : !queryName ? 'no-unbound-container-queries' : !containerNames.has(queryName) ? 'no-mismatched-container-name' : !isDimensionQuery(query) ? 'no-non-dimension-container-queries' : 'no-container-queries';
if (isImportedContainerType(key, context) && isStaticContainerKey(key.expression) && query && queryName && containerNames.has(queryName) && isDimensionQuery(query)) {
return;
}
context.report({
node: node.key,
messageId: reportMessageId
});
},
/**
* This is needed to handle unknown node types. Otherwise an error is thrown.
*/
fallback: 'iteration'
});
}
}
};
}
});
var _default = exports.default = rule;