@hero-design/snowflake-guard 1.0.7-alpha0

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.
Files changed (49) hide show
  1. package/.dockerignore +12 -0
  2. package/.env.example +9 -0
  3. package/.eslintrc.js +8 -0
  4. package/CHANGELOG.md +47 -0
  5. package/Dockerfile +8 -0
  6. package/LICENSE +15 -0
  7. package/README.md +33 -0
  8. package/app.yml +137 -0
  9. package/jest.config.js +9 -0
  10. package/lib/netlify/functions/snowflake.d.ts +2 -0
  11. package/lib/netlify/functions/snowflake.js +10 -0
  12. package/lib/src/__mocks__/sourceSample.d.ts +2 -0
  13. package/lib/src/__mocks__/sourceSample.js +27 -0
  14. package/lib/src/__tests__/parseSource.spec.d.ts +1 -0
  15. package/lib/src/__tests__/parseSource.spec.js +41 -0
  16. package/lib/src/index.d.ts +3 -0
  17. package/lib/src/index.js +142 -0
  18. package/lib/src/parseSource.d.ts +7 -0
  19. package/lib/src/parseSource.js +102 -0
  20. package/lib/src/parsers/typescript.d.ts +3 -0
  21. package/lib/src/parsers/typescript.js +37 -0
  22. package/lib/src/reports/constants.d.ts +215 -0
  23. package/lib/src/reports/constants.js +848 -0
  24. package/lib/src/reports/reportClassName.d.ts +3 -0
  25. package/lib/src/reports/reportClassName.js +15 -0
  26. package/lib/src/reports/reportCustomStyleProperties.d.ts +10 -0
  27. package/lib/src/reports/reportCustomStyleProperties.js +109 -0
  28. package/lib/src/reports/reportInlineStyle.d.ts +7 -0
  29. package/lib/src/reports/reportInlineStyle.js +179 -0
  30. package/lib/src/reports/reportStyledComponents.d.ts +6 -0
  31. package/lib/src/reports/reportStyledComponents.js +95 -0
  32. package/lib/src/reports/types.d.ts +3 -0
  33. package/lib/src/reports/types.js +2 -0
  34. package/lib/src/test.tsx +123 -0
  35. package/netlify/functions/snowflake.ts +9 -0
  36. package/netlify.toml +21 -0
  37. package/package.json +44 -0
  38. package/src/__mocks__/sourceSample.tsx +67 -0
  39. package/src/__tests__/parseSource.spec.ts +15 -0
  40. package/src/index.ts +201 -0
  41. package/src/parseSource.ts +97 -0
  42. package/src/parsers/typescript.ts +8 -0
  43. package/src/reports/constants.ts +965 -0
  44. package/src/reports/reportClassName.ts +20 -0
  45. package/src/reports/reportCustomStyleProperties.ts +125 -0
  46. package/src/reports/reportInlineStyle.ts +221 -0
  47. package/src/reports/reportStyledComponents.ts +109 -0
  48. package/src/reports/types.ts +5 -0
  49. package/tsconfig.json +15 -0
package/.dockerignore ADDED
@@ -0,0 +1,12 @@
1
+ **/node_modules/
2
+ **/.git
3
+ **/README.md
4
+ **/LICENSE
5
+ **/.vscode
6
+ **/npm-debug.log
7
+ **/coverage
8
+ **/.env
9
+ **/.editorconfig
10
+ **/dist
11
+ **/*.pem
12
+ Dockerfile
package/.env.example ADDED
@@ -0,0 +1,9 @@
1
+ # The ID of your GitHub App
2
+ APP_ID=
3
+ WEBHOOK_SECRET=development
4
+
5
+ # Use `trace` to get verbose logging or `info` to show less
6
+ LOG_LEVEL=debug
7
+
8
+ # Go to https://smee.io/new set this to the URL that you are redirected to.
9
+ WEBHOOK_PROXY_URL=
package/.eslintrc.js ADDED
@@ -0,0 +1,8 @@
1
+ module.exports = {
2
+ root: true,
3
+ parserOptions: {
4
+ tsconfigRootDir: __dirname,
5
+ project: ['./tsconfig.json'],
6
+ },
7
+ extends: ['hd'],
8
+ };
package/CHANGELOG.md ADDED
@@ -0,0 +1,47 @@
1
+ # @hero-design/snowflake-guard
2
+
3
+ ## 1.0.7
4
+
5
+ ### Patch Changes
6
+
7
+ - [#3126](https://github.com/Thinkei/hero-design/pull/3126) [`11e08d8c2`](https://github.com/Thinkei/hero-design/commit/11e08d8c244d0e7bf99e4ccb5a17aadf575bdda8) Thanks [@haudao-eh](https://github.com/haudao-eh)! - [ANG-2671] Allow to bypass snowflakes by comments
8
+
9
+ ## 1.0.6
10
+
11
+ ### Patch Changes
12
+
13
+ - [#3103](https://github.com/Thinkei/hero-design/pull/3103) [`97bfc5144`](https://github.com/Thinkei/hero-design/commit/97bfc514495d50b7d36d0186764744b8244a9166) Thanks [@haudao-eh](https://github.com/haudao-eh)! - Fix getting wrong component name
14
+
15
+ ## 1.0.5
16
+
17
+ ### Patch Changes
18
+
19
+ - [#3091](https://github.com/Thinkei/hero-design/pull/3091) [`fbb723675`](https://github.com/Thinkei/hero-design/commit/fbb7236755deb2b2b0bfdbe89154d727bc8f5d0e) Thanks [@haudao-eh](https://github.com/haudao-eh)! - Report snowflakes based on components' individual rulset
20
+
21
+ - [#3078](https://github.com/Thinkei/hero-design/pull/3078) [`8d69517f1`](https://github.com/Thinkei/hero-design/commit/8d69517f16b249d6c3582af826b5ba6df30e1183) Thanks [@haudao-eh](https://github.com/haudao-eh)! - Remove frontend scripts
22
+
23
+ - [#3095](https://github.com/Thinkei/hero-design/pull/3095) [`8a94a5213`](https://github.com/Thinkei/hero-design/commit/8a94a5213126aa154bf7d77c65abf650bab1349c) Thanks [@haudao-eh](https://github.com/haudao-eh)! - Update bot review behaviour
24
+
25
+ ## 1.0.4
26
+
27
+ ### Patch Changes
28
+
29
+ - [#3070](https://github.com/Thinkei/hero-design/pull/3070) [`ca5b8f588`](https://github.com/Thinkei/hero-design/commit/ca5b8f5884de2e7c7445d032d4e34092883dd46c) Thanks [@haudao-eh](https://github.com/haudao-eh)! - [ANG-2641] Migrate snowflake script from frontend-script
30
+
31
+ ## 1.0.3
32
+
33
+ ### Patch Changes
34
+
35
+ - [#2838](https://github.com/Thinkei/hero-design/pull/2838) [`b8ea41d92`](https://github.com/Thinkei/hero-design/commit/b8ea41d924f8b62d7fd4ff9a2418b27877ca8bda) Thanks [@haudao-eh](https://github.com/haudao-eh)! - Fix comment on wrong lines
36
+
37
+ ## 1.0.2
38
+
39
+ ### Patch Changes
40
+
41
+ - [#2625](https://github.com/Thinkei/hero-design/pull/2625) [`4bb963963`](https://github.com/Thinkei/hero-design/commit/4bb963963d053604d3cb055b239174abab441271) Thanks [@haudao-eh](https://github.com/haudao-eh)! - Stop fetching removed files.
42
+
43
+ ## 1.0.1
44
+
45
+ ### Patch Changes
46
+
47
+ - [#2530](https://github.com/Thinkei/hero-design/pull/2530) [`ec37290b4`](https://github.com/Thinkei/hero-design/commit/ec37290b4ea1d09f9034b024bf731c8dcdded7a7) Thanks [@haudao-eh](https://github.com/haudao-eh)! - [Snowflake Guard] Allow to comment on file when detected line is not part of the diff
package/Dockerfile ADDED
@@ -0,0 +1,8 @@
1
+ FROM node:19-slim
2
+ WORKDIR /usr/src/app
3
+ COPY package.json package-lock.json ./
4
+ RUN npm ci --production
5
+ RUN npm cache clean --force
6
+ ENV NODE_ENV="production"
7
+ COPY . .
8
+ CMD [ "npm", "start" ]
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2023, Hau Dao
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # @hero-design/snowflake-guard
2
+
3
+ > A GitHub App built with [Probot](https://github.com/probot/probot) that A hero-design bot detecting snowflake usage
4
+
5
+ ## Setup
6
+
7
+ ```sh
8
+ # Install dependencies
9
+ npm install
10
+
11
+ # Run the bot
12
+ npm start
13
+ ```
14
+
15
+ ## Docker
16
+
17
+ ```sh
18
+ # 1. Build container
19
+ docker build -t @hero-design/snowflake-guard .
20
+
21
+ # 2. Start container
22
+ docker run -e APP_ID=<app-id> -e PRIVATE_KEY=<pem-value> @hero-design/snowflake-guard
23
+ ```
24
+
25
+ ## Contributing
26
+
27
+ If you have suggestions for how @hero-design/snowflake-guard could be improved, or want to report a bug, open an issue! We'd love all and any contributions.
28
+
29
+ For more, check out the [Contributing Guide](CONTRIBUTING.md).
30
+
31
+ ## License
32
+
33
+ [ISC](LICENSE) © 2023 Hau Dao
package/app.yml ADDED
@@ -0,0 +1,137 @@
1
+ # This is a GitHub App Manifest. These settings will be used by default when
2
+ # initially configuring your GitHub App.
3
+ #
4
+ # NOTE: changing this file will not update your GitHub App settings.
5
+ # You must visit github.com/settings/apps/your-app-name to edit them.
6
+ #
7
+ # Read more about configuring your GitHub App:
8
+ # https://probot.github.io/docs/development/#configuring-a-github-app
9
+ #
10
+ # Read more about GitHub App Manifests:
11
+ # https://developer.github.com/apps/building-github-apps/creating-github-apps-from-a-manifest/
12
+
13
+ # The list of events the GitHub App subscribes to.
14
+ # Uncomment the event names below to enable them.
15
+ default_events:
16
+ # - check_run
17
+ # - check_suite
18
+ # - commit_comment
19
+ # - create
20
+ # - delete
21
+ # - deployment
22
+ # - deployment_status
23
+ # - fork
24
+ # - gollum
25
+ # - issue_comment
26
+ # - issues
27
+ # - label
28
+ # - milestone
29
+ # - member
30
+ # - membership
31
+ # - org_block
32
+ # - organization
33
+ # - page_build
34
+ # - project
35
+ # - project_card
36
+ # - project_column
37
+ # - public
38
+ - pull_request
39
+ # - pull_request_review
40
+ # - pull_request_review_comment
41
+ # - push
42
+ # - release
43
+ # - repository
44
+ # - repository_import
45
+ # - status
46
+ # - team
47
+ # - team_add
48
+ # - watch
49
+
50
+ # The set of permissions needed by the GitHub App. The format of the object uses
51
+ # the permission name for the key (for example, issues) and the access type for
52
+ # the value (for example, write).
53
+ # Valid values are `read`, `write`, and `none`
54
+ default_permissions:
55
+ # Repository creation, deletion, settings, teams, and collaborators.
56
+ # https://developer.github.com/v3/apps/permissions/#permission-on-administration
57
+ # administration: read
58
+
59
+ # Checks on code.
60
+ # https://developer.github.com/v3/apps/permissions/#permission-on-checks
61
+ # checks: read
62
+
63
+ # Repository contents, commits, branches, downloads, releases, and merges.
64
+ # https://developer.github.com/v3/apps/permissions/#permission-on-contents
65
+ contents: read
66
+
67
+ # Deployments and deployment statuses.
68
+ # https://developer.github.com/v3/apps/permissions/#permission-on-deployments
69
+ # deployments: read
70
+
71
+ # Issues and related comments, assignees, labels, and milestones.
72
+ # https://developer.github.com/v3/apps/permissions/#permission-on-issues
73
+ # issues: write
74
+
75
+ # Search repositories, list collaborators, and access repository metadata.
76
+ # https://developer.github.com/v3/apps/permissions/#metadata-permissions
77
+ metadata: read
78
+
79
+ # Retrieve Pages statuses, configuration, and builds, as well as create new builds.
80
+ # https://developer.github.com/v3/apps/permissions/#permission-on-pages
81
+ # pages: read
82
+
83
+ # Pull requests and related comments, assignees, labels, milestones, and merges.
84
+ # https://developer.github.com/v3/apps/permissions/#permission-on-pull-requests
85
+ pull_requests: write
86
+
87
+ # Manage the post-receive hooks for a repository.
88
+ # https://developer.github.com/v3/apps/permissions/#permission-on-repository-hooks
89
+ # repository_hooks: read
90
+
91
+ # Manage repository projects, columns, and cards.
92
+ # https://developer.github.com/v3/apps/permissions/#permission-on-repository-projects
93
+ # repository_projects: read
94
+
95
+ # Retrieve security vulnerability alerts.
96
+ # https://developer.github.com/v4/object/repositoryvulnerabilityalert/
97
+ # vulnerability_alerts: read
98
+
99
+ # Commit statuses.
100
+ # https://developer.github.com/v3/apps/permissions/#permission-on-statuses
101
+ # statuses: read
102
+
103
+ # Organization members and teams.
104
+ # https://developer.github.com/v3/apps/permissions/#permission-on-members
105
+ # members: read
106
+
107
+ # View and manage users blocked by the organization.
108
+ # https://developer.github.com/v3/apps/permissions/#permission-on-organization-user-blocking
109
+ # organization_user_blocking: read
110
+
111
+ # Manage organization projects, columns, and cards.
112
+ # https://developer.github.com/v3/apps/permissions/#permission-on-organization-projects
113
+ # organization_projects: read
114
+
115
+ # Manage team discussions and related comments.
116
+ # https://developer.github.com/v3/apps/permissions/#permission-on-team-discussions
117
+ # team_discussions: read
118
+
119
+ # Manage the post-receive hooks for an organization.
120
+ # https://developer.github.com/v3/apps/permissions/#permission-on-organization-hooks
121
+ # organization_hooks: read
122
+
123
+ # Get notified of, and update, content references.
124
+ # https://developer.github.com/v3/apps/permissions/
125
+ # organization_administration: read
126
+ # The name of the GitHub App. Defaults to the name specified in package.json
127
+ # name: My Probot App
128
+
129
+ # The homepage of your GitHub App.
130
+ # url: https://example.com/
131
+
132
+ # A description of the GitHub App.
133
+ # description: A description of my awesome app
134
+
135
+ # Set to true when your GitHub App is available to the public or false when it is only accessible to the owner of the app.
136
+ # Default: true
137
+ # public: false
package/jest.config.js ADDED
@@ -0,0 +1,9 @@
1
+ module.exports = {
2
+ roots: ["<rootDir>/src/"],
3
+ transform: {
4
+ "^.+\\.tsx?$": "ts-jest",
5
+ },
6
+ testRegex: "(/__tests__/.*|\\.(test|spec))\\.[tj]sx?$",
7
+ moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
8
+ testEnvironment: "node",
9
+ };
@@ -0,0 +1,2 @@
1
+ declare const handler: (event: import("aws-lambda").APIGatewayProxyEvent, context: import("aws-lambda").Context) => Promise<import("aws-lambda").APIGatewayProxyResult>;
2
+ export { handler };
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.handler = void 0;
7
+ const adapter_aws_lambda_serverless_1 = require("@probot/adapter-aws-lambda-serverless");
8
+ const index_1 = __importDefault(require("../../src/index"));
9
+ const handler = (0, adapter_aws_lambda_serverless_1.createLambdaFunction)(index_1.default, { probot: (0, adapter_aws_lambda_serverless_1.createProbot)() });
10
+ exports.handler = handler;
@@ -0,0 +1,2 @@
1
+ declare const Sample: () => void;
2
+ export default Sample;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const react_1 = __importDefault(require("react"));
7
+ const react_2 = require("@hero-design/react");
8
+ const styled_components_1 = __importDefault(require("styled-components"));
9
+ // Snowflakes using styled-components;
10
+ const StyledButton = (0, styled_components_1.default)(react_2.Button) `
11
+ padding: 10px;
12
+ `;
13
+ const StyledLinkButton = (0, styled_components_1.default)(react_2.Button.Link) `
14
+ color: red;
15
+ `;
16
+ const { Link } = react_2.Button;
17
+ const StyledLink = (0, styled_components_1.default)(Link) `
18
+ color: red;
19
+ `;
20
+ const Sample = () => {
21
+ <>
22
+ <StyledButton />
23
+ <StyledLinkButton />
24
+ <StyledLink />
25
+ </>;
26
+ };
27
+ exports.default = Sample;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,41 @@
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 parseSource_1 = __importDefault(require("../parseSource"));
31
+ describe('parseSource', () => {
32
+ it('reports correct snowflakes', () => {
33
+ const source = fs.readFileSync('./src/__mocks__/sourceSample.tsx', 'utf-8');
34
+ expect((0, parseSource_1.default)(source)).toEqual({
35
+ classNameLocs: [44, 42, 43],
36
+ styleLocs: [54, 47, 49, 50, 51, 52, 53],
37
+ sxLocs: [63, 58, 59, 60, 61, 62],
38
+ styledComponentLocs: [6, 10, 15],
39
+ });
40
+ });
41
+ });
@@ -0,0 +1,3 @@
1
+ import { Probot } from 'probot';
2
+ declare const _default: (app: Probot) => void;
3
+ export = _default;
@@ -0,0 +1,142 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ const probot_1 = require("probot");
15
+ const parseSource_1 = __importDefault(require("./parseSource"));
16
+ const TSX_REGEX = /\.tsx$/;
17
+ const TEST_REGEX = /__tests__/;
18
+ const DIFF_LOCS_REGEX = /@@(.*)@@/g;
19
+ const SNOWFLAKE_COMMENTS = {
20
+ 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).',
21
+ sx: 'Snowflake detected! A component is customized via sx prop. Make sure to not use [prohibited CSS properties](https://docs.google.com/spreadsheets/d/1Dj8vqLdFaf-CSaSVoYqyYZIkGqF6OoyP7K4G1_9L62U/edit?usp=sharing).',
22
+ 'styled-component': 'Please do not use styled-component to customize this component, use sx prop or inline style instead.',
23
+ 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.',
24
+ };
25
+ const getDiffLocs = (diffStrs) => {
26
+ const locs = [];
27
+ diffStrs.forEach((diffStr) => {
28
+ const [startLocStr, numberOfLinesStr] = diffStr
29
+ .split('+')[1]
30
+ .split(' ')[0]
31
+ .split(',');
32
+ const startLoc = Number(startLocStr);
33
+ const numberOfLines = Number(numberOfLinesStr);
34
+ locs.push([startLoc, numberOfLines]);
35
+ });
36
+ return locs;
37
+ };
38
+ const checkIfDetectedSnowflakesInDiff = (diffLocs, locToComment) => {
39
+ const locIdx = diffLocs.findIndex(([start, numberOfLines]) => {
40
+ return locToComment >= start && locToComment < start + numberOfLines;
41
+ });
42
+ return locIdx !== -1;
43
+ };
44
+ module.exports = (app) => {
45
+ app.on(['pull_request.opened', 'pull_request.synchronize'], (context) => __awaiter(void 0, void 0, void 0, function* () {
46
+ // Get PR info
47
+ const prNumber = context.payload.number;
48
+ const repoInfo = {
49
+ repo: context.payload.repository.name,
50
+ owner: context.payload.repository.owner.login,
51
+ };
52
+ const prBranch = context.payload.pull_request.head.ref;
53
+ // List all changed files
54
+ const prFiles = yield context.octokit.pulls.listFiles(Object.assign(Object.assign({}, repoInfo), { pull_number: prNumber }));
55
+ const tsxFiles = prFiles.data.filter((file) => TSX_REGEX.test(file.filename) &&
56
+ !TEST_REGEX.test(file.filename) &&
57
+ file.status !== 'removed');
58
+ // Saving file patches to get diff locations
59
+ const prFilePatches = tsxFiles.reduce((acc, file) => {
60
+ acc[file.filename] = file.patch || '';
61
+ return acc;
62
+ }, {});
63
+ // Get file contents
64
+ const prFileContentPromises = tsxFiles.map((file) => context.octokit.repos.getContent(Object.assign(Object.assign({}, repoInfo), { path: file.filename, ref: prBranch })));
65
+ const prFileContents = yield Promise.all(prFileContentPromises);
66
+ const styleComments = [];
67
+ const sxComments = [];
68
+ const styledComponentComments = [];
69
+ const classNameComments = [];
70
+ prFileContents.forEach((file) => __awaiter(void 0, void 0, void 0, function* () {
71
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
72
+ // @ts-ignore
73
+ const filePath = file.data.path;
74
+ const diffLocs = getDiffLocs(prFilePatches[filePath].match(DIFF_LOCS_REGEX) || []);
75
+ const stringContent = Buffer.from(
76
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
77
+ // @ts-ignore
78
+ file.data.content, 'base64').toString();
79
+ // Parse file content to check for snowflakes
80
+ const snowflakeReport = (0, parseSource_1.default)(stringContent);
81
+ snowflakeReport.styleLocs.forEach((loc) => {
82
+ if (checkIfDetectedSnowflakesInDiff(diffLocs, loc)) {
83
+ styleComments.push({
84
+ path: filePath,
85
+ body: SNOWFLAKE_COMMENTS['style'],
86
+ line: loc,
87
+ });
88
+ }
89
+ });
90
+ snowflakeReport.sxLocs.forEach((loc) => {
91
+ if (checkIfDetectedSnowflakesInDiff(diffLocs, loc)) {
92
+ sxComments.push({
93
+ path: filePath,
94
+ body: SNOWFLAKE_COMMENTS['sx'],
95
+ line: loc,
96
+ });
97
+ }
98
+ });
99
+ snowflakeReport.styledComponentLocs.forEach((loc) => {
100
+ if (checkIfDetectedSnowflakesInDiff(diffLocs, loc)) {
101
+ styledComponentComments.push({
102
+ path: filePath,
103
+ body: SNOWFLAKE_COMMENTS['styled-component'],
104
+ line: loc,
105
+ });
106
+ }
107
+ });
108
+ snowflakeReport.classNameLocs.forEach((loc) => {
109
+ if (checkIfDetectedSnowflakesInDiff(diffLocs, loc)) {
110
+ classNameComments.push({
111
+ path: filePath,
112
+ body: SNOWFLAKE_COMMENTS['className'],
113
+ line: loc,
114
+ });
115
+ }
116
+ });
117
+ }));
118
+ const personalOctokit = new probot_1.ProbotOctokit({
119
+ auth: { token: process.env.EH_BOT_GITHUB_TOKEN },
120
+ });
121
+ // No snowflakes detected or only potential snowflakes using classname
122
+ if (styleComments.length === 0 &&
123
+ sxComments.length === 0 &&
124
+ styledComponentComments.length === 0) {
125
+ const reviewBody = classNameComments.length > 0
126
+ ? {
127
+ body: '[WARNING] Potential snowflakes detected in this PR using classnames. Please review the following comments.',
128
+ comments: classNameComments,
129
+ }
130
+ : {
131
+ body: 'No snowflakes detected in this PR.',
132
+ };
133
+ return personalOctokit.pulls.createReview(Object.assign(Object.assign(Object.assign({}, repoInfo), { pull_number: prNumber, commit_id: context.payload.pull_request.head.sha, event: 'APPROVE' }), reviewBody));
134
+ }
135
+ return personalOctokit.pulls.createReview(Object.assign(Object.assign({}, repoInfo), { pull_number: prNumber, commit_id: context.payload.pull_request.head.sha, event: 'REQUEST_CHANGES', body: 'Snowflake Guard Bot has detected some snowflakes in this PR. Please review the following comments.', comments: [
136
+ ...styleComments,
137
+ ...sxComments,
138
+ ...styledComponentComments,
139
+ ...classNameComments,
140
+ ] }));
141
+ }));
142
+ };
@@ -0,0 +1,7 @@
1
+ declare const parseSource: (source: string) => {
2
+ classNameLocs: number[];
3
+ styleLocs: number[];
4
+ sxLocs: number[];
5
+ styledComponentLocs: number[];
6
+ };
7
+ export default parseSource;
@@ -0,0 +1,102 @@
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 tsParser = __importStar(require("./parsers/typescript"));
31
+ const reportCustomStyleProperties_1 = __importDefault(require("./reports/reportCustomStyleProperties"));
32
+ const reportStyledComponents_1 = __importDefault(require("./reports/reportStyledComponents"));
33
+ const constants_1 = require("./reports/constants");
34
+ const parseSource = (source) => {
35
+ let hasHeroDesignImport = false;
36
+ let hasStyledComponentsImport = false;
37
+ const componentList = {};
38
+ let styledAliasName = 'styled';
39
+ let styledComponentLocs = [];
40
+ let classNameLocs = [];
41
+ let styleLocs = [];
42
+ let sxLocs = [];
43
+ const approvedCmtLocs = [];
44
+ const ast = recast.parse(source, { parser: tsParser });
45
+ recast.visit(ast, {
46
+ visitImportDeclaration(path) {
47
+ this.traverse(path);
48
+ const importedFrom = path.value.source.value;
49
+ // Check if file imports components from '@hero-design/react'
50
+ if (importedFrom === '@hero-design/react') {
51
+ recast.visit(path.node, {
52
+ visitImportSpecifier(importPath) {
53
+ this.traverse(importPath);
54
+ if (constants_1.HD_COMPONENTS.includes(importPath.value.imported.name)) {
55
+ componentList[importPath.value.local.name] =
56
+ importPath.value.imported.name;
57
+ hasHeroDesignImport = true;
58
+ }
59
+ },
60
+ });
61
+ }
62
+ // Check if file imports from 'styled-components'
63
+ if (importedFrom === 'styled-components') {
64
+ recast.visit(path.node, {
65
+ visitImportDefaultSpecifier(importPath) {
66
+ this.traverse(importPath);
67
+ styledAliasName = importPath.value.local.name;
68
+ hasStyledComponentsImport = true;
69
+ },
70
+ });
71
+ }
72
+ },
73
+ visitComment(path) {
74
+ this.traverse(path);
75
+ const comment = path.value.value;
76
+ if (comment.toLowerCase().includes(constants_1.APPROVED_COMMENT.toLowerCase())) {
77
+ approvedCmtLocs.push(path.value.loc.start.line);
78
+ }
79
+ },
80
+ });
81
+ const isNotApprovedSnowflakes = (loc) => !approvedCmtLocs.includes(loc);
82
+ if (hasHeroDesignImport) {
83
+ // Case 1: Using className to customise components
84
+ // Case 2: Using style object to customise components
85
+ // Case 3: Using sx object to customise components
86
+ const customPropLocs = (0, reportCustomStyleProperties_1.default)(ast, componentList);
87
+ classNameLocs = customPropLocs.className.filter(isNotApprovedSnowflakes);
88
+ styleLocs = customPropLocs.style.filter(isNotApprovedSnowflakes);
89
+ sxLocs = customPropLocs.sx.filter(isNotApprovedSnowflakes);
90
+ // Case 4: Using styled-components to customise components
91
+ if (hasStyledComponentsImport) {
92
+ styledComponentLocs = (0, reportStyledComponents_1.default)(ast, componentList, styledAliasName).filter(isNotApprovedSnowflakes);
93
+ }
94
+ }
95
+ return {
96
+ classNameLocs,
97
+ styleLocs,
98
+ sxLocs,
99
+ styledComponentLocs,
100
+ };
101
+ };
102
+ exports.default = parseSource;
@@ -0,0 +1,3 @@
1
+ import * as babelParser from '@babel/parser';
2
+ import { Overrides } from 'recast/parsers/_babel_options';
3
+ export declare const parse: (source: string, options?: Overrides) => babelParser.ParseResult<import("@babel/types").File>;