@hubspot/eslint-config-ui-extensions 0.12.0 → 1.0.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 +166 -23
- package/dist/__tests__/index.spec.d.ts +1 -0
- package/dist/__tests__/index.spec.js +283 -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-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 +154 -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 +72 -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-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 +33 -0
- package/dist/utils/import-utils.js +47 -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
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { RuleTester } from 'eslint';
|
|
2
|
+
import tseslint from 'typescript-eslint';
|
|
3
|
+
import { noBrowserDialogs } from "../../rules/no-browser-dialogs.js";
|
|
4
|
+
import { languageOptions } from "../../runtime.js";
|
|
5
|
+
const ruleTester = new RuleTester({
|
|
6
|
+
languageOptions: {
|
|
7
|
+
...languageOptions,
|
|
8
|
+
parserOptions: {
|
|
9
|
+
ecmaVersion: 'latest',
|
|
10
|
+
sourceType: 'module',
|
|
11
|
+
ecmaFeatures: {
|
|
12
|
+
jsx: true,
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
// Separate tester for TypeScript code
|
|
18
|
+
const tsRuleTester = new RuleTester({
|
|
19
|
+
languageOptions: {
|
|
20
|
+
...languageOptions,
|
|
21
|
+
parser: tseslint.parser,
|
|
22
|
+
parserOptions: {
|
|
23
|
+
ecmaVersion: 'latest',
|
|
24
|
+
sourceType: 'module',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
describe('no-browser-dialogs', () => {
|
|
29
|
+
// Main test suite with JavaScript code
|
|
30
|
+
ruleTester.run('no-browser-dialogs', noBrowserDialogs, {
|
|
31
|
+
valid: [
|
|
32
|
+
{
|
|
33
|
+
name: 'allows dialog methods on other objects',
|
|
34
|
+
code: `
|
|
35
|
+
const custom = { alert: () => {}, confirm: () => {}, prompt: () => {} };
|
|
36
|
+
custom.alert('msg');
|
|
37
|
+
custom.confirm('msg');
|
|
38
|
+
custom.prompt('msg');
|
|
39
|
+
`,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'allows dialog methods as function parameters',
|
|
43
|
+
code: `
|
|
44
|
+
function process(alert, confirm, prompt) {
|
|
45
|
+
alert('msg');
|
|
46
|
+
confirm('msg');
|
|
47
|
+
prompt('msg');
|
|
48
|
+
}
|
|
49
|
+
`,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'allows locally declared dialog functions',
|
|
53
|
+
code: `
|
|
54
|
+
const alert = (msg) => actions.addAlert(msg);
|
|
55
|
+
const confirm = (msg) => showModal(msg);
|
|
56
|
+
const prompt = (msg) => showInputModal(msg);
|
|
57
|
+
alert('msg');
|
|
58
|
+
confirm('msg');
|
|
59
|
+
prompt('msg');
|
|
60
|
+
`,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'allows destructured dialog methods from objects',
|
|
64
|
+
code: `
|
|
65
|
+
const { alert, confirm, prompt } = customDialogs;
|
|
66
|
+
alert('msg');
|
|
67
|
+
confirm('msg');
|
|
68
|
+
prompt('msg');
|
|
69
|
+
`,
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'ignores dialog methods in comments and strings',
|
|
73
|
+
code: `
|
|
74
|
+
// Use alert() to show messages
|
|
75
|
+
const msg = 'Use confirm() for decisions';
|
|
76
|
+
/* prompt() gets user input */
|
|
77
|
+
actions.addAlert('Hello');
|
|
78
|
+
`,
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
invalid: [
|
|
82
|
+
{
|
|
83
|
+
name: 'reports errors for global calls to dialog APIs',
|
|
84
|
+
code: `
|
|
85
|
+
alert('Attention');
|
|
86
|
+
confirm('Are you sure?');
|
|
87
|
+
prompt('Enter your name:');
|
|
88
|
+
`,
|
|
89
|
+
errors: [
|
|
90
|
+
{
|
|
91
|
+
messageId: 'nativeAlert',
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
messageId: 'nativeConfirm',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
messageId: 'nativePrompt',
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: 'reports errors for window calls to dialog APIs',
|
|
103
|
+
code: `
|
|
104
|
+
window.alert('Attention');
|
|
105
|
+
window['confirm']('Are you sure?');
|
|
106
|
+
window.prompt('Enter your name:');
|
|
107
|
+
|
|
108
|
+
`,
|
|
109
|
+
errors: [
|
|
110
|
+
{
|
|
111
|
+
messageId: 'nativeAlert',
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
messageId: 'nativeConfirm',
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
messageId: 'nativePrompt',
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
name: 'reports errors for globalThis calls to dialog APIs',
|
|
123
|
+
code: `
|
|
124
|
+
globalThis['alert']('Attention');
|
|
125
|
+
globalThis.confirm('Are you sure?');
|
|
126
|
+
globalThis['prompt']('Enter your name:');
|
|
127
|
+
`,
|
|
128
|
+
errors: [
|
|
129
|
+
{
|
|
130
|
+
messageId: 'nativeAlert',
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
messageId: 'nativeConfirm',
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
messageId: 'nativePrompt',
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: 'reports errors for self calls to dialog APIs',
|
|
142
|
+
code: `
|
|
143
|
+
self.alert('Attention');
|
|
144
|
+
self['confirm']('Are you sure?');
|
|
145
|
+
self.prompt('Enter your name:');
|
|
146
|
+
`,
|
|
147
|
+
errors: [
|
|
148
|
+
{
|
|
149
|
+
messageId: 'nativeAlert',
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
messageId: 'nativeConfirm',
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
messageId: 'nativePrompt',
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
name: 'reports error in React component with JSX',
|
|
161
|
+
code: `
|
|
162
|
+
import { Text } from '@hubspot/ui-extensions';
|
|
163
|
+
function Component() {
|
|
164
|
+
alert('Error');
|
|
165
|
+
return <Text>Hello</Text>;
|
|
166
|
+
}
|
|
167
|
+
`,
|
|
168
|
+
errors: [{ messageId: 'nativeAlert' }],
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
name: 'reports error in async, arrow, and class method contexts',
|
|
172
|
+
code: `
|
|
173
|
+
async function test1() { alert('async'); }
|
|
174
|
+
const test2 = () => confirm('arrow');
|
|
175
|
+
class Test { method() { prompt('class'); } }
|
|
176
|
+
`,
|
|
177
|
+
errors: [
|
|
178
|
+
{ messageId: 'nativeAlert' },
|
|
179
|
+
{ messageId: 'nativeConfirm' },
|
|
180
|
+
{ messageId: 'nativePrompt' },
|
|
181
|
+
],
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
name: 'reports error in try/catch and nested scopes',
|
|
185
|
+
code: `
|
|
186
|
+
try { alert('error'); } catch (e) { confirm('caught'); }
|
|
187
|
+
function outer() { function inner() { prompt('nested'); } }
|
|
188
|
+
`,
|
|
189
|
+
errors: [
|
|
190
|
+
{ messageId: 'nativeAlert' },
|
|
191
|
+
{ messageId: 'nativeConfirm' },
|
|
192
|
+
{ messageId: 'nativePrompt' },
|
|
193
|
+
],
|
|
194
|
+
},
|
|
195
|
+
],
|
|
196
|
+
});
|
|
197
|
+
// TypeScript-specific tests (requires TypeScript parser)
|
|
198
|
+
tsRuleTester.run('no-browser-dialogs (TypeScript)', noBrowserDialogs, {
|
|
199
|
+
valid: [
|
|
200
|
+
{
|
|
201
|
+
name: 'allows dialog types in type alias',
|
|
202
|
+
code: `
|
|
203
|
+
type AlertType = typeof alert;
|
|
204
|
+
type ConfirmType = typeof confirm;
|
|
205
|
+
type PromptType = typeof prompt;
|
|
206
|
+
`,
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
name: 'allows dialog types in interface property',
|
|
210
|
+
code: `
|
|
211
|
+
interface TestClient {
|
|
212
|
+
alert: typeof alert;
|
|
213
|
+
confirm: typeof confirm;
|
|
214
|
+
prompt: typeof prompt;
|
|
215
|
+
}
|
|
216
|
+
`,
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
name: 'allows dialog types in function return type',
|
|
220
|
+
code: `
|
|
221
|
+
function getRequestFn(): typeof alert {
|
|
222
|
+
return action.addAlert;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function getConfirmFn(): typeof confirm {
|
|
226
|
+
return action.addConfirm;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function getPromptFn(): typeof prompt {
|
|
230
|
+
return action.addPrompt;
|
|
231
|
+
}
|
|
232
|
+
`,
|
|
233
|
+
},
|
|
234
|
+
],
|
|
235
|
+
invalid: [
|
|
236
|
+
{
|
|
237
|
+
name: 'reports error for dialog calls even after type declarations',
|
|
238
|
+
code: `
|
|
239
|
+
type AlertType = typeof alert;
|
|
240
|
+
type ConfirmType = typeof confirm;
|
|
241
|
+
type PromptType = typeof prompt;
|
|
242
|
+
|
|
243
|
+
function getData() {
|
|
244
|
+
alert('Attention');
|
|
245
|
+
confirm('Are you sure?');
|
|
246
|
+
prompt('Enter your name:');
|
|
247
|
+
}
|
|
248
|
+
`,
|
|
249
|
+
errors: [
|
|
250
|
+
{
|
|
251
|
+
messageId: 'nativeAlert',
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
messageId: 'nativeConfirm',
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
messageId: 'nativePrompt',
|
|
258
|
+
},
|
|
259
|
+
],
|
|
260
|
+
},
|
|
261
|
+
],
|
|
262
|
+
});
|
|
263
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
import { RuleTester } from 'eslint';
|
|
2
|
+
import tseslint from 'typescript-eslint';
|
|
3
|
+
import { noBrowserStorage } from "../../rules/no-browser-storage.js";
|
|
4
|
+
import { languageOptions } from "../../runtime.js";
|
|
5
|
+
const ruleTester = new RuleTester({
|
|
6
|
+
languageOptions: {
|
|
7
|
+
...languageOptions,
|
|
8
|
+
parserOptions: {
|
|
9
|
+
ecmaVersion: 'latest',
|
|
10
|
+
sourceType: 'module',
|
|
11
|
+
ecmaFeatures: {
|
|
12
|
+
jsx: true,
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
// Separate tester for TypeScript code
|
|
18
|
+
const tsRuleTester = new RuleTester({
|
|
19
|
+
languageOptions: {
|
|
20
|
+
...languageOptions,
|
|
21
|
+
parser: tseslint.parser,
|
|
22
|
+
parserOptions: {
|
|
23
|
+
ecmaVersion: 'latest',
|
|
24
|
+
sourceType: 'module',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
describe('no-browser-storage', () => {
|
|
29
|
+
// Main test suite with JavaScript code
|
|
30
|
+
ruleTester.run('no-browser-storage', noBrowserStorage, {
|
|
31
|
+
valid: [
|
|
32
|
+
{
|
|
33
|
+
name: 'allows localStorage as a local variable',
|
|
34
|
+
code: `
|
|
35
|
+
const localStorage = { getItem: () => null };
|
|
36
|
+
localStorage.getItem('key');
|
|
37
|
+
`,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'allows sessionStorage as a local variable',
|
|
41
|
+
code: `
|
|
42
|
+
const sessionStorage = { setItem: () => {} };
|
|
43
|
+
sessionStorage.setItem('key', 'value');
|
|
44
|
+
`,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'allows localStorage as a function parameter',
|
|
48
|
+
code: `
|
|
49
|
+
function save(localStorage) {
|
|
50
|
+
return localStorage.getItem('data');
|
|
51
|
+
}
|
|
52
|
+
`,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: 'allows sessionStorage as a function parameter',
|
|
56
|
+
code: `
|
|
57
|
+
function load(sessionStorage) {
|
|
58
|
+
return sessionStorage.getItem('data');
|
|
59
|
+
}
|
|
60
|
+
`,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'allows localStorage as a destructured property',
|
|
64
|
+
code: `
|
|
65
|
+
const { localStorage } = customStorage;
|
|
66
|
+
localStorage.getItem('key');
|
|
67
|
+
`,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: 'allows sessionStorage as a destructured property',
|
|
71
|
+
code: `
|
|
72
|
+
const { sessionStorage } = customStorage;
|
|
73
|
+
sessionStorage.setItem('key', 'value');
|
|
74
|
+
`,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: 'allows localStorage property on other objects',
|
|
78
|
+
code: `
|
|
79
|
+
const storage = { localStorage: {} };
|
|
80
|
+
storage.localStorage.getItem('key');
|
|
81
|
+
`,
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: 'allows sessionStorage property on other objects',
|
|
85
|
+
code: `
|
|
86
|
+
const storage = { sessionStorage: {} };
|
|
87
|
+
storage.sessionStorage.setItem('key', 'value');
|
|
88
|
+
`,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'allows string "localStorage" and "sessionStorage" in strings, comments, etc.',
|
|
92
|
+
code: `
|
|
93
|
+
const key = 'localStorage.data';
|
|
94
|
+
// Check sessionStorage for changes
|
|
95
|
+
`,
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
invalid: [
|
|
99
|
+
{
|
|
100
|
+
name: 'reports error for localStorage method calls',
|
|
101
|
+
code: `
|
|
102
|
+
localStorage.getItem('key');
|
|
103
|
+
localStorage.setItem('key', 'value');
|
|
104
|
+
localStorage.removeItem('key');
|
|
105
|
+
localStorage.clear();
|
|
106
|
+
`,
|
|
107
|
+
errors: [
|
|
108
|
+
{ messageId: 'noBrowserStorage' },
|
|
109
|
+
{ messageId: 'noBrowserStorage' },
|
|
110
|
+
{ messageId: 'noBrowserStorage' },
|
|
111
|
+
{ messageId: 'noBrowserStorage' },
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: 'reports error for sessionStorage method calls',
|
|
116
|
+
code: `
|
|
117
|
+
sessionStorage.getItem('key');
|
|
118
|
+
sessionStorage.setItem('key', 'value');
|
|
119
|
+
sessionStorage.removeItem('key');
|
|
120
|
+
sessionStorage.clear();
|
|
121
|
+
`,
|
|
122
|
+
errors: [
|
|
123
|
+
{ messageId: 'noBrowserStorage' },
|
|
124
|
+
{ messageId: 'noBrowserStorage' },
|
|
125
|
+
{ messageId: 'noBrowserStorage' },
|
|
126
|
+
{ messageId: 'noBrowserStorage' },
|
|
127
|
+
],
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: 'reports error for localStorage property access',
|
|
131
|
+
code: `
|
|
132
|
+
const length = localStorage.length;
|
|
133
|
+
const key = localStorage.key(0);
|
|
134
|
+
`,
|
|
135
|
+
errors: [
|
|
136
|
+
{ messageId: 'noBrowserStorage' },
|
|
137
|
+
{ messageId: 'noBrowserStorage' },
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: 'reports error for sessionStorage property access',
|
|
142
|
+
code: `
|
|
143
|
+
const length = sessionStorage.length;
|
|
144
|
+
const key = sessionStorage.key(0);
|
|
145
|
+
`,
|
|
146
|
+
errors: [
|
|
147
|
+
{ messageId: 'noBrowserStorage' },
|
|
148
|
+
{ messageId: 'noBrowserStorage' },
|
|
149
|
+
],
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
name: 'reports error for localStorage reference assignment',
|
|
153
|
+
code: `
|
|
154
|
+
const storage = localStorage;
|
|
155
|
+
storage.setItem('key', 'value');
|
|
156
|
+
`,
|
|
157
|
+
errors: [
|
|
158
|
+
{
|
|
159
|
+
messageId: 'noBrowserStorage',
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
name: 'reports error for sessionStorage reference assignment',
|
|
165
|
+
code: `
|
|
166
|
+
const storage = sessionStorage;
|
|
167
|
+
storage.getItem('key');
|
|
168
|
+
`,
|
|
169
|
+
errors: [
|
|
170
|
+
{
|
|
171
|
+
messageId: 'noBrowserStorage',
|
|
172
|
+
},
|
|
173
|
+
],
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
name: 'reports error for window.localStorage access',
|
|
177
|
+
code: `
|
|
178
|
+
window.localStorage.setItem('key', 'value');
|
|
179
|
+
window.localStorage.getItem('key');
|
|
180
|
+
window['localStorage'].clear();
|
|
181
|
+
`,
|
|
182
|
+
errors: [
|
|
183
|
+
{ messageId: 'noBrowserStorage' },
|
|
184
|
+
{ messageId: 'noBrowserStorage' },
|
|
185
|
+
{ messageId: 'noBrowserStorage' },
|
|
186
|
+
],
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: 'reports error for window.sessionStorage access',
|
|
190
|
+
code: `
|
|
191
|
+
window.sessionStorage.setItem('key', 'value');
|
|
192
|
+
window.sessionStorage.getItem('key');
|
|
193
|
+
window['sessionStorage'].clear();
|
|
194
|
+
`,
|
|
195
|
+
errors: [
|
|
196
|
+
{ messageId: 'noBrowserStorage' },
|
|
197
|
+
{ messageId: 'noBrowserStorage' },
|
|
198
|
+
{ messageId: 'noBrowserStorage' },
|
|
199
|
+
],
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
name: 'reports error for globalThis.localStorage access',
|
|
203
|
+
code: `
|
|
204
|
+
globalThis.localStorage.setItem('key', 'value');
|
|
205
|
+
globalThis['localStorage'].getItem('key');
|
|
206
|
+
`,
|
|
207
|
+
errors: [
|
|
208
|
+
{ messageId: 'noBrowserStorage' },
|
|
209
|
+
{ messageId: 'noBrowserStorage' },
|
|
210
|
+
],
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
name: 'reports error for globalThis.sessionStorage access',
|
|
214
|
+
code: `
|
|
215
|
+
globalThis.sessionStorage.setItem('key', 'value');
|
|
216
|
+
globalThis['sessionStorage'].getItem('key');
|
|
217
|
+
`,
|
|
218
|
+
errors: [
|
|
219
|
+
{ messageId: 'noBrowserStorage' },
|
|
220
|
+
{ messageId: 'noBrowserStorage' },
|
|
221
|
+
],
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
name: 'reports error for self.localStorage access',
|
|
225
|
+
code: `
|
|
226
|
+
self.localStorage.setItem('key', 'value');
|
|
227
|
+
self['localStorage'].getItem('key');
|
|
228
|
+
`,
|
|
229
|
+
errors: [
|
|
230
|
+
{ messageId: 'noBrowserStorage' },
|
|
231
|
+
{ messageId: 'noBrowserStorage' },
|
|
232
|
+
],
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
name: 'reports error for self.sessionStorage access',
|
|
236
|
+
code: `
|
|
237
|
+
self.sessionStorage.setItem('key', 'value');
|
|
238
|
+
self['sessionStorage'].getItem('key');
|
|
239
|
+
`,
|
|
240
|
+
errors: [
|
|
241
|
+
{ messageId: 'noBrowserStorage' },
|
|
242
|
+
{ messageId: 'noBrowserStorage' },
|
|
243
|
+
],
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
name: 'reports error in React component with JSX',
|
|
247
|
+
code: `
|
|
248
|
+
import { Text } from '@hubspot/ui-extensions';
|
|
249
|
+
function Component() {
|
|
250
|
+
const data = localStorage.getItem('key');
|
|
251
|
+
return <Text>Hello</Text>;
|
|
252
|
+
}
|
|
253
|
+
`,
|
|
254
|
+
errors: [{ messageId: 'noBrowserStorage' }],
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
name: 'reports error in async, arrow, and class method contexts',
|
|
258
|
+
code: `
|
|
259
|
+
async function test1() { localStorage.getItem('key'); }
|
|
260
|
+
const test2 = () => sessionStorage.setItem('key', 'value');
|
|
261
|
+
class Test { method() { return localStorage.length; } }
|
|
262
|
+
`,
|
|
263
|
+
errors: [
|
|
264
|
+
{ messageId: 'noBrowserStorage' },
|
|
265
|
+
{ messageId: 'noBrowserStorage' },
|
|
266
|
+
{ messageId: 'noBrowserStorage' },
|
|
267
|
+
],
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
name: 'reports error in try/catch and nested scopes',
|
|
271
|
+
code: `
|
|
272
|
+
try {
|
|
273
|
+
localStorage.setItem('key', 'value');
|
|
274
|
+
} catch (e) {
|
|
275
|
+
console.log(e);
|
|
276
|
+
}
|
|
277
|
+
function outer() {
|
|
278
|
+
function inner() {
|
|
279
|
+
return sessionStorage.getItem('key');
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
`,
|
|
283
|
+
errors: [
|
|
284
|
+
{ messageId: 'noBrowserStorage' },
|
|
285
|
+
{ messageId: 'noBrowserStorage' },
|
|
286
|
+
],
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
name: 'reports error for chained property access',
|
|
290
|
+
code: `
|
|
291
|
+
const firstKey = localStorage.key(0);
|
|
292
|
+
const value = sessionStorage.getItem(firstKey);
|
|
293
|
+
`,
|
|
294
|
+
errors: [
|
|
295
|
+
{
|
|
296
|
+
messageId: 'noBrowserStorage',
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
messageId: 'noBrowserStorage',
|
|
300
|
+
},
|
|
301
|
+
],
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
name: 'reports error for storage in conditional',
|
|
305
|
+
code: `
|
|
306
|
+
if (localStorage.length > 0) {
|
|
307
|
+
sessionStorage.clear();
|
|
308
|
+
}
|
|
309
|
+
`,
|
|
310
|
+
errors: [
|
|
311
|
+
{
|
|
312
|
+
messageId: 'noBrowserStorage',
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
messageId: 'noBrowserStorage',
|
|
316
|
+
},
|
|
317
|
+
],
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
name: 'reports errors for mixed localStorage and sessionStorage usage',
|
|
321
|
+
code: `
|
|
322
|
+
const data = localStorage.getItem('key');
|
|
323
|
+
sessionStorage.setItem('other', data);
|
|
324
|
+
window.localStorage.clear();
|
|
325
|
+
self.sessionStorage.clear();
|
|
326
|
+
`,
|
|
327
|
+
errors: [
|
|
328
|
+
{ messageId: 'noBrowserStorage' },
|
|
329
|
+
{ messageId: 'noBrowserStorage' },
|
|
330
|
+
{ messageId: 'noBrowserStorage' },
|
|
331
|
+
{ messageId: 'noBrowserStorage' },
|
|
332
|
+
],
|
|
333
|
+
},
|
|
334
|
+
],
|
|
335
|
+
});
|
|
336
|
+
// TypeScript-specific tests (requires TypeScript parser)
|
|
337
|
+
tsRuleTester.run('no-browser-storage (TypeScript)', noBrowserStorage, {
|
|
338
|
+
valid: [
|
|
339
|
+
{
|
|
340
|
+
name: 'allows localStorage type in type alias',
|
|
341
|
+
code: `
|
|
342
|
+
type LocalStorageType = typeof localStorage;
|
|
343
|
+
`,
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
name: 'allows sessionStorage type in type alias',
|
|
347
|
+
code: `
|
|
348
|
+
type SessionStorageType = typeof sessionStorage;
|
|
349
|
+
`,
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
name: 'allows storage types in interface property',
|
|
353
|
+
code: `
|
|
354
|
+
interface StorageManager {
|
|
355
|
+
local: typeof localStorage;
|
|
356
|
+
session: typeof sessionStorage;
|
|
357
|
+
}
|
|
358
|
+
`,
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
name: 'allows storage types in function return type',
|
|
362
|
+
code: `
|
|
363
|
+
function getLocalStorage(): typeof localStorage {
|
|
364
|
+
return {} as any;
|
|
365
|
+
}
|
|
366
|
+
function getSessionStorage(): typeof sessionStorage {
|
|
367
|
+
return {} as any;
|
|
368
|
+
}
|
|
369
|
+
`,
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
name: 'allows Storage type references',
|
|
373
|
+
code: `
|
|
374
|
+
interface CustomStorage extends Storage {
|
|
375
|
+
customMethod(): void;
|
|
376
|
+
}
|
|
377
|
+
`,
|
|
378
|
+
},
|
|
379
|
+
],
|
|
380
|
+
invalid: [
|
|
381
|
+
{
|
|
382
|
+
name: 'reports error for localStorage usage even after type declarations',
|
|
383
|
+
code: `
|
|
384
|
+
type LocalStorageType = typeof localStorage;
|
|
385
|
+
|
|
386
|
+
function getData() {
|
|
387
|
+
const data = localStorage.getItem('key');
|
|
388
|
+
}
|
|
389
|
+
`,
|
|
390
|
+
errors: [
|
|
391
|
+
{
|
|
392
|
+
messageId: 'noBrowserStorage',
|
|
393
|
+
},
|
|
394
|
+
],
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
name: 'reports error for sessionStorage usage even after type declarations',
|
|
398
|
+
code: `
|
|
399
|
+
type SessionStorageType = typeof sessionStorage;
|
|
400
|
+
|
|
401
|
+
function setData() {
|
|
402
|
+
sessionStorage.setItem('key', 'value');
|
|
403
|
+
}
|
|
404
|
+
`,
|
|
405
|
+
errors: [
|
|
406
|
+
{
|
|
407
|
+
messageId: 'noBrowserStorage',
|
|
408
|
+
},
|
|
409
|
+
],
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
name: 'reports error for storage in TypeScript with explicit types',
|
|
413
|
+
code: `
|
|
414
|
+
const data: string | null = localStorage.getItem('key');
|
|
415
|
+
const length: number = sessionStorage.length;
|
|
416
|
+
`,
|
|
417
|
+
errors: [
|
|
418
|
+
{
|
|
419
|
+
messageId: 'noBrowserStorage',
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
messageId: 'noBrowserStorage',
|
|
423
|
+
},
|
|
424
|
+
],
|
|
425
|
+
},
|
|
426
|
+
],
|
|
427
|
+
});
|
|
428
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|