UNPKG

@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
"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;