@hero-design/snowflake-guard 1.0.13 → 1.1.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/README.md CHANGED
@@ -10,9 +10,18 @@
10
10
  cp .env.example .env
11
11
  ```
12
12
 
13
- 2. Install snowflake-guard[Dev] [app](https://github.com/apps/hero-design-snowflake-guard-dev) to a repository that you wish to run the app on.
13
+ 2. Set up [internal-tool-integrations service](https://github.com/Thinkei/internal-tool-integrations) for the bot to store report.
14
14
 
15
- 3. Start the app.
15
+ - Replace `DB_HOST` with the host of the internal-tool-integrations service.
16
+ - Use the same `SNOWFLAKE_GUARD_SECRET` as the one in the internal-tool-integrations service.
17
+
18
+ In case you don't want to store snowflake report, comment out the `saving report` section in `src/index.ts`.
19
+
20
+ 3. Install snowflake-guard[Dev] [app](https://github.com/apps/hero-design-snowflake-guard-dev) to a repository that you wish to run the app on.
21
+
22
+ 4. Start `internal-tool-integrations` service.
23
+
24
+ 5. Start the bot.
16
25
 
17
26
  ```sh
18
27
  # Install dependencies
@@ -21,7 +30,7 @@ yarn install
21
30
  # Run the bot
22
31
  yarn start
23
32
  ```
24
- 4. Open or update a pull request to trigger webhook events.
33
+ 6. Open or update a pull request to trigger webhook events.
25
34
 
26
35
  ## Note
27
36
 
@@ -32,11 +32,118 @@ describe('parseSource', () => {
32
32
  it('reports correct snowflakes', () => {
33
33
  const source = fs.readFileSync('./src/__mocks__/sourceSample.tsx', 'utf-8');
34
34
  expect((0, parseSource_1.default)(source)).toEqual({
35
+ approvedLocs: [55, 65, 64],
35
36
  classNameLocs: [44, 42, 43],
36
- styleLocs: [54, 47, 49, 50, 51, 52, 53],
37
- sxLocs: [63, 58, 59, 60, 61, 62],
37
+ styleLocs: [54, 67, 47, 49, 50, 51, 52, 53],
38
38
  styledComponentLocs: [6, 10, 15],
39
- approvedLocs: [55, 65, 64],
39
+ sxLocs: [63, 58, 59, 60, 61, 62],
40
+ violatingAttributes: [
41
+ {
42
+ attributeName: 'padding',
43
+ attributeValue: '20',
44
+ componentName: 'Button.Link',
45
+ inlineStyleProps: 'style',
46
+ loc: 54,
47
+ },
48
+ {
49
+ attributeName: 'padding',
50
+ attributeValue: '20',
51
+ componentName: 'Button.Link',
52
+ inlineStyleProps: 'sx',
53
+ loc: 63,
54
+ },
55
+ {
56
+ attributeName: 'padding',
57
+ attributeValue: '20',
58
+ componentName: 'Button.Link',
59
+ inlineStyleProps: 'style',
60
+ loc: 67,
61
+ },
62
+ {
63
+ attributeName: 'backgroundColor',
64
+ attributeValue: "'red'",
65
+ componentName: 'Button.Link',
66
+ inlineStyleProps: 'style',
67
+ loc: 67,
68
+ },
69
+ {
70
+ attributeName: 'width',
71
+ attributeValue: '200',
72
+ componentName: 'Empty',
73
+ inlineStyleProps: 'style',
74
+ loc: 47,
75
+ },
76
+ {
77
+ attributeName: 'width',
78
+ attributeValue: '200',
79
+ componentName: 'Card.Header',
80
+ inlineStyleProps: 'style',
81
+ loc: 49,
82
+ },
83
+ {
84
+ attributeName: 'padding',
85
+ attributeValue: '30',
86
+ componentName: 'Button',
87
+ inlineStyleProps: 'style',
88
+ loc: 50,
89
+ },
90
+ {
91
+ attributeName: 'padding',
92
+ attributeValue: '30',
93
+ componentName: 'Button',
94
+ inlineStyleProps: 'style',
95
+ loc: 51,
96
+ },
97
+ {
98
+ attributeName: 'padding',
99
+ attributeValue: '30',
100
+ componentName: 'Button',
101
+ inlineStyleProps: 'style',
102
+ loc: 52,
103
+ },
104
+ {
105
+ attributeName: 'padding',
106
+ attributeValue: '30',
107
+ componentName: 'Button',
108
+ inlineStyleProps: 'style',
109
+ loc: 53,
110
+ },
111
+ {
112
+ attributeName: 'mt',
113
+ attributeValue: '10',
114
+ componentName: 'Badge.Count',
115
+ inlineStyleProps: 'sx',
116
+ loc: 58,
117
+ },
118
+ {
119
+ attributeName: 'pt',
120
+ attributeValue: '10',
121
+ componentName: 'Badge',
122
+ inlineStyleProps: 'sx',
123
+ loc: 59,
124
+ },
125
+ {
126
+ attributeName: 'pt',
127
+ attributeValue: '10',
128
+ componentName: 'Badge',
129
+ inlineStyleProps: 'sx',
130
+ loc: 60,
131
+ },
132
+ {
133
+ attributeName: 'pt',
134
+ attributeValue: '10',
135
+ componentName: 'Badge',
136
+ inlineStyleProps: 'sx',
137
+ loc: 61,
138
+ },
139
+ {
140
+ attributeName: 'pt',
141
+ attributeValue: '10',
142
+ componentName: 'Badge',
143
+ inlineStyleProps: 'sx',
144
+ loc: 62,
145
+ },
146
+ ],
40
147
  });
41
148
  });
42
149
  });
@@ -1,8 +1,17 @@
1
+ import type { CompoundComponentName } from './reports/types';
2
+ import { InlineStyleProps } from './reports/reportInlineStyle';
1
3
  declare const parseSource: (source: string) => {
2
4
  classNameLocs: number[];
3
5
  styleLocs: number[];
4
6
  sxLocs: number[];
5
7
  styledComponentLocs: number[];
6
8
  approvedLocs: number[];
9
+ violatingAttributes: {
10
+ attributeName: string;
11
+ attributeValue: string | null;
12
+ inlineStyleProps: InlineStyleProps;
13
+ componentName: CompoundComponentName;
14
+ loc: number | undefined;
15
+ }[];
7
16
  };
8
17
  export default parseSource;
@@ -40,6 +40,7 @@ const parseSource = (source) => {
40
40
  let classNameLocs = [];
41
41
  let styleLocs = [];
42
42
  let sxLocs = [];
43
+ let violatingAttributes = [];
43
44
  const approvedCmtLocs = [];
44
45
  const approvedClassnameLocs = [];
45
46
  const ast = recast.parse(source, { parser: tsParser });
@@ -96,6 +97,14 @@ const parseSource = (source) => {
96
97
  if (hasStyledComponentsImport) {
97
98
  styledComponentLocs = (0, reportStyledComponents_1.default)(ast, componentList, styledAliasName).filter(isNotApprovedSnowflakes);
98
99
  }
100
+ violatingAttributes = customPropLocs.violatingAttributes.filter((report) => {
101
+ if (report.loc) {
102
+ return !approvedCmtLocs.includes(report.loc);
103
+ }
104
+ else {
105
+ return true;
106
+ }
107
+ });
99
108
  }
100
109
  return {
101
110
  classNameLocs,
@@ -103,6 +112,7 @@ const parseSource = (source) => {
103
112
  sxLocs,
104
113
  styledComponentLocs,
105
114
  approvedLocs: [...approvedCmtLocs, ...approvedClassnameLocs],
115
+ violatingAttributes,
106
116
  };
107
117
  };
108
118
  exports.default = parseSource;
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ const recast = __importStar(require("recast"));
30
+ const reportInlineStyle_1 = __importDefault(require("../reportInlineStyle"));
31
+ const constants_1 = require("../constants");
32
+ const tsParser = __importStar(require("../../parsers/typescript"));
33
+ describe('reportInlineStyle', () => {
34
+ it('should return line numbers and violating attributes for inline styles that violate the ruleset', () => {
35
+ const code = `
36
+ const MyComponent = () => (
37
+ <Card style={{
38
+ color: 'red',
39
+ backgroundColor: 'blue'
40
+ }}
41
+ sx={{
42
+ padding: 10,
43
+ backgroundColor: 'blue'
44
+ }} />
45
+ );
46
+ `;
47
+ // Step 1: Parse the code to generate the AST
48
+ const ast = recast.parse(code, { parser: tsParser });
49
+ const attributes = [];
50
+ // Step 2: Traverse the AST to extract relevant JSXAttributes (style and sx)
51
+ recast.visit(ast, {
52
+ visitJSXAttribute(path) {
53
+ const node = path.node;
54
+ attributes.push(node);
55
+ this.traverse(path);
56
+ },
57
+ });
58
+ const componentName = 'Card';
59
+ // Mock the RULESET_MAP and SX_RULESET_MAP for this test
60
+ constants_1.RULESET_MAP.Card = ['color', 'backgroundColor'];
61
+ constants_1.SX_RULESET_MAP.Card = ['padding', 'backgroundColor'];
62
+ // Step 3: Run the function with the dynamically extracted attributes
63
+ const result = (0, reportInlineStyle_1.default)(ast, attributes, componentName);
64
+ expect(result.locs.style).toEqual([3]);
65
+ expect(result.locs.sx).toEqual([7]);
66
+ expect(result.violatingAttributes).toEqual([
67
+ {
68
+ attributeName: 'color',
69
+ attributeValue: "'red'",
70
+ componentName: 'Card',
71
+ inlineStyleProps: 'style',
72
+ loc: 3,
73
+ },
74
+ {
75
+ attributeName: 'backgroundColor',
76
+ attributeValue: "'blue'",
77
+ componentName: 'Card',
78
+ inlineStyleProps: 'style',
79
+ loc: 3,
80
+ },
81
+ {
82
+ attributeName: 'padding',
83
+ attributeValue: '10',
84
+ componentName: 'Card',
85
+ inlineStyleProps: 'sx',
86
+ loc: 7,
87
+ },
88
+ {
89
+ attributeName: 'backgroundColor',
90
+ attributeValue: "'blue'",
91
+ componentName: 'Card',
92
+ inlineStyleProps: 'sx',
93
+ loc: 7,
94
+ },
95
+ ]);
96
+ });
97
+ it('should return empty arrays when no violations are found', () => {
98
+ const code = `
99
+ const MyComponent = () => (
100
+ <Card style={{ margin: '10px' }} sx={{ margin: 5 }} />
101
+ );
102
+ `;
103
+ // Step 1: Parse the code to generate the AST
104
+ const ast = recast.parse(code, { parser: tsParser });
105
+ const attributes = [];
106
+ // Step 2: Traverse the AST to extract relevant JSXAttributes (style and sx)
107
+ recast.visit(ast, {
108
+ visitJSXAttribute(path) {
109
+ const node = path.node;
110
+ attributes.push(node);
111
+ this.traverse(path);
112
+ },
113
+ });
114
+ const componentName = 'Card';
115
+ // Mock the RULESET_MAP and SX_RULESET_MAP for this test
116
+ constants_1.RULESET_MAP.Card = ['color'];
117
+ constants_1.SX_RULESET_MAP.Card = ['padding'];
118
+ const result = (0, reportInlineStyle_1.default)(ast, attributes, componentName);
119
+ expect(result.locs.style).toEqual([]);
120
+ expect(result.locs.sx).toEqual([]);
121
+ expect(result.violatingAttributes).toEqual([]);
122
+ expect(result.violatingAttributes).toEqual([]);
123
+ });
124
+ });
@@ -22,6 +22,7 @@ declare const RULESET_MAP: {
22
22
  'Checkbox.Group': string[];
23
23
  'Checkbox.Button': string[];
24
24
  'Checkbox.ButtonGroup': string[];
25
+ Chip: string[];
25
26
  Collapse: string[];
26
27
  Comment: string[];
27
28
  'Comment.Editor': string[];
@@ -38,6 +39,7 @@ declare const RULESET_MAP: {
38
39
  'DatePicker.Fortnightly': string[];
39
40
  'DatePicker.SingleMonth': string[];
40
41
  'DatePicker.SingleYear': string[];
42
+ 'DatePicker.FixedRange': string[];
41
43
  Divider: string[];
42
44
  Dropdown: string[];
43
45
  Empty: string[];
@@ -128,6 +130,7 @@ declare const SX_RULESET_MAP: {
128
130
  'Checkbox.Group': string[];
129
131
  'Checkbox.Button': string[];
130
132
  'Checkbox.ButtonGroup': string[];
133
+ Chip: string[];
131
134
  Collapse: string[];
132
135
  Comment: string[];
133
136
  'Comment.Editor': string[];
@@ -144,6 +147,7 @@ declare const SX_RULESET_MAP: {
144
147
  'DatePicker.Fortnightly': string[];
145
148
  'DatePicker.SingleMonth': string[];
146
149
  'DatePicker.SingleYear': string[];
150
+ 'DatePicker.FixedRange': string[];
147
151
  Divider: string[];
148
152
  Dropdown: string[];
149
153
  Empty: string[];
@@ -210,7 +214,7 @@ declare const SX_RULESET_MAP: {
210
214
  'Typography.Text': string[];
211
215
  Widget: string[];
212
216
  };
213
- declare const HD_COMPONENTS: readonly ["Alert", "Badge", "Banner", "Breadcrumb", "Button", "Card", "Carousel", "Chart", "Checkbox", "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"];
217
+ declare const HD_COMPONENTS: readonly ["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"];
214
218
  declare const APPROVED_COMMENT = "@snowflake-guard/snowflake-approved-by-andromeda";
215
219
  declare const APPROVED_CLASSNAME_COMMENT = "@snowflake-guard/none-css-classname";
216
220
  export { HD_COMPONENTS, RULESET_MAP, SX_RULESET_MAP, APPROVED_COMMENT, APPROVED_CLASSNAME_COMMENT, };
@@ -177,6 +177,12 @@ const RULESET_MAP = {
177
177
  ...SHADOW_ATTRS,
178
178
  ...HEIGHT_ATTRS,
179
179
  ],
180
+ Chip: [
181
+ ...COMMON_PROHIBITED_ATTRS,
182
+ ...SHADOW_ATTRS,
183
+ ...WIDTH_ATTRS,
184
+ ...HEIGHT_ATTRS,
185
+ ],
180
186
  Collapse: [...COMMON_PROHIBITED_ATTRS, ...SHADOW_ATTRS, ...HEIGHT_ATTRS],
181
187
  Comment: [...COMMON_PROHIBITED_ATTRS, ...SHADOW_ATTRS, ...HEIGHT_ATTRS],
182
188
  'Comment.Editor': [
@@ -255,6 +261,11 @@ const RULESET_MAP = {
255
261
  ...SHADOW_ATTRS,
256
262
  ...HEIGHT_ATTRS,
257
263
  ],
264
+ 'DatePicker.FixedRange': [
265
+ ...COMMON_PROHIBITED_ATTRS,
266
+ ...SHADOW_ATTRS,
267
+ ...HEIGHT_ATTRS,
268
+ ],
258
269
  Divider: [
259
270
  ...COMMON_PROHIBITED_ATTRS,
260
271
  ...SHADOW_ATTRS,
@@ -552,12 +563,7 @@ const RULESET_MAP = {
552
563
  ],
553
564
  TimePicker: [...COMMON_PROHIBITED_ATTRS, ...SHADOW_ATTRS, ...HEIGHT_ATTRS],
554
565
  Timeline: [...COMMON_PROHIBITED_ATTRS, ...SHADOW_ATTRS, ...HEIGHT_ATTRS],
555
- Tooltip: [
556
- ...COMMON_PROHIBITED_ATTRS,
557
- ...SHADOW_ATTRS,
558
- ...WIDTH_ATTRS,
559
- ...HEIGHT_ATTRS,
560
- ],
566
+ Tooltip: [...COMMON_PROHIBITED_ATTRS, ...SHADOW_ATTRS],
561
567
  'Typography.Title': [
562
568
  ...COMMON_PROHIBITED_ATTRS,
563
569
  ...SHADOW_ATTRS,
@@ -605,7 +611,7 @@ const SX_RULESET_MAP = Object.assign(Object.assign({}, RULESET_MAP), { Alert: [.
605
611
  ], 'Checkbox.ButtonGroup': [
606
612
  ...RULESET_MAP['Checkbox.ButtonGroup'],
607
613
  ...COMMON_SX_PROHIBITED_ATTRS,
608
- ], Collapse: [...RULESET_MAP.Collapse, ...COMMON_SX_PROHIBITED_ATTRS], Comment: [...RULESET_MAP.Comment, ...COMMON_SX_PROHIBITED_ATTRS], 'Comment.Editor': [
614
+ ], Chip: [...RULESET_MAP.Chip, ...COMMON_SX_PROHIBITED_ATTRS], Collapse: [...RULESET_MAP.Collapse, ...COMMON_SX_PROHIBITED_ATTRS], Comment: [...RULESET_MAP.Comment, ...COMMON_SX_PROHIBITED_ATTRS], 'Comment.Editor': [
609
615
  ...RULESET_MAP['Comment.Editor'],
610
616
  ...COMMON_SX_PROHIBITED_ATTRS,
611
617
  ], ContextPanel: [
@@ -649,6 +655,9 @@ const SX_RULESET_MAP = Object.assign(Object.assign({}, RULESET_MAP), { Alert: [.
649
655
  ], 'DatePicker.SingleYear': [
650
656
  ...RULESET_MAP['DatePicker.SingleYear'],
651
657
  ...COMMON_SX_PROHIBITED_ATTRS,
658
+ ], 'DatePicker.FixedRange': [
659
+ ...RULESET_MAP['DatePicker.FixedRange'],
660
+ ...COMMON_SX_PROHIBITED_ATTRS,
652
661
  ], Divider: [
653
662
  ...RULESET_MAP.Divider,
654
663
  ...COMMON_SX_PROHIBITED_ATTRS,
@@ -799,6 +808,7 @@ const HD_COMPONENTS = [
799
808
  'Carousel',
800
809
  'Chart',
801
810
  'Checkbox',
811
+ 'Chip',
802
812
  'Collapse',
803
813
  'Comment',
804
814
  'ContextPanel',
@@ -1,10 +1,18 @@
1
1
  import * as recast from 'recast';
2
- import type { ComponentName } from './types';
2
+ import { InlineStyleProps } from './reportInlineStyle';
3
+ import type { ComponentName, CompoundComponentName } from './types';
3
4
  declare const reportCustomProperties: (ast: recast.types.ASTNode, componentList: {
4
- [k: string]: "Alert" | "Badge" | "Banner" | "Breadcrumb" | "Button" | "Card" | "Carousel" | "Chart" | "Checkbox" | "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";
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";
5
6
  }) => {
6
7
  className: number[];
7
8
  style: number[];
8
9
  sx: number[];
10
+ violatingAttributes: {
11
+ attributeName: string;
12
+ attributeValue: string | null;
13
+ inlineStyleProps: InlineStyleProps;
14
+ componentName: CompoundComponentName;
15
+ loc: number | undefined;
16
+ }[];
9
17
  };
10
18
  export default reportCustomProperties;
@@ -30,10 +30,11 @@ const recast = __importStar(require("recast"));
30
30
  const reportClassName_1 = __importDefault(require("./reportClassName"));
31
31
  const reportInlineStyle_1 = __importDefault(require("./reportInlineStyle"));
32
32
  const reportCustomProperties = (ast, componentList) => {
33
- const locs = {
33
+ const report = {
34
34
  className: [],
35
35
  style: [],
36
36
  sx: [],
37
+ violatingAttributes: [],
37
38
  };
38
39
  const localComponentList = Object.keys(componentList);
39
40
  recast.visit(ast, {
@@ -44,10 +45,17 @@ const reportCustomProperties = (ast, componentList) => {
44
45
  localComponentList.includes(path.value.name.name)) {
45
46
  const attributes = path.value
46
47
  .attributes;
47
- const styleObjectLocs = (0, reportInlineStyle_1.default)(ast, attributes, componentList[path.value.name.name]);
48
- locs.className = [...locs.className, ...(0, reportClassName_1.default)(attributes)];
49
- locs.style = [...locs.style, ...styleObjectLocs.style];
50
- locs.sx = [...locs.sx, ...styleObjectLocs.sx];
48
+ const { locs: styleObjectLocs, violatingAttributes } = (0, reportInlineStyle_1.default)(ast, attributes, componentList[path.value.name.name]);
49
+ report.className = [
50
+ ...report.className,
51
+ ...(0, reportClassName_1.default)(attributes),
52
+ ];
53
+ report.style = [...report.style, ...styleObjectLocs.style];
54
+ report.sx = [...report.sx, ...styleObjectLocs.sx];
55
+ report.violatingAttributes = [
56
+ ...report.violatingAttributes,
57
+ ...violatingAttributes,
58
+ ];
51
59
  }
52
60
  // Case 2: Custom compound component, e.g. <Card.Header />
53
61
  if (path.value.name.type === 'JSXMemberExpression' &&
@@ -58,10 +66,17 @@ const reportCustomProperties = (ast, componentList) => {
58
66
  ].join('.');
59
67
  const attributes = path.value
60
68
  .attributes;
61
- const styleObjectLocs = (0, reportInlineStyle_1.default)(ast, attributes, compoundComponentName);
62
- locs.className = [...locs.className, ...(0, reportClassName_1.default)(attributes)];
63
- locs.style = [...locs.style, ...styleObjectLocs.style];
64
- locs.sx = [...locs.sx, ...styleObjectLocs.sx];
69
+ const { locs: styleObjectLocs, violatingAttributes } = (0, reportInlineStyle_1.default)(ast, attributes, compoundComponentName);
70
+ report.className = [
71
+ ...report.className,
72
+ ...(0, reportClassName_1.default)(attributes),
73
+ ];
74
+ report.style = [...report.style, ...styleObjectLocs.style];
75
+ report.sx = [...report.sx, ...styleObjectLocs.sx];
76
+ report.violatingAttributes = [
77
+ ...report.violatingAttributes,
78
+ ...violatingAttributes,
79
+ ];
65
80
  }
66
81
  },
67
82
  // Case 3: Custom compound component with spead operator. e.g. const { Header } = Card, then <Header />
@@ -90,13 +105,17 @@ const reportCustomProperties = (ast, componentList) => {
90
105
  openPath.value.name.name,
91
106
  ].join('.');
92
107
  const { attributes } = openPath.value;
93
- const styleObjectLocs = (0, reportInlineStyle_1.default)(ast, attributes, compoundComponentName);
94
- locs.className = [
95
- ...locs.className,
108
+ const { locs: styleObjectLocs, violatingAttributes } = (0, reportInlineStyle_1.default)(ast, attributes, compoundComponentName);
109
+ report.className = [
110
+ ...report.className,
96
111
  ...(0, reportClassName_1.default)(attributes),
97
112
  ];
98
- locs.style = [...locs.style, ...styleObjectLocs.style];
99
- locs.sx = [...locs.sx, ...styleObjectLocs.sx];
113
+ report.style = [...report.style, ...styleObjectLocs.style];
114
+ report.sx = [...report.sx, ...styleObjectLocs.sx];
115
+ report.violatingAttributes = [
116
+ ...report.violatingAttributes,
117
+ ...violatingAttributes,
118
+ ];
100
119
  }
101
120
  },
102
121
  });
@@ -104,6 +123,6 @@ const reportCustomProperties = (ast, componentList) => {
104
123
  }
105
124
  },
106
125
  });
107
- return locs;
126
+ return report;
108
127
  };
109
128
  exports.default = reportCustomProperties;
@@ -1,7 +1,19 @@
1
1
  import * as recast from 'recast';
2
2
  import type { CompoundComponentName } from './types';
3
+ export type InlineStyleProps = 'style' | 'sx';
4
+ export declare const INLINE_STYLE_PROPERTIES: string[];
5
+ type ViolatingAttribute = {
6
+ attributeName: string;
7
+ attributeValue: string | null;
8
+ inlineStyleProps: InlineStyleProps;
9
+ componentName: CompoundComponentName;
10
+ loc: number | undefined;
11
+ };
3
12
  declare const reportInlineStyle: (ast: recast.types.ASTNode, attributes: recast.types.namedTypes.JSXAttribute[], componentName: CompoundComponentName) => {
4
- style: number[];
5
- sx: number[];
13
+ locs: {
14
+ style: number[];
15
+ sx: number[];
16
+ };
17
+ violatingAttributes: ViolatingAttribute[];
6
18
  };
7
19
  export default reportInlineStyle;
@@ -23,35 +23,51 @@ 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
27
  const recast = __importStar(require("recast"));
27
28
  const constants_1 = require("./constants");
28
29
  const BLACKLIST_PROPERTIES = {
29
30
  style: constants_1.RULESET_MAP,
30
31
  sx: constants_1.SX_RULESET_MAP,
31
32
  };
32
- const INLINE_STYLE_PROPERTIES = ['style', 'sx'];
33
+ exports.INLINE_STYLE_PROPERTIES = ['style', 'sx'];
34
+ const addViolatingAttribute = (prop, styleObjName, componentName, loc, violatingAttributes) => {
35
+ if (prop.key.type !== 'Identifier') {
36
+ return;
37
+ }
38
+ violatingAttributes.push({
39
+ attributeName: prop.key.name,
40
+ attributeValue: recast.print(prop.value).code,
41
+ inlineStyleProps: styleObjName,
42
+ componentName,
43
+ loc,
44
+ });
45
+ };
33
46
  const reportInlineStyle = (ast, attributes, componentName) => {
34
47
  const locs = {
35
48
  style: [],
36
49
  sx: [],
37
50
  };
51
+ const violatingAttributes = [];
38
52
  let hasCustomStyle = false;
39
53
  let styleObjName;
40
54
  attributes.forEach((attr) => {
41
55
  var _a;
42
56
  if (attr.type === 'JSXAttribute' &&
43
57
  typeof attr.name.name === 'string' &&
44
- INLINE_STYLE_PROPERTIES.includes(attr.name.name) &&
58
+ exports.INLINE_STYLE_PROPERTIES.includes(attr.name.name) &&
45
59
  ((_a = attr.value) === null || _a === void 0 ? void 0 : _a.type) === 'JSXExpressionContainer') {
46
60
  styleObjName = attr.name.name;
47
61
  const { expression } = attr.value;
48
62
  if (expression.type === 'ObjectExpression') {
49
63
  expression.properties.forEach((prop) => {
64
+ var _a;
50
65
  // Case 1: Use direct object, e.g. <Card style={{ color: 'red' }} />
51
66
  if (prop.type === 'ObjectProperty' &&
52
67
  prop.key.type === 'Identifier' &&
53
68
  BLACKLIST_PROPERTIES[styleObjName][componentName].includes(prop.key.name)) {
54
69
  hasCustomStyle = true;
70
+ addViolatingAttribute(prop, styleObjName, componentName, (_a = attr.loc) === null || _a === void 0 ? void 0 : _a.start.line, violatingAttributes);
55
71
  }
56
72
  // Case 2: Use spread operator, e.g. <Card style={{ ...customStyle }} />
57
73
  if (prop.type === 'SpreadElement' &&
@@ -67,10 +83,12 @@ const reportInlineStyle = (ast, attributes, componentName) => {
67
83
  declaration.id.name === variableName &&
68
84
  ((_a = declaration.init) === null || _a === void 0 ? void 0 : _a.type) === 'ObjectExpression') {
69
85
  declaration.init.properties.forEach((deProp) => {
86
+ var _a;
70
87
  if (deProp.type === 'ObjectProperty' &&
71
88
  deProp.key.type === 'Identifier' &&
72
89
  BLACKLIST_PROPERTIES[styleObjName][componentName].includes(deProp.key.name)) {
73
90
  hasCustomStyle = true;
91
+ addViolatingAttribute(deProp, styleObjName, componentName, (_a = attr.loc) === null || _a === void 0 ? void 0 : _a.start.line, violatingAttributes);
74
92
  }
75
93
  });
76
94
  }
@@ -99,10 +117,12 @@ const reportInlineStyle = (ast, attributes, componentName) => {
99
117
  deProp.key.name === propName &&
100
118
  deProp.value.type === 'ObjectExpression') {
101
119
  deProp.value.properties.forEach((p) => {
120
+ var _a;
102
121
  if (p.type === 'ObjectProperty' &&
103
122
  p.key.type === 'Identifier' &&
104
123
  BLACKLIST_PROPERTIES[styleObjName][componentName].includes(p.key.name)) {
105
124
  hasCustomStyle = true;
125
+ addViolatingAttribute(p, styleObjName, componentName, (_a = attr.loc) === null || _a === void 0 ? void 0 : _a.start.line, violatingAttributes);
106
126
  }
107
127
  });
108
128
  }
@@ -126,10 +146,12 @@ const reportInlineStyle = (ast, attributes, componentName) => {
126
146
  declaration.id.name === variableName &&
127
147
  ((_a = declaration.init) === null || _a === void 0 ? void 0 : _a.type) === 'ObjectExpression') {
128
148
  declaration.init.properties.forEach((prop) => {
149
+ var _a;
129
150
  if (prop.type === 'ObjectProperty' &&
130
151
  prop.key.type === 'Identifier' &&
131
152
  BLACKLIST_PROPERTIES[styleObjName][componentName].includes(prop.key.name)) {
132
153
  hasCustomStyle = true;
154
+ addViolatingAttribute(prop, styleObjName, componentName, (_a = attr.loc) === null || _a === void 0 ? void 0 : _a.start.line, violatingAttributes);
133
155
  }
134
156
  });
135
157
  }
@@ -157,10 +179,12 @@ const reportInlineStyle = (ast, attributes, componentName) => {
157
179
  prop.key.name === propName &&
158
180
  prop.value.type === 'ObjectExpression') {
159
181
  prop.value.properties.forEach((p) => {
182
+ var _a;
160
183
  if (p.type === 'ObjectProperty' &&
161
184
  p.key.type === 'Identifier' &&
162
185
  BLACKLIST_PROPERTIES[styleObjName][componentName].includes(p.key.name)) {
163
186
  hasCustomStyle = true;
187
+ addViolatingAttribute(p, styleObjName, componentName, (_a = attr.loc) === null || _a === void 0 ? void 0 : _a.start.line, violatingAttributes);
164
188
  }
165
189
  });
166
190
  }
@@ -174,6 +198,6 @@ const reportInlineStyle = (ast, attributes, componentName) => {
174
198
  }
175
199
  }
176
200
  });
177
- return locs;
201
+ return { locs, violatingAttributes };
178
202
  };
179
203
  exports.default = reportInlineStyle;
@@ -1,6 +1,6 @@
1
1
  import * as recast from 'recast';
2
2
  import type { ComponentName } from './types';
3
3
  declare const reportStyledComponents: (ast: recast.types.ASTNode, componentList: {
4
- [k: string]: "Alert" | "Badge" | "Banner" | "Breadcrumb" | "Button" | "Card" | "Carousel" | "Chart" | "Checkbox" | "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";
4
+ [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";
5
5
  }, styledAliasName: string) => number[];
6
6
  export default reportStyledComponents;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hero-design/snowflake-guard",
3
- "version": "1.0.13",
3
+ "version": "1.1.0",
4
4
  "description": "A hero-design bot detecting snowflake usage",
5
5
  "author": "Hau Dao",
6
6
  "license": "ISC",