@hero-design/snowflake-guard 1.2.4 → 1.3.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 +2 -0
- package/lib/src/__tests__/parseMobileSource.spec.d.ts +1 -0
- package/lib/src/__tests__/parseMobileSource.spec.js +313 -0
- package/lib/src/index.js +31 -23
- package/lib/src/parseMobileSource.d.ts +15 -0
- package/lib/src/parseMobileSource.js +125 -0
- package/lib/src/parsers/flow.d.ts +3 -0
- package/lib/src/parsers/flow.js +37 -0
- package/lib/src/reports/mobile/__tests__/reportCustomStyleProperties.spec.d.ts +1 -0
- package/lib/src/reports/mobile/__tests__/reportCustomStyleProperties.spec.js +123 -0
- package/lib/src/reports/mobile/__tests__/reportInlineStyle.spec.d.ts +1 -0
- package/lib/src/reports/mobile/__tests__/reportInlineStyle.spec.js +103 -0
- package/lib/src/reports/mobile/constants.d.ts +14 -1
- package/lib/src/reports/mobile/constants.js +71 -14
- package/lib/src/reports/mobile/reportCustomStyleProperties.d.ts +41 -0
- package/lib/src/reports/mobile/reportCustomStyleProperties.js +185 -0
- package/lib/src/reports/mobile/reportInlineStyle.d.ts +21 -0
- package/lib/src/reports/mobile/reportInlineStyle.js +214 -0
- package/lib/src/reports/mobile/reportStyledComponents.d.ts +1 -1
- package/lib/src/reports/mobile/reportStyledComponents.js +3 -3
- package/lib/src/reports/mobile/testUtils.d.ts +1 -0
- package/lib/src/reports/mobile/testUtils.js +6 -1
- package/lib/src/reports/reportCustomStyleProperties.d.ts +1 -1
- package/lib/src/reports/reportStyledComponents.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
cp .env.example .env
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
For React Native projects, include your repo name in the `MOBILE_REPO_NAMES` env.
|
|
14
|
+
|
|
13
15
|
2. Set up [internal-tool-integrations service](https://github.com/Thinkei/internal-tool-integrations) for the bot to store report.
|
|
14
16
|
|
|
15
17
|
- Replace `DB_HOST` with the host of the internal-tool-integrations service.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,313 @@
|
|
|
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 fs = __importStar(require("fs"));
|
|
30
|
+
const parseMobileSource_1 = __importDefault(require("../parseMobileSource"));
|
|
31
|
+
describe('parseSource', () => {
|
|
32
|
+
it('reports correct snowflakes with typescript files', () => {
|
|
33
|
+
const source = fs.readFileSync('./src/__mocks__/mobileSourceSample.tsx', 'utf-8');
|
|
34
|
+
expect((0, parseMobileSource_1.default)(source)).toEqual({
|
|
35
|
+
approvedLocs: [54, 56, 72, 81, 18],
|
|
36
|
+
styleLocs: [71, 75, 82, 48, 50, 51, 52, 53, 64, 66, 67, 68, 69, 70],
|
|
37
|
+
styledComponentLocs: [14, 23, 28],
|
|
38
|
+
violatingAttributes: [
|
|
39
|
+
{
|
|
40
|
+
attributeName: 'padding',
|
|
41
|
+
attributeValue: '20',
|
|
42
|
+
componentName: 'Button.Icon',
|
|
43
|
+
inlineStyleProps: 'style',
|
|
44
|
+
loc: 71,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
attributeName: 'padding',
|
|
48
|
+
attributeValue: '20',
|
|
49
|
+
componentName: 'Button.Icon',
|
|
50
|
+
inlineStyleProps: 'style',
|
|
51
|
+
loc: 75,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
attributeName: 'backgroundColor',
|
|
55
|
+
attributeValue: "'red'",
|
|
56
|
+
componentName: 'Button.Icon',
|
|
57
|
+
inlineStyleProps: 'style',
|
|
58
|
+
loc: 75,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
attributeName: 'backgroundColor',
|
|
62
|
+
attributeValue: "'red'",
|
|
63
|
+
componentName: 'Button.Icon',
|
|
64
|
+
inlineStyleProps: 'style',
|
|
65
|
+
loc: 82,
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
attributeName: 'padding',
|
|
69
|
+
attributeValue: '10',
|
|
70
|
+
componentName: 'Tabs',
|
|
71
|
+
inlineStyleProps: 'barStyle',
|
|
72
|
+
loc: 48,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
attributeName: 'width',
|
|
76
|
+
attributeValue: '100',
|
|
77
|
+
componentName: 'Tabs',
|
|
78
|
+
inlineStyleProps: 'containerStyle',
|
|
79
|
+
loc: 48,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
attributeName: 'width',
|
|
83
|
+
attributeValue: '100',
|
|
84
|
+
componentName: 'Tabs.Scroll',
|
|
85
|
+
inlineStyleProps: 'containerStyle',
|
|
86
|
+
loc: 50,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
attributeName: 'color',
|
|
90
|
+
attributeValue: "'red'",
|
|
91
|
+
componentName: 'TextInput',
|
|
92
|
+
inlineStyleProps: 'textStyle',
|
|
93
|
+
loc: 51,
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
attributeName: 'color',
|
|
97
|
+
attributeValue: "'red'",
|
|
98
|
+
componentName: 'Search.OneLine',
|
|
99
|
+
inlineStyleProps: 'textStyle',
|
|
100
|
+
loc: 52,
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
attributeName: 'borderColor',
|
|
104
|
+
attributeValue: "'red'",
|
|
105
|
+
componentName: 'Search.OneLine',
|
|
106
|
+
inlineStyleProps: 'textStyle',
|
|
107
|
+
loc: 52,
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
attributeName: 'color',
|
|
111
|
+
attributeValue: "'red'",
|
|
112
|
+
componentName: 'Toolbar.Message',
|
|
113
|
+
inlineStyleProps: 'textStyle',
|
|
114
|
+
loc: 53,
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
attributeName: 'borderColor',
|
|
118
|
+
attributeValue: "'red'",
|
|
119
|
+
componentName: 'Toolbar.Message',
|
|
120
|
+
inlineStyleProps: 'textStyle',
|
|
121
|
+
loc: 53,
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
attributeName: 'width',
|
|
125
|
+
attributeValue: '200',
|
|
126
|
+
componentName: 'Empty',
|
|
127
|
+
inlineStyleProps: 'style',
|
|
128
|
+
loc: 64,
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
attributeName: 'width',
|
|
132
|
+
attributeValue: '200',
|
|
133
|
+
componentName: 'Button.Utility',
|
|
134
|
+
inlineStyleProps: 'style',
|
|
135
|
+
loc: 66,
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
attributeName: 'padding',
|
|
139
|
+
attributeValue: '30',
|
|
140
|
+
componentName: 'Button',
|
|
141
|
+
inlineStyleProps: 'style',
|
|
142
|
+
loc: 67,
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
attributeName: 'padding',
|
|
146
|
+
attributeValue: '30',
|
|
147
|
+
componentName: 'Button',
|
|
148
|
+
inlineStyleProps: 'style',
|
|
149
|
+
loc: 68,
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
attributeName: 'padding',
|
|
153
|
+
attributeValue: '30',
|
|
154
|
+
componentName: 'Button',
|
|
155
|
+
inlineStyleProps: 'style',
|
|
156
|
+
loc: 69,
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
attributeName: 'padding',
|
|
160
|
+
attributeValue: '30',
|
|
161
|
+
componentName: 'Button',
|
|
162
|
+
inlineStyleProps: 'style',
|
|
163
|
+
loc: 70,
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
it('reports correct snowflakes with flow files', () => {
|
|
169
|
+
const source = fs.readFileSync('./src/__mocks__/mobileSourceSample.js', 'utf-8');
|
|
170
|
+
expect((0, parseMobileSource_1.default)(source)).toEqual({
|
|
171
|
+
approvedLocs: [55, 57, 73, 82, 19],
|
|
172
|
+
styleLocs: [72, 76, 83, 49, 51, 52, 53, 54, 65, 67, 68, 69, 70, 71],
|
|
173
|
+
styledComponentLocs: [15, 24, 29],
|
|
174
|
+
violatingAttributes: [
|
|
175
|
+
{
|
|
176
|
+
attributeName: 'padding',
|
|
177
|
+
attributeValue: '20',
|
|
178
|
+
componentName: 'Button.Icon',
|
|
179
|
+
inlineStyleProps: 'style',
|
|
180
|
+
loc: 72,
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
attributeName: 'padding',
|
|
184
|
+
attributeValue: '20',
|
|
185
|
+
componentName: 'Button.Icon',
|
|
186
|
+
inlineStyleProps: 'style',
|
|
187
|
+
loc: 76,
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
attributeName: 'backgroundColor',
|
|
191
|
+
attributeValue: "'red'",
|
|
192
|
+
componentName: 'Button.Icon',
|
|
193
|
+
inlineStyleProps: 'style',
|
|
194
|
+
loc: 76,
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
attributeName: 'backgroundColor',
|
|
198
|
+
attributeValue: "'red'",
|
|
199
|
+
componentName: 'Button.Icon',
|
|
200
|
+
inlineStyleProps: 'style',
|
|
201
|
+
loc: 83,
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
attributeName: 'padding',
|
|
205
|
+
attributeValue: '10',
|
|
206
|
+
componentName: 'Tabs',
|
|
207
|
+
inlineStyleProps: 'barStyle',
|
|
208
|
+
loc: 49,
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
attributeName: 'width',
|
|
212
|
+
attributeValue: '100',
|
|
213
|
+
componentName: 'Tabs',
|
|
214
|
+
inlineStyleProps: 'containerStyle',
|
|
215
|
+
loc: 49,
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
attributeName: 'width',
|
|
219
|
+
attributeValue: '100',
|
|
220
|
+
componentName: 'Tabs.Scroll',
|
|
221
|
+
inlineStyleProps: 'containerStyle',
|
|
222
|
+
loc: 51,
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
attributeName: 'color',
|
|
226
|
+
attributeValue: "'red'",
|
|
227
|
+
componentName: 'TextInput',
|
|
228
|
+
inlineStyleProps: 'textStyle',
|
|
229
|
+
loc: 52,
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
attributeName: 'color',
|
|
233
|
+
attributeValue: "'red'",
|
|
234
|
+
componentName: 'Search.OneLine',
|
|
235
|
+
inlineStyleProps: 'textStyle',
|
|
236
|
+
loc: 53,
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
attributeName: 'borderColor',
|
|
240
|
+
attributeValue: "'red'",
|
|
241
|
+
componentName: 'Search.OneLine',
|
|
242
|
+
inlineStyleProps: 'textStyle',
|
|
243
|
+
loc: 53,
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
attributeName: 'color',
|
|
247
|
+
attributeValue: "'red'",
|
|
248
|
+
componentName: 'Toolbar.Message',
|
|
249
|
+
inlineStyleProps: 'textStyle',
|
|
250
|
+
loc: 54,
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
attributeName: 'borderColor',
|
|
254
|
+
attributeValue: "'red'",
|
|
255
|
+
componentName: 'Toolbar.Message',
|
|
256
|
+
inlineStyleProps: 'textStyle',
|
|
257
|
+
loc: 54,
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
attributeName: 'width',
|
|
261
|
+
attributeValue: '200',
|
|
262
|
+
componentName: 'Empty',
|
|
263
|
+
inlineStyleProps: 'style',
|
|
264
|
+
loc: 65,
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
attributeName: 'width',
|
|
268
|
+
attributeValue: '200',
|
|
269
|
+
componentName: 'Button.Utility',
|
|
270
|
+
inlineStyleProps: 'style',
|
|
271
|
+
loc: 67,
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
attributeName: 'padding',
|
|
275
|
+
attributeValue: '30',
|
|
276
|
+
componentName: 'Button',
|
|
277
|
+
inlineStyleProps: 'style',
|
|
278
|
+
loc: 68,
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
attributeName: 'padding',
|
|
282
|
+
attributeValue: '30',
|
|
283
|
+
componentName: 'Button',
|
|
284
|
+
inlineStyleProps: 'style',
|
|
285
|
+
loc: 69,
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
attributeName: 'padding',
|
|
289
|
+
attributeValue: '30',
|
|
290
|
+
componentName: 'Button',
|
|
291
|
+
inlineStyleProps: 'style',
|
|
292
|
+
loc: 70,
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
attributeName: 'padding',
|
|
296
|
+
attributeValue: '30',
|
|
297
|
+
componentName: 'Button',
|
|
298
|
+
inlineStyleProps: 'style',
|
|
299
|
+
loc: 71,
|
|
300
|
+
},
|
|
301
|
+
],
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
it('does not include approved snowflakes in the report', () => {
|
|
305
|
+
const source = fs.readFileSync('./src/__mocks__/mobileApprovalSourceSample.tsx', 'utf-8');
|
|
306
|
+
expect((0, parseMobileSource_1.default)(source)).toEqual({
|
|
307
|
+
approvedLocs: [14, 16, 7],
|
|
308
|
+
styleLocs: [],
|
|
309
|
+
styledComponentLocs: [],
|
|
310
|
+
violatingAttributes: [],
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
});
|
package/lib/src/index.js
CHANGED
|
@@ -11,12 +11,14 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
11
11
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
12
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
13
|
};
|
|
14
|
+
const parseMobileSource_1 = __importDefault(require("./parseMobileSource"));
|
|
14
15
|
const parseSource_1 = __importDefault(require("./parseSource"));
|
|
15
16
|
const constants_1 = require("./reports/constants");
|
|
16
17
|
const fetchGraphql_1 = __importDefault(require("./graphql/fetchGraphql"));
|
|
17
18
|
const queryGenerators_1 = require("./graphql/queryGenerators");
|
|
18
19
|
const getDiffLocs_1 = require("./utils/getDiffLocs");
|
|
19
|
-
const
|
|
20
|
+
const WEB_REGEX = /\.tsx$/;
|
|
21
|
+
const MOBILE_REGEX = /\.(tsx|jsx|js)$/;
|
|
20
22
|
const TEST_REGEX = /__tests__/;
|
|
21
23
|
const SNOWFLAKE_COMMENTS = {
|
|
22
24
|
style: 'Snowflake detected! A component is customized using inline styles. Make sure to not use [prohibited CSS properties](https://docs.google.com/spreadsheets/d/1Dj8vqLdFaf-CSaSVoYqyYZIkGqF6OoyP7K4G1_9L62U/edit?usp=sharing).',
|
|
@@ -24,6 +26,7 @@ const SNOWFLAKE_COMMENTS = {
|
|
|
24
26
|
'styled-component': 'Please do not use styled-component to customize this component, use sx prop or inline style instead.',
|
|
25
27
|
className: `Please make sure that this className is not used as a CSS classname for component customization purposes, use sx prop or inline style instead. In case this is none-css classname, please flag it with this comment \`${constants_1.APPROVED_CLASSNAME_COMMENT}\`.`,
|
|
26
28
|
};
|
|
29
|
+
const MOBILE_REPO_NAMES = JSON.parse(process.env.MOBILE_REPO_NAMES || '[]');
|
|
27
30
|
const checkIfDetectedSnowflakesInDiff = (diffLocs, locToComment) => {
|
|
28
31
|
const locIdx = diffLocs.findIndex(([start, end]) => {
|
|
29
32
|
return locToComment >= start && locToComment <= end;
|
|
@@ -42,16 +45,19 @@ module.exports = (app) => {
|
|
|
42
45
|
const prBranch = context.payload.pull_request.head.ref;
|
|
43
46
|
// List all changed files
|
|
44
47
|
const prFiles = yield context.octokit.pulls.listFiles(Object.assign(Object.assign({}, repoInfo), { pull_number: prNumber }));
|
|
45
|
-
const
|
|
48
|
+
const isMobile = MOBILE_REPO_NAMES.includes(repoInfo.repo);
|
|
49
|
+
const parseSource = isMobile ? parseMobileSource_1.default : parseSource_1.default;
|
|
50
|
+
const SOURCE_REGEX = isMobile ? MOBILE_REGEX : WEB_REGEX;
|
|
51
|
+
const sourceFiles = prFiles.data.filter((file) => SOURCE_REGEX.test(file.filename) &&
|
|
46
52
|
!TEST_REGEX.test(file.filename) &&
|
|
47
53
|
file.status !== 'removed');
|
|
48
54
|
// Saving file patches to get diff locations
|
|
49
|
-
const prFilePatches =
|
|
55
|
+
const prFilePatches = sourceFiles.reduce((acc, file) => {
|
|
50
56
|
acc[file.filename] = file.patch || '';
|
|
51
57
|
return acc;
|
|
52
58
|
}, {});
|
|
53
59
|
// Get file contents
|
|
54
|
-
const prFileContentPromises =
|
|
60
|
+
const prFileContentPromises = sourceFiles.map((file) => context.octokit.repos.getContent(Object.assign(Object.assign({}, repoInfo), { path: file.filename, ref: prBranch })));
|
|
55
61
|
const prFileContents = yield Promise.all(prFileContentPromises);
|
|
56
62
|
const snowflakeComments = [];
|
|
57
63
|
const approvedSnowflakeLocs = [];
|
|
@@ -65,7 +71,7 @@ module.exports = (app) => {
|
|
|
65
71
|
// @ts-ignore
|
|
66
72
|
file.data.content, 'base64').toString();
|
|
67
73
|
// Parse file content to check for snowflakes
|
|
68
|
-
const snowflakeReport = (
|
|
74
|
+
const snowflakeReport = parseSource(stringContent);
|
|
69
75
|
snowflakeReport.styleLocs.forEach((loc) => {
|
|
70
76
|
if (checkIfDetectedSnowflakesInDiff(diffLocs, loc)) {
|
|
71
77
|
snowflakeComments.push({
|
|
@@ -75,15 +81,6 @@ module.exports = (app) => {
|
|
|
75
81
|
});
|
|
76
82
|
}
|
|
77
83
|
});
|
|
78
|
-
snowflakeReport.sxLocs.forEach((loc) => {
|
|
79
|
-
if (checkIfDetectedSnowflakesInDiff(diffLocs, loc)) {
|
|
80
|
-
snowflakeComments.push({
|
|
81
|
-
path: filePath,
|
|
82
|
-
body: SNOWFLAKE_COMMENTS['sx'],
|
|
83
|
-
line: loc,
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
84
|
snowflakeReport.styledComponentLocs.forEach((loc) => {
|
|
88
85
|
if (checkIfDetectedSnowflakesInDiff(diffLocs, loc)) {
|
|
89
86
|
snowflakeComments.push({
|
|
@@ -93,20 +90,31 @@ module.exports = (app) => {
|
|
|
93
90
|
});
|
|
94
91
|
}
|
|
95
92
|
});
|
|
96
|
-
snowflakeReport.classNameLocs.forEach((loc) => {
|
|
97
|
-
if (checkIfDetectedSnowflakesInDiff(diffLocs, loc)) {
|
|
98
|
-
snowflakeComments.push({
|
|
99
|
-
path: filePath,
|
|
100
|
-
body: SNOWFLAKE_COMMENTS['className'],
|
|
101
|
-
line: loc,
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
93
|
snowflakeReport.approvedLocs.forEach((loc) => {
|
|
106
94
|
if (checkIfDetectedSnowflakesInDiff(diffLocs, loc)) {
|
|
107
95
|
approvedSnowflakeLocs.push(loc);
|
|
108
96
|
}
|
|
109
97
|
});
|
|
98
|
+
if (!isMobile) {
|
|
99
|
+
snowflakeReport.sxLocs.forEach((loc) => {
|
|
100
|
+
if (checkIfDetectedSnowflakesInDiff(diffLocs, loc)) {
|
|
101
|
+
snowflakeComments.push({
|
|
102
|
+
path: filePath,
|
|
103
|
+
body: SNOWFLAKE_COMMENTS['sx'],
|
|
104
|
+
line: loc,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
snowflakeReport.classNameLocs.forEach((loc) => {
|
|
109
|
+
if (checkIfDetectedSnowflakesInDiff(diffLocs, loc)) {
|
|
110
|
+
snowflakeComments.push({
|
|
111
|
+
path: filePath,
|
|
112
|
+
body: SNOWFLAKE_COMMENTS['className'],
|
|
113
|
+
line: loc,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
110
118
|
}));
|
|
111
119
|
// Saving report
|
|
112
120
|
const snowflakeCount = snowflakeComments.length;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { InlineStyleProps } from './reports/mobile/reportInlineStyle';
|
|
2
|
+
import type { CompoundMobileComponentName } from './reports/mobile/types';
|
|
3
|
+
declare const parseMobileSource: (source: string) => {
|
|
4
|
+
styleLocs: number[];
|
|
5
|
+
styledComponentLocs: number[];
|
|
6
|
+
approvedLocs: number[];
|
|
7
|
+
violatingAttributes: {
|
|
8
|
+
attributeName: string;
|
|
9
|
+
attributeValue: string | null;
|
|
10
|
+
inlineStyleProps: InlineStyleProps;
|
|
11
|
+
componentName: CompoundMobileComponentName;
|
|
12
|
+
loc: number | undefined;
|
|
13
|
+
}[];
|
|
14
|
+
};
|
|
15
|
+
export default parseMobileSource;
|
|
@@ -0,0 +1,125 @@
|
|
|
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 flowParser = __importStar(require("./parsers/flow"));
|
|
31
|
+
const tsParser = __importStar(require("./parsers/typescript"));
|
|
32
|
+
const constants_1 = require("./reports/mobile/constants");
|
|
33
|
+
const constants_2 = require("./reports/constants");
|
|
34
|
+
const reportCustomStyleProperties_1 = __importDefault(require("./reports/mobile/reportCustomStyleProperties"));
|
|
35
|
+
const reportStyledComponents_1 = __importDefault(require("./reports/mobile/reportStyledComponents"));
|
|
36
|
+
const parseMobileSource = (source) => {
|
|
37
|
+
let hasHeroDesignImport = false;
|
|
38
|
+
let hasStyledComponentsImport = false;
|
|
39
|
+
const componentList = {};
|
|
40
|
+
let styledAliasName = 'styled';
|
|
41
|
+
let styledComponentLocs = [];
|
|
42
|
+
let styleLocs = [];
|
|
43
|
+
let violatingAttributes = [];
|
|
44
|
+
const approvedInlineStyleCmts = [];
|
|
45
|
+
const approvedStyledComponentLocs = [];
|
|
46
|
+
const ast = recast.parse(source, {
|
|
47
|
+
parser: source.includes('@flow') ? flowParser : tsParser,
|
|
48
|
+
});
|
|
49
|
+
recast.visit(ast, {
|
|
50
|
+
visitImportDeclaration(path) {
|
|
51
|
+
this.traverse(path);
|
|
52
|
+
const importedFrom = path.value.source.value;
|
|
53
|
+
// Check if file imports components from '@hero-design/rn'
|
|
54
|
+
if (importedFrom === '@hero-design/rn') {
|
|
55
|
+
recast.visit(path.node, {
|
|
56
|
+
visitImportSpecifier(importPath) {
|
|
57
|
+
this.traverse(importPath);
|
|
58
|
+
if (constants_1.HD_MOBILE_COMPONENTS.includes(importPath.value.imported.name)) {
|
|
59
|
+
componentList[importPath.value.local.name] =
|
|
60
|
+
importPath.value.imported.name;
|
|
61
|
+
hasHeroDesignImport = true;
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
// Check if file imports from '@emotion/native'
|
|
67
|
+
if (importedFrom === '@emotion/native') {
|
|
68
|
+
recast.visit(path.node, {
|
|
69
|
+
visitImportDefaultSpecifier(importPath) {
|
|
70
|
+
this.traverse(importPath);
|
|
71
|
+
styledAliasName = importPath.value.local.name;
|
|
72
|
+
hasStyledComponentsImport = true;
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
visitComment(path) {
|
|
78
|
+
this.traverse(path);
|
|
79
|
+
const comment = path.value.value;
|
|
80
|
+
if (comment
|
|
81
|
+
.toLowerCase()
|
|
82
|
+
.includes(constants_2.APPROVED_INLINE_STYLE_COMMENT.toLowerCase())) {
|
|
83
|
+
approvedInlineStyleCmts.push({
|
|
84
|
+
loc: path.value.loc.start.line,
|
|
85
|
+
comment,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
if (comment
|
|
89
|
+
.toLowerCase()
|
|
90
|
+
.includes(constants_2.APPROVED_STYLED_COMPONENTS_COMMENT.toLowerCase())) {
|
|
91
|
+
approvedStyledComponentLocs.push(path.value.loc.start.line);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
const isNotApprovedStyledComponentSnowflakes = (loc) => !approvedStyledComponentLocs.includes(loc - 1);
|
|
96
|
+
if (hasHeroDesignImport) {
|
|
97
|
+
// Case 1: Using style and other style config objects to customise components
|
|
98
|
+
const customPropLocs = (0, reportCustomStyleProperties_1.default)(ast, componentList, {
|
|
99
|
+
styleCmts: approvedInlineStyleCmts,
|
|
100
|
+
});
|
|
101
|
+
styleLocs = [
|
|
102
|
+
...new Set([
|
|
103
|
+
...customPropLocs.style,
|
|
104
|
+
...customPropLocs.barStyle,
|
|
105
|
+
...customPropLocs.containerStyle,
|
|
106
|
+
...customPropLocs.textStyle,
|
|
107
|
+
]),
|
|
108
|
+
];
|
|
109
|
+
// Case 2: Using styled-components to customise components
|
|
110
|
+
if (hasStyledComponentsImport) {
|
|
111
|
+
styledComponentLocs = (0, reportStyledComponents_1.default)(ast, componentList, styledAliasName).filter(isNotApprovedStyledComponentSnowflakes);
|
|
112
|
+
}
|
|
113
|
+
violatingAttributes = customPropLocs.violatingAttributes;
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
styleLocs,
|
|
117
|
+
styledComponentLocs,
|
|
118
|
+
approvedLocs: [
|
|
119
|
+
...approvedInlineStyleCmts.map((cmt) => cmt.loc),
|
|
120
|
+
...approvedStyledComponentLocs,
|
|
121
|
+
],
|
|
122
|
+
violatingAttributes,
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
exports.default = parseMobileSource;
|
|
@@ -0,0 +1,37 @@
|
|
|
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
|
+
exports.parse = void 0;
|
|
30
|
+
const babelParser = __importStar(require("@babel/parser"));
|
|
31
|
+
const _babel_options_1 = __importDefault(require("recast/parsers/_babel_options"));
|
|
32
|
+
const parse = (source, options) => {
|
|
33
|
+
const babelOptions = (0, _babel_options_1.default)(options);
|
|
34
|
+
babelOptions.plugins.push('jsx', 'flow');
|
|
35
|
+
return babelParser.parse(source, babelOptions);
|
|
36
|
+
};
|
|
37
|
+
exports.parse = parse;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|