@atlaskit/eslint-plugin-design-system 8.13.1 → 8.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +1 -0
- package/constellation/index/usage.mdx +41 -0
- package/dist/cjs/presets/all.codegen.js +2 -1
- package/dist/cjs/rules/index.codegen.js +3 -1
- package/dist/cjs/rules/no-css-tagged-template-expression/create-no-tagged-template-expression-rule/generate.js +142 -0
- package/dist/cjs/rules/no-css-tagged-template-expression/create-no-tagged-template-expression-rule/get-tagged-template-expression-offset.js +33 -0
- package/dist/cjs/rules/no-css-tagged-template-expression/create-no-tagged-template-expression-rule/index.js +89 -0
- package/dist/cjs/rules/no-css-tagged-template-expression/create-no-tagged-template-expression-rule/to-arguments.js +251 -0
- package/dist/cjs/rules/no-css-tagged-template-expression/create-no-tagged-template-expression-rule/types.js +5 -0
- package/dist/cjs/rules/no-css-tagged-template-expression/index.js +26 -0
- package/dist/cjs/rules/no-css-tagged-template-expression/is-supported-import.js +27 -0
- package/dist/es2019/presets/all.codegen.js +2 -1
- package/dist/es2019/rules/index.codegen.js +3 -1
- package/dist/es2019/rules/no-css-tagged-template-expression/create-no-tagged-template-expression-rule/generate.js +106 -0
- package/dist/es2019/rules/no-css-tagged-template-expression/create-no-tagged-template-expression-rule/get-tagged-template-expression-offset.js +29 -0
- package/dist/es2019/rules/no-css-tagged-template-expression/create-no-tagged-template-expression-rule/index.js +59 -0
- package/dist/es2019/rules/no-css-tagged-template-expression/create-no-tagged-template-expression-rule/to-arguments.js +187 -0
- package/dist/es2019/rules/no-css-tagged-template-expression/create-no-tagged-template-expression-rule/types.js +1 -0
- package/dist/es2019/rules/no-css-tagged-template-expression/index.js +20 -0
- package/dist/es2019/rules/no-css-tagged-template-expression/is-supported-import.js +19 -0
- package/dist/esm/presets/all.codegen.js +2 -1
- package/dist/esm/rules/index.codegen.js +3 -1
- package/dist/esm/rules/no-css-tagged-template-expression/create-no-tagged-template-expression-rule/generate.js +135 -0
- package/dist/esm/rules/no-css-tagged-template-expression/create-no-tagged-template-expression-rule/get-tagged-template-expression-offset.js +27 -0
- package/dist/esm/rules/no-css-tagged-template-expression/create-no-tagged-template-expression-rule/index.js +82 -0
- package/dist/esm/rules/no-css-tagged-template-expression/create-no-tagged-template-expression-rule/to-arguments.js +244 -0
- package/dist/esm/rules/no-css-tagged-template-expression/create-no-tagged-template-expression-rule/types.js +1 -0
- package/dist/esm/rules/no-css-tagged-template-expression/index.js +20 -0
- package/dist/esm/rules/no-css-tagged-template-expression/is-supported-import.js +21 -0
- package/dist/types/index.codegen.d.ts +1 -0
- package/dist/types/presets/all.codegen.d.ts +2 -1
- package/dist/types/rules/index.codegen.d.ts +1 -0
- package/dist/types/rules/no-css-tagged-template-expression/create-no-tagged-template-expression-rule/generate.d.ts +2 -0
- package/dist/types/rules/no-css-tagged-template-expression/create-no-tagged-template-expression-rule/get-tagged-template-expression-offset.d.ts +4 -0
- package/dist/types/rules/no-css-tagged-template-expression/create-no-tagged-template-expression-rule/index.d.ts +6 -0
- package/dist/types/rules/no-css-tagged-template-expression/create-no-tagged-template-expression-rule/to-arguments.d.ts +4 -0
- package/dist/types/rules/no-css-tagged-template-expression/create-no-tagged-template-expression-rule/types.d.ts +25 -0
- package/dist/types/rules/no-css-tagged-template-expression/index.d.ts +2 -0
- package/dist/types/rules/no-css-tagged-template-expression/is-supported-import.d.ts +12 -0
- package/dist/types-ts4.5/index.codegen.d.ts +1 -0
- package/dist/types-ts4.5/presets/all.codegen.d.ts +2 -1
- package/dist/types-ts4.5/rules/index.codegen.d.ts +1 -0
- package/dist/types-ts4.5/rules/no-css-tagged-template-expression/create-no-tagged-template-expression-rule/generate.d.ts +2 -0
- package/dist/types-ts4.5/rules/no-css-tagged-template-expression/create-no-tagged-template-expression-rule/get-tagged-template-expression-offset.d.ts +4 -0
- package/dist/types-ts4.5/rules/no-css-tagged-template-expression/create-no-tagged-template-expression-rule/index.d.ts +6 -0
- package/dist/types-ts4.5/rules/no-css-tagged-template-expression/create-no-tagged-template-expression-rule/to-arguments.d.ts +4 -0
- package/dist/types-ts4.5/rules/no-css-tagged-template-expression/create-no-tagged-template-expression-rule/types.d.ts +25 -0
- package/dist/types-ts4.5/rules/no-css-tagged-template-expression/index.d.ts +2 -0
- package/dist/types-ts4.5/rules/no-css-tagged-template-expression/is-supported-import.d.ts +12 -0
- package/package.json +1 -1
- package/report.api.md +2 -0
- package/tmp/api-report-tmp.d.ts +2 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var _createRule = require("../utils/create-rule");
|
|
8
|
+
var _createNoTaggedTemplateExpressionRule = require("./create-no-tagged-template-expression-rule");
|
|
9
|
+
var _isSupportedImport = require("./is-supported-import");
|
|
10
|
+
var rule = (0, _createRule.createLintRule)({
|
|
11
|
+
meta: {
|
|
12
|
+
name: 'no-css-tagged-template-expression',
|
|
13
|
+
fixable: 'code',
|
|
14
|
+
type: 'problem',
|
|
15
|
+
docs: {
|
|
16
|
+
description: 'Disallows any `css` tagged template expressions that originate from Emotion, Styled Components or Compiled',
|
|
17
|
+
recommended: false,
|
|
18
|
+
severity: 'error'
|
|
19
|
+
},
|
|
20
|
+
messages: {
|
|
21
|
+
unexpectedTaggedTemplate: 'Unexpected `css` tagged template expression'
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
create: (0, _createNoTaggedTemplateExpressionRule.createNoTaggedTemplateExpressionRule)((0, _isSupportedImport.isSupportedImport)('css'), 'unexpectedTaggedTemplate')
|
|
25
|
+
});
|
|
26
|
+
var _default = exports.default = rule;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.isSupportedImport = exports.SUPPORTED_IMPORTS = void 0;
|
|
7
|
+
var SUPPORTED_IMPORTS = exports.SUPPORTED_IMPORTS = {
|
|
8
|
+
compiled: '@compiled/react',
|
|
9
|
+
emotionReact: '@emotion/react',
|
|
10
|
+
emotionCore: '@emotion/core',
|
|
11
|
+
styledComponents: 'styled-components'
|
|
12
|
+
};
|
|
13
|
+
var isImportSpecifierWrapper = function isImportSpecifierWrapper(name) {
|
|
14
|
+
return function (def) {
|
|
15
|
+
var _def$parent, _def$parent2, _def$parent3, _def$parent4, _def$parent5;
|
|
16
|
+
return def.node.type === 'ImportSpecifier' && def.node.imported.type === 'Identifier' && def.node.imported.name === name && ((_def$parent = def.parent) === null || _def$parent === void 0 ? void 0 : _def$parent.type) === 'ImportDeclaration' && (((_def$parent2 = def.parent) === null || _def$parent2 === void 0 ? void 0 : _def$parent2.source.value) === SUPPORTED_IMPORTS.compiled || ((_def$parent3 = def.parent) === null || _def$parent3 === void 0 ? void 0 : _def$parent3.source.value) === SUPPORTED_IMPORTS.emotionReact || ((_def$parent4 = def.parent) === null || _def$parent4 === void 0 ? void 0 : _def$parent4.source.value) === SUPPORTED_IMPORTS.emotionCore || ((_def$parent5 = def.parent) === null || _def$parent5 === void 0 ? void 0 : _def$parent5.source.value) === SUPPORTED_IMPORTS.styledComponents);
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
var isSupportedImport = exports.isSupportedImport = function isSupportedImport(name) {
|
|
20
|
+
var isImportSpecifier = isImportSpecifierWrapper(name);
|
|
21
|
+
return function (node, references) {
|
|
22
|
+
return node.type === 'Identifier' && references.some(function (reference) {
|
|
23
|
+
var _reference$resolved;
|
|
24
|
+
return reference.identifier === node && ((_reference$resolved = reference.resolved) === null || _reference$resolved === void 0 ? void 0 : _reference$resolved.defs.some(isImportSpecifier));
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* THIS FILE WAS CREATED VIA CODEGEN DO NOT MODIFY {@see http://go/af-codegen}
|
|
3
|
-
* @codegen <<SignedSource::
|
|
3
|
+
* @codegen <<SignedSource::594898d8c5dc8b9a5610d62e7f300a53>>
|
|
4
4
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
5
5
|
*/
|
|
6
6
|
export default {
|
|
@@ -11,6 +11,7 @@ export default {
|
|
|
11
11
|
'@atlaskit/design-system/ensure-design-token-usage/preview': 'warn',
|
|
12
12
|
'@atlaskit/design-system/icon-label': 'warn',
|
|
13
13
|
'@atlaskit/design-system/no-banned-imports': 'error',
|
|
14
|
+
'@atlaskit/design-system/no-css-tagged-template-expression': 'error',
|
|
14
15
|
'@atlaskit/design-system/no-deprecated-apis': 'error',
|
|
15
16
|
'@atlaskit/design-system/no-deprecated-design-token-usage': 'warn',
|
|
16
17
|
'@atlaskit/design-system/no-deprecated-imports': 'error',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* THIS FILE WAS CREATED VIA CODEGEN DO NOT MODIFY {@see http://go/af-codegen}
|
|
3
|
-
* @codegen <<SignedSource::
|
|
3
|
+
* @codegen <<SignedSource::8927e5adaec5639c9712dbfb26968de4>>
|
|
4
4
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
5
5
|
*/
|
|
6
6
|
import consistentCssPropUsage from './consistent-css-prop-usage';
|
|
@@ -8,6 +8,7 @@ import ensureDesignTokenUsage from './ensure-design-token-usage';
|
|
|
8
8
|
import ensureDesignTokenUsagePreview from './ensure-design-token-usage-preview';
|
|
9
9
|
import iconLabel from './icon-label';
|
|
10
10
|
import noBannedImports from './no-banned-imports';
|
|
11
|
+
import noCssTaggedTemplateExpression from './no-css-tagged-template-expression';
|
|
11
12
|
import noDeprecatedApis from './no-deprecated-apis';
|
|
12
13
|
import noDeprecatedDesignTokenUsage from './no-deprecated-design-token-usage';
|
|
13
14
|
import noDeprecatedImports from './no-deprecated-imports';
|
|
@@ -27,6 +28,7 @@ export default {
|
|
|
27
28
|
'ensure-design-token-usage/preview': ensureDesignTokenUsagePreview,
|
|
28
29
|
'icon-label': iconLabel,
|
|
29
30
|
'no-banned-imports': noBannedImports,
|
|
31
|
+
'no-css-tagged-template-expression': noCssTaggedTemplateExpression,
|
|
30
32
|
'no-deprecated-apis': noDeprecatedApis,
|
|
31
33
|
'no-deprecated-design-token-usage': noDeprecatedDesignTokenUsage,
|
|
32
34
|
'no-deprecated-imports': noDeprecatedImports,
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// Original source from Compiled https://github.com/atlassian-labs/compiled/blob/master/packages/eslint-plugin/src/utils/create-no-tagged-template-expression-rule/generate.ts
|
|
2
|
+
|
|
3
|
+
const createKey = key => {
|
|
4
|
+
if (/^\w+$/g.test(key)) {
|
|
5
|
+
return key;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// Wrap the key in square brackets if the key includes a binding. i.e.`.foo ${BINDING_NAME} .bar`
|
|
9
|
+
if (key.charAt(0) === '`' && key.charAt(key.length - 1) === '`') {
|
|
10
|
+
return `[${key}]`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Wrap the key in quotes if it uses unsafe characters
|
|
14
|
+
if (!key.includes('"')) {
|
|
15
|
+
return `"${key}"`;
|
|
16
|
+
}
|
|
17
|
+
return `[\`${key}\`]`;
|
|
18
|
+
};
|
|
19
|
+
const addQuotes = literal => literal[0] === `"` ? `'${literal}'` : `"${literal}"`;
|
|
20
|
+
const createValue = value => {
|
|
21
|
+
const {
|
|
22
|
+
type
|
|
23
|
+
} = value;
|
|
24
|
+
if (type === 'expression') {
|
|
25
|
+
return value.expression.trim();
|
|
26
|
+
}
|
|
27
|
+
const literal = value.value;
|
|
28
|
+
return typeof literal === 'string' && literal[0] !== '`' ? addQuotes(literal) : literal;
|
|
29
|
+
};
|
|
30
|
+
const indent = (offset, level) => ' '.repeat(offset + level * 2);
|
|
31
|
+
const generateBlock = (blocks, offset, level) => {
|
|
32
|
+
let chars = '{' + '\n';
|
|
33
|
+
for (const [i, block] of blocks.entries()) {
|
|
34
|
+
chars += indent(offset, level + 1);
|
|
35
|
+
switch (block.type) {
|
|
36
|
+
case 'declaration':
|
|
37
|
+
{
|
|
38
|
+
chars += createKey(block.property) + ': ' + createValue(block.value);
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
case 'rule':
|
|
42
|
+
{
|
|
43
|
+
chars += createKey(block.selector) + ': ' + generateArguments(block.declarations, offset, level + 1).trim();
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
default:
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
if (blocks.length > 1 && i < blocks.length - 1) {
|
|
50
|
+
chars += ',';
|
|
51
|
+
chars += '\n';
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
chars += '\n' + indent(offset, level) + '}';
|
|
55
|
+
return chars;
|
|
56
|
+
};
|
|
57
|
+
const generateArguments = (args, offset, level) => {
|
|
58
|
+
let chars = '';
|
|
59
|
+
if (level > 1 && args.length > 1) {
|
|
60
|
+
chars += '[';
|
|
61
|
+
}
|
|
62
|
+
for (const [i, arg] of args.entries()) {
|
|
63
|
+
switch (arg.type) {
|
|
64
|
+
case 'block':
|
|
65
|
+
{
|
|
66
|
+
if (args.length === 1) {
|
|
67
|
+
chars += generateBlock(arg.blocks, offset, level).trim();
|
|
68
|
+
} else {
|
|
69
|
+
chars += '\n';
|
|
70
|
+
chars += indent(offset, level + 1);
|
|
71
|
+
chars += generateBlock(arg.blocks, offset, level + 1).trim();
|
|
72
|
+
}
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
case 'expression':
|
|
76
|
+
{
|
|
77
|
+
chars += '\n';
|
|
78
|
+
chars += indent(offset, level + 1);
|
|
79
|
+
chars += arg.expression;
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
default:
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
if (args.length > 1 && i < args.length - 1) {
|
|
86
|
+
chars += ',';
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (level > 1 && args.length > 1) {
|
|
90
|
+
chars += '\n';
|
|
91
|
+
chars += indent(offset, level);
|
|
92
|
+
chars += ']';
|
|
93
|
+
}
|
|
94
|
+
return chars;
|
|
95
|
+
};
|
|
96
|
+
export const generate = (args, offset, level = 0) => {
|
|
97
|
+
let chars = '';
|
|
98
|
+
chars += '(';
|
|
99
|
+
chars += generateArguments(args, offset, level);
|
|
100
|
+
if (args.length > 1) {
|
|
101
|
+
chars += '\n';
|
|
102
|
+
chars += indent(offset, level);
|
|
103
|
+
}
|
|
104
|
+
chars += ')';
|
|
105
|
+
return chars;
|
|
106
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Original source from Compiled https://github.com/atlassian-labs/compiled/blob/master/packages/eslint-plugin/src/utils/create-no-tagged-template-expression-rule/get-tagged-template-expression-offset.ts
|
|
2
|
+
|
|
3
|
+
export const getTaggedTemplateExpressionOffset = node => {
|
|
4
|
+
const {
|
|
5
|
+
parent
|
|
6
|
+
} = node;
|
|
7
|
+
switch (parent.type || '') {
|
|
8
|
+
case 'ExportDefaultDeclaration':
|
|
9
|
+
{
|
|
10
|
+
return parent.loc.start.column;
|
|
11
|
+
}
|
|
12
|
+
case 'VariableDeclarator':
|
|
13
|
+
{
|
|
14
|
+
const maybeVariableDeclaration = parent.parent;
|
|
15
|
+
if (maybeVariableDeclaration.type === 'VariableDeclaration') {
|
|
16
|
+
const maybeExportNamedDeclaration = maybeVariableDeclaration.parent;
|
|
17
|
+
if (maybeExportNamedDeclaration.type === 'ExportNamedDeclaration') {
|
|
18
|
+
return maybeExportNamedDeclaration.loc.start.column;
|
|
19
|
+
} else {
|
|
20
|
+
return maybeVariableDeclaration.loc.start.column;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
default:
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
return node.loc.start.column;
|
|
29
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Original source from Compiled https://github.com/atlassian-labs/compiled/blob/master/packages/eslint-plugin/src/utils/create-no-tagged-template-expression-rule/index.ts
|
|
2
|
+
|
|
3
|
+
import { generate } from './generate';
|
|
4
|
+
import { getTaggedTemplateExpressionOffset } from './get-tagged-template-expression-offset';
|
|
5
|
+
import { toArguments } from './to-arguments';
|
|
6
|
+
export const createNoTaggedTemplateExpressionRule = (isUsage, messageId) => context => ({
|
|
7
|
+
TaggedTemplateExpression(node) {
|
|
8
|
+
const {
|
|
9
|
+
references
|
|
10
|
+
} = context.getScope();
|
|
11
|
+
if (!isUsage(node.tag, references)) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
context.report({
|
|
15
|
+
messageId,
|
|
16
|
+
node,
|
|
17
|
+
*fix(fixer) {
|
|
18
|
+
const {
|
|
19
|
+
quasi
|
|
20
|
+
} = node;
|
|
21
|
+
const source = context.getSourceCode();
|
|
22
|
+
|
|
23
|
+
// TODO Eventually handle comments instead of skipping them
|
|
24
|
+
// Skip auto-fixing comments
|
|
25
|
+
if (quasi.quasis.map(q => q.value.raw).join('').match(/\/\*[\s\S]*\*\//g)) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Replace empty tagged template expression with the equivalent object call expression
|
|
30
|
+
if (!quasi.expressions.length && quasi.quasis.length === 1 && !quasi.quasis[0].value.raw.trim()) {
|
|
31
|
+
yield fixer.replaceText(quasi, '({})');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const args = toArguments(source, quasi);
|
|
35
|
+
|
|
36
|
+
// Skip invalid CSS
|
|
37
|
+
if (args.length < 1) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const oldCode = source.getText(node);
|
|
41
|
+
// Remove quasi:
|
|
42
|
+
// styled.div<Props>`
|
|
43
|
+
// color: red;
|
|
44
|
+
// `
|
|
45
|
+
// becomes
|
|
46
|
+
// styled.div<Props>
|
|
47
|
+
const withoutQuasi = oldCode.replace(source.getText(quasi), '');
|
|
48
|
+
const newCode = withoutQuasi +
|
|
49
|
+
// Indent the arguments after the tagged template expression range
|
|
50
|
+
generate(args, getTaggedTemplateExpressionOffset(node));
|
|
51
|
+
if (oldCode === newCode) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
yield fixer.insertTextBefore(node, newCode);
|
|
55
|
+
yield fixer.remove(node);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
});
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// Original source from Compiled https://github.com/atlassian-labs/compiled/blob/master/packages/eslint-plugin/src/utils/create-no-tagged-template-expression-rule/to-arguments.ts
|
|
2
|
+
|
|
3
|
+
const getArguments = (chars, expressions = []) => {
|
|
4
|
+
if (!chars.trim().length && expressions) {
|
|
5
|
+
return expressions.map(({
|
|
6
|
+
expression
|
|
7
|
+
}) => ({
|
|
8
|
+
type: 'expression',
|
|
9
|
+
expression
|
|
10
|
+
}));
|
|
11
|
+
}
|
|
12
|
+
const args = [];
|
|
13
|
+
if (!chars.includes(':')) {
|
|
14
|
+
return args;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Split the property and value
|
|
18
|
+
// e.g. `color: red` becomes ['color', 'red']
|
|
19
|
+
// also consider `background: url("https://some-url-b")`, which has a colon in the value.
|
|
20
|
+
const [property, ...v] = chars.split(':');
|
|
21
|
+
const value = v.join(':');
|
|
22
|
+
|
|
23
|
+
// Extract any expressions listed before the property that were not delimited by a ;
|
|
24
|
+
if (expressions.length) {
|
|
25
|
+
const lastPropertyRe = /[\w-]+(?![\s\S]*[\w-]+)/g;
|
|
26
|
+
const prop = lastPropertyRe.exec(property);
|
|
27
|
+
if (prop) {
|
|
28
|
+
let i = 0;
|
|
29
|
+
while (expressions[i] && expressions[i].pos < prop.index) {
|
|
30
|
+
args.push({
|
|
31
|
+
type: 'expression',
|
|
32
|
+
expression: expressions[i].expression
|
|
33
|
+
});
|
|
34
|
+
i++;
|
|
35
|
+
}
|
|
36
|
+
// Remove any expressions that have been added as an arg as they are not part of the declaration
|
|
37
|
+
expressions = expressions.slice(i);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const getValue = () => {
|
|
41
|
+
if (!value.trim().length && expressions.length) {
|
|
42
|
+
return {
|
|
43
|
+
type: 'expression',
|
|
44
|
+
expression: expressions.map(e => e.expression).join('')
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
if (expressions.length) {
|
|
48
|
+
// When there are expressions in the value, insert the expressions and wrap the value in a template literal
|
|
49
|
+
let val = chars;
|
|
50
|
+
let offset = 1;
|
|
51
|
+
for (const {
|
|
52
|
+
expression,
|
|
53
|
+
pos
|
|
54
|
+
} of expressions) {
|
|
55
|
+
const interpolation = '${' + expression + '}';
|
|
56
|
+
val = val.substring(0, pos + offset) + interpolation + val.substring(pos + offset);
|
|
57
|
+
offset += interpolation.length;
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
type: 'literal',
|
|
61
|
+
value: '`' + val.replace(property + ':', '').trim() + '`'
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
type: 'literal',
|
|
66
|
+
value: isNaN(Number(value)) ? value.trim() : parseFloat(value)
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
args.push({
|
|
70
|
+
type: 'declaration',
|
|
71
|
+
// Make the property camelCase
|
|
72
|
+
property: property.trim().replace(/-[a-z]/g, match => match[1].toUpperCase()),
|
|
73
|
+
value: getValue()
|
|
74
|
+
});
|
|
75
|
+
return args;
|
|
76
|
+
};
|
|
77
|
+
const getSelectorValue = (chars, expressions) => {
|
|
78
|
+
// If no variable, returns chars immediately.
|
|
79
|
+
// i.e. `.foo { color: red }` returns '.foo'
|
|
80
|
+
if (expressions.length === 0) {
|
|
81
|
+
return chars.trim();
|
|
82
|
+
}
|
|
83
|
+
let val = chars;
|
|
84
|
+
let offset = 1;
|
|
85
|
+
for (const {
|
|
86
|
+
expression,
|
|
87
|
+
pos
|
|
88
|
+
} of expressions) {
|
|
89
|
+
const interpolation = '${' + expression + '}';
|
|
90
|
+
val = val.substring(0, pos + offset) + interpolation + val.substring(pos + offset);
|
|
91
|
+
offset += interpolation.length;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// For simplicity, use template literals even if the whole selector is a variable
|
|
95
|
+
// i.e. the output of `${VAR} { color: red }` is { [`${VAR}`]: { color: "red" } }
|
|
96
|
+
return '`' + val.trim() + '`';
|
|
97
|
+
};
|
|
98
|
+
export const toArguments = (source, template) => {
|
|
99
|
+
const args = [];
|
|
100
|
+
const state = {
|
|
101
|
+
chars: '',
|
|
102
|
+
current: {
|
|
103
|
+
parent: undefined,
|
|
104
|
+
args
|
|
105
|
+
},
|
|
106
|
+
expressions: []
|
|
107
|
+
};
|
|
108
|
+
const addArgument = argument => {
|
|
109
|
+
const {
|
|
110
|
+
args
|
|
111
|
+
} = state.current;
|
|
112
|
+
if (argument.type === 'expression') {
|
|
113
|
+
if (argument.expression.length) {
|
|
114
|
+
args.push(argument);
|
|
115
|
+
}
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const lastArg = args[state.current.args.length - 1];
|
|
119
|
+
if ((lastArg === null || lastArg === void 0 ? void 0 : lastArg.type) === 'block') {
|
|
120
|
+
lastArg.blocks.push(argument);
|
|
121
|
+
} else {
|
|
122
|
+
args.push({
|
|
123
|
+
type: 'block',
|
|
124
|
+
blocks: [argument]
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
const addArguments = () => {
|
|
129
|
+
const args = getArguments(state.chars, state.expressions);
|
|
130
|
+
for (const arg of args) {
|
|
131
|
+
addArgument(arg);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
for (const [i, quasi] of template.quasis.entries()) {
|
|
135
|
+
// Deal with selectors across multiple lines
|
|
136
|
+
const styleTemplateElement = quasi.value.raw.replace(/(\r\n|\n|\r)/gm, ' ').replace(/\s+/g, ' ');
|
|
137
|
+
for (const char of styleTemplateElement) {
|
|
138
|
+
switch (char) {
|
|
139
|
+
case '{':
|
|
140
|
+
{
|
|
141
|
+
const declarations = [];
|
|
142
|
+
addArgument({
|
|
143
|
+
type: 'rule',
|
|
144
|
+
selector: getSelectorValue(state.chars, state.expressions),
|
|
145
|
+
declarations
|
|
146
|
+
});
|
|
147
|
+
state.chars = '';
|
|
148
|
+
state.current = {
|
|
149
|
+
parent: state.current,
|
|
150
|
+
args: declarations
|
|
151
|
+
};
|
|
152
|
+
state.expressions = [];
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
case '}':
|
|
156
|
+
{
|
|
157
|
+
// Add any leftover arguments that were not delimited
|
|
158
|
+
addArguments();
|
|
159
|
+
state.chars = '';
|
|
160
|
+
state.current = state.current.parent;
|
|
161
|
+
state.expressions = [];
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
case ';':
|
|
165
|
+
{
|
|
166
|
+
addArguments();
|
|
167
|
+
state.chars = '';
|
|
168
|
+
state.expressions = [];
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
default:
|
|
172
|
+
state.chars += char;
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (i < template.expressions.length) {
|
|
177
|
+
state.expressions.push({
|
|
178
|
+
pos: state.chars.length - 1,
|
|
179
|
+
expression: source.getText(template.expressions[i])
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Add any leftover arguments that were not delimited
|
|
185
|
+
addArguments();
|
|
186
|
+
return args;
|
|
187
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createLintRule } from '../utils/create-rule';
|
|
2
|
+
import { createNoTaggedTemplateExpressionRule } from './create-no-tagged-template-expression-rule';
|
|
3
|
+
import { isSupportedImport } from './is-supported-import';
|
|
4
|
+
const rule = createLintRule({
|
|
5
|
+
meta: {
|
|
6
|
+
name: 'no-css-tagged-template-expression',
|
|
7
|
+
fixable: 'code',
|
|
8
|
+
type: 'problem',
|
|
9
|
+
docs: {
|
|
10
|
+
description: 'Disallows any `css` tagged template expressions that originate from Emotion, Styled Components or Compiled',
|
|
11
|
+
recommended: false,
|
|
12
|
+
severity: 'error'
|
|
13
|
+
},
|
|
14
|
+
messages: {
|
|
15
|
+
unexpectedTaggedTemplate: 'Unexpected `css` tagged template expression'
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
create: createNoTaggedTemplateExpressionRule(isSupportedImport('css'), 'unexpectedTaggedTemplate')
|
|
19
|
+
});
|
|
20
|
+
export default rule;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const SUPPORTED_IMPORTS = {
|
|
2
|
+
compiled: '@compiled/react',
|
|
3
|
+
emotionReact: '@emotion/react',
|
|
4
|
+
emotionCore: '@emotion/core',
|
|
5
|
+
styledComponents: 'styled-components'
|
|
6
|
+
};
|
|
7
|
+
const isImportSpecifierWrapper = name => {
|
|
8
|
+
return def => {
|
|
9
|
+
var _def$parent, _def$parent2, _def$parent3, _def$parent4, _def$parent5;
|
|
10
|
+
return def.node.type === 'ImportSpecifier' && def.node.imported.type === 'Identifier' && def.node.imported.name === name && ((_def$parent = def.parent) === null || _def$parent === void 0 ? void 0 : _def$parent.type) === 'ImportDeclaration' && (((_def$parent2 = def.parent) === null || _def$parent2 === void 0 ? void 0 : _def$parent2.source.value) === SUPPORTED_IMPORTS.compiled || ((_def$parent3 = def.parent) === null || _def$parent3 === void 0 ? void 0 : _def$parent3.source.value) === SUPPORTED_IMPORTS.emotionReact || ((_def$parent4 = def.parent) === null || _def$parent4 === void 0 ? void 0 : _def$parent4.source.value) === SUPPORTED_IMPORTS.emotionCore || ((_def$parent5 = def.parent) === null || _def$parent5 === void 0 ? void 0 : _def$parent5.source.value) === SUPPORTED_IMPORTS.styledComponents);
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
export const isSupportedImport = name => {
|
|
14
|
+
const isImportSpecifier = isImportSpecifierWrapper(name);
|
|
15
|
+
return (node, references) => node.type === 'Identifier' && references.some(reference => {
|
|
16
|
+
var _reference$resolved;
|
|
17
|
+
return reference.identifier === node && ((_reference$resolved = reference.resolved) === null || _reference$resolved === void 0 ? void 0 : _reference$resolved.defs.some(isImportSpecifier));
|
|
18
|
+
});
|
|
19
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* THIS FILE WAS CREATED VIA CODEGEN DO NOT MODIFY {@see http://go/af-codegen}
|
|
3
|
-
* @codegen <<SignedSource::
|
|
3
|
+
* @codegen <<SignedSource::594898d8c5dc8b9a5610d62e7f300a53>>
|
|
4
4
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
5
5
|
*/
|
|
6
6
|
export default {
|
|
@@ -11,6 +11,7 @@ export default {
|
|
|
11
11
|
'@atlaskit/design-system/ensure-design-token-usage/preview': 'warn',
|
|
12
12
|
'@atlaskit/design-system/icon-label': 'warn',
|
|
13
13
|
'@atlaskit/design-system/no-banned-imports': 'error',
|
|
14
|
+
'@atlaskit/design-system/no-css-tagged-template-expression': 'error',
|
|
14
15
|
'@atlaskit/design-system/no-deprecated-apis': 'error',
|
|
15
16
|
'@atlaskit/design-system/no-deprecated-design-token-usage': 'warn',
|
|
16
17
|
'@atlaskit/design-system/no-deprecated-imports': 'error',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* THIS FILE WAS CREATED VIA CODEGEN DO NOT MODIFY {@see http://go/af-codegen}
|
|
3
|
-
* @codegen <<SignedSource::
|
|
3
|
+
* @codegen <<SignedSource::8927e5adaec5639c9712dbfb26968de4>>
|
|
4
4
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
5
5
|
*/
|
|
6
6
|
import consistentCssPropUsage from './consistent-css-prop-usage';
|
|
@@ -8,6 +8,7 @@ import ensureDesignTokenUsage from './ensure-design-token-usage';
|
|
|
8
8
|
import ensureDesignTokenUsagePreview from './ensure-design-token-usage-preview';
|
|
9
9
|
import iconLabel from './icon-label';
|
|
10
10
|
import noBannedImports from './no-banned-imports';
|
|
11
|
+
import noCssTaggedTemplateExpression from './no-css-tagged-template-expression';
|
|
11
12
|
import noDeprecatedApis from './no-deprecated-apis';
|
|
12
13
|
import noDeprecatedDesignTokenUsage from './no-deprecated-design-token-usage';
|
|
13
14
|
import noDeprecatedImports from './no-deprecated-imports';
|
|
@@ -27,6 +28,7 @@ export default {
|
|
|
27
28
|
'ensure-design-token-usage/preview': ensureDesignTokenUsagePreview,
|
|
28
29
|
'icon-label': iconLabel,
|
|
29
30
|
'no-banned-imports': noBannedImports,
|
|
31
|
+
'no-css-tagged-template-expression': noCssTaggedTemplateExpression,
|
|
30
32
|
'no-deprecated-apis': noDeprecatedApis,
|
|
31
33
|
'no-deprecated-design-token-usage': noDeprecatedDesignTokenUsage,
|
|
32
34
|
'no-deprecated-imports': noDeprecatedImports,
|