@hubspot/eslint-config-ui-extensions 1.0.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 +1 -0
- package/dist/__tests__/index.spec.js +20 -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__/utils/import-utils.spec.js +23 -1
- package/dist/index.js +3 -0
- package/dist/rules/no-experimental-imports.d.ts +2 -0
- package/dist/rules/no-experimental-imports.js +29 -0
- package/dist/runtime.js +1 -1
- package/dist/utils/import-utils.d.ts +1 -0
- package/dist/utils/import-utils.js +5 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -180,6 +180,7 @@ export default defineConfig([
|
|
|
180
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
181
|
| [no-console](docs/rules/no-console.md) | Prevent usage of console methods in UI Extensions in favor of the SDK logger utility. | |
|
|
182
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. | |
|
|
183
184
|
| [no-html-elements](docs/rules/no-html-elements.md) | Disallow usage of unsupported HTML elements in UI Extensions. | |
|
|
184
185
|
| [no-invalid-extension-point-imports](docs/rules/no-invalid-extension-point-imports.md) | Prevent importing components from unsupported extension points. | |
|
|
185
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. | 🔧 |
|
|
@@ -151,6 +151,26 @@ describe('config', () => {
|
|
|
151
151
|
expect(customRuleError).toBeUndefined();
|
|
152
152
|
});
|
|
153
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
|
+
});
|
|
154
174
|
describe('custom no-console rule', () => {
|
|
155
175
|
it('should warn when using console.log()', async () => {
|
|
156
176
|
const code = `
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { RuleTester } from 'eslint';
|
|
2
|
+
import { noExperimentalImports } from "../../rules/no-experimental-imports.js";
|
|
3
|
+
import { languageOptions } from "../../runtime.js";
|
|
4
|
+
const ruleTesterOptions = {
|
|
5
|
+
languageOptions: {
|
|
6
|
+
...languageOptions,
|
|
7
|
+
parserOptions: {
|
|
8
|
+
ecmaFeatures: {
|
|
9
|
+
jsx: true,
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
const experimentalImport = `
|
|
15
|
+
import { Grid } from '@hubspot/ui-extensions/experimental';
|
|
16
|
+
`;
|
|
17
|
+
const experimentalSubpathImport = `
|
|
18
|
+
import { getDistanceFromTshirtSize } from '@hubspot/ui-extensions/experimental/Layout/constants';
|
|
19
|
+
`;
|
|
20
|
+
const stableImport = `
|
|
21
|
+
import { Text, Button } from '@hubspot/ui-extensions';
|
|
22
|
+
`;
|
|
23
|
+
const otherPackageImport = `
|
|
24
|
+
import { useState } from 'react';
|
|
25
|
+
`;
|
|
26
|
+
describe('no-experimental-imports', () => {
|
|
27
|
+
const ruleTester = new RuleTester(ruleTesterOptions);
|
|
28
|
+
ruleTester.run('no-experimental-imports', noExperimentalImports, {
|
|
29
|
+
valid: [
|
|
30
|
+
{
|
|
31
|
+
name: 'stable @hubspot/ui-extensions import',
|
|
32
|
+
code: stableImport,
|
|
33
|
+
filename: '/path/to/project/src/app/cards/Component.tsx',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'other package import',
|
|
37
|
+
code: otherPackageImport,
|
|
38
|
+
filename: '/path/to/project/src/app/cards/Component.tsx',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'crm extension point import',
|
|
42
|
+
code: `import { useCrmProperties } from '@hubspot/ui-extensions/crm/hooks';`,
|
|
43
|
+
filename: '/path/to/project/src/app/cards/Component.tsx',
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
invalid: [
|
|
47
|
+
{
|
|
48
|
+
name: 'import from experimental path',
|
|
49
|
+
code: experimentalImport,
|
|
50
|
+
filename: '/path/to/project/src/app/cards/Component.tsx',
|
|
51
|
+
errors: [{ messageId: 'noExperimentalImports' }],
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'import from experimental subpath',
|
|
55
|
+
code: experimentalSubpathImport,
|
|
56
|
+
filename: '/path/to/project/src/app/extensions/Component.tsx',
|
|
57
|
+
errors: [{ messageId: 'noExperimentalImports' }],
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'default import from experimental',
|
|
61
|
+
code: `import ExperimentalComponent from '@hubspot/ui-extensions/experimental';`,
|
|
62
|
+
filename: '/path/to/project/src/app/settings/Component.tsx',
|
|
63
|
+
errors: [{ messageId: 'noExperimentalImports' }],
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getImportedComponentName, isHubspotExtensibilityImport, isRelativeImport, } from "../../utils/import-utils.js";
|
|
1
|
+
import { getImportedComponentName, isExperimentalImport, isHubspotExtensibilityImport, isRelativeImport, } from "../../utils/import-utils.js";
|
|
2
2
|
describe('import-utils', () => {
|
|
3
3
|
describe('isHubspotExtensibilityImport', () => {
|
|
4
4
|
it('should return true for @hubspot/ui-extensions imports', () => {
|
|
@@ -151,4 +151,26 @@ describe('import-utils', () => {
|
|
|
151
151
|
expect(isRelativeImport('')).toBe(false);
|
|
152
152
|
});
|
|
153
153
|
});
|
|
154
|
+
describe('isExperimentalImport', () => {
|
|
155
|
+
it('should return true for @hubspot/ui-extensions/experimental', () => {
|
|
156
|
+
expect(isExperimentalImport('@hubspot/ui-extensions/experimental')).toBe(true);
|
|
157
|
+
});
|
|
158
|
+
it('should return true for experimental subpaths', () => {
|
|
159
|
+
expect(isExperimentalImport('@hubspot/ui-extensions/experimental/Layout')).toBe(true);
|
|
160
|
+
expect(isExperimentalImport('@hubspot/ui-extensions/experimental/Layout/Grid')).toBe(true);
|
|
161
|
+
});
|
|
162
|
+
it('should return false for stable @hubspot/ui-extensions imports', () => {
|
|
163
|
+
expect(isExperimentalImport('@hubspot/ui-extensions')).toBe(false);
|
|
164
|
+
expect(isExperimentalImport('@hubspot/ui-extensions/crm')).toBe(false);
|
|
165
|
+
expect(isExperimentalImport('@hubspot/ui-extensions/hooks')).toBe(false);
|
|
166
|
+
});
|
|
167
|
+
it('should return false for non-hubspot packages', () => {
|
|
168
|
+
expect(isExperimentalImport('react')).toBe(false);
|
|
169
|
+
expect(isExperimentalImport('some-package/experimental')).toBe(false);
|
|
170
|
+
});
|
|
171
|
+
it('should return false for non-string values', () => {
|
|
172
|
+
expect(isExperimentalImport(null)).toBe(false);
|
|
173
|
+
expect(isExperimentalImport(undefined)).toBe(false);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
154
176
|
});
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { noBrowserDialogs } from "./rules/no-browser-dialogs.js";
|
|
|
3
3
|
import { noBrowserStorage } from "./rules/no-browser-storage.js";
|
|
4
4
|
import { noConsole } from "./rules/no-console.js";
|
|
5
5
|
import { noDomAccess } from "./rules/no-dom-access.js";
|
|
6
|
+
import { noExperimentalImports } from "./rules/no-experimental-imports.js";
|
|
6
7
|
import { unsupportedHtmlElements } from "./rules/no-html-elements.js";
|
|
7
8
|
import { noInvalidExtensionPointImports } from "./rules/no-invalid-extension-point-imports.js";
|
|
8
9
|
import { noInvalidImageSrc } from "./rules/no-invalid-image-src.js";
|
|
@@ -18,6 +19,7 @@ const rules = {
|
|
|
18
19
|
'no-browser-dialogs': noBrowserDialogs,
|
|
19
20
|
'no-console': noConsole,
|
|
20
21
|
'no-dom-access': noDomAccess,
|
|
22
|
+
'no-experimental-imports': noExperimentalImports,
|
|
21
23
|
'no-browser-storage': noBrowserStorage,
|
|
22
24
|
'no-parent-imports': noParentImports,
|
|
23
25
|
'no-restricted-globals': noRestrictedGlobals,
|
|
@@ -55,6 +57,7 @@ export const config = [
|
|
|
55
57
|
'@hubspot/ui-extensions/no-native-http': 'error',
|
|
56
58
|
'@hubspot/ui-extensions/no-browser-dialogs': 'error',
|
|
57
59
|
'@hubspot/ui-extensions/no-console': 'warn',
|
|
60
|
+
'@hubspot/ui-extensions/no-experimental-imports': 'warn',
|
|
58
61
|
'@hubspot/ui-extensions/no-dom-access': 'error',
|
|
59
62
|
'@hubspot/ui-extensions/no-browser-storage': 'error',
|
|
60
63
|
'@hubspot/ui-extensions/no-parent-imports': 'error',
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { isExperimentalImport } from "../utils/import-utils.js";
|
|
2
|
+
export const noExperimentalImports = {
|
|
3
|
+
meta: {
|
|
4
|
+
type: 'suggestion',
|
|
5
|
+
docs: {
|
|
6
|
+
description: 'Warn when importing from the experimental path of @hubspot/ui-extensions.',
|
|
7
|
+
url: 'https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/linting/rules/no-experimental-imports',
|
|
8
|
+
},
|
|
9
|
+
messages: {
|
|
10
|
+
noExperimentalImports: 'Imports from "{{importSource}}" use experimental APIs. Experimental components are not stable and are not recommended for production.',
|
|
11
|
+
},
|
|
12
|
+
schema: [],
|
|
13
|
+
},
|
|
14
|
+
create(context) {
|
|
15
|
+
return {
|
|
16
|
+
ImportDeclaration(node) {
|
|
17
|
+
const importSource = node.source.value;
|
|
18
|
+
if (!isExperimentalImport(importSource)) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
context.report({
|
|
22
|
+
node,
|
|
23
|
+
messageId: 'noExperimentalImports',
|
|
24
|
+
data: { importSource },
|
|
25
|
+
});
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
};
|
package/dist/runtime.js
CHANGED
|
@@ -31,3 +31,4 @@ export declare const getImportedComponentName: (node: ImportDeclaration, compone
|
|
|
31
31
|
* @returns True if the import source is a relative import
|
|
32
32
|
*/
|
|
33
33
|
export declare const isRelativeImport: (importSource: unknown) => importSource is string;
|
|
34
|
+
export declare const isExperimentalImport: (importSource: unknown) => boolean;
|
|
@@ -45,3 +45,8 @@ export const isRelativeImport = (importSource) => {
|
|
|
45
45
|
return (typeof importSource === 'string' &&
|
|
46
46
|
(importSource.startsWith('./') || importSource.startsWith('../')));
|
|
47
47
|
};
|
|
48
|
+
export const isExperimentalImport = (importSource) => {
|
|
49
|
+
return (typeof importSource === 'string' &&
|
|
50
|
+
isHubspotExtensibilityImport(importSource) &&
|
|
51
|
+
importSource.includes('/experimental'));
|
|
52
|
+
};
|