@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 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,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,2 @@
1
+ import type { Rule } from 'eslint';
2
+ export declare const noExperimentalImports: Rule.RuleModule;
@@ -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
@@ -1,6 +1,6 @@
1
1
  import globals from 'globals';
2
2
  export const languageOptions = {
3
- ecmaVersion: 6,
3
+ ecmaVersion: 2020,
4
4
  sourceType: 'module',
5
5
  globals: {
6
6
  ...globals.worker,
@@ -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
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hubspot/eslint-config-ui-extensions",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "description": "eslint plugins for HubSpot React-based UI Extensions",
6
6
  "keywords": [