@hero-design/snowflake-guard 1.1.0 → 1.2.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/lib/src/__tests__/parseSource.spec.js +6 -0
- package/lib/src/parseSource.js +1 -3
- package/lib/src/reports/__tests__/reportInlineStyle.spec.js +12 -3
- package/lib/src/reports/reportCustomStyleProperties.d.ts +2 -1
- package/lib/src/reports/reportCustomStyleProperties.js +9 -6
- package/lib/src/reports/reportInlineStyle.d.ts +7 -1
- package/lib/src/reports/reportInlineStyle.js +35 -6
- package/package.json +4 -1
package/lib/src/parseSource.js
CHANGED
|
@@ -41,7 +41,10 @@ describe('reportInlineStyle', () => {
|
|
|
41
41
|
sx={{
|
|
42
42
|
padding: 10,
|
|
43
43
|
backgroundColor: 'blue'
|
|
44
|
-
}}
|
|
44
|
+
}}
|
|
45
|
+
variant="primary"
|
|
46
|
+
intent="success"
|
|
47
|
+
/>
|
|
45
48
|
);
|
|
46
49
|
`;
|
|
47
50
|
// Step 1: Parse the code to generate the AST
|
|
@@ -50,7 +53,7 @@ describe('reportInlineStyle', () => {
|
|
|
50
53
|
// Step 2: Traverse the AST to extract relevant JSXAttributes (style and sx)
|
|
51
54
|
recast.visit(ast, {
|
|
52
55
|
visitJSXAttribute(path) {
|
|
53
|
-
const node = path
|
|
56
|
+
const { node } = path;
|
|
54
57
|
attributes.push(node);
|
|
55
58
|
this.traverse(path);
|
|
56
59
|
},
|
|
@@ -61,6 +64,12 @@ describe('reportInlineStyle', () => {
|
|
|
61
64
|
constants_1.SX_RULESET_MAP.Card = ['padding', 'backgroundColor'];
|
|
62
65
|
// Step 3: Run the function with the dynamically extracted attributes
|
|
63
66
|
const result = (0, reportInlineStyle_1.default)(ast, attributes, componentName);
|
|
67
|
+
expect(result.additionalProps).toMatchObject([
|
|
68
|
+
{
|
|
69
|
+
propValue: '"primary"',
|
|
70
|
+
propName: 'variant',
|
|
71
|
+
},
|
|
72
|
+
]);
|
|
64
73
|
expect(result.locs.style).toEqual([3]);
|
|
65
74
|
expect(result.locs.sx).toEqual([7]);
|
|
66
75
|
expect(result.violatingAttributes).toEqual([
|
|
@@ -106,7 +115,7 @@ describe('reportInlineStyle', () => {
|
|
|
106
115
|
// Step 2: Traverse the AST to extract relevant JSXAttributes (style and sx)
|
|
107
116
|
recast.visit(ast, {
|
|
108
117
|
visitJSXAttribute(path) {
|
|
109
|
-
const node = path
|
|
118
|
+
const { node } = path;
|
|
110
119
|
attributes.push(node);
|
|
111
120
|
this.traverse(path);
|
|
112
121
|
},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as recast from 'recast';
|
|
2
|
-
import { InlineStyleProps } from './reportInlineStyle';
|
|
2
|
+
import { AdditionalProp, InlineStyleProps } from './reportInlineStyle';
|
|
3
3
|
import type { ComponentName, CompoundComponentName } from './types';
|
|
4
4
|
declare const reportCustomProperties: (ast: recast.types.ASTNode, componentList: {
|
|
5
5
|
[k: string]: "Alert" | "Badge" | "Banner" | "Breadcrumb" | "Button" | "Card" | "Carousel" | "Chart" | "Checkbox" | "Chip" | "Collapse" | "Comment" | "ContextPanel" | "DatePicker" | "Divider" | "Dropdown" | "Empty" | "File" | "Filters" | "Form" | "Grid" | "Icon" | "InPageNavigation" | "Input" | "MediaQuery" | "Menu" | "Modal" | "Notification" | "PageHeader" | "Pagination" | "Portal" | "Portlet" | "Progress" | "Radio" | "Rate" | "Result" | "Select" | "SelectButton" | "SideBar" | "Slider" | "Spinner" | "Statistic" | "Steps" | "Switch" | "Table" | "Tabs" | "Tag" | "TagInput" | "TimePicker" | "Timeline" | "Tooltip" | "Typography" | "Widget";
|
|
@@ -13,6 +13,7 @@ declare const reportCustomProperties: (ast: recast.types.ASTNode, componentList:
|
|
|
13
13
|
inlineStyleProps: InlineStyleProps;
|
|
14
14
|
componentName: CompoundComponentName;
|
|
15
15
|
loc: number | undefined;
|
|
16
|
+
additionalProps?: AdditionalProp[];
|
|
16
17
|
}[];
|
|
17
18
|
};
|
|
18
19
|
export default reportCustomProperties;
|
|
@@ -29,6 +29,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
29
29
|
const recast = __importStar(require("recast"));
|
|
30
30
|
const reportClassName_1 = __importDefault(require("./reportClassName"));
|
|
31
31
|
const reportInlineStyle_1 = __importDefault(require("./reportInlineStyle"));
|
|
32
|
+
const mapViolatingAttributesAndAdditionalProps = (violatingAttributes, additionalProps) => {
|
|
33
|
+
return violatingAttributes.map((violatingAttribute) => (Object.assign(Object.assign({}, violatingAttribute), (additionalProps && additionalProps.length ? { additionalProps } : {}))));
|
|
34
|
+
};
|
|
32
35
|
const reportCustomProperties = (ast, componentList) => {
|
|
33
36
|
const report = {
|
|
34
37
|
className: [],
|
|
@@ -45,7 +48,7 @@ const reportCustomProperties = (ast, componentList) => {
|
|
|
45
48
|
localComponentList.includes(path.value.name.name)) {
|
|
46
49
|
const attributes = path.value
|
|
47
50
|
.attributes;
|
|
48
|
-
const { locs: styleObjectLocs, violatingAttributes } = (0, reportInlineStyle_1.default)(ast, attributes, componentList[path.value.name.name]);
|
|
51
|
+
const { locs: styleObjectLocs, violatingAttributes, additionalProps, } = (0, reportInlineStyle_1.default)(ast, attributes, componentList[path.value.name.name]);
|
|
49
52
|
report.className = [
|
|
50
53
|
...report.className,
|
|
51
54
|
...(0, reportClassName_1.default)(attributes),
|
|
@@ -54,7 +57,7 @@ const reportCustomProperties = (ast, componentList) => {
|
|
|
54
57
|
report.sx = [...report.sx, ...styleObjectLocs.sx];
|
|
55
58
|
report.violatingAttributes = [
|
|
56
59
|
...report.violatingAttributes,
|
|
57
|
-
...violatingAttributes,
|
|
60
|
+
...mapViolatingAttributesAndAdditionalProps(violatingAttributes, additionalProps),
|
|
58
61
|
];
|
|
59
62
|
}
|
|
60
63
|
// Case 2: Custom compound component, e.g. <Card.Header />
|
|
@@ -66,7 +69,7 @@ const reportCustomProperties = (ast, componentList) => {
|
|
|
66
69
|
].join('.');
|
|
67
70
|
const attributes = path.value
|
|
68
71
|
.attributes;
|
|
69
|
-
const { locs: styleObjectLocs, violatingAttributes } = (0, reportInlineStyle_1.default)(ast, attributes, compoundComponentName);
|
|
72
|
+
const { locs: styleObjectLocs, violatingAttributes, additionalProps, } = (0, reportInlineStyle_1.default)(ast, attributes, compoundComponentName);
|
|
70
73
|
report.className = [
|
|
71
74
|
...report.className,
|
|
72
75
|
...(0, reportClassName_1.default)(attributes),
|
|
@@ -75,7 +78,7 @@ const reportCustomProperties = (ast, componentList) => {
|
|
|
75
78
|
report.sx = [...report.sx, ...styleObjectLocs.sx];
|
|
76
79
|
report.violatingAttributes = [
|
|
77
80
|
...report.violatingAttributes,
|
|
78
|
-
...violatingAttributes,
|
|
81
|
+
...mapViolatingAttributesAndAdditionalProps(violatingAttributes, additionalProps),
|
|
79
82
|
];
|
|
80
83
|
}
|
|
81
84
|
},
|
|
@@ -105,7 +108,7 @@ const reportCustomProperties = (ast, componentList) => {
|
|
|
105
108
|
openPath.value.name.name,
|
|
106
109
|
].join('.');
|
|
107
110
|
const { attributes } = openPath.value;
|
|
108
|
-
const { locs: styleObjectLocs, violatingAttributes } = (0, reportInlineStyle_1.default)(ast, attributes, compoundComponentName);
|
|
111
|
+
const { locs: styleObjectLocs, violatingAttributes, additionalProps, } = (0, reportInlineStyle_1.default)(ast, attributes, compoundComponentName);
|
|
109
112
|
report.className = [
|
|
110
113
|
...report.className,
|
|
111
114
|
...(0, reportClassName_1.default)(attributes),
|
|
@@ -114,7 +117,7 @@ const reportCustomProperties = (ast, componentList) => {
|
|
|
114
117
|
report.sx = [...report.sx, ...styleObjectLocs.sx];
|
|
115
118
|
report.violatingAttributes = [
|
|
116
119
|
...report.violatingAttributes,
|
|
117
|
-
...violatingAttributes,
|
|
120
|
+
...mapViolatingAttributesAndAdditionalProps(violatingAttributes, additionalProps),
|
|
118
121
|
];
|
|
119
122
|
}
|
|
120
123
|
},
|
|
@@ -2,18 +2,24 @@ import * as recast from 'recast';
|
|
|
2
2
|
import type { CompoundComponentName } from './types';
|
|
3
3
|
export type InlineStyleProps = 'style' | 'sx';
|
|
4
4
|
export declare const INLINE_STYLE_PROPERTIES: string[];
|
|
5
|
-
|
|
5
|
+
export declare const ADDITIONAL_PROPERTIES: string[];
|
|
6
|
+
export type ViolatingAttribute = {
|
|
6
7
|
attributeName: string;
|
|
7
8
|
attributeValue: string | null;
|
|
8
9
|
inlineStyleProps: InlineStyleProps;
|
|
9
10
|
componentName: CompoundComponentName;
|
|
10
11
|
loc: number | undefined;
|
|
11
12
|
};
|
|
13
|
+
export type AdditionalProp = {
|
|
14
|
+
propName: string;
|
|
15
|
+
propValue: string | null;
|
|
16
|
+
};
|
|
12
17
|
declare const reportInlineStyle: (ast: recast.types.ASTNode, attributes: recast.types.namedTypes.JSXAttribute[], componentName: CompoundComponentName) => {
|
|
13
18
|
locs: {
|
|
14
19
|
style: number[];
|
|
15
20
|
sx: number[];
|
|
16
21
|
};
|
|
17
22
|
violatingAttributes: ViolatingAttribute[];
|
|
23
|
+
additionalProps: AdditionalProp[];
|
|
18
24
|
};
|
|
19
25
|
export default reportInlineStyle;
|
|
@@ -23,7 +23,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
23
23
|
return result;
|
|
24
24
|
};
|
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
-
exports.INLINE_STYLE_PROPERTIES = void 0;
|
|
26
|
+
exports.ADDITIONAL_PROPERTIES = exports.INLINE_STYLE_PROPERTIES = void 0;
|
|
27
27
|
const recast = __importStar(require("recast"));
|
|
28
28
|
const constants_1 = require("./constants");
|
|
29
29
|
const BLACKLIST_PROPERTIES = {
|
|
@@ -31,6 +31,7 @@ const BLACKLIST_PROPERTIES = {
|
|
|
31
31
|
sx: constants_1.SX_RULESET_MAP,
|
|
32
32
|
};
|
|
33
33
|
exports.INLINE_STYLE_PROPERTIES = ['style', 'sx'];
|
|
34
|
+
exports.ADDITIONAL_PROPERTIES = ['variant']; // Add any additional props you want to track
|
|
34
35
|
const addViolatingAttribute = (prop, styleObjName, componentName, loc, violatingAttributes) => {
|
|
35
36
|
if (prop.key.type !== 'Identifier') {
|
|
36
37
|
return;
|
|
@@ -43,20 +44,48 @@ const addViolatingAttribute = (prop, styleObjName, componentName, loc, violating
|
|
|
43
44
|
loc,
|
|
44
45
|
});
|
|
45
46
|
};
|
|
47
|
+
const getPropValue = (attr) => {
|
|
48
|
+
var _a;
|
|
49
|
+
if (!attr.value) {
|
|
50
|
+
return ''; // Handle case where value is missing
|
|
51
|
+
}
|
|
52
|
+
if (typeof attr.value === 'string') {
|
|
53
|
+
return attr.value; // Directly return string value
|
|
54
|
+
}
|
|
55
|
+
if (((_a = attr.value) === null || _a === void 0 ? void 0 : _a.type) === 'JSXExpressionContainer') {
|
|
56
|
+
return recast.print(attr.value.expression).code;
|
|
57
|
+
}
|
|
58
|
+
// Use recast to print the expression if it's not a simple string
|
|
59
|
+
return recast.print(attr.value).code;
|
|
60
|
+
};
|
|
46
61
|
const reportInlineStyle = (ast, attributes, componentName) => {
|
|
47
62
|
const locs = {
|
|
48
63
|
style: [],
|
|
49
64
|
sx: [],
|
|
50
65
|
};
|
|
51
66
|
const violatingAttributes = [];
|
|
67
|
+
const additionalProps = [];
|
|
52
68
|
let hasCustomStyle = false;
|
|
53
69
|
let styleObjName;
|
|
54
70
|
attributes.forEach((attr) => {
|
|
55
71
|
var _a;
|
|
56
|
-
if (attr.type
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
72
|
+
if (attr.type !== 'JSXAttribute') {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (typeof attr.name.name !== 'string') {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (exports.ADDITIONAL_PROPERTIES.includes(attr.name.name)) {
|
|
79
|
+
// Handle expression container like `style={{ ... }}`
|
|
80
|
+
additionalProps.push({
|
|
81
|
+
propName: attr.name.name,
|
|
82
|
+
propValue: getPropValue(attr), // Print expression as string
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
if (((_a = attr.value) === null || _a === void 0 ? void 0 : _a.type) !== 'JSXExpressionContainer') {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (exports.INLINE_STYLE_PROPERTIES.includes(attr.name.name)) {
|
|
60
89
|
styleObjName = attr.name.name;
|
|
61
90
|
const { expression } = attr.value;
|
|
62
91
|
if (expression.type === 'ObjectExpression') {
|
|
@@ -198,6 +227,6 @@ const reportInlineStyle = (ast, attributes, componentName) => {
|
|
|
198
227
|
}
|
|
199
228
|
}
|
|
200
229
|
});
|
|
201
|
-
return { locs, violatingAttributes };
|
|
230
|
+
return { locs, violatingAttributes, additionalProps };
|
|
202
231
|
};
|
|
203
232
|
exports.default = reportInlineStyle;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hero-design/snowflake-guard",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "A hero-design bot detecting snowflake usage",
|
|
5
5
|
"author": "Hau Dao",
|
|
6
6
|
"license": "ISC",
|
|
@@ -18,7 +18,9 @@
|
|
|
18
18
|
"scripts": {
|
|
19
19
|
"build": "tsc",
|
|
20
20
|
"start": "yarn build && probot run ./lib/src/index.js",
|
|
21
|
+
"type-check": "tsc --noEmit",
|
|
21
22
|
"test": "jest --passWithNoTests",
|
|
23
|
+
"lint": "eslint src",
|
|
22
24
|
"deploy": "netlify deploy --site snowflake-guard.netlify.app --prod --auth $NETLIFY_AUTH_TOKEN",
|
|
23
25
|
"publish:npm": "yarn publish --access public"
|
|
24
26
|
},
|
|
@@ -34,6 +36,7 @@
|
|
|
34
36
|
"@types/jest": "^29.0.0",
|
|
35
37
|
"@types/node": "^18.0.0",
|
|
36
38
|
"config-tsconfig": "8.42.4",
|
|
39
|
+
"eslint": "^8.56.0",
|
|
37
40
|
"eslint-config-hd": "8.42.4",
|
|
38
41
|
"jest": "^29.0.0",
|
|
39
42
|
"nock": "^13.0.5",
|