@hubspot/eslint-config-ui-extensions 0.12.0 → 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 +168 -24
- package/dist/__tests__/index.spec.d.ts +1 -0
- package/dist/__tests__/index.spec.js +303 -0
- package/dist/__tests__/rules/no-browser-dialogs.spec.d.ts +1 -0
- package/dist/__tests__/rules/no-browser-dialogs.spec.js +263 -0
- package/dist/__tests__/rules/no-browser-storage.spec.d.ts +1 -0
- package/dist/__tests__/rules/no-browser-storage.spec.js +428 -0
- package/dist/__tests__/rules/no-console.spec.d.ts +1 -0
- package/dist/__tests__/rules/no-console.spec.js +262 -0
- package/dist/__tests__/rules/no-dom-access.spec.d.ts +1 -0
- package/dist/__tests__/rules/no-dom-access.spec.js +288 -0
- package/dist/__tests__/rules/no-experimental-imports.spec.d.ts +1 -0
- package/dist/__tests__/rules/no-experimental-imports.spec.js +67 -0
- package/dist/__tests__/rules/no-html-elements.spec.d.ts +1 -0
- package/dist/__tests__/rules/no-html-elements.spec.js +142 -0
- package/dist/__tests__/rules/no-invalid-extension-point-imports.spec.d.ts +1 -0
- package/dist/__tests__/rules/no-invalid-extension-point-imports.spec.js +269 -0
- package/dist/__tests__/rules/no-invalid-image-src.spec.d.ts +1 -0
- package/dist/__tests__/rules/no-invalid-image-src.spec.js +227 -0
- package/dist/__tests__/rules/no-native-http.spec.d.ts +1 -0
- package/dist/__tests__/rules/no-native-http.spec.js +596 -0
- package/dist/__tests__/rules/no-parent-imports.spec.d.ts +1 -0
- package/dist/__tests__/rules/no-parent-imports.spec.js +337 -0
- package/dist/__tests__/rules/no-restricted-globals.spec.d.ts +1 -0
- package/dist/__tests__/rules/no-restricted-globals.spec.js +434 -0
- package/dist/__tests__/utils/ast-utils.spec.d.ts +1 -0
- package/dist/__tests__/utils/ast-utils.spec.js +297 -0
- package/dist/__tests__/utils/extension-point-utils.spec.d.ts +1 -0
- package/dist/__tests__/utils/extension-point-utils.spec.js +125 -0
- package/dist/__tests__/utils/import-utils.spec.d.ts +1 -0
- package/dist/__tests__/utils/import-utils.spec.js +176 -0
- package/dist/__tests__/utils/jsx-utils.spec.d.ts +1 -0
- package/dist/__tests__/utils/jsx-utils.spec.js +120 -0
- package/dist/__tests__/utils/scope-utils.spec.d.ts +1 -0
- package/dist/__tests__/utils/scope-utils.spec.js +80 -0
- package/dist/constants.d.ts +3 -0
- package/dist/constants.js +124 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +75 -0
- package/dist/rules/no-browser-dialogs.d.ts +2 -0
- package/dist/rules/no-browser-dialogs.js +62 -0
- package/dist/rules/no-browser-storage.d.ts +2 -0
- package/dist/rules/no-browser-storage.js +78 -0
- package/dist/rules/no-console.d.ts +2 -0
- package/dist/rules/no-console.js +78 -0
- package/dist/rules/no-dom-access.d.ts +2 -0
- package/dist/rules/no-dom-access.js +63 -0
- package/dist/rules/no-experimental-imports.d.ts +2 -0
- package/dist/rules/no-experimental-imports.js +29 -0
- package/dist/rules/no-html-elements.d.ts +2 -0
- package/dist/rules/no-html-elements.js +39 -0
- package/dist/rules/no-invalid-extension-point-imports.d.ts +2 -0
- package/dist/rules/no-invalid-extension-point-imports.js +54 -0
- package/dist/rules/no-invalid-image-src.d.ts +2 -0
- package/dist/rules/no-invalid-image-src.js +120 -0
- package/dist/rules/no-native-http.d.ts +2 -0
- package/dist/rules/no-native-http.js +70 -0
- package/dist/rules/no-parent-imports.d.ts +2 -0
- package/dist/rules/no-parent-imports.js +46 -0
- package/dist/rules/no-restricted-globals.d.ts +2 -0
- package/dist/rules/no-restricted-globals.js +132 -0
- package/dist/runtime.d.ts +2 -0
- package/dist/runtime.js +13 -0
- package/dist/utils/ast-utils.d.ts +42 -0
- package/dist/utils/ast-utils.js +79 -0
- package/dist/utils/extension-point-utils.d.ts +57 -0
- package/dist/utils/extension-point-utils.js +108 -0
- package/dist/utils/import-utils.d.ts +34 -0
- package/dist/utils/import-utils.js +52 -0
- package/dist/utils/jsx-utils.d.ts +30 -0
- package/dist/utils/jsx-utils.js +40 -0
- package/dist/utils/scope-utils.d.ts +30 -0
- package/dist/utils/scope-utils.js +66 -0
- package/package.json +43 -24
- package/CHANGELOG.md +0 -51
- package/eslintrc.js +0 -12
- package/lib/index.js +0 -57
- package/lib/runtime.js +0 -17
package/README.md
CHANGED
|
@@ -1,47 +1,191 @@
|
|
|
1
1
|
# @hubspot/eslint-config-ui-extensions
|
|
2
2
|
|
|
3
|
-
[
|
|
3
|
+
[ESLint shareable config](https://eslint.org/docs/latest/extend/shareable-configs) for building UI Extensions on HubSpot with React.
|
|
4
|
+
|
|
5
|
+
This package provides ESLint rules specifically designed for the HubSpot UI Extensions environment. It catches common issues like using unavailable browser APIs, incorrect imports, and other patterns that won't work in the sandboxed web worker context.
|
|
4
6
|
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
> **Upgrading from v0.x?** See the [Migration Guide](./MIGRATION.md) for step-by-step instructions.
|
|
10
|
+
|
|
11
|
+
Install [ESLint](https://eslint.org/) 9+ alongside this package:
|
|
8
12
|
|
|
9
13
|
```sh
|
|
10
14
|
npm i --save-dev eslint @hubspot/eslint-config-ui-extensions
|
|
11
15
|
```
|
|
12
16
|
|
|
17
|
+
## Basic Usage
|
|
13
18
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
Create an .eslintrc.js file in the root of your project:
|
|
19
|
+
Create an `eslint.config.js` file in the root of your project:
|
|
17
20
|
|
|
18
21
|
```js
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
import { config } from '@hubspot/eslint-config-ui-extensions';
|
|
23
|
+
|
|
24
|
+
export default [
|
|
25
|
+
...config,
|
|
26
|
+
// Your project-specific overrides
|
|
27
|
+
];
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Make sure your `package.json` has `"type": "module"` set, then add a lint script:
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"type": "module",
|
|
35
|
+
"scripts": {
|
|
36
|
+
"lint": "eslint ."
|
|
37
|
+
}
|
|
38
|
+
}
|
|
23
39
|
```
|
|
24
40
|
|
|
25
|
-
|
|
41
|
+
Run `npm run lint` to check for linting issues.
|
|
42
|
+
|
|
43
|
+
## Recommended Setup
|
|
44
|
+
|
|
45
|
+
The basic config above only includes HubSpot UI Extensions-specific rules. For a production-ready setup, we recommend adding standard ESLint rules, TypeScript support, React Hooks linting, Prettier compatibility, and unused import detection.
|
|
46
|
+
|
|
47
|
+
### Install Additional Dependencies
|
|
26
48
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
49
|
+
```sh
|
|
50
|
+
npm i --save-dev @eslint/js typescript-eslint eslint-plugin-react eslint-plugin-react-hooks eslint-config-prettier eslint-plugin-unused-imports
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Full Recommended Config
|
|
30
54
|
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
55
|
+
```js
|
|
56
|
+
import { defineConfig } from 'eslint/config';
|
|
57
|
+
import js from '@eslint/js';
|
|
58
|
+
import tseslint from 'typescript-eslint';
|
|
59
|
+
import react from 'eslint-plugin-react';
|
|
60
|
+
import reactHooks from 'eslint-plugin-react-hooks';
|
|
61
|
+
import unusedImports from 'eslint-plugin-unused-imports';
|
|
62
|
+
import prettier from 'eslint-config-prettier';
|
|
63
|
+
import { config as uiExtensionsConfig } from '@hubspot/eslint-config-ui-extensions';
|
|
64
|
+
|
|
65
|
+
export default defineConfig([
|
|
66
|
+
// Standard ESLint recommended rules
|
|
67
|
+
js.configs.recommended,
|
|
68
|
+
|
|
69
|
+
// TypeScript support with stylistic rules
|
|
70
|
+
tseslint.configs.recommended,
|
|
71
|
+
tseslint.configs.stylistic,
|
|
72
|
+
|
|
73
|
+
// React recommended rules + JSX runtime support (React 17+)
|
|
74
|
+
react.configs.flat.recommended,
|
|
75
|
+
react.configs.flat['jsx-runtime'],
|
|
76
|
+
{
|
|
77
|
+
settings: {
|
|
78
|
+
react: { version: 'detect' },
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
// HubSpot UI Extensions rules
|
|
83
|
+
...uiExtensionsConfig,
|
|
84
|
+
|
|
85
|
+
// React Hooks rules
|
|
86
|
+
reactHooks.configs.flat['recommended-latest'],
|
|
87
|
+
|
|
88
|
+
// Unused imports detection (auto-fixable with --fix)
|
|
89
|
+
{
|
|
90
|
+
plugins: {
|
|
91
|
+
'unused-imports': unusedImports,
|
|
92
|
+
},
|
|
93
|
+
rules: {
|
|
94
|
+
'no-unused-vars': 'off',
|
|
95
|
+
'@typescript-eslint/no-unused-vars': 'off',
|
|
96
|
+
'unused-imports/no-unused-imports': 'error',
|
|
97
|
+
'unused-imports/no-unused-vars': [
|
|
98
|
+
'warn',
|
|
99
|
+
{
|
|
100
|
+
vars: 'all',
|
|
101
|
+
varsIgnorePattern: '^_',
|
|
102
|
+
args: 'after-used',
|
|
103
|
+
argsIgnorePattern: '^_',
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
// Prettier compatibility (must be last to override other configs)
|
|
110
|
+
prettier,
|
|
111
|
+
]);
|
|
35
112
|
```
|
|
36
113
|
|
|
37
|
-
|
|
114
|
+
### What Each Addition Provides
|
|
38
115
|
|
|
116
|
+
| Package | Purpose |
|
|
117
|
+
| :------ | :------ |
|
|
118
|
+
| `@eslint/js` | ESLint's built-in recommended rules for catching common JavaScript issues |
|
|
119
|
+
| `typescript-eslint` | TypeScript parsing and linting rules (`stylistic` adds consistent code style enforcement) |
|
|
120
|
+
| `eslint-plugin-react` | React-specific rules; `jsx-runtime` config is for React 17+ (no need to import React in every file) |
|
|
121
|
+
| `eslint-plugin-react-hooks` | Enforces [Rules of Hooks](https://react.dev/reference/rules/rules-of-hooks) and verifies dependency arrays |
|
|
122
|
+
| `eslint-config-prettier` | Disables ESLint rules that conflict with Prettier formatting |
|
|
123
|
+
| `eslint-plugin-unused-imports` | Auto-fixable detection and removal of unused imports |
|
|
39
124
|
|
|
40
|
-
|
|
125
|
+
### JavaScript-Only Projects
|
|
41
126
|
|
|
42
|
-
|
|
127
|
+
If you're not using TypeScript, you can simplify the config:
|
|
128
|
+
|
|
129
|
+
```js
|
|
130
|
+
import { defineConfig } from 'eslint/config';
|
|
131
|
+
import js from '@eslint/js';
|
|
132
|
+
import react from 'eslint-plugin-react';
|
|
133
|
+
import reactHooks from 'eslint-plugin-react-hooks';
|
|
134
|
+
import unusedImports from 'eslint-plugin-unused-imports';
|
|
135
|
+
import prettier from 'eslint-config-prettier';
|
|
136
|
+
import { config as uiExtensionsConfig } from '@hubspot/eslint-config-ui-extensions';
|
|
137
|
+
|
|
138
|
+
export default defineConfig([
|
|
139
|
+
js.configs.recommended,
|
|
140
|
+
react.configs.flat.recommended,
|
|
141
|
+
react.configs.flat['jsx-runtime'],
|
|
142
|
+
{
|
|
143
|
+
settings: {
|
|
144
|
+
react: { version: 'detect' },
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
...uiExtensionsConfig,
|
|
148
|
+
reactHooks.configs.flat['recommended-latest'],
|
|
149
|
+
{
|
|
150
|
+
plugins: {
|
|
151
|
+
'unused-imports': unusedImports,
|
|
152
|
+
},
|
|
153
|
+
rules: {
|
|
154
|
+
'no-unused-vars': 'off',
|
|
155
|
+
'unused-imports/no-unused-imports': 'error',
|
|
156
|
+
'unused-imports/no-unused-vars': [
|
|
157
|
+
'warn',
|
|
158
|
+
{
|
|
159
|
+
vars: 'all',
|
|
160
|
+
varsIgnorePattern: '^_',
|
|
161
|
+
args: 'after-used',
|
|
162
|
+
argsIgnorePattern: '^_',
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
prettier,
|
|
168
|
+
]);
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Rules
|
|
43
172
|
|
|
44
|
-
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
173
|
+
<!-- begin auto-generated rules list -->
|
|
174
|
+
|
|
175
|
+
🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).
|
|
176
|
+
|
|
177
|
+
| Name | Description | 🔧 |
|
|
178
|
+
| :------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :- |
|
|
179
|
+
| [no-browser-dialogs](docs/rules/no-browser-dialogs.md) | Prevent usage of native browser dialog APIs (alert, confirm, and prompt) in UI Extensions. | |
|
|
180
|
+
| [no-browser-storage](docs/rules/no-browser-storage.md) | Prevent usage of browser storage APIs (localStorage and sessionStorage) in UI Extensions, which run in a sandboxed web worker environment where these APIs are not available. | |
|
|
181
|
+
| [no-console](docs/rules/no-console.md) | Prevent usage of console methods in UI Extensions in favor of the SDK logger utility. | |
|
|
182
|
+
| [no-dom-access](docs/rules/no-dom-access.md) | Prevent access to the DOM (document object) in UI Extensions, which run in a sandboxed web worker without DOM access. | |
|
|
183
|
+
| [no-experimental-imports](docs/rules/no-experimental-imports.md) | Warn when importing from the experimental path of @hubspot/ui-extensions. | |
|
|
184
|
+
| [no-html-elements](docs/rules/no-html-elements.md) | Disallow usage of unsupported HTML elements in UI Extensions. | |
|
|
185
|
+
| [no-invalid-extension-point-imports](docs/rules/no-invalid-extension-point-imports.md) | Prevent importing components from unsupported extension points. | |
|
|
186
|
+
| [no-invalid-image-src](docs/rules/no-invalid-image-src.md) | Only allow valid image URLs in the src attribute of Image components from @hubspot/ui-extensions. | 🔧 |
|
|
187
|
+
| [no-native-http](docs/rules/no-native-http.md) | Prevent usage of native browser HTTP APIs (fetch and XMLHttpRequest) in UI Extensions. | |
|
|
188
|
+
| [no-parent-imports](docs/rules/no-parent-imports.md) | Prevent importing files from outside the extension point root directory | |
|
|
189
|
+
| [no-restricted-globals](docs/rules/no-restricted-globals.md) | Prevent usage of browser globals that are not available in the sandboxed web worker environment. | |
|
|
190
|
+
|
|
191
|
+
<!-- end auto-generated rules list -->
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { ESLint } from 'eslint';
|
|
2
|
+
import { config } from "../index.js";
|
|
3
|
+
/**
|
|
4
|
+
* A helper function to lint code with the imported ESLint config.
|
|
5
|
+
* Returns the linting messages.
|
|
6
|
+
*/
|
|
7
|
+
async function lintCode(code, filePath, cwd) {
|
|
8
|
+
const eslint = new ESLint({
|
|
9
|
+
overrideConfigFile: true,
|
|
10
|
+
overrideConfig: config,
|
|
11
|
+
...(cwd && { cwd }),
|
|
12
|
+
});
|
|
13
|
+
const results = await eslint.lintText(code, { filePath });
|
|
14
|
+
return results[0].messages;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Config regression tests
|
|
18
|
+
*
|
|
19
|
+
* These tests ensure we don't accidentally break the exported config by:
|
|
20
|
+
* - Verifying critical rules are present and fire correctly
|
|
21
|
+
* - Validating the config structure loads without errors
|
|
22
|
+
*
|
|
23
|
+
* For imported rules, we simply want to make sure they are registered correctly.
|
|
24
|
+
* The maintainer of the ruleset is responsible for testing the actual rule logic.
|
|
25
|
+
*
|
|
26
|
+
* When custom rules are added:
|
|
27
|
+
* - They'll have dedicated test files for in-depth testing
|
|
28
|
+
* - They should also have a smoke test here to verify they're registered
|
|
29
|
+
*/
|
|
30
|
+
describe('config', () => {
|
|
31
|
+
describe('custom no-restricted-globals rule', () => {
|
|
32
|
+
it('should error when using restricted globals', async () => {
|
|
33
|
+
const code = `
|
|
34
|
+
const path = location.pathname;
|
|
35
|
+
`;
|
|
36
|
+
const messages = await lintCode(code, 'test.js');
|
|
37
|
+
const customRuleError = messages.find((m) => m.ruleId === '@hubspot/ui-extensions/no-restricted-globals');
|
|
38
|
+
expect(customRuleError).toBeDefined();
|
|
39
|
+
expect(customRuleError?.severity).toBe(2);
|
|
40
|
+
expect(customRuleError?.message).toContain('location');
|
|
41
|
+
});
|
|
42
|
+
it('should allow local variables with restricted names', async () => {
|
|
43
|
+
const code = `
|
|
44
|
+
const location = { pathname: '/test' };
|
|
45
|
+
console.log(location.pathname);
|
|
46
|
+
`;
|
|
47
|
+
const messages = await lintCode(code, 'test.js');
|
|
48
|
+
const customRuleError = messages.find((m) => m.ruleId === '@hubspot/ui-extensions/no-restricted-globals');
|
|
49
|
+
expect(customRuleError).toBeUndefined();
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
describe('JSX support', () => {
|
|
53
|
+
it('should parse JSX syntax without errors', async () => {
|
|
54
|
+
const code = `
|
|
55
|
+
function App() {
|
|
56
|
+
return <Button>Click Me</Button>;
|
|
57
|
+
}
|
|
58
|
+
`;
|
|
59
|
+
const messages = await lintCode(code, 'test.tsx');
|
|
60
|
+
const parseErrors = messages.filter((m) => m.fatal);
|
|
61
|
+
expect(parseErrors).toHaveLength(0);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
describe('custom no-html-elements rule', () => {
|
|
65
|
+
it('should error when using unsupported HTML elements', async () => {
|
|
66
|
+
const code = `
|
|
67
|
+
const element = <div>Hello</div>;
|
|
68
|
+
`;
|
|
69
|
+
const messages = await lintCode(code, 'test.jsx');
|
|
70
|
+
const customRuleError = messages.find((m) => m.ruleId === '@hubspot/ui-extensions/no-html-elements');
|
|
71
|
+
expect(customRuleError).toBeDefined();
|
|
72
|
+
expect(customRuleError?.severity).toBe(2);
|
|
73
|
+
});
|
|
74
|
+
it('should allow usage of supported elements', async () => {
|
|
75
|
+
const code = `
|
|
76
|
+
const element = <Button>Click Me</Button>;
|
|
77
|
+
`;
|
|
78
|
+
const messages = await lintCode(code, 'test.jsx');
|
|
79
|
+
const customRuleError = messages.find((m) => m.ruleId === '@hubspot/ui-extensions/no-html-elements');
|
|
80
|
+
expect(customRuleError).toBeUndefined();
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
describe('custom no-invalid-extension-point-imports rule', () => {
|
|
84
|
+
it('should error when importing from an invalid extension point', async () => {
|
|
85
|
+
const code = `
|
|
86
|
+
import { SettingsComponent } from '@hubspot/ui-extensions/settings';
|
|
87
|
+
`;
|
|
88
|
+
const messages = await lintCode(code, 'src/app/cards/MyCard.tsx', '/path/to/project/src/app/cards');
|
|
89
|
+
const customRuleError = messages.find((m) => m.ruleId ===
|
|
90
|
+
'@hubspot/ui-extensions/no-invalid-extension-point-imports');
|
|
91
|
+
expect(customRuleError).toBeDefined();
|
|
92
|
+
expect(customRuleError?.severity).toBe(2);
|
|
93
|
+
expect(customRuleError?.message).toContain('Imports from "settings" are not allowed in cards.');
|
|
94
|
+
});
|
|
95
|
+
it('should allow valid extension point imports', async () => {
|
|
96
|
+
const code = `
|
|
97
|
+
import { useCrmProperties } from '@hubspot/ui-extensions/crm/hooks';
|
|
98
|
+
`;
|
|
99
|
+
const messages = await lintCode(code, 'src/app/cards/MyCard.tsx', '/path/to/project/src/app/cards');
|
|
100
|
+
const customRuleError = messages.find((m) => m.ruleId ===
|
|
101
|
+
'@hubspot/ui-extensions/no-invalid-extension-point-imports');
|
|
102
|
+
expect(customRuleError).toBeUndefined();
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
describe('custom no-native-http rule', () => {
|
|
106
|
+
it('should error when using native fetch()', async () => {
|
|
107
|
+
const code = `
|
|
108
|
+
function getData() {
|
|
109
|
+
return fetch('https://api.example.com');
|
|
110
|
+
}
|
|
111
|
+
`;
|
|
112
|
+
const messages = await lintCode(code, 'test.js');
|
|
113
|
+
const customRuleError = messages.find((m) => m.ruleId === '@hubspot/ui-extensions/no-native-http');
|
|
114
|
+
expect(customRuleError).toBeDefined();
|
|
115
|
+
expect(customRuleError?.severity).toBe(2);
|
|
116
|
+
expect(customRuleError?.message).toContain('Native fetch()');
|
|
117
|
+
});
|
|
118
|
+
it('should allow hubspot.fetch()', async () => {
|
|
119
|
+
const code = `
|
|
120
|
+
function getData() {
|
|
121
|
+
return hubspot.fetch('https://api.example.com');
|
|
122
|
+
}
|
|
123
|
+
`;
|
|
124
|
+
const messages = await lintCode(code, 'test.js');
|
|
125
|
+
const customRuleError = messages.find((m) => m.ruleId === '@hubspot/ui-extensions/no-native-http');
|
|
126
|
+
expect(customRuleError).toBeUndefined();
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
describe('custom no-browser-dialogs rule', () => {
|
|
130
|
+
it('should error when using native alert()', async () => {
|
|
131
|
+
const code = `
|
|
132
|
+
function showMessage() {
|
|
133
|
+
alert('Hello World');
|
|
134
|
+
}
|
|
135
|
+
`;
|
|
136
|
+
const messages = await lintCode(code, 'test.js');
|
|
137
|
+
const customRuleError = messages.find((m) => m.ruleId === '@hubspot/ui-extensions/no-browser-dialogs');
|
|
138
|
+
expect(customRuleError).toBeDefined();
|
|
139
|
+
expect(customRuleError?.severity).toBe(2);
|
|
140
|
+
expect(customRuleError?.message).toContain('alert');
|
|
141
|
+
});
|
|
142
|
+
it('should allow custom alert functions', async () => {
|
|
143
|
+
const code = `
|
|
144
|
+
function showMessage() {
|
|
145
|
+
const alert = (msg) => actions.addAlert(msg);
|
|
146
|
+
alert('Hello World');
|
|
147
|
+
}
|
|
148
|
+
`;
|
|
149
|
+
const messages = await lintCode(code, 'test.js');
|
|
150
|
+
const customRuleError = messages.find((m) => m.ruleId === '@hubspot/ui-extensions/no-browser-dialogs');
|
|
151
|
+
expect(customRuleError).toBeUndefined();
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
describe('custom no-experimental-imports rule', () => {
|
|
155
|
+
it('should warn when importing from experimental path', async () => {
|
|
156
|
+
const code = `
|
|
157
|
+
import { Grid } from '@hubspot/ui-extensions/experimental';
|
|
158
|
+
`;
|
|
159
|
+
const messages = await lintCode(code, 'src/app/cards/Component.tsx', '/path/to/project');
|
|
160
|
+
const customRuleWarning = messages.find((m) => m.ruleId === '@hubspot/ui-extensions/no-experimental-imports');
|
|
161
|
+
expect(customRuleWarning).toBeDefined();
|
|
162
|
+
expect(customRuleWarning?.severity).toBe(1);
|
|
163
|
+
expect(customRuleWarning?.message).toContain('experimental');
|
|
164
|
+
});
|
|
165
|
+
it('should allow stable @hubspot/ui-extensions imports', async () => {
|
|
166
|
+
const code = `
|
|
167
|
+
import { Text, Button } from '@hubspot/ui-extensions';
|
|
168
|
+
`;
|
|
169
|
+
const messages = await lintCode(code, 'src/app/cards/Component.tsx', '/path/to/project');
|
|
170
|
+
const customRuleWarning = messages.find((m) => m.ruleId === '@hubspot/ui-extensions/no-experimental-imports');
|
|
171
|
+
expect(customRuleWarning).toBeUndefined();
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
describe('custom no-console rule', () => {
|
|
175
|
+
it('should warn when using console.log()', async () => {
|
|
176
|
+
const code = `
|
|
177
|
+
function debug() {
|
|
178
|
+
console.log('Debug message');
|
|
179
|
+
}
|
|
180
|
+
`;
|
|
181
|
+
const messages = await lintCode(code, 'test.js');
|
|
182
|
+
const customRuleWarning = messages.find((m) => m.ruleId === '@hubspot/ui-extensions/no-console');
|
|
183
|
+
expect(customRuleWarning).toBeDefined();
|
|
184
|
+
expect(customRuleWarning?.severity).toBe(1);
|
|
185
|
+
expect(customRuleWarning?.message).toContain('console.log()');
|
|
186
|
+
});
|
|
187
|
+
it('should allow custom log functions', async () => {
|
|
188
|
+
const code = `
|
|
189
|
+
function debug() {
|
|
190
|
+
const log = (msg) => logger.debug(msg);
|
|
191
|
+
log('Debug message');
|
|
192
|
+
}
|
|
193
|
+
`;
|
|
194
|
+
const messages = await lintCode(code, 'test.js');
|
|
195
|
+
const customRuleWarning = messages.find((m) => m.ruleId === '@hubspot/ui-extensions/no-console');
|
|
196
|
+
expect(customRuleWarning).toBeUndefined();
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
describe('custom no-dom-access rule', () => {
|
|
200
|
+
it('should error when accessing document', async () => {
|
|
201
|
+
const code = `
|
|
202
|
+
function getElement() {
|
|
203
|
+
return document.querySelector('.test');
|
|
204
|
+
}
|
|
205
|
+
`;
|
|
206
|
+
const messages = await lintCode(code, 'test.js');
|
|
207
|
+
const customRuleError = messages.find((m) => m.ruleId === '@hubspot/ui-extensions/no-dom-access');
|
|
208
|
+
expect(customRuleError).toBeDefined();
|
|
209
|
+
expect(customRuleError?.severity).toBe(2);
|
|
210
|
+
expect(customRuleError?.message).toContain('document');
|
|
211
|
+
});
|
|
212
|
+
it('should allow local document variable', async () => {
|
|
213
|
+
const code = `
|
|
214
|
+
function parse() {
|
|
215
|
+
const document = { body: null };
|
|
216
|
+
return document.body;
|
|
217
|
+
}
|
|
218
|
+
`;
|
|
219
|
+
const messages = await lintCode(code, 'test.js');
|
|
220
|
+
const customRuleError = messages.find((m) => m.ruleId === '@hubspot/ui-extensions/no-dom-access');
|
|
221
|
+
expect(customRuleError).toBeUndefined();
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
describe('custom no-parent-imports rule', () => {
|
|
225
|
+
it('should error when importing from outside extension point directory', async () => {
|
|
226
|
+
const code = `
|
|
227
|
+
import { util } from '../shared/utils';
|
|
228
|
+
`;
|
|
229
|
+
const messages = await lintCode(code, 'src/app/cards/Component.tsx', '/path/to/project');
|
|
230
|
+
const customRuleError = messages.find((m) => m.ruleId === '@hubspot/ui-extensions/no-parent-imports');
|
|
231
|
+
expect(customRuleError).toBeDefined();
|
|
232
|
+
expect(customRuleError?.severity).toBe(2);
|
|
233
|
+
expect(customRuleError?.message).toContain('Imports from outside "cards" will fail at build time');
|
|
234
|
+
expect(customRuleError?.message).toContain('npm workspaces');
|
|
235
|
+
});
|
|
236
|
+
it('should allow imports within extension point directory', async () => {
|
|
237
|
+
const code = `
|
|
238
|
+
import { util } from './utils';
|
|
239
|
+
import { Component } from '../components/Button';
|
|
240
|
+
`;
|
|
241
|
+
const messages = await lintCode(code, 'src/app/cards/nested/Component.tsx', '/path/to/project');
|
|
242
|
+
const customRuleError = messages.find((m) => m.ruleId === '@hubspot/ui-extensions/no-parent-imports');
|
|
243
|
+
expect(customRuleError).toBeUndefined();
|
|
244
|
+
});
|
|
245
|
+
it('should allow npm package imports', async () => {
|
|
246
|
+
const code = `
|
|
247
|
+
import { useState } from 'react';
|
|
248
|
+
import { Button } from '@hubspot/ui-extensions';
|
|
249
|
+
`;
|
|
250
|
+
const messages = await lintCode(code, 'src/app/cards/Component.tsx', '/path/to/project');
|
|
251
|
+
const customRuleError = messages.find((m) => m.ruleId === '@hubspot/ui-extensions/no-parent-imports');
|
|
252
|
+
expect(customRuleError).toBeUndefined();
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
describe('custom no-browser-storage rule', () => {
|
|
256
|
+
it('should error when accessing localStorage', async () => {
|
|
257
|
+
const code = `
|
|
258
|
+
function saveData() {
|
|
259
|
+
localStorage.setItem('key', 'value');
|
|
260
|
+
}
|
|
261
|
+
`;
|
|
262
|
+
const messages = await lintCode(code, 'test.js');
|
|
263
|
+
const customRuleError = messages.find((m) => m.ruleId === '@hubspot/ui-extensions/no-browser-storage');
|
|
264
|
+
expect(customRuleError).toBeDefined();
|
|
265
|
+
expect(customRuleError?.severity).toBe(2);
|
|
266
|
+
expect(customRuleError?.message).toContain('localStorage');
|
|
267
|
+
});
|
|
268
|
+
it('should error when accessing sessionStorage', async () => {
|
|
269
|
+
const code = `
|
|
270
|
+
function loadData() {
|
|
271
|
+
return sessionStorage.getItem('key');
|
|
272
|
+
}
|
|
273
|
+
`;
|
|
274
|
+
const messages = await lintCode(code, 'test.js');
|
|
275
|
+
const customRuleError = messages.find((m) => m.ruleId === '@hubspot/ui-extensions/no-browser-storage');
|
|
276
|
+
expect(customRuleError).toBeDefined();
|
|
277
|
+
expect(customRuleError?.severity).toBe(2);
|
|
278
|
+
expect(customRuleError?.message).toContain('sessionStorage');
|
|
279
|
+
});
|
|
280
|
+
it('should allow local localStorage variable', async () => {
|
|
281
|
+
const code = `
|
|
282
|
+
function saveData() {
|
|
283
|
+
const localStorage = { setItem: () => {} };
|
|
284
|
+
localStorage.setItem('key', 'value');
|
|
285
|
+
}
|
|
286
|
+
`;
|
|
287
|
+
const messages = await lintCode(code, 'test.js');
|
|
288
|
+
const customRuleError = messages.find((m) => m.ruleId === '@hubspot/ui-extensions/no-browser-storage');
|
|
289
|
+
expect(customRuleError).toBeUndefined();
|
|
290
|
+
});
|
|
291
|
+
it('should allow local sessionStorage variable', async () => {
|
|
292
|
+
const code = `
|
|
293
|
+
function loadData() {
|
|
294
|
+
const sessionStorage = { getItem: () => null };
|
|
295
|
+
return sessionStorage.getItem('key');
|
|
296
|
+
}
|
|
297
|
+
`;
|
|
298
|
+
const messages = await lintCode(code, 'test.js');
|
|
299
|
+
const customRuleError = messages.find((m) => m.ruleId === '@hubspot/ui-extensions/no-browser-storage');
|
|
300
|
+
expect(customRuleError).toBeUndefined();
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|