@superdispatch/eslint-plugin-ui 0.12.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/LICENSE +21 -0
- package/README.md +9 -0
- package/package.json +24 -0
- package/pkg/.tsbuildinfo +1 -0
- package/pkg/index.d.ts +13 -0
- package/pkg/index.js +18 -0
- package/pkg/rules/no-color-literals.d.ts +6 -0
- package/pkg/rules/no-color-literals.js +83 -0
- package/pkg/rules/no-restricted-modules.d.ts +6 -0
- package/pkg/rules/no-restricted-modules.js +76 -0
- package/pkg/utils/createRule.d.ts +15 -0
- package/pkg/utils/createRule.js +5 -0
- package/src/index.ts +18 -0
- package/src/rules/no-color-literals.spec.ts +252 -0
- package/src/rules/no-color-literals.ts +93 -0
- package/src/rules/no-restricted-modules.spec.ts +94 -0
- package/src/rules/no-restricted-modules.ts +92 -0
- package/src/utils/createRule.ts +5 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { Color } from '@superdispatch/ui';
|
|
2
|
+
import { TSESTree } from '@typescript-eslint/types';
|
|
3
|
+
import { createRule } from '../utils/createRule';
|
|
4
|
+
|
|
5
|
+
//
|
|
6
|
+
// Utils
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
function normalizeHex(hex: string): string {
|
|
10
|
+
hex = hex.toLowerCase();
|
|
11
|
+
|
|
12
|
+
// #ffffff -> #fff
|
|
13
|
+
if (new Set(hex).size === 2) {
|
|
14
|
+
hex = hex.charAt(0) + hex.charAt(1).repeat(3);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return hex;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const COLOR_KEY_CACHE = new Map<string, string>();
|
|
21
|
+
function findColorName(color: string): undefined | string {
|
|
22
|
+
if (COLOR_KEY_CACHE.size === 0) {
|
|
23
|
+
for (const [key, hex] of Object.entries(Color)) {
|
|
24
|
+
COLOR_KEY_CACHE.set(normalizeHex(hex), key);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return COLOR_KEY_CACHE.get(color);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const HEX_COLOR_PATTERN = /(#\b([a-f0-9]{3}|[a-f0-9]{6})\b)/gim;
|
|
32
|
+
function* listColorNames(text: string): Generator<string, void> {
|
|
33
|
+
for (const [, hex] of text.matchAll(HEX_COLOR_PATTERN)) {
|
|
34
|
+
if (hex) {
|
|
35
|
+
const color = normalizeHex(hex);
|
|
36
|
+
const name = findColorName(color);
|
|
37
|
+
if (name) yield name;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
//
|
|
43
|
+
// Rule
|
|
44
|
+
//
|
|
45
|
+
|
|
46
|
+
const messages = {
|
|
47
|
+
suggestColor: 'Use `Color.{{name}}` from "@superdispatch/ui"',
|
|
48
|
+
} as const;
|
|
49
|
+
|
|
50
|
+
export type MessageIds = keyof typeof messages;
|
|
51
|
+
|
|
52
|
+
export const rule = createRule<[], MessageIds>({
|
|
53
|
+
name: 'no-restricted-modules',
|
|
54
|
+
defaultOptions: [],
|
|
55
|
+
meta: {
|
|
56
|
+
messages,
|
|
57
|
+
type: 'problem',
|
|
58
|
+
fixable: 'code',
|
|
59
|
+
docs: {
|
|
60
|
+
recommended: 'error',
|
|
61
|
+
category: 'Possible Errors',
|
|
62
|
+
description: 'Disallows to use Material UI modules',
|
|
63
|
+
},
|
|
64
|
+
schema: [],
|
|
65
|
+
},
|
|
66
|
+
create(context) {
|
|
67
|
+
return {
|
|
68
|
+
Literal(node: TSESTree.Literal): void {
|
|
69
|
+
if (typeof node.value == 'string') {
|
|
70
|
+
for (const name of listColorNames(node.value)) {
|
|
71
|
+
context.report({
|
|
72
|
+
node,
|
|
73
|
+
data: { name },
|
|
74
|
+
messageId: 'suggestColor',
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
TemplateLiteral(node: TSESTree.TemplateLiteral): void {
|
|
81
|
+
for (const quasi of node.quasis) {
|
|
82
|
+
for (const name of listColorNames(quasi.value.raw)) {
|
|
83
|
+
context.report({
|
|
84
|
+
node,
|
|
85
|
+
data: { name },
|
|
86
|
+
messageId: 'suggestColor',
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
},
|
|
93
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { ESLintUtils } from '@typescript-eslint/experimental-utils';
|
|
2
|
+
import { rule } from './no-restricted-modules';
|
|
3
|
+
|
|
4
|
+
const ruleTester = new ESLintUtils.RuleTester({
|
|
5
|
+
parser: '@typescript-eslint/parser',
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
ruleTester.run('no-restricted-modules', rule, {
|
|
9
|
+
valid: ['import { Box } from "styled-system"'],
|
|
10
|
+
invalid: [
|
|
11
|
+
{
|
|
12
|
+
code: 'import { Box } from "@material-ui/core"',
|
|
13
|
+
errors: [
|
|
14
|
+
{
|
|
15
|
+
line: 1,
|
|
16
|
+
endLine: 1,
|
|
17
|
+
column: 10,
|
|
18
|
+
endColumn: 13,
|
|
19
|
+
messageId: 'restrict',
|
|
20
|
+
data: {
|
|
21
|
+
restrictedName: 'Box',
|
|
22
|
+
restrictedModule: '@material-ui/core',
|
|
23
|
+
suggestedName: 'Box',
|
|
24
|
+
suggestedModule: '@superdispatch/ui-lab',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
{
|
|
31
|
+
code: 'import { Box as div } from "@material-ui/core"',
|
|
32
|
+
errors: [
|
|
33
|
+
{
|
|
34
|
+
line: 1,
|
|
35
|
+
endLine: 1,
|
|
36
|
+
column: 10,
|
|
37
|
+
endColumn: 20,
|
|
38
|
+
messageId: 'restrict',
|
|
39
|
+
data: {
|
|
40
|
+
restrictedName: 'Box',
|
|
41
|
+
restrictedModule: '@material-ui/core',
|
|
42
|
+
suggestedName: 'Box',
|
|
43
|
+
suggestedModule: '@superdispatch/ui-lab',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
{
|
|
50
|
+
code: 'import { Avatar, Button, Card, Grid, Snackbar, SnackbarContent } from "@material-ui/core"',
|
|
51
|
+
errors: [
|
|
52
|
+
{
|
|
53
|
+
messageId: 'restrict',
|
|
54
|
+
data: {
|
|
55
|
+
restrictedName: 'Button',
|
|
56
|
+
restrictedModule: '@material-ui/core',
|
|
57
|
+
suggestedName: 'Button',
|
|
58
|
+
suggestedModule: '@superdispatch/ui-lab',
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
{
|
|
63
|
+
messageId: 'restrict',
|
|
64
|
+
data: {
|
|
65
|
+
restrictedName: 'Grid',
|
|
66
|
+
restrictedModule: '@material-ui/core',
|
|
67
|
+
suggestedName: 'Columns',
|
|
68
|
+
suggestedModule: '@superdispatch/ui-lab',
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
{
|
|
73
|
+
messageId: 'restrict',
|
|
74
|
+
data: {
|
|
75
|
+
restrictedName: 'Snackbar',
|
|
76
|
+
restrictedModule: '@material-ui/core',
|
|
77
|
+
suggestedName: 'Snackbar',
|
|
78
|
+
suggestedModule: '@superdispatch/ui',
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
{
|
|
83
|
+
messageId: 'restrict',
|
|
84
|
+
data: {
|
|
85
|
+
restrictedName: 'SnackbarContent',
|
|
86
|
+
restrictedModule: '@material-ui/core',
|
|
87
|
+
suggestedName: 'SnackbarContent',
|
|
88
|
+
suggestedModule: '@superdispatch/ui',
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { AST_NODE_TYPES } from '@typescript-eslint/experimental-utils';
|
|
2
|
+
import { TSESTree } from '@typescript-eslint/types';
|
|
3
|
+
import { createRule } from '../utils/createRule';
|
|
4
|
+
|
|
5
|
+
const messages = {
|
|
6
|
+
restrict:
|
|
7
|
+
'Usage of `{{restrictedName}}` from `{{restrictedModule}}` is restricted, use `{{suggestedName}}` from the `{{suggestedModule}}`',
|
|
8
|
+
} as const;
|
|
9
|
+
|
|
10
|
+
export type MessageIds = keyof typeof messages;
|
|
11
|
+
|
|
12
|
+
type Restrictions = {
|
|
13
|
+
readonly [TPackage in string]?: {
|
|
14
|
+
readonly [TModule in string]?: [name: string, source: string];
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const RESTRICTIONS: Restrictions = {
|
|
19
|
+
'@material-ui/lab': {
|
|
20
|
+
Alert: ['Alert', '@superdispatch/ui-lab'],
|
|
21
|
+
},
|
|
22
|
+
'@material-ui/core': {
|
|
23
|
+
Grid: ['Columns', '@superdispatch/ui-lab'],
|
|
24
|
+
Box: ['Box', '@superdispatch/ui-lab'],
|
|
25
|
+
Button: ['Button', '@superdispatch/ui-lab'],
|
|
26
|
+
Snackbar: ['Snackbar', '@superdispatch/ui'],
|
|
27
|
+
SnackbarContent: ['SnackbarContent', '@superdispatch/ui'],
|
|
28
|
+
},
|
|
29
|
+
'@superdispatch/ui': {
|
|
30
|
+
Button: ['Button', '@superdispatch/ui-lab'],
|
|
31
|
+
GridStack: ['Stack', '@superdispatch/ui'],
|
|
32
|
+
InlineGrid: ['Inline', '@superdispatch/ui'],
|
|
33
|
+
DescriptionList: ['Stack', '@superdispatch/ui-lab'],
|
|
34
|
+
DescriptionListItem: ['DescriptionItem', '@superdispatch/ui-lab'],
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const rule = createRule<[], MessageIds>({
|
|
39
|
+
name: 'no-restricted-modules',
|
|
40
|
+
defaultOptions: [],
|
|
41
|
+
meta: {
|
|
42
|
+
messages,
|
|
43
|
+
type: 'problem',
|
|
44
|
+
fixable: 'code',
|
|
45
|
+
docs: {
|
|
46
|
+
recommended: 'error',
|
|
47
|
+
category: 'Possible Errors',
|
|
48
|
+
description: 'Disallows to use Material UI modules',
|
|
49
|
+
},
|
|
50
|
+
schema: [],
|
|
51
|
+
},
|
|
52
|
+
create(context) {
|
|
53
|
+
return {
|
|
54
|
+
ImportDeclaration(node: TSESTree.ImportDeclaration): void {
|
|
55
|
+
const { value: restrictedModule } = node.source;
|
|
56
|
+
|
|
57
|
+
if (typeof restrictedModule != 'string') return;
|
|
58
|
+
|
|
59
|
+
const packageRestrictions = RESTRICTIONS[restrictedModule];
|
|
60
|
+
|
|
61
|
+
if (!packageRestrictions) return;
|
|
62
|
+
|
|
63
|
+
for (const specifier of node.specifiers) {
|
|
64
|
+
if (specifier.type === AST_NODE_TYPES.ImportNamespaceSpecifier) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const restrictedName =
|
|
69
|
+
specifier.type === AST_NODE_TYPES.ImportDefaultSpecifier
|
|
70
|
+
? 'default'
|
|
71
|
+
: specifier.imported.name;
|
|
72
|
+
const moduleRestrictions = packageRestrictions[restrictedName];
|
|
73
|
+
|
|
74
|
+
if (!moduleRestrictions) continue;
|
|
75
|
+
|
|
76
|
+
const [suggestedName, suggestedModule] = moduleRestrictions;
|
|
77
|
+
|
|
78
|
+
context.report({
|
|
79
|
+
node: specifier,
|
|
80
|
+
data: {
|
|
81
|
+
restrictedName,
|
|
82
|
+
restrictedModule,
|
|
83
|
+
suggestedName,
|
|
84
|
+
suggestedModule,
|
|
85
|
+
},
|
|
86
|
+
messageId: 'restrict',
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
},
|
|
92
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"include": ["src"],
|
|
3
|
+
"extends": "../../tsconfig.build",
|
|
4
|
+
"exclude": ["**/*.spec.*", "**/__tests__/**", "**/__testutils__/**"],
|
|
5
|
+
"compilerOptions": {
|
|
6
|
+
"target": "ES2019",
|
|
7
|
+
"module": "CommonJS",
|
|
8
|
+
"composite": true,
|
|
9
|
+
"rootDir": "src",
|
|
10
|
+
"outDir": "pkg",
|
|
11
|
+
"tsBuildInfoFile": "pkg/.tsbuildinfo"
|
|
12
|
+
}
|
|
13
|
+
}
|