@hero-design/snowflake-guard 1.0.14 → 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/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,124 @@ 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
+ additionalProps: [
97
+ {
98
+ propName: 'variant',
99
+ propValue: '"text"',
100
+ },
101
+ ],
102
+ },
103
+ {
104
+ attributeName: 'padding',
105
+ attributeValue: '30',
106
+ componentName: 'Button',
107
+ inlineStyleProps: 'style',
108
+ loc: 52,
109
+ },
110
+ {
111
+ attributeName: 'padding',
112
+ attributeValue: '30',
113
+ componentName: 'Button',
114
+ inlineStyleProps: 'style',
115
+ loc: 53,
116
+ },
117
+ {
118
+ attributeName: 'mt',
119
+ attributeValue: '10',
120
+ componentName: 'Badge.Count',
121
+ inlineStyleProps: 'sx',
122
+ loc: 58,
123
+ },
124
+ {
125
+ attributeName: 'pt',
126
+ attributeValue: '10',
127
+ componentName: 'Badge',
128
+ inlineStyleProps: 'sx',
129
+ loc: 59,
130
+ },
131
+ {
132
+ attributeName: 'pt',
133
+ attributeValue: '10',
134
+ componentName: 'Badge',
135
+ inlineStyleProps: 'sx',
136
+ loc: 60,
137
+ },
138
+ {
139
+ attributeName: 'pt',
140
+ attributeValue: '10',
141
+ componentName: 'Badge',
142
+ inlineStyleProps: 'sx',
143
+ loc: 61,
144
+ },
145
+ {
146
+ attributeName: 'pt',
147
+ attributeValue: '10',
148
+ componentName: 'Badge',
149
+ inlineStyleProps: 'sx',
150
+ loc: 62,
151
+ },
152
+ ],
40
153
  });
41
154
  });
42
155
  });
@@ -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,12 @@ 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
+ return true;
105
+ });
99
106
  }
100
107
  return {
101
108
  classNameLocs,
@@ -103,6 +110,7 @@ const parseSource = (source) => {
103
110
  sxLocs,
104
111
  styledComponentLocs,
105
112
  approvedLocs: [...approvedCmtLocs, ...approvedClassnameLocs],
113
+ violatingAttributes,
106
114
  };
107
115
  };
108
116
  exports.default = parseSource;
@@ -0,0 +1,133 @@
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
+ variant="primary"
46
+ intent="success"
47
+ />
48
+ );
49
+ `;
50
+ // Step 1: Parse the code to generate the AST
51
+ const ast = recast.parse(code, { parser: tsParser });
52
+ const attributes = [];
53
+ // Step 2: Traverse the AST to extract relevant JSXAttributes (style and sx)
54
+ recast.visit(ast, {
55
+ visitJSXAttribute(path) {
56
+ const { node } = path;
57
+ attributes.push(node);
58
+ this.traverse(path);
59
+ },
60
+ });
61
+ const componentName = 'Card';
62
+ // Mock the RULESET_MAP and SX_RULESET_MAP for this test
63
+ constants_1.RULESET_MAP.Card = ['color', 'backgroundColor'];
64
+ constants_1.SX_RULESET_MAP.Card = ['padding', 'backgroundColor'];
65
+ // Step 3: Run the function with the dynamically extracted attributes
66
+ const result = (0, reportInlineStyle_1.default)(ast, attributes, componentName);
67
+ expect(result.additionalProps).toMatchObject([
68
+ {
69
+ propValue: '"primary"',
70
+ propName: 'variant',
71
+ },
72
+ ]);
73
+ expect(result.locs.style).toEqual([3]);
74
+ expect(result.locs.sx).toEqual([7]);
75
+ expect(result.violatingAttributes).toEqual([
76
+ {
77
+ attributeName: 'color',
78
+ attributeValue: "'red'",
79
+ componentName: 'Card',
80
+ inlineStyleProps: 'style',
81
+ loc: 3,
82
+ },
83
+ {
84
+ attributeName: 'backgroundColor',
85
+ attributeValue: "'blue'",
86
+ componentName: 'Card',
87
+ inlineStyleProps: 'style',
88
+ loc: 3,
89
+ },
90
+ {
91
+ attributeName: 'padding',
92
+ attributeValue: '10',
93
+ componentName: 'Card',
94
+ inlineStyleProps: 'sx',
95
+ loc: 7,
96
+ },
97
+ {
98
+ attributeName: 'backgroundColor',
99
+ attributeValue: "'blue'",
100
+ componentName: 'Card',
101
+ inlineStyleProps: 'sx',
102
+ loc: 7,
103
+ },
104
+ ]);
105
+ });
106
+ it('should return empty arrays when no violations are found', () => {
107
+ const code = `
108
+ const MyComponent = () => (
109
+ <Card style={{ margin: '10px' }} sx={{ margin: 5 }} />
110
+ );
111
+ `;
112
+ // Step 1: Parse the code to generate the AST
113
+ const ast = recast.parse(code, { parser: tsParser });
114
+ const attributes = [];
115
+ // Step 2: Traverse the AST to extract relevant JSXAttributes (style and sx)
116
+ recast.visit(ast, {
117
+ visitJSXAttribute(path) {
118
+ const { node } = path;
119
+ attributes.push(node);
120
+ this.traverse(path);
121
+ },
122
+ });
123
+ const componentName = 'Card';
124
+ // Mock the RULESET_MAP and SX_RULESET_MAP for this test
125
+ constants_1.RULESET_MAP.Card = ['color'];
126
+ constants_1.SX_RULESET_MAP.Card = ['padding'];
127
+ const result = (0, reportInlineStyle_1.default)(ast, attributes, componentName);
128
+ expect(result.locs.style).toEqual([]);
129
+ expect(result.locs.sx).toEqual([]);
130
+ expect(result.violatingAttributes).toEqual([]);
131
+ expect(result.violatingAttributes).toEqual([]);
132
+ });
133
+ });
@@ -1,10 +1,19 @@
1
1
  import * as recast from 'recast';
2
- import type { ComponentName } from './types';
2
+ import { AdditionalProp, InlineStyleProps } from './reportInlineStyle';
3
+ import type { ComponentName, CompoundComponentName } from './types';
3
4
  declare const reportCustomProperties: (ast: recast.types.ASTNode, componentList: {
4
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
+ additionalProps?: AdditionalProp[];
17
+ }[];
9
18
  };
10
19
  export default reportCustomProperties;
@@ -29,11 +29,15 @@ 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
- const locs = {
36
+ const report = {
34
37
  className: [],
35
38
  style: [],
36
39
  sx: [],
40
+ violatingAttributes: [],
37
41
  };
38
42
  const localComponentList = Object.keys(componentList);
39
43
  recast.visit(ast, {
@@ -44,10 +48,17 @@ const reportCustomProperties = (ast, componentList) => {
44
48
  localComponentList.includes(path.value.name.name)) {
45
49
  const attributes = path.value
46
50
  .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];
51
+ const { locs: styleObjectLocs, violatingAttributes, additionalProps, } = (0, reportInlineStyle_1.default)(ast, attributes, componentList[path.value.name.name]);
52
+ report.className = [
53
+ ...report.className,
54
+ ...(0, reportClassName_1.default)(attributes),
55
+ ];
56
+ report.style = [...report.style, ...styleObjectLocs.style];
57
+ report.sx = [...report.sx, ...styleObjectLocs.sx];
58
+ report.violatingAttributes = [
59
+ ...report.violatingAttributes,
60
+ ...mapViolatingAttributesAndAdditionalProps(violatingAttributes, additionalProps),
61
+ ];
51
62
  }
52
63
  // Case 2: Custom compound component, e.g. <Card.Header />
53
64
  if (path.value.name.type === 'JSXMemberExpression' &&
@@ -58,10 +69,17 @@ const reportCustomProperties = (ast, componentList) => {
58
69
  ].join('.');
59
70
  const attributes = path.value
60
71
  .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];
72
+ const { locs: styleObjectLocs, violatingAttributes, additionalProps, } = (0, reportInlineStyle_1.default)(ast, attributes, compoundComponentName);
73
+ report.className = [
74
+ ...report.className,
75
+ ...(0, reportClassName_1.default)(attributes),
76
+ ];
77
+ report.style = [...report.style, ...styleObjectLocs.style];
78
+ report.sx = [...report.sx, ...styleObjectLocs.sx];
79
+ report.violatingAttributes = [
80
+ ...report.violatingAttributes,
81
+ ...mapViolatingAttributesAndAdditionalProps(violatingAttributes, additionalProps),
82
+ ];
65
83
  }
66
84
  },
67
85
  // Case 3: Custom compound component with spead operator. e.g. const { Header } = Card, then <Header />
@@ -90,13 +108,17 @@ const reportCustomProperties = (ast, componentList) => {
90
108
  openPath.value.name.name,
91
109
  ].join('.');
92
110
  const { attributes } = openPath.value;
93
- const styleObjectLocs = (0, reportInlineStyle_1.default)(ast, attributes, compoundComponentName);
94
- locs.className = [
95
- ...locs.className,
111
+ const { locs: styleObjectLocs, violatingAttributes, additionalProps, } = (0, reportInlineStyle_1.default)(ast, attributes, compoundComponentName);
112
+ report.className = [
113
+ ...report.className,
96
114
  ...(0, reportClassName_1.default)(attributes),
97
115
  ];
98
- locs.style = [...locs.style, ...styleObjectLocs.style];
99
- locs.sx = [...locs.sx, ...styleObjectLocs.sx];
116
+ report.style = [...report.style, ...styleObjectLocs.style];
117
+ report.sx = [...report.sx, ...styleObjectLocs.sx];
118
+ report.violatingAttributes = [
119
+ ...report.violatingAttributes,
120
+ ...mapViolatingAttributesAndAdditionalProps(violatingAttributes, additionalProps),
121
+ ];
100
122
  }
101
123
  },
102
124
  });
@@ -104,6 +126,6 @@ const reportCustomProperties = (ast, componentList) => {
104
126
  }
105
127
  },
106
128
  });
107
- return locs;
129
+ return report;
108
130
  };
109
131
  exports.default = reportCustomProperties;
@@ -1,7 +1,25 @@
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
+ export declare const ADDITIONAL_PROPERTIES: string[];
6
+ export type ViolatingAttribute = {
7
+ attributeName: string;
8
+ attributeValue: string | null;
9
+ inlineStyleProps: InlineStyleProps;
10
+ componentName: CompoundComponentName;
11
+ loc: number | undefined;
12
+ };
13
+ export type AdditionalProp = {
14
+ propName: string;
15
+ propValue: string | null;
16
+ };
3
17
  declare const reportInlineStyle: (ast: recast.types.ASTNode, attributes: recast.types.namedTypes.JSXAttribute[], componentName: CompoundComponentName) => {
4
- style: number[];
5
- sx: number[];
18
+ locs: {
19
+ style: number[];
20
+ sx: number[];
21
+ };
22
+ violatingAttributes: ViolatingAttribute[];
23
+ additionalProps: AdditionalProp[];
6
24
  };
7
25
  export default reportInlineStyle;
@@ -23,35 +23,80 @@ var __importStar = (this && this.__importStar) || function (mod) {
23
23
  return result;
24
24
  };
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.ADDITIONAL_PROPERTIES = 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
+ exports.ADDITIONAL_PROPERTIES = ['variant']; // Add any additional props you want to track
35
+ const addViolatingAttribute = (prop, styleObjName, componentName, loc, violatingAttributes) => {
36
+ if (prop.key.type !== 'Identifier') {
37
+ return;
38
+ }
39
+ violatingAttributes.push({
40
+ attributeName: prop.key.name,
41
+ attributeValue: recast.print(prop.value).code,
42
+ inlineStyleProps: styleObjName,
43
+ componentName,
44
+ loc,
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
+ };
33
61
  const reportInlineStyle = (ast, attributes, componentName) => {
34
62
  const locs = {
35
63
  style: [],
36
64
  sx: [],
37
65
  };
66
+ const violatingAttributes = [];
67
+ const additionalProps = [];
38
68
  let hasCustomStyle = false;
39
69
  let styleObjName;
40
70
  attributes.forEach((attr) => {
41
71
  var _a;
42
- if (attr.type === 'JSXAttribute' &&
43
- typeof attr.name.name === 'string' &&
44
- INLINE_STYLE_PROPERTIES.includes(attr.name.name) &&
45
- ((_a = attr.value) === null || _a === void 0 ? void 0 : _a.type) === 'JSXExpressionContainer') {
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)) {
46
89
  styleObjName = attr.name.name;
47
90
  const { expression } = attr.value;
48
91
  if (expression.type === 'ObjectExpression') {
49
92
  expression.properties.forEach((prop) => {
93
+ var _a;
50
94
  // Case 1: Use direct object, e.g. <Card style={{ color: 'red' }} />
51
95
  if (prop.type === 'ObjectProperty' &&
52
96
  prop.key.type === 'Identifier' &&
53
97
  BLACKLIST_PROPERTIES[styleObjName][componentName].includes(prop.key.name)) {
54
98
  hasCustomStyle = true;
99
+ addViolatingAttribute(prop, styleObjName, componentName, (_a = attr.loc) === null || _a === void 0 ? void 0 : _a.start.line, violatingAttributes);
55
100
  }
56
101
  // Case 2: Use spread operator, e.g. <Card style={{ ...customStyle }} />
57
102
  if (prop.type === 'SpreadElement' &&
@@ -67,10 +112,12 @@ const reportInlineStyle = (ast, attributes, componentName) => {
67
112
  declaration.id.name === variableName &&
68
113
  ((_a = declaration.init) === null || _a === void 0 ? void 0 : _a.type) === 'ObjectExpression') {
69
114
  declaration.init.properties.forEach((deProp) => {
115
+ var _a;
70
116
  if (deProp.type === 'ObjectProperty' &&
71
117
  deProp.key.type === 'Identifier' &&
72
118
  BLACKLIST_PROPERTIES[styleObjName][componentName].includes(deProp.key.name)) {
73
119
  hasCustomStyle = true;
120
+ addViolatingAttribute(deProp, styleObjName, componentName, (_a = attr.loc) === null || _a === void 0 ? void 0 : _a.start.line, violatingAttributes);
74
121
  }
75
122
  });
76
123
  }
@@ -99,10 +146,12 @@ const reportInlineStyle = (ast, attributes, componentName) => {
99
146
  deProp.key.name === propName &&
100
147
  deProp.value.type === 'ObjectExpression') {
101
148
  deProp.value.properties.forEach((p) => {
149
+ var _a;
102
150
  if (p.type === 'ObjectProperty' &&
103
151
  p.key.type === 'Identifier' &&
104
152
  BLACKLIST_PROPERTIES[styleObjName][componentName].includes(p.key.name)) {
105
153
  hasCustomStyle = true;
154
+ addViolatingAttribute(p, styleObjName, componentName, (_a = attr.loc) === null || _a === void 0 ? void 0 : _a.start.line, violatingAttributes);
106
155
  }
107
156
  });
108
157
  }
@@ -126,10 +175,12 @@ const reportInlineStyle = (ast, attributes, componentName) => {
126
175
  declaration.id.name === variableName &&
127
176
  ((_a = declaration.init) === null || _a === void 0 ? void 0 : _a.type) === 'ObjectExpression') {
128
177
  declaration.init.properties.forEach((prop) => {
178
+ var _a;
129
179
  if (prop.type === 'ObjectProperty' &&
130
180
  prop.key.type === 'Identifier' &&
131
181
  BLACKLIST_PROPERTIES[styleObjName][componentName].includes(prop.key.name)) {
132
182
  hasCustomStyle = true;
183
+ addViolatingAttribute(prop, styleObjName, componentName, (_a = attr.loc) === null || _a === void 0 ? void 0 : _a.start.line, violatingAttributes);
133
184
  }
134
185
  });
135
186
  }
@@ -157,10 +208,12 @@ const reportInlineStyle = (ast, attributes, componentName) => {
157
208
  prop.key.name === propName &&
158
209
  prop.value.type === 'ObjectExpression') {
159
210
  prop.value.properties.forEach((p) => {
211
+ var _a;
160
212
  if (p.type === 'ObjectProperty' &&
161
213
  p.key.type === 'Identifier' &&
162
214
  BLACKLIST_PROPERTIES[styleObjName][componentName].includes(p.key.name)) {
163
215
  hasCustomStyle = true;
216
+ addViolatingAttribute(p, styleObjName, componentName, (_a = attr.loc) === null || _a === void 0 ? void 0 : _a.start.line, violatingAttributes);
164
217
  }
165
218
  });
166
219
  }
@@ -174,6 +227,6 @@ const reportInlineStyle = (ast, attributes, componentName) => {
174
227
  }
175
228
  }
176
229
  });
177
- return locs;
230
+ return { locs, violatingAttributes, additionalProps };
178
231
  };
179
232
  exports.default = reportInlineStyle;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hero-design/snowflake-guard",
3
- "version": "1.0.14",
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",