@doneisbetter/gds-eslint-config 2.6.1
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/index.js +146 -0
- package/package.json +23 -0
package/index.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
const RAW_COLOR_PATTERN = /#(?:[0-9a-fA-F]{3,8})\b|rgb[a]?\s*\(/;
|
|
2
|
+
const RAW_SPACING_PATTERN = /\b(?:padding|margin|gap|radius|borderRadius)\s*:\s*['"`]?\d+(?:px|rem)/;
|
|
3
|
+
const DEFAULT_FORBIDDEN_IMPORTS = ['@/components/ui/', '@radix-ui/', 'tailwindcss', 'lucide-react'];
|
|
4
|
+
|
|
5
|
+
function shouldIgnoreFile(filename = '') {
|
|
6
|
+
return /(?:^|\/)(?:dist|coverage|node_modules)\//.test(filename);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function isForbiddenImport(source, allowedImports) {
|
|
10
|
+
if (allowedImports.includes(source)) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return DEFAULT_FORBIDDEN_IMPORTS.some((entry) => {
|
|
15
|
+
if (entry.endsWith('/')) {
|
|
16
|
+
return source.startsWith(entry);
|
|
17
|
+
}
|
|
18
|
+
if (entry === 'tailwindcss') {
|
|
19
|
+
return source === 'tailwindcss' || source.startsWith('tailwindcss/');
|
|
20
|
+
}
|
|
21
|
+
return source === entry;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function createRules(options = {}) {
|
|
26
|
+
const allowedImports = options.allowedImports ?? [];
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
'no-raw-design-values': {
|
|
30
|
+
meta: {
|
|
31
|
+
type: 'problem',
|
|
32
|
+
docs: {
|
|
33
|
+
description: 'Forbid raw colors and spacing/radius values in feature UI code.',
|
|
34
|
+
},
|
|
35
|
+
schema: [],
|
|
36
|
+
messages: {
|
|
37
|
+
rawColor: 'Use GDS theme tokens instead of raw color literals in feature UI code.',
|
|
38
|
+
rawSpacing: 'Use GDS spacing and radius tokens instead of hard-coded layout values in feature UI code.',
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
create(context) {
|
|
42
|
+
const filename = context.filename ?? '';
|
|
43
|
+
if (shouldIgnoreFile(filename) || /(?:^|\/)(?:theme|tokens)\//.test(filename)) {
|
|
44
|
+
return {};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const reportNode = (node, messageId) => {
|
|
48
|
+
context.report({ node, messageId });
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
Literal(node) {
|
|
53
|
+
if (typeof node.value !== 'string') {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (RAW_COLOR_PATTERN.test(node.value)) {
|
|
58
|
+
reportNode(node, 'rawColor');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (RAW_SPACING_PATTERN.test(node.value)) {
|
|
62
|
+
reportNode(node, 'rawSpacing');
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
TemplateElement(node) {
|
|
66
|
+
if (RAW_COLOR_PATTERN.test(node.value.raw)) {
|
|
67
|
+
reportNode(node, 'rawColor');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (RAW_SPACING_PATTERN.test(node.value.raw)) {
|
|
71
|
+
reportNode(node, 'rawSpacing');
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
'no-forbidden-ui-imports': {
|
|
78
|
+
meta: {
|
|
79
|
+
type: 'problem',
|
|
80
|
+
docs: {
|
|
81
|
+
description: 'Forbid legacy or non-approved UI imports where GDS should be used.',
|
|
82
|
+
},
|
|
83
|
+
schema: [],
|
|
84
|
+
messages: {
|
|
85
|
+
forbiddenImport: 'Import from canonical GDS surfaces instead of {{source}}.',
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
create(context) {
|
|
89
|
+
const filename = context.filename ?? '';
|
|
90
|
+
if (shouldIgnoreFile(filename)) {
|
|
91
|
+
return {};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
ImportDeclaration(node) {
|
|
96
|
+
const source = node.source.value;
|
|
97
|
+
if (typeof source === 'string' && isForbiddenImport(source, allowedImports)) {
|
|
98
|
+
context.report({
|
|
99
|
+
node,
|
|
100
|
+
messageId: 'forbiddenImport',
|
|
101
|
+
data: { source },
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function resolveAllowedImports(manifest = {}) {
|
|
112
|
+
const imports = new Set();
|
|
113
|
+
for (const exception of manifest.approvedExceptions ?? []) {
|
|
114
|
+
if (exception.dependency) {
|
|
115
|
+
imports.add(exception.dependency);
|
|
116
|
+
}
|
|
117
|
+
for (const value of exception.allowImports ?? []) {
|
|
118
|
+
imports.add(value);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return [...imports];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function createGdsConfig(options = {}) {
|
|
125
|
+
const rules = createRules(options);
|
|
126
|
+
return [
|
|
127
|
+
{
|
|
128
|
+
plugins: {
|
|
129
|
+
gds: {
|
|
130
|
+
meta: {
|
|
131
|
+
name: '@doneisbetter/gds-eslint-config',
|
|
132
|
+
},
|
|
133
|
+
rules,
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
rules: {
|
|
137
|
+
'gds/no-raw-design-values': 'error',
|
|
138
|
+
'gds/no-forbidden-ui-imports': 'error',
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export const gdsConfig = createGdsConfig();
|
|
145
|
+
|
|
146
|
+
export default gdsConfig;
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@doneisbetter/gds-eslint-config",
|
|
3
|
+
"version": "2.6.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./index.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"index.js"
|
|
11
|
+
],
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/sovereignsquad/general-design-system.git",
|
|
18
|
+
"directory": "packages/gds-eslint-config"
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"eslint": "^9.0.0 || ^10.0.0"
|
|
22
|
+
}
|
|
23
|
+
}
|