biome-plugin-suitescript 0.5.0 → 0.6.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 +78 -72
- package/biome-manifest.json +24 -1
- package/dist/rules/noHardcodedSecrets.js +2 -1
- package/dist/rules/noLogModule.js +3 -2
- package/dist/rules/noMissingModules.js +1 -1
- package/dist/rules/noModuleScopeApiCalls.js +6 -2
- package/dist/rules/noSecretKeyGuidAndSecret.js +3 -1
- package/dist/rules/noServerConsole.js +30 -0
- package/dist/rules/noSuiteQLTypeInSearch.js +2 -2
- package/dist/rules/noUnsupportedServerWidgetFieldType.js +2 -1
- package/dist/rules/noUtilModule.js +19 -0
- package/dist/rules/noWeakHash.js +4 -3
- package/dist/rules/preferUtilEach.js +42 -0
- package/dist/rules/preferUtilExtend.js +26 -0
- package/dist/rules/preferUtilTypeChecks.js +110 -0
- package/dist/rules/requireLogModule.js +1 -1
- package/dist/rules/requireServerWidgetOptions.js +1 -1
- package/dist/rules/requireUtilModule.js +26 -0
- package/package.json +16 -7
package/README.md
CHANGED
|
@@ -64,78 +64,84 @@ decided is in [docs/rule-priority.md](docs/rule-priority.md).
|
|
|
64
64
|
|
|
65
65
|
<!-- rules-table:start -->
|
|
66
66
|
|
|
67
|
-
| Rule | Severity | Presets | Description | Rule source
|
|
68
|
-
| -------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
69
|
-
| [`noBrowserGlobals`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noBrowserGlobals.md) | error | :green_circle::large_blue_circle: | No `document`, `window`, `localStorage`, `XMLHttpRequest`, `alert`, `navigator`, `URLSearchParams` or `fetch` in server scripts | Tier 1: [SAFE guide](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-sdf-safe-guide/SKILL.md) · Tier 3: [Oracle help: FAQ](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/chapter_N3917921.html)
|
|
70
|
-
| [`noGlobalShadowing`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noGlobalShadowing.md) | error | :green_circle::large_blue_circle: | No variable named `log` or `util` other than the N/log, N/util module itself | Tier 1: [upgrade skill](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-suitescript-upgrade/SKILL.md)
|
|
71
|
-
| [`noMissingModules`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noMissingModules.md) | error | :green_circle::large_blue_circle: | A module used (`record.load()`) must be loaded in `define`/`import` (`log` and `util` are globals) | Tier 0: [ADR 0006](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/adr/0006-
|
|
72
|
-
| [`noModuleScopeApiCalls`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noModuleScopeApiCalls.md) | error | :green_circle::large_blue_circle: | No `N/` API calls while the `define` callback runs; initialize from entry points (calls that only build an object, `createColumn`, `createFilter`, `createSort` and `createSetting`, are skipped) | Tier 1: [SAFE guide](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-sdf-safe-guide/SKILL.md)
|
|
73
|
-
| [`noNativeIterators`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noNativeIterators.md) | error | :green_circle::large_blue_circle: | No generator functions or `Symbol.iterator` (GraalJS) | Tier 1: [SAFE guide](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-sdf-safe-guide/SKILL.md)
|
|
74
|
-
| [`noSuiteQLTypeInSearch`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noSuiteQLTypeInSearch.md) | error | :green_circle::large_blue_circle: | No SuiteQL abbreviation (`PurchOrd`) as the `type` of `search.create` | Tier 1: [SAFE guide](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-sdf-safe-guide/SKILL.md)
|
|
75
|
-
| [`noSuiteScript1Api`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noSuiteScript1Api.md) | error | :green_circle::large_blue_circle: | No `nlapi*`/`nlobj*` or 1.0-only methods (`getFieldValue`, `getLineItemValue`, `e.getCode()`) | Tier 1: [upgrade skill](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-suitescript-upgrade/SKILL.md)
|
|
76
|
-
| [`noHardcodedSecrets`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noHardcodedSecrets.md) | error | :key: | No string literal assigned to `password`, `secret`, `apiKey`, .... **Opt-in, in no preset: it guesses from names, so it can misfire on look-alike names** | Tier 0: [ADR 0005](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/adr/0005-guessing-rules-are-in-no-preset.md) · Tier 1: [OWASP skill](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-owasp-secure-coding/SKILL.md) · Tier 3: [Oracle help: security considerations](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/chapter_159804448843.html)
|
|
77
|
-
| [`noDynamicRequire`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noDynamicRequire.md) | warn | :green_circle::large_blue_circle: | `require()` takes string literal module names, not expressions | Tier 1: [OWASP skill](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-owasp-secure-coding/SKILL.md) · Tier 4: [amd `no-dynamic-require`](https://github.com/acdvs/eslint-plugin-amd/blob/main/lib/rules/no-dynamic-require.ts)
|
|
78
|
-
| [`noDynamicSuiteQL`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noDynamicSuiteQL.md) | warn | :green_circle::large_blue_circle: | `runSuiteQL` queries use `?` placeholders, not concatenation or template literals (calls that pass `params` are skipped) | Tier 1: [OWASP skill](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-owasp-secure-coding/SKILL.md)
|
|
79
|
-
| [`noInnerHtml`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noInnerHtml.md) | warn | :green_circle::large_blue_circle: | No non-static value assigned to `innerHTML`/`outerHTML` or passed to `insertAdjacentHTML`/`document.write` (client scripts) | Tier 1: [OWASP skill](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-owasp-secure-coding/SKILL.md)
|
|
80
|
-
| [`noPostMessageWildcard`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noPostMessageWildcard.md) | warn | :green_circle::large_blue_circle: | No `'*'` target origin in `postMessage` | Tier 1: [OWASP skill](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-owasp-secure-coding/SKILL.md)
|
|
81
|
-
| [`noWildcardCors`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noWildcardCors.md) | warn | :green_circle::large_blue_circle: | No `Access-Control-Allow-Origin: *` response header | Tier 1: [OWASP skill](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-owasp-secure-coding/SKILL.md)
|
|
82
|
-
| [`requireEachReturn`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/requireEachReturn.md) | warn | :green_circle::large_blue_circle: | A `.run().each` or `.iterator().each` callback returns a value (`true` to continue) | Tier 1: [upgrade skill](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-suitescript-upgrade/SKILL.md), [learning skill](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-suitescript-learning/SKILL.md)
|
|
83
|
-
| [`noRecordInLoop`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noRecordInLoop.md) | warn | :large_blue_circle: | No `record.load`, `query.runSuiteQL` or `search.create` inside loops or `forEach`/`each`/`map` callbacks | Tier 1: [SAFE guide](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-sdf-safe-guide/SKILL.md)
|
|
84
|
-
| [`
|
|
85
|
-
| [`
|
|
86
|
-
| [`
|
|
87
|
-
| [`
|
|
88
|
-
| [`
|
|
89
|
-
| [`
|
|
90
|
-
| [`
|
|
91
|
-
| [`
|
|
92
|
-
| [`
|
|
93
|
-
| [`
|
|
94
|
-
| [`
|
|
95
|
-
| [`
|
|
96
|
-
| [`
|
|
97
|
-
| [`
|
|
98
|
-
| [`
|
|
99
|
-
| [`
|
|
100
|
-
| [`
|
|
101
|
-
| [`
|
|
102
|
-
| [`
|
|
103
|
-
| [`
|
|
104
|
-
| [`
|
|
105
|
-
| [`
|
|
106
|
-
| [`
|
|
107
|
-
| [`
|
|
108
|
-
| [`
|
|
109
|
-
| [`
|
|
110
|
-
| [`
|
|
111
|
-
| [`
|
|
112
|
-
| [`
|
|
113
|
-
| [`
|
|
114
|
-
| [`
|
|
115
|
-
| [`
|
|
116
|
-
| [`
|
|
117
|
-
| [`
|
|
118
|
-
| [`
|
|
119
|
-
| [`
|
|
120
|
-
| [`
|
|
121
|
-
| [`
|
|
122
|
-
| [`
|
|
123
|
-
| [`
|
|
124
|
-
| [`
|
|
125
|
-
| [`
|
|
126
|
-
| [`
|
|
127
|
-
| [`
|
|
128
|
-
| [`
|
|
129
|
-
| [`
|
|
130
|
-
| [`
|
|
131
|
-
| [`
|
|
132
|
-
| `
|
|
133
|
-
| `
|
|
134
|
-
| `
|
|
135
|
-
| `
|
|
136
|
-
| `
|
|
137
|
-
| `
|
|
138
|
-
| `
|
|
67
|
+
| Rule | Severity | Presets | Description | Rule source |
|
|
68
|
+
| -------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
69
|
+
| [`noBrowserGlobals`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noBrowserGlobals.md) | error | :green_circle::large_blue_circle: | No `document`, `window`, `localStorage`, `XMLHttpRequest`, `alert`, `navigator`, `URLSearchParams` or `fetch` in server scripts | Tier 1: [SAFE guide](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-sdf-safe-guide/SKILL.md) · Tier 3: [Oracle help: FAQ](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/chapter_N3917921.html) |
|
|
70
|
+
| [`noGlobalShadowing`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noGlobalShadowing.md) | error | :green_circle::large_blue_circle: | No variable named `log` or `util` other than the N/log, N/util module itself | Tier 1: [upgrade skill](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-suitescript-upgrade/SKILL.md) |
|
|
71
|
+
| [`noMissingModules`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noMissingModules.md) | error | :green_circle::large_blue_circle: | A module used (`record.load()`) must be loaded in `define`/`import` (`log` and `util` are globals) | Tier 0: [ADR 0006](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/adr/0006-suitescript-globals-have-two-opposite-loading-styles.md) · Tier 1: [upgrade skill](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-suitescript-upgrade/SKILL.md), [learning skill](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-suitescript-learning/SKILL.md) |
|
|
72
|
+
| [`noModuleScopeApiCalls`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noModuleScopeApiCalls.md) | error | :green_circle::large_blue_circle: | No `N/` API calls while the `define` callback runs; initialize from entry points (calls that only build an object, `createColumn`, `createFilter`, `createSort` and `createSetting`, are skipped) | Tier 1: [SAFE guide](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-sdf-safe-guide/SKILL.md) |
|
|
73
|
+
| [`noNativeIterators`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noNativeIterators.md) | error | :green_circle::large_blue_circle: | No generator functions or `Symbol.iterator` (GraalJS) | Tier 1: [SAFE guide](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-sdf-safe-guide/SKILL.md) |
|
|
74
|
+
| [`noSuiteQLTypeInSearch`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noSuiteQLTypeInSearch.md) | error | :green_circle::large_blue_circle: | No SuiteQL abbreviation (`PurchOrd`) as the `type` of `search.create` | Tier 1: [SAFE guide](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-sdf-safe-guide/SKILL.md) |
|
|
75
|
+
| [`noSuiteScript1Api`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noSuiteScript1Api.md) | error | :green_circle::large_blue_circle: | No `nlapi*`/`nlobj*` or 1.0-only methods (`getFieldValue`, `getLineItemValue`, `e.getCode()`) | Tier 1: [upgrade skill](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-suitescript-upgrade/SKILL.md) |
|
|
76
|
+
| [`noHardcodedSecrets`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noHardcodedSecrets.md) | error | :key: | No string literal assigned to `password`, `secret`, `apiKey`, .... **Opt-in, in no preset: it guesses from names, so it can misfire on look-alike names** | Tier 0: [ADR 0005](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/adr/0005-guessing-rules-are-in-no-preset.md) · Tier 1: [OWASP skill](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-owasp-secure-coding/SKILL.md) · Tier 3: [Oracle help: security considerations](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/chapter_159804448843.html) |
|
|
77
|
+
| [`noDynamicRequire`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noDynamicRequire.md) | warn | :green_circle::large_blue_circle: | `require()` takes string literal module names, not expressions | Tier 1: [OWASP skill](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-owasp-secure-coding/SKILL.md) · Tier 4: [amd `no-dynamic-require`](https://github.com/acdvs/eslint-plugin-amd/blob/main/lib/rules/no-dynamic-require.ts) |
|
|
78
|
+
| [`noDynamicSuiteQL`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noDynamicSuiteQL.md) | warn | :green_circle::large_blue_circle: | `runSuiteQL` queries use `?` placeholders, not concatenation or template literals (calls that pass `params` are skipped) | Tier 1: [OWASP skill](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-owasp-secure-coding/SKILL.md) |
|
|
79
|
+
| [`noInnerHtml`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noInnerHtml.md) | warn | :green_circle::large_blue_circle: | No non-static value assigned to `innerHTML`/`outerHTML` or passed to `insertAdjacentHTML`/`document.write` (client scripts) | Tier 1: [OWASP skill](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-owasp-secure-coding/SKILL.md) |
|
|
80
|
+
| [`noPostMessageWildcard`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noPostMessageWildcard.md) | warn | :green_circle::large_blue_circle: | No `'*'` target origin in `postMessage` | Tier 1: [OWASP skill](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-owasp-secure-coding/SKILL.md) |
|
|
81
|
+
| [`noWildcardCors`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noWildcardCors.md) | warn | :green_circle::large_blue_circle: | No `Access-Control-Allow-Origin: *` response header | Tier 1: [OWASP skill](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-owasp-secure-coding/SKILL.md) |
|
|
82
|
+
| [`requireEachReturn`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/requireEachReturn.md) | warn | :green_circle::large_blue_circle: | A `.run().each` or `.iterator().each` callback returns a value (`true` to continue) | Tier 1: [upgrade skill](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-suitescript-upgrade/SKILL.md), [learning skill](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-suitescript-learning/SKILL.md) |
|
|
83
|
+
| [`noRecordInLoop`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noRecordInLoop.md) | warn | :large_blue_circle: | No `record.load`, `query.runSuiteQL` or `search.create` inside loops or `forEach`/`each`/`map` callbacks | Tier 1: [SAFE guide](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-sdf-safe-guide/SKILL.md) |
|
|
84
|
+
| [`noServerConsole`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noServerConsole.md) | warn | :large_blue_circle: | `console` output goes nowhere in server scripts; use `log.*` (`console` is for client scripts, so Biome's native `noConsole`, which cannot tell them apart, does not fit alongside this rule) | Tier 1: [SAFE guide](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-sdf-safe-guide/quick-reference.md) |
|
|
85
|
+
| [`requireLogModule`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/requireLogModule.md) | warn | :large_blue_circle: | A used `log.*` needs `N/log` loaded (the upgrade skill recommends it; the global needs no loading) | Tier 0: [ADR 0006](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/adr/0006-suitescript-globals-have-two-opposite-loading-styles.md) · Tier 1: [upgrade skill](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-suitescript-upgrade/SKILL.md), [learning skill](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-suitescript-learning/SKILL.md) |
|
|
86
|
+
| [`requireUtilModule`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/requireUtilModule.md) | warn | :large_blue_circle: | A used `util.*` needs `N/util` loaded (the upgrade skill recommends it for `log`; the global needs no loading) | Tier 1: [upgrade skill (conversion guide)](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-suitescript-upgrade/references/conversion-guide.md) |
|
|
87
|
+
| [`noInsecureCookie`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noInsecureCookie.md) | warn | :key: | A literal `Set-Cookie` value has `HttpOnly`, `Secure` and `SameSite`. **Opt-in, in no preset: only literal values are checked, and we have not confirmed a Suitelet may set `Set-Cookie`** | Tier 0: [ADR 0005](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/adr/0005-guessing-rules-are-in-no-preset.md) · Tier 1: [OWASP skill](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-owasp-secure-coding/SKILL.md) |
|
|
88
|
+
| [`noLocationHeader`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noLocationHeader.md) | warn | :key: | No `Location` header built from a variable; use `N/redirect`. **Opt-in, in no preset: the value may already be validated, and the skill's references still set `Location` after sanitising it** | Tier 0: [ADR 0005](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/adr/0005-guessing-rules-are-in-no-preset.md) · Tier 1: [OWASP skill](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-owasp-secure-coding/SKILL.md) |
|
|
89
|
+
| [`noPublicFileUpload`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noPublicFileUpload.md) | warn | :key: | No `isOnline: true` (public file). **Opt-in, in no preset: public assets legitimately set `isOnline`** | Tier 0: [ADR 0005](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/adr/0005-guessing-rules-are-in-no-preset.md) · Tier 1: [OWASP skill: file upload](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-owasp-secure-coding/references/07-file-upload-download.md) |
|
|
90
|
+
| [`noSensitiveBrowserStorage`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noSensitiveBrowserStorage.md) | warn | :key: | No token- or password-named key in `localStorage`/`sessionStorage.setItem`. **Opt-in, in no preset: it guesses from the key name** | Tier 0: [ADR 0005](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/adr/0005-guessing-rules-are-in-no-preset.md) · Tier 1: [OWASP skill](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-owasp-secure-coding/SKILL.md) |
|
|
91
|
+
| [`noStackInResponse`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noStackInResponse.md) | warn | :key: | No `.stack` inside `response.write`/`writeLine` arguments. **Opt-in, in no preset: a stack returned from a RESTlet is not detected, and the skill's own logging example includes `e.stack`** | Tier 0: [ADR 0005](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/adr/0005-guessing-rules-are-in-no-preset.md) · Tier 1: [OWASP skill](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-owasp-secure-coding/SKILL.md) |
|
|
92
|
+
| [`noSuiteQLSelectStar`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noSuiteQLSelectStar.md) | warn | :key: | No `SELECT *` in a literal SuiteQL query. **Opt-in, in no preset: the guide itself recommends a `SELECT * ... ROWNUM <= 1` probe, and only literal queries are seen** | Tier 0: [ADR 0005](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/adr/0005-guessing-rules-are-in-no-preset.md) · Tier 1: [SAFE guide: performance](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-sdf-safe-guide/references/03-performance-optimization.md) |
|
|
93
|
+
| [`noSuiteQLSystemNoteJoin`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noSuiteQLSystemNoteJoin.md) | warn | :key: | No `JOIN SystemNote` in a literal SuiteQL query. **Opt-in, in no preset: a tightly filtered join is fine, and only literal queries are seen** | Tier 0: [ADR 0005](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/adr/0005-guessing-rules-are-in-no-preset.md) · Tier 1: [SAFE guide](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-sdf-safe-guide/SKILL.md) |
|
|
94
|
+
| [`noWeakHash`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noWeakHash.md) | warn | :key: | No `HashAlg.MD5` or `HashAlg.SHA1`. **Opt-in, in no preset: it cannot tell a security use from a vendor integration that requires MD5 or SHA-1, and the type definitions list only `SHA256` and `SHA512` for `crypto.HashAlg`, so the member names come from the skill alone** | Tier 0: [ADR 0005](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/adr/0005-guessing-rules-are-in-no-preset.md) · Tier 1: [OWASP skill](https://github.com/nemchik/biome-plugin-suitescript/blob/main/.agents/skills/netsuite-owasp-secure-coding/SKILL.md) |
|
|
95
|
+
| [`noAuthorizationHeaderInInternalRequest`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noAuthorizationHeaderInInternalRequest.md) | error | :green_circle::large_blue_circle: | No `Authorization` header in `https.requestRestlet`/`requestSuitelet`/`requestSuiteTalkRest` | Tier 2: [headintheclouddev `N/https`](https://github.com/headintheclouddev/typings-suitescript-2.0/blob/master/N/https.d.ts) · Tier 3: [Oracle help: https.requestRestlet](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_159139340774.html) |
|
|
96
|
+
| [`noPositionalOptions`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noPositionalOptions.md) | error | :green_circle::large_blue_circle: | No literal passed where a module function takes an options object (`record.load('customer', 1)`) | Tier 2: [headintheclouddev `N/*`](https://github.com/headintheclouddev/typings-suitescript-2.0/tree/master/N) |
|
|
97
|
+
| [`noSecretKeyGuidAndSecret`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noSecretKeyGuidAndSecret.md) | error | :green_circle::large_blue_circle: | `https.createSecretKey` takes `guid` or `secret`, not both | Tier 2: [headintheclouddev `N/https`](https://github.com/headintheclouddev/typings-suitescript-2.0/blob/master/N/https.d.ts) · Tier 3: [Oracle help: https.createSecretKey](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_4418247967.html) |
|
|
98
|
+
| [`noUnknownModuleMember`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noUnknownModuleMember.md) | error | :green_circle::large_blue_circle: | Only members the type definitions export are read from an `N/` module (`record.loadRecord` is not one) | Tier 2: [headintheclouddev `N/*`](https://github.com/headintheclouddev/typings-suitescript-2.0/tree/master/N) |
|
|
99
|
+
| [`noUnsupportedServerWidgetFieldType`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noUnsupportedServerWidgetFieldType.md) | error | :green_circle::large_blue_circle: | No `DATETIME` in `addField` and no unsupported type in `List.addColumn` | Tier 2: [headintheclouddev `N/ui/serverWidget`](https://github.com/headintheclouddev/typings-suitescript-2.0/blob/master/N/ui/serverWidget.d.ts) · Tier 3: [Oracle help: serverWidget.FieldType](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_4332671056.html) |
|
|
100
|
+
| [`requireOptions`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/requireOptions.md) | error | :green_circle::large_blue_circle: | Every required option of a module function (`record.load`, `search.create`, `email.send`, ...) is present | Tier 2: [headintheclouddev `N/*`](https://github.com/headintheclouddev/typings-suitescript-2.0/tree/master/N) |
|
|
101
|
+
| [`requireQueryOptions`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/requireQueryOptions.md) | error | :green_circle::large_blue_circle: | Required `createColumn`/`createCondition`/`createSort` options (`fieldId` or `formula`, `type` with `formula`, ...) are present | Tier 2: [headintheclouddev `N/query`](https://github.com/headintheclouddev/typings-suitescript-2.0/blob/master/N/query.d.ts) · Tier 3: [Oracle help: Query.createColumn](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_1510780373.html) |
|
|
102
|
+
| [`requireServerResponseOptions`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/requireServerResponseOptions.md) | error | :green_circle::large_blue_circle: | Required `response.setHeader`/`write`/`writeFile`/... options are present | Tier 2: [headintheclouddev `N/http`](https://github.com/headintheclouddev/typings-suitescript-2.0/blob/master/N/http.d.ts) · Tier 3: [Oracle help: ServerResponse](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_4567652689.html) |
|
|
103
|
+
| [`requireServerWidgetOptions`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/requireServerWidgetOptions.md) | error | :green_circle::large_blue_circle: | Required `addField`/`addButton`/`addSublist`/`addColumn`/... options (`id`, `label`, `type`, ...) are present | Tier 2: [headintheclouddev `N/ui/serverWidget`](https://github.com/headintheclouddev/typings-suitescript-2.0/blob/master/N/ui/serverWidget.d.ts) · Tier 3: [Oracle help: N/ui/serverWidget](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_4321345532.html) |
|
|
104
|
+
| [`noInvalidEnumMember`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noInvalidEnumMember.md) | warn | :large_blue_circle: | A member of a module enum (`record.Type.CUSTOMR`) is one the type definitions list | Tier 2: [headintheclouddev `N/*`](https://github.com/headintheclouddev/typings-suitescript-2.0/tree/master/N) |
|
|
105
|
+
| [`noUnknownOptions`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noUnknownOptions.md) | warn | :large_blue_circle: | No option the type definitions do not list for a module function (`record.load({ typ: 'x' })`) | Tier 2: [headintheclouddev `N/*`](https://github.com/headintheclouddev/typings-suitescript-2.0/tree/master/N) |
|
|
106
|
+
| [`entryPoints`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/entryPoints.md) | error | :green_circle::large_blue_circle: | Returned entry points must belong to one script type and include its required ones | Tier 3: [Oracle help: entry point validation](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/chapter_4527904430.html), [Oracle help: validation errors](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_4503568059.html) · Tier 4: [acdvs `entry-points`](https://github.com/acdvs/eslint-plugin-suitescript/blob/master/lib/rules/entry-points.ts), [btonasse `entry-points`](https://github.com/btonasse/eslint-plugin-suitescript/blob/master/lib/rules/entry-points.js) |
|
|
107
|
+
| [`noBeforeSubmitRecordReload`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noBeforeSubmitRecordReload.md) | error | :green_circle::large_blue_circle: | No `record.load`/`submitFields`/`delete` of `context.newRecord.id` inside `beforeSubmit` | Tier 3: [Oracle help: script architecture](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/subsect_0804042112.html) |
|
|
108
|
+
| [`noBlockedResponseHeader`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noBlockedResponseHeader.md) | error | :green_circle::large_blue_circle: | No header NetSuite blocks on a Suitelet response (`Location`, `Server`, `Date`, ...) | Tier 3: [Oracle help: N/https](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_4418229131.html) |
|
|
109
|
+
| [`noConflictingClientScriptProps`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noConflictingClientScriptProps.md) | error | :green_circle::large_blue_circle: | Do not set both `clientScriptFileId` and `clientScriptModulePath` on a form | Tier 3: [Oracle help: Form.clientScriptModulePath](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_4625445350.html) |
|
|
110
|
+
| [`noInsecureHttpsUrl`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noInsecureHttpsUrl.md) | error | :green_circle::large_blue_circle: | No `http://` URL passed to `https.*` (N/https refuses plain HTTP) | Tier 3: [Oracle help: N/https](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_4418229131.html) |
|
|
111
|
+
| [`noMultipleDefine`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noMultipleDefine.md) | error | :green_circle::large_blue_circle: | A script calls `define()` only once | Tier 3: [Oracle help: entry point validation](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/chapter_4527904430.html), [Oracle help: validation errors](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_4503568059.html) |
|
|
112
|
+
| [`noQueryColumnFieldAndFormula`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noQueryColumnFieldAndFormula.md) | error | :green_circle::large_blue_circle: | A query column cannot set both `fieldId` and `formula` | Tier 3: [Oracle help: Column.fieldId](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_1510788976.html), [Oracle help: Column.formula](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_1510789062.html) |
|
|
113
|
+
| [`noRequireInsteadOfDefine`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noRequireInsteadOfDefine.md) | error | :green_circle::large_blue_circle: | A script uses `define()`, not a top-level `require([...], cb)` | Tier 3: [Oracle help: invalid script example](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_4527901694.html) |
|
|
114
|
+
| [`noWrongScriptTypeModules`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noWrongScriptTypeModules.md) | error | :green_circle::large_blue_circle: | No client-only module (`N/currentRecord`, `N/ui/dialog`) in a server script, or a server-only one in a client script | Tier 3: [Oracle help: SuiteScript 2.1 modules](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/chapter_4220488571.html) |
|
|
115
|
+
| [`logRequireTitle`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/logRequireTitle.md) | warn | :green_circle::large_blue_circle: | `log.debug/audit/error/emergency({...})` needs `title` | Tier 0: [ADR 0002](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/adr/0002-options-are-separate-rules.md) · Tier 3: [Oracle help: log.debug](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_4430385329.html) · Tier 4: [acdvs `log-args`](https://github.com/acdvs/eslint-plugin-suitescript/blob/master/lib/rules/log-args.ts), [btonasse `log-args`](https://github.com/btonasse/eslint-plugin-suitescript/blob/master/lib/rules/log-args.js) |
|
|
116
|
+
| [`noBlockedHttpsHeader`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noBlockedHttpsHeader.md) | warn | :green_circle::large_blue_circle: | No header NetSuite discards from an `https.*` request (`Host`, `Content-Length`, ...) | Tier 3: [Oracle help: N/https](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_4418229131.html) |
|
|
117
|
+
| [`noEmptyEntryPoints`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noEmptyEntryPoints.md) | warn | :green_circle::large_blue_circle: | A returned entry point with an empty body | Tier 3: [Oracle help: script architecture](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/subsect_0804042112.html) |
|
|
118
|
+
| [`noHardcodedAuthPasswords`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noHardcodedAuthPasswords.md) | warn | :green_circle::large_blue_circle: | No string literal passed as a password to `auth.changePassword`/`changeEmail` | Tier 3: [Oracle help: security considerations](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/chapter_159804448843.html), [Oracle help: auth.changePassword](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_4298157647.html) |
|
|
119
|
+
| [`noHeaderUnderscores`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noHeaderUnderscores.md) | warn | :green_circle::large_blue_circle: | No underscore in a custom `https.*` header name | Tier 3: [Oracle help: N/https](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_4418229131.html) |
|
|
120
|
+
| [`requireClientValidateReturn`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/requireClientValidateReturn.md) | warn | :green_circle::large_blue_circle: | `saveRecord` and `validate*` return a value (only a body with no `return <value>` is flagged) | Tier 3: [Oracle help: saveRecord](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_4410693749.html), [Oracle help: validateField](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_4410693152.html) |
|
|
121
|
+
| [`requireCustpagePrefix`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/requireCustpagePrefix.md) | warn | :green_circle::large_blue_circle: | The `id` of a UI object added to an existing page (`context.form.addField`, ...) starts with `custpage` | Tier 3: [Oracle help: N/ui/serverWidget](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_4321345532.html) |
|
|
122
|
+
| [`requireOrderByForPagedSuiteQL`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/requireOrderByForPagedSuiteQL.md) | warn | :green_circle::large_blue_circle: | A literal `runSuiteQLPaged` query has an `ORDER BY` | Tier 3: [Oracle help: query.runSuiteQLPaged](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_157960586441.html) |
|
|
123
|
+
| [`noClientLog`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noClientLog.md) | warn | :large_blue_circle: | `log.*` is ignored in client scripts attached to a form; use `console` | Tier 3: [Oracle help: N/log](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_4574548135.html) |
|
|
124
|
+
| [`noMapReduceSearchResults`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noMapReduceSearchResults.md) | warn | :large_blue_circle: | `getInputData` returns the search object, not `getRange()` results | Tier 3: [Oracle help: map/reduce best practices](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_0801064715.html) |
|
|
125
|
+
| [`noLogModule`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noLogModule.md) | warn | :key: | Opt-in style: use the global `log`, not `N/log` (the opposite of `requireLogModule`). **Opt-in, in no preset: the opposite style to `requireLogModule`; enable only one** | Tier 0: [ADR 0005](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/adr/0005-guessing-rules-are-in-no-preset.md), [ADR 0006](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/adr/0006-suitescript-globals-have-two-opposite-loading-styles.md) · Tier 3: [Oracle help: log object](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_4387812838.html) · Tier 4: [acdvs `no-log-module`](https://github.com/acdvs/eslint-plugin-suitescript/blob/master/lib/rules/no-log-module.ts), [btonasse `no-log-module`](https://github.com/btonasse/eslint-plugin-suitescript/blob/master/lib/rules/no-log-module.js) |
|
|
126
|
+
| [`noUtilModule`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noUtilModule.md) | warn | :key: | Opt-in style: use the global `util`, not `N/util` (the opposite of `requireUtilModule`). **Opt-in, in no preset: the opposite style to `requireUtilModule`; enable only one** | Tier 3: [Oracle help: util Object](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_4387813290.html) |
|
|
127
|
+
| [`noExtraParams`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noExtraParams.md) | error | :white_circle::green_circle::large_blue_circle: | More callback parameters than dependencies (the extra parameters are always `undefined`) | Tier 4: [btonasse `no-extra-modules`](https://github.com/btonasse/eslint-plugin-suitescript/blob/master/lib/rules/no-extra-modules.js), [amd `no-extra-params`](https://github.com/acdvs/eslint-plugin-amd/blob/main/lib/rules/no-extra-params.ts) |
|
|
128
|
+
| [`noInvalidModules`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noInvalidModules.md) | error | :white_circle::green_circle::large_blue_circle: | Unknown `N/...` modules | Tier 4: [acdvs `no-invalid-modules`](https://github.com/acdvs/eslint-plugin-suitescript/blob/master/lib/rules/no-invalid-modules.ts), [btonasse `no-invalid-modules`](https://github.com/btonasse/eslint-plugin-suitescript/blob/master/lib/rules/no-invalid-modules.js) |
|
|
129
|
+
| [`noAmdName`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noAmdName.md) | error | :green_circle::large_blue_circle: | No module id as the first `define` argument (fixable) | Tier 4: [btonasse `no-amd-name`](https://github.com/btonasse/eslint-plugin-suitescript/blob/master/lib/rules/no-amd-name.js), [amd `no-name`](https://github.com/acdvs/eslint-plugin-amd/blob/main/lib/rules/no-name.ts) |
|
|
130
|
+
| [`noInvalidDefine`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noInvalidDefine.md) | error | :green_circle::large_blue_circle: | `define()` has one of the valid AMD shapes (a function or object factory, an optional name and dependency array) | Tier 4: [amd `no-invalid-define`](https://github.com/acdvs/eslint-plugin-amd/blob/main/lib/rules/no-invalid-define.ts) |
|
|
131
|
+
| [`noModuleExtensions`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noModuleExtensions.md) | error | :green_circle::large_blue_circle: | No `.js` in `define` dependency names (fixable) | Tier 4: [btonasse `no-module-extensions`](https://github.com/btonasse/eslint-plugin-suitescript/blob/master/lib/rules/no-module-extensions.js), [amd `no-module-extensions`](https://github.com/acdvs/eslint-plugin-amd/blob/main/lib/rules/no-module-extensions.ts) |
|
|
132
|
+
| [`noExtraDeps`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/noExtraDeps.md) | warn | :white_circle::green_circle::large_blue_circle: | More dependencies than callback parameters (the extra modules load but are unused) | Tier 4: [btonasse `no-extra-modules`](https://github.com/btonasse/eslint-plugin-suitescript/blob/master/lib/rules/no-extra-modules.js), [amd `no-extra-deps`](https://github.com/acdvs/eslint-plugin-amd/blob/main/lib/rules/no-extra-deps.ts) |
|
|
133
|
+
| [`moduleVars`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/moduleVars.md) | warn | :green_circle::large_blue_circle: | Conventional identifier per `N/` module (`N/record` → `record`) | Tier 4: [acdvs `module-vars`](https://github.com/acdvs/eslint-plugin-suitescript/blob/master/lib/rules/module-vars.ts), [btonasse `module-vars`](https://github.com/btonasse/eslint-plugin-suitescript/blob/master/lib/rules/module-vars.js) |
|
|
134
|
+
| [`logRequireDetails`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/logRequireDetails.md) | warn | :large_blue_circle: | ...needs `details` (Oracle marks it optional, so this house style is in `all` only) | Tier 0: [ADR 0002](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/adr/0002-options-are-separate-rules.md) · Tier 4: [acdvs `log-args`](https://github.com/acdvs/eslint-plugin-suitescript/blob/master/lib/rules/log-args.ts), [btonasse `log-args`](https://github.com/btonasse/eslint-plugin-suitescript/blob/master/lib/rules/log-args.js) |
|
|
135
|
+
| [`preferUtilEach`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/preferUtilEach.md) | warn | :large_blue_circle: | Use `util.each` instead of `forEach` on an array literal or `Object.keys/values/entries(o)` (a bare `x.forEach` is skipped: `util.each` does nothing for a `Set` or `Map`) | Tier 4: [house preference](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rule-priority.md#source-tiers) · Verified: [util-each](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/verification/results/util-each.md) |
|
|
136
|
+
| [`preferUtilExtend`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/preferUtilExtend.md) | warn | :large_blue_circle: | Use `util.extend(receiver, contributor)` instead of two-argument `Object.assign` (the same behavior, verified) | Tier 4: [house preference](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rule-priority.md#source-tiers) · Verified: [util-extend](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/verification/results/util-extend.md) |
|
|
137
|
+
| [`preferUtilTypeChecks`](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rules/preferUtilTypeChecks.md) | warn | :large_blue_circle: | Use `util.isArray`/`isString`/`isNumber`/`isBoolean`/`isFunction`/`isAsyncFunction`/`isDate`/`isRegExp` instead of the native patterns (`Array.isArray`, `typeof`, `instanceof`, `Object.prototype.toString.call`); `typeof` differs only for boxed values | Tier 4: [house preference](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/rule-priority.md#source-tiers) · Verified: [util-type-checks](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/verification/results/util-type-checks.md), [util-isobject](https://github.com/nemchik/biome-plugin-suitescript/blob/main/docs/verification/results/util-isobject.md) |
|
|
138
|
+
| `noGlobalEval` (Biome) | error | :green_circle::large_blue_circle: | No `eval()` (Oracle's OWASP skill: "Never use `eval`") | Tier 5: [Biome `noGlobalEval`](https://biomejs.dev/linter/rules/no-global-eval/) |
|
|
139
|
+
| `noImpliedEval` (Biome) | error | :green_circle::large_blue_circle: | No `new Function()` or string-form `setTimeout`/`setInterval` (OWASP skill) | Tier 5: [Biome `noImpliedEval`](https://biomejs.dev/linter/rules/no-implied-eval/) |
|
|
140
|
+
| `noVar` (Biome) | warn | :large_blue_circle: | Use `const`/`let`, not `var` (upgrade skill: "Modernize JavaScript") | Tier 5: [Biome `noVar`](https://biomejs.dev/linter/rules/no-var/) |
|
|
141
|
+
| `useConst` (Biome) | warn | :large_blue_circle: | Use `const` for never-reassigned bindings (upgrade skill) | Tier 5: [Biome `useConst`](https://biomejs.dev/linter/rules/use-const/) |
|
|
142
|
+
| `useForOf` (Biome) | warn | :large_blue_circle: | Use `for...of` where the index is only used to read (upgrade skill) | Tier 5: [Biome `useForOf`](https://biomejs.dev/linter/rules/use-for-of/) |
|
|
143
|
+
| `useIncludes` (Biome) | warn | :large_blue_circle: | Use `includes`, not `indexOf` (upgrade skill; needs type information, so it may report nothing) | Tier 5: [Biome `useIncludes`](https://biomejs.dev/linter/rules/use-includes/) |
|
|
144
|
+
| `useTemplate` (Biome) | warn | :large_blue_circle: | Use template literals, not string concatenation (upgrade skill) | Tier 5: [Biome `useTemplate`](https://biomejs.dev/linter/rules/use-template/) |
|
|
139
145
|
|
|
140
146
|
<!-- rules-table:end -->
|
|
141
147
|
|
package/biome-manifest.json
CHANGED
|
@@ -67,9 +67,14 @@
|
|
|
67
67
|
"noUnknownOptions",
|
|
68
68
|
"noInvalidEnumMember",
|
|
69
69
|
"requireLogModule",
|
|
70
|
+
"requireUtilModule",
|
|
71
|
+
"preferUtilTypeChecks",
|
|
72
|
+
"preferUtilExtend",
|
|
73
|
+
"preferUtilEach",
|
|
70
74
|
"noRecordInLoop",
|
|
71
75
|
"noMapReduceSearchResults",
|
|
72
|
-
"noClientLog"
|
|
76
|
+
"noClientLog",
|
|
77
|
+
"noServerConsole"
|
|
73
78
|
],
|
|
74
79
|
"minimal": ["noExtraDeps", "noExtraParams", "noInvalidModules"],
|
|
75
80
|
"recommended": [
|
|
@@ -251,6 +256,9 @@
|
|
|
251
256
|
{
|
|
252
257
|
"noSensitiveBrowserStorage": "./dist/rules/noSensitiveBrowserStorage.js"
|
|
253
258
|
},
|
|
259
|
+
{
|
|
260
|
+
"noServerConsole": "./dist/rules/noServerConsole.js"
|
|
261
|
+
},
|
|
254
262
|
{
|
|
255
263
|
"noStackInResponse": "./dist/rules/noStackInResponse.js"
|
|
256
264
|
},
|
|
@@ -275,6 +283,9 @@
|
|
|
275
283
|
{
|
|
276
284
|
"noUnsupportedServerWidgetFieldType": "./dist/rules/noUnsupportedServerWidgetFieldType.js"
|
|
277
285
|
},
|
|
286
|
+
{
|
|
287
|
+
"noUtilModule": "./dist/rules/noUtilModule.js"
|
|
288
|
+
},
|
|
278
289
|
{
|
|
279
290
|
"noWeakHash": "./dist/rules/noWeakHash.js"
|
|
280
291
|
},
|
|
@@ -284,6 +295,15 @@
|
|
|
284
295
|
{
|
|
285
296
|
"noWrongScriptTypeModules": "./dist/rules/noWrongScriptTypeModules.js"
|
|
286
297
|
},
|
|
298
|
+
{
|
|
299
|
+
"preferUtilEach": "./dist/rules/preferUtilEach.js"
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
"preferUtilExtend": "./dist/rules/preferUtilExtend.js"
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
"preferUtilTypeChecks": "./dist/rules/preferUtilTypeChecks.js"
|
|
306
|
+
},
|
|
287
307
|
{
|
|
288
308
|
"requireClientValidateReturn": "./dist/rules/requireClientValidateReturn.js"
|
|
289
309
|
},
|
|
@@ -310,6 +330,9 @@
|
|
|
310
330
|
},
|
|
311
331
|
{
|
|
312
332
|
"requireServerWidgetOptions": "./dist/rules/requireServerWidgetOptions.js"
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
"requireUtilModule": "./dist/rules/requireUtilModule.js"
|
|
313
336
|
}
|
|
314
337
|
]
|
|
315
338
|
},
|
|
@@ -6,7 +6,8 @@ const unquote = (text) => text.replace(/^['"]|['"]$/g, "");
|
|
|
6
6
|
// its `SKILL.md` shows script parameters as the safe place while its
|
|
7
7
|
// references/02 says script parameters are not a secret store and points to API
|
|
8
8
|
// Secrets. `SKILL.md` outranks the reference; the message follows the reference
|
|
9
|
-
// because it is the more precise advice.
|
|
9
|
+
// because it is the more precise advice (docs/verification/contradictions.md,
|
|
10
|
+
// C-002, not testable). A heuristic on names, so
|
|
10
11
|
// no preset includes it; enable it explicitly. Only exact secret-like names with
|
|
11
12
|
// a non-empty string literal are flagged (not `passwordId` or `tokenName`).
|
|
12
13
|
export const noHardcodedSecrets = defineRule({
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { ast, defineRule, registerDiagnostic } from "@biomejs/runtime/plugin";
|
|
2
2
|
import { dependencies } from "../lib/biome-modules.js";
|
|
3
|
-
// Tier 0: docs/adr/0005-guessing-rules-are-in-no-preset.md, docs/adr/0006-
|
|
3
|
+
// Tier 0: docs/adr/0005-guessing-rules-are-in-no-preset.md, docs/adr/0006-suitescript-globals-have-two-opposite-loading-styles.md.
|
|
4
4
|
// Opt-in style: use the global `log` instead of loading N/log. Oracle's help
|
|
5
5
|
// center says "NetSuite loads the log Object by default for all script types. You
|
|
6
6
|
// do not need to load it manually", which is what this rule follows. It is the
|
|
7
7
|
// opposite of requireLogModule, so enable only one of them. (The upgrade skill
|
|
8
8
|
// says the global exists but loading is recommended, which requireLogModule
|
|
9
|
-
// follows; the learning skill says it is not global and loses
|
|
9
|
+
// follows; the learning skill says it is not global and loses, and a deployed RESTlet
|
|
10
|
+
// confirms the global exists (2026.2): docs/verification/results/globals-log-util.md, C-001.) Logging in a client script attached to a form is
|
|
10
11
|
// ignored either way; see noClientLog.
|
|
11
12
|
export const noLogModule = defineRule({
|
|
12
13
|
query: ast("JS_CALL_EXPRESSION", "JS_IMPORT"),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defineRule, registerDiagnostic, semantic, } from "@biomejs/runtime/plugin";
|
|
2
2
|
import { MODULES } from "../lib/modules.js";
|
|
3
3
|
import { inSuiteScriptModule } from "../lib/suitescript.js";
|
|
4
|
-
// Tier 0: docs/adr/0006-
|
|
4
|
+
// Tier 0: docs/adr/0006-suitescript-globals-have-two-opposite-loading-styles.md.
|
|
5
5
|
// `log` and `util` are globals in SuiteScript 2.1 ("NetSuite loads the log Object
|
|
6
6
|
// by default for all script types. You do not need to load it manually."), so
|
|
7
7
|
// neither needs loading. requireLogModule is the style that wants N/log loaded.
|
|
@@ -12,8 +12,12 @@ const FACTORIES = /\.(createColumn|createFilter|createSort|createSetting)$/;
|
|
|
12
12
|
// callback execution. Calling `runtime.getCurrentScript()`, `record.load()`, or
|
|
13
13
|
// any N/ API at module scope causes deployment failure." Initialize lazily from an
|
|
14
14
|
// entry point instead. N/log is exempt because log is a global. The failure claim
|
|
15
|
-
// comes only from this skill
|
|
16
|
-
//
|
|
15
|
+
// comes only from this skill. A deployed RESTlet (2026.2) shows it holds for
|
|
16
|
+
// record.create and record.load (the module stops loading), while a search throws a
|
|
17
|
+
// catchable SSS_USAGE_LIMIT_EXCEEDED and runtime.getCurrentScript() and
|
|
18
|
+
// search.createColumn() work: docs/verification/results/module-scope-record-calls.md,
|
|
19
|
+
// module-scope-search-call.md and module-scope-runtime-calls.md, C-011. Narrowing the
|
|
20
|
+
// rule to the calls that fail is issue #86.
|
|
17
21
|
// Calls that only build an object (`search.createColumn`, `createFilter`,
|
|
18
22
|
// `createSort`, `createSetting`) are skipped, because nothing says they fail; remove
|
|
19
23
|
// them from FACTORIES if that turns out to be wrong.
|
|
@@ -2,7 +2,9 @@ import { ast, defineRule, registerDiagnostic } from "@biomejs/runtime/plugin";
|
|
|
2
2
|
import { definedOptions } from "../lib/required-options.js";
|
|
3
3
|
// Oracle (https.createSecretKey): "You cannot use both the options.guid parameter
|
|
4
4
|
// and secret parameter in combination." The type definitions (N/https.d.ts) carry
|
|
5
|
-
// the same comment and make both options optional.
|
|
5
|
+
// the same comment and make both options optional. The documentation's sample
|
|
6
|
+
// uses an option its method page does not list: docs/verification/contradictions.md, C-006
|
|
7
|
+
// (unverified).
|
|
6
8
|
export const noSecretKeyGuidAndSecret = defineRule({
|
|
7
9
|
query: ast("JS_CALL_EXPRESSION"),
|
|
8
10
|
run(node) {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { ast, defineRule, registerDiagnostic } from "@biomejs/runtime/plugin";
|
|
2
|
+
import { scriptSide } from "../lib/script-side.js";
|
|
3
|
+
// Tier 1 (SAFE guide, quick-reference.md, pitfall 107): "Use `log.debug()` only for
|
|
4
|
+
// server-side scripts", with `console.log()` for client scripts.
|
|
5
|
+
// Tier 4 (platform behavior): `console` runs in a server script, but its output goes
|
|
6
|
+
// nowhere, so only N/log produces logs there. This is a convention, not a runtime failure.
|
|
7
|
+
// Only a module positively inferred as a server script is checked: a library or a mixed
|
|
8
|
+
// module may be shared with client scripts, where `console` is correct.
|
|
9
|
+
const LOG_LEVEL = {
|
|
10
|
+
debug: "debug",
|
|
11
|
+
error: "error",
|
|
12
|
+
info: "audit",
|
|
13
|
+
log: "debug",
|
|
14
|
+
warn: "audit",
|
|
15
|
+
};
|
|
16
|
+
export const noServerConsole = defineRule({
|
|
17
|
+
query: ast("JS_CALL_EXPRESSION"),
|
|
18
|
+
run(node) {
|
|
19
|
+
const callee = node.callee;
|
|
20
|
+
if (callee?.kind !== "JS_STATIC_MEMBER_EXPRESSION")
|
|
21
|
+
return;
|
|
22
|
+
const [object, member] = callee.children();
|
|
23
|
+
if (object?.text !== "console" || !member)
|
|
24
|
+
return;
|
|
25
|
+
const level = LOG_LEVEL[member.text];
|
|
26
|
+
if (!level || scriptSide(node) !== "server")
|
|
27
|
+
return;
|
|
28
|
+
registerDiagnostic(node, "warning", `console.${member.text}() output goes nowhere in server scripts; use log.${level}() instead.`);
|
|
29
|
+
},
|
|
30
|
+
});
|
|
@@ -24,8 +24,8 @@ const ABBREVIATIONS = new Set([
|
|
|
24
24
|
// a runtime error: `The record type [PURCHORD] is invalid.` Always use
|
|
25
25
|
// `search.Type.*` enum values (or their lowercase string equivalents) in N/search,
|
|
26
26
|
// and SuiteQL abbreviations only inside SuiteQL query strings." The failure claim
|
|
27
|
-
// comes only from this skill and
|
|
28
|
-
//
|
|
27
|
+
// comes only from this skill, and a deployed RESTlet confirms it (2026.2):
|
|
28
|
+
// docs/verification/results/suiteql-type-in-search.md, C-012.
|
|
29
29
|
export const noSuiteQLTypeInSearch = defineRule({
|
|
30
30
|
query: ast("JS_CALL_EXPRESSION"),
|
|
31
31
|
run(node) {
|
|
@@ -14,7 +14,8 @@ const COLUMN_UNSUPPORTED = new Set([
|
|
|
14
14
|
]);
|
|
15
15
|
// Oracle (serverWidget.FieldType): "The DATETIME field type is not supported with
|
|
16
16
|
// addField methods, you must specify DATETIMETZ." The type definitions
|
|
17
|
-
// (N/ui/serverWidget.d.ts) have no DATETIME member in FieldType at all
|
|
17
|
+
// (N/ui/serverWidget.d.ts) have no DATETIME member in FieldType at all
|
|
18
|
+
// (unverified: docs/verification/contradictions.md, C-004). Oracle also
|
|
18
19
|
// says "The CHECKBOX, DATETIME,
|
|
19
20
|
// DATETIMETZ, FILE, HELP, INLINEHTML, LABEL, MULTISELECT, SELECT, and RADIO field
|
|
20
21
|
// types are not supported with List.addColumn(options)." Sublist.addField has its
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ast, defineRule, registerDiagnostic } from "@biomejs/runtime/plugin";
|
|
2
|
+
import { dependencies } from "../lib/biome-modules.js";
|
|
3
|
+
// Tier 0: docs/adr/0005-guessing-rules-are-in-no-preset.md, docs/adr/0006-suitescript-globals-have-two-opposite-loading-styles.md.
|
|
4
|
+
// Opt-in style: use the global `util` instead of loading N/util. Oracle's help
|
|
5
|
+
// center says "NetSuite loads the util Object ... by default for all script types.
|
|
6
|
+
// You do not need to load it manually", which is what this rule follows. It is the
|
|
7
|
+
// opposite of requireUtilModule, so enable only one of them. (The upgrade skill
|
|
8
|
+
// says the global exists but recommends loading the module for `log`, which
|
|
9
|
+
// requireUtilModule extends to `util`.)
|
|
10
|
+
export const noUtilModule = defineRule({
|
|
11
|
+
query: ast("JS_CALL_EXPRESSION", "JS_IMPORT"),
|
|
12
|
+
run(node) {
|
|
13
|
+
for (const { nameNode, name } of dependencies(node)) {
|
|
14
|
+
if (nameNode && name === "N/util") {
|
|
15
|
+
registerDiagnostic(nameNode, "warning", "Use global util instead of the N/util module.");
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
});
|
package/dist/rules/noWeakHash.js
CHANGED
|
@@ -4,9 +4,10 @@ import { ast, defineRule, registerDiagnostic } from "@biomejs/runtime/plugin";
|
|
|
4
4
|
// SHA-256 as the minimum hashing algorithm. MD5 and SHA-1 are broken and must
|
|
5
5
|
// never be used for security-sensitive operations." LOW CONFIDENCE, so no preset
|
|
6
6
|
// includes it: it cannot tell a security use from a vendor integration that
|
|
7
|
-
// requires MD5 or SHA-1. The type definitions (N/crypto.d.ts) list only
|
|
8
|
-
// SHA512 in crypto.HashAlg,
|
|
9
|
-
//
|
|
7
|
+
// requires MD5 or SHA-1. The type definitions (N/crypto.d.ts) and Oracle's page list only
|
|
8
|
+
// SHA256 and SHA512 in crypto.HashAlg, but a deployed RESTlet shows MD5 and SHA1 exist as
|
|
9
|
+
// members and that createHash rejects them with HASHING_ALGORITHM_IS_NO_LONGER_VALID
|
|
10
|
+
// (2026.2): docs/verification/results/crypto-hashalg.md, C-008.
|
|
10
11
|
export const noWeakHash = defineRule({
|
|
11
12
|
query: ast("JS_STATIC_MEMBER_EXPRESSION"),
|
|
12
13
|
run(node) {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { ast, defineRule, registerDiagnostic } from "@biomejs/runtime/plugin";
|
|
2
|
+
import { callArguments } from "../lib/options.js";
|
|
3
|
+
import { inSuiteScriptModule } from "../lib/suitescript.js";
|
|
4
|
+
const OBJECT_ITERATORS = /^Object\.(keys|values|entries)$/;
|
|
5
|
+
// Tier 4 (SuiteScript-specific, opinionated): prefer `util.each(iterable, callback)`
|
|
6
|
+
// to `forEach` when the receiver is certainly an array. Oracle's util.each page:
|
|
7
|
+
// "Iterates over each member in an Object or Array", returning the original collection.
|
|
8
|
+
// No source states that `util.each` is better; this is a house preference.
|
|
9
|
+
// Verified at runtime (docs/verification/results/util-each.md, NetSuite 2026.2):
|
|
10
|
+
// util.each iterates arrays and the own keys of plain objects, but does nothing for a
|
|
11
|
+
// Set or a Map, and returning false does not stop it. So only receivers that are
|
|
12
|
+
// certainly arrays are flagged: an array literal and Object.keys/values/entries(o).
|
|
13
|
+
// A bare `x.forEach(...)` is not flagged, because x may be a Set or a Map, which
|
|
14
|
+
// util.each would silently skip. `for...of` is not flagged either (util.each has no
|
|
15
|
+
// early exit and does not take iterables).
|
|
16
|
+
export const preferUtilEach = defineRule({
|
|
17
|
+
query: ast("JS_CALL_EXPRESSION"),
|
|
18
|
+
run(node) {
|
|
19
|
+
const callee = node.callee;
|
|
20
|
+
if (callee?.kind !== "JS_STATIC_MEMBER_EXPRESSION")
|
|
21
|
+
return;
|
|
22
|
+
const [receiver, member] = callee.children();
|
|
23
|
+
if (member?.text !== "forEach" || !receiver)
|
|
24
|
+
return;
|
|
25
|
+
if (callArguments(node).length !== 1)
|
|
26
|
+
return;
|
|
27
|
+
let message;
|
|
28
|
+
if (receiver.kind === "JS_ARRAY_EXPRESSION") {
|
|
29
|
+
message = "Use util.each(array, callback) instead of .forEach().";
|
|
30
|
+
}
|
|
31
|
+
else if (receiver.kind === "JS_CALL_EXPRESSION" &&
|
|
32
|
+
OBJECT_ITERATORS.test(receiver.callee?.text ?? "")) {
|
|
33
|
+
const target = callArguments(receiver)[0]?.text;
|
|
34
|
+
if (!target)
|
|
35
|
+
return;
|
|
36
|
+
message = `Use util.each(${target}, (value, key) => ...) instead of ${receiver.callee?.text}().forEach(); util.each passes the value first and the key second.`;
|
|
37
|
+
}
|
|
38
|
+
if (!message || !inSuiteScriptModule(node))
|
|
39
|
+
return;
|
|
40
|
+
registerDiagnostic(node, "warning", message);
|
|
41
|
+
},
|
|
42
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ast, defineRule, registerDiagnostic } from "@biomejs/runtime/plugin";
|
|
2
|
+
import { callArguments } from "../lib/options.js";
|
|
3
|
+
import { inSuiteScriptModule } from "../lib/suitescript.js";
|
|
4
|
+
// Tier 4 (SuiteScript-specific, opinionated): prefer `util.extend(receiver, contributor)`
|
|
5
|
+
// to `Object.assign(receiver, contributor)`. Oracle's util.extend page: "Copies the
|
|
6
|
+
// properties in a source object to a destination object", returning the destination.
|
|
7
|
+
// No source states that `util.extend` is better; this is a house preference.
|
|
8
|
+
// Verified at runtime (docs/verification/results/util-extend.md, NetSuite 2026.2):
|
|
9
|
+
// util.extend mutates and returns the receiver, copies own enumerable properties
|
|
10
|
+
// shallowly and accepts a null contributor, exactly like two-argument Object.assign.
|
|
11
|
+
// Object spread (a new object) and for...in copies (inherited keys) are not
|
|
12
|
+
// equivalent, so they are not flagged, nor is Object.assign with three or more
|
|
13
|
+
// arguments or a spread argument.
|
|
14
|
+
export const preferUtilExtend = defineRule({
|
|
15
|
+
query: ast("JS_CALL_EXPRESSION"),
|
|
16
|
+
run(node) {
|
|
17
|
+
if (node.callee?.text !== "Object.assign")
|
|
18
|
+
return;
|
|
19
|
+
const args = callArguments(node);
|
|
20
|
+
if (args.length !== 2 || args.some((a) => a.kind === "JS_SPREAD"))
|
|
21
|
+
return;
|
|
22
|
+
if (!inSuiteScriptModule(node))
|
|
23
|
+
return;
|
|
24
|
+
registerDiagnostic(node, "warning", "Use util.extend(receiver, contributor) instead of Object.assign().");
|
|
25
|
+
},
|
|
26
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { ast, defineRule, registerDiagnostic, } from "@biomejs/runtime/plugin";
|
|
2
|
+
import { callArguments } from "../lib/options.js";
|
|
3
|
+
import { inSuiteScriptModule } from "../lib/suitescript.js";
|
|
4
|
+
// Tier 4 (SuiteScript-specific, opinionated): prefer the `util` type checks to the
|
|
5
|
+
// native patterns that do the same job. N/util offers isArray, isString, isNumber,
|
|
6
|
+
// isBoolean, isFunction, isAsyncFunction, isDate and isRegExp (Oracle's N/util
|
|
7
|
+
// module page, and the type definitions' N/util.d.ts, which omit isAsyncFunction).
|
|
8
|
+
// No source states that `util` is better; this is a house preference for one
|
|
9
|
+
// vocabulary of type checks, so it is `warn` and in `all` only.
|
|
10
|
+
// Verified at runtime (docs/verification/results/util-type-checks.md, NetSuite
|
|
11
|
+
// 2026.2): util.isString, isNumber and isBoolean also accept boxed values, so a
|
|
12
|
+
// `typeof` check differs from them for `new String('a')` (rare in practice, and
|
|
13
|
+
// documented on the rule page); the Object.prototype.toString forms match exactly.
|
|
14
|
+
// isFunction is true for async functions, as `typeof x === 'function'` is.
|
|
15
|
+
// No rule covers isObject (no native pattern matches it, see
|
|
16
|
+
// docs/verification/results/util-isobject.md) or nanoTime.
|
|
17
|
+
// Flagged patterns:
|
|
18
|
+
// Array.isArray(x), x instanceof Array, x.constructor === Array, toString.call(x) === '[object Array]'
|
|
19
|
+
// typeof x === 'string' | 'number' | 'boolean' | 'function'
|
|
20
|
+
// toString.call(x) === '[object String]' | '[object Number]' | '[object Boolean]'
|
|
21
|
+
// x instanceof Date | RegExp, toString.call(x) === '[object Date]' | '[object RegExp]'
|
|
22
|
+
// toString.call(x) === '[object AsyncFunction]', x.constructor.name === 'AsyncFunction',
|
|
23
|
+
// x instanceof AsyncFunction (the usual name of the constructor)
|
|
24
|
+
const TYPEOF = {
|
|
25
|
+
boolean: "isBoolean",
|
|
26
|
+
function: "isFunction",
|
|
27
|
+
number: "isNumber",
|
|
28
|
+
string: "isString",
|
|
29
|
+
};
|
|
30
|
+
const TAGS = {
|
|
31
|
+
Array: "isArray",
|
|
32
|
+
AsyncFunction: "isAsyncFunction",
|
|
33
|
+
Boolean: "isBoolean",
|
|
34
|
+
Date: "isDate",
|
|
35
|
+
Number: "isNumber",
|
|
36
|
+
RegExp: "isRegExp",
|
|
37
|
+
String: "isString",
|
|
38
|
+
};
|
|
39
|
+
const INSTANCEOF = {
|
|
40
|
+
Array: "isArray",
|
|
41
|
+
AsyncFunction: "isAsyncFunction",
|
|
42
|
+
Date: "isDate",
|
|
43
|
+
RegExp: "isRegExp",
|
|
44
|
+
};
|
|
45
|
+
const TO_STRING = /^(Object\.prototype\.|\{\}\.)?toString\.(call|apply)$/;
|
|
46
|
+
const EQUALITY = new Set(["===", "==", "!==", "!="]);
|
|
47
|
+
const unquote = (text) => text.replace(/^['"`]|['"`]$/g, "");
|
|
48
|
+
/** An own entry of a table, so a key such as `constructor` finds nothing. */
|
|
49
|
+
const own = (table, key) => key !== undefined && Object.hasOwn(table, key) ? table[key] : undefined;
|
|
50
|
+
function stringLiteral(node) {
|
|
51
|
+
return node?.kind === "JS_STRING_LITERAL_EXPRESSION"
|
|
52
|
+
? unquote(node.text)
|
|
53
|
+
: undefined;
|
|
54
|
+
}
|
|
55
|
+
/** The method `util` offers for a `typeof`/toString/constructor comparison, if the two sides are one. */
|
|
56
|
+
function comparison(one, other) {
|
|
57
|
+
const literal = stringLiteral(other);
|
|
58
|
+
if (!one || literal === undefined)
|
|
59
|
+
return undefined;
|
|
60
|
+
if (one.kind === "JS_UNARY_EXPRESSION" && /^typeof\b/.test(one.text)) {
|
|
61
|
+
return own(TYPEOF, literal);
|
|
62
|
+
}
|
|
63
|
+
if (one.kind === "JS_CALL_EXPRESSION") {
|
|
64
|
+
if (!TO_STRING.test(one.callee?.text ?? ""))
|
|
65
|
+
return undefined;
|
|
66
|
+
const tag = /^\[object (\w+)\]$/.exec(literal)?.[1];
|
|
67
|
+
return own(TAGS, tag);
|
|
68
|
+
}
|
|
69
|
+
const text = one.text.replace(/\s+/g, "");
|
|
70
|
+
if (one.kind === "JS_STATIC_MEMBER_EXPRESSION" &&
|
|
71
|
+
text.endsWith(".constructor.name")) {
|
|
72
|
+
return literal === "AsyncFunction" ? "isAsyncFunction" : undefined;
|
|
73
|
+
}
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
export const preferUtilTypeChecks = defineRule({
|
|
77
|
+
query: ast("JS_BINARY_EXPRESSION", "JS_CALL_EXPRESSION", "JS_INSTANCEOF_EXPRESSION"),
|
|
78
|
+
run(node) {
|
|
79
|
+
let method;
|
|
80
|
+
if (node.kind === "JS_CALL_EXPRESSION") {
|
|
81
|
+
if (node.callee?.text !== "Array.isArray")
|
|
82
|
+
return;
|
|
83
|
+
if (callArguments(node).length !== 1)
|
|
84
|
+
return;
|
|
85
|
+
method = "isArray";
|
|
86
|
+
}
|
|
87
|
+
else if (node.kind === "JS_INSTANCEOF_EXPRESSION") {
|
|
88
|
+
const right = node.right;
|
|
89
|
+
if (right?.kind === "JS_IDENTIFIER_EXPRESSION") {
|
|
90
|
+
method = own(INSTANCEOF, right.text);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
if (!EQUALITY.has(node.operatorToken ?? ""))
|
|
95
|
+
return;
|
|
96
|
+
const { left, right } = node;
|
|
97
|
+
method = comparison(left, right) ?? comparison(right, left);
|
|
98
|
+
if (!method &&
|
|
99
|
+
left?.kind === "JS_STATIC_MEMBER_EXPRESSION" &&
|
|
100
|
+
left.text.replace(/\s+/g, "").endsWith(".constructor") &&
|
|
101
|
+
right?.text === "Array") {
|
|
102
|
+
method = "isArray";
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (!method || !inSuiteScriptModule(node))
|
|
106
|
+
return;
|
|
107
|
+
const shown = node.text.replace(/\s+/g, " ");
|
|
108
|
+
registerDiagnostic(node, "warning", `Use util.${method}() instead of \`${shown.length > 60 ? `${shown.slice(0, 57)}...` : shown}\`.`);
|
|
109
|
+
},
|
|
110
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineRule, registerDiagnostic, semantic, } from "@biomejs/runtime/plugin";
|
|
2
2
|
import { inSuiteScriptModule } from "../lib/suitescript.js";
|
|
3
|
-
// Tier 0: docs/adr/0006-
|
|
3
|
+
// Tier 0: docs/adr/0006-suitescript-globals-have-two-opposite-loading-styles.md.
|
|
4
4
|
// Load N/log when you call log.*. Oracle's upgrade skill says `log` is global but
|
|
5
5
|
// "explicitly including it in `define()` makes dependencies clearer and is
|
|
6
6
|
// recommended", and that practice is what this rule enforces. The help center's
|
|
@@ -17,7 +17,7 @@ const REQUIRED = {
|
|
|
17
17
|
// (N/ui/serverWidget.d.ts) outrank the pages and disagree on two methods: they make
|
|
18
18
|
// addButton's `id` optional and addSecretKeyField's `restrictToScriptIds`
|
|
19
19
|
// optional, where the pages require both, so those two follow the type
|
|
20
|
-
// definitions. The receiver cannot
|
|
20
|
+
// definitions (unverified: docs/verification/contradictions.md, C-003). The receiver cannot
|
|
21
21
|
// be typed, so a method of the same name on another object can misfire. Only an
|
|
22
22
|
// options object literal without a spread is checked.
|
|
23
23
|
export const requireServerWidgetOptions = defineRule({
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { defineRule, registerDiagnostic, semantic, } from "@biomejs/runtime/plugin";
|
|
2
|
+
import { inSuiteScriptModule } from "../lib/suitescript.js";
|
|
3
|
+
// Tier 0: docs/adr/0006-suitescript-globals-have-two-opposite-loading-styles.md.
|
|
4
|
+
// Load N/util when you call util.*. Oracle's upgrade skill (references/conversion-guide.md)
|
|
5
|
+
// says "`log` and `util` are global objects in SS2.1; you can use them without
|
|
6
|
+
// adding to define(). However, explicitly including `N/log` makes dependencies
|
|
7
|
+
// clearer", and its SKILL.md recommends loading N/log for that reason. The skill names
|
|
8
|
+
// only N/log; this rule applies the same recommendation to `util`, so it is a
|
|
9
|
+
// convention (`warn`, in `all`). The help center's "NetSuite loads the util Object ...
|
|
10
|
+
// by default for all script types. You do not need to load it manually" is why
|
|
11
|
+
// noMissingModules leaves `util` alone. This is the opposite style to noUtilModule,
|
|
12
|
+
// so enable only one of them.
|
|
13
|
+
export const requireUtilModule = defineRule({
|
|
14
|
+
query: semantic("JS_REFERENCE_IDENTIFIER"),
|
|
15
|
+
run(node, { model }) {
|
|
16
|
+
if (node.text !== "util" || model.binding(node))
|
|
17
|
+
return;
|
|
18
|
+
const member = node.parent?.parent;
|
|
19
|
+
if (member?.kind !== "JS_STATIC_MEMBER_EXPRESSION" ||
|
|
20
|
+
member.children()[0]?.text !== "util")
|
|
21
|
+
return;
|
|
22
|
+
if (!inSuiteScriptModule(node))
|
|
23
|
+
return;
|
|
24
|
+
registerDiagnostic(node, "warning", "'util' is used but N/util is not loaded.");
|
|
25
|
+
},
|
|
26
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "biome-plugin-suitescript",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Biome lint plugins and a tag checker for NetSuite SuiteScript 2.1, ported from eslint-plugin-suitescript.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"biome-plugin",
|
|
@@ -54,16 +54,25 @@
|
|
|
54
54
|
"sync-typings": "node scripts/sync-typings.ts",
|
|
55
55
|
"pretest": "npm run build",
|
|
56
56
|
"test": "node --test test/*.test.ts",
|
|
57
|
-
"typecheck": "tsc"
|
|
57
|
+
"typecheck": "tsc",
|
|
58
|
+
"verify:certificate": "node scripts/verify.ts certificate",
|
|
59
|
+
"verify:cleanup": "node scripts/verify.ts cleanup",
|
|
60
|
+
"verify:deploy": "node scripts/verify.ts deploy",
|
|
61
|
+
"verify:doctor": "node scripts/verify.ts doctor",
|
|
62
|
+
"verify:env": "node scripts/verify.ts env",
|
|
63
|
+
"verify:passkey": "node scripts/verify.ts passkey",
|
|
64
|
+
"verify:run": "node scripts/verify.ts run",
|
|
65
|
+
"verify:setup-ci": "node scripts/verify.ts setup-ci"
|
|
58
66
|
},
|
|
59
67
|
"dependencies": {
|
|
60
|
-
"oxc-parser": "^0.
|
|
68
|
+
"oxc-parser": "^0.157.0",
|
|
61
69
|
"tinyglobby": "^0.2.17"
|
|
62
70
|
},
|
|
63
71
|
"devDependencies": {
|
|
64
|
-
"@biomejs/biome": "2.6.
|
|
65
|
-
"@biomejs/runtime": "2.6.
|
|
66
|
-
"@hitc/netsuite-types": "2026.2.
|
|
72
|
+
"@biomejs/biome": "2.6.5",
|
|
73
|
+
"@biomejs/runtime": "2.6.5",
|
|
74
|
+
"@hitc/netsuite-types": "2026.2.5",
|
|
75
|
+
"@oracle/suitecloud-cli": "4.1.1",
|
|
67
76
|
"@types/node": "24.19.2",
|
|
68
77
|
"git-pull-run": "2.1.0",
|
|
69
78
|
"lefthook": "2.2.2",
|
|
@@ -84,7 +93,7 @@
|
|
|
84
93
|
},
|
|
85
94
|
"packageManager": {
|
|
86
95
|
"name": "npm",
|
|
87
|
-
"version": "12.2.
|
|
96
|
+
"version": "12.2.2",
|
|
88
97
|
"onFail": "warn"
|
|
89
98
|
}
|
|
90
99
|
},
|