biome-plugin-suitescript 0.2.0 → 0.4.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.
Files changed (69) hide show
  1. package/README.md +154 -78
  2. package/biome-manifest.json +179 -17
  3. package/configs/all.json +29 -0
  4. package/configs/minimal.json +14 -0
  5. package/configs/recommended.json +20 -0
  6. package/dist/lib/define.js +35 -5
  7. package/dist/lib/headers.js +12 -0
  8. package/dist/lib/module-calls.js +65 -0
  9. package/dist/lib/module-script-types.js +7 -1
  10. package/dist/lib/options.js +35 -0
  11. package/dist/lib/required-options.js +14 -0
  12. package/dist/lib/script-types.js +25 -14
  13. package/dist/lib/suiteql.js +16 -0
  14. package/dist/lib/typings-data.js +4327 -0
  15. package/dist/lib/typings.js +2 -0
  16. package/dist/rules/logRequireDetails.js +4 -0
  17. package/dist/rules/logRequireTitle.js +1 -0
  18. package/dist/rules/noAuthorizationHeaderInInternalRequest.js +24 -0
  19. package/dist/rules/noBlockedHttpsHeader.js +32 -0
  20. package/dist/rules/noBlockedResponseHeader.js +35 -0
  21. package/dist/rules/noBrowserGlobals.js +2 -0
  22. package/dist/rules/noConflictingClientScriptProps.js +36 -0
  23. package/dist/rules/noDynamicRequire.js +23 -0
  24. package/dist/rules/noDynamicSuiteQL.js +11 -6
  25. package/dist/rules/noExtraDeps.js +16 -0
  26. package/dist/rules/noExtraParams.js +17 -0
  27. package/dist/rules/noGlobalShadowing.js +6 -2
  28. package/dist/rules/noHardcodedAuthPasswords.js +27 -0
  29. package/dist/rules/noHardcodedSecrets.js +7 -2
  30. package/dist/rules/noInnerHtml.js +41 -0
  31. package/dist/rules/noInsecureCookie.js +27 -0
  32. package/dist/rules/noInvalidDefine.js +47 -0
  33. package/dist/rules/noInvalidEnumMember.js +24 -0
  34. package/dist/rules/noLocationHeader.js +20 -0
  35. package/dist/rules/noLogModule.js +8 -5
  36. package/dist/rules/noMissingModules.js +6 -3
  37. package/dist/rules/noModuleScopeApiCalls.js +14 -4
  38. package/dist/rules/noMultipleDefine.js +14 -0
  39. package/dist/rules/noNativeIterators.js +8 -5
  40. package/dist/rules/noPositionalOptions.js +26 -0
  41. package/dist/rules/noPostMessageWildcard.js +15 -0
  42. package/dist/rules/noPublicFileUpload.js +20 -0
  43. package/dist/rules/noRecordInLoop.js +8 -5
  44. package/dist/rules/noRequireInsteadOfDefine.js +25 -0
  45. package/dist/rules/noSecretKeyGuidAndSecret.js +16 -0
  46. package/dist/rules/noSensitiveBrowserStorage.js +24 -0
  47. package/dist/rules/noStackInResponse.js +21 -0
  48. package/dist/rules/noSuiteQLSelectStar.js +18 -0
  49. package/dist/rules/noSuiteQLSystemNoteJoin.js +19 -0
  50. package/dist/rules/noSuiteQLTypeInSearch.js +41 -0
  51. package/dist/rules/noSuiteScript1Api.js +77 -3
  52. package/dist/rules/noUnknownModuleMember.js +23 -0
  53. package/dist/rules/noUnknownOptions.js +31 -0
  54. package/dist/rules/noUnsupportedServerWidgetFieldType.js +40 -0
  55. package/dist/rules/noWeakHash.js +17 -0
  56. package/dist/rules/noWildcardCors.js +16 -0
  57. package/dist/rules/noWrongScriptTypeModules.js +3 -1
  58. package/dist/rules/requireCustpagePrefix.js +14 -13
  59. package/dist/rules/requireEachReturn.js +38 -0
  60. package/dist/rules/requireLogModule.js +25 -0
  61. package/dist/rules/requireOptions.js +26 -0
  62. package/dist/rules/requireOrderByForPagedSuiteQL.js +17 -0
  63. package/dist/rules/requireQueryOptions.js +53 -0
  64. package/dist/rules/requireServerResponseOptions.js +33 -0
  65. package/dist/rules/requireServerWidgetOptions.js +37 -0
  66. package/package.json +10 -1
  67. package/dist/rules/noEval.js +0 -21
  68. package/dist/rules/noExtraModules.js +0 -23
  69. package/dist/rules/noUserEventContextSave.js +0 -12
package/README.md CHANGED
@@ -1,10 +1,15 @@
1
1
  # biome-plugin-suitescript
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/biome-plugin-suitescript)](https://www.npmjs.com/package/biome-plugin-suitescript)
4
+ [![CI](https://img.shields.io/github/actions/workflow/status/nemchik/biome-plugin-suitescript/ci.yml?branch=main&label=CI)](https://github.com/nemchik/biome-plugin-suitescript/actions/workflows/ci.yml)
5
+ [![license](https://img.shields.io/npm/l/biome-plugin-suitescript)](LICENSE)
6
+
3
7
  [Biome](https://biomejs.dev) lint plugins and a tag checker for NetSuite **SuiteScript 2.1**, ported from
4
8
  [acdvs/eslint-plugin-suitescript](https://github.com/acdvs/eslint-plugin-suitescript) and
5
9
  [btonasse/eslint-plugin-suitescript](https://github.com/btonasse/eslint-plugin-suitescript).
6
10
 
7
- Requires Biome **>= 2.6.4** (npm-distributed plugins use `biome-manifest.json`) and Node >= 22 for the CLI.
11
+ Requires Biome **>= 2.6.4** (npm-distributed plugins use `biome-manifest.json`) and Node >= 24 for the CLI. Node 24 LTS is also what Oracle's
12
+ [SuiteCloud CLI for Node.js requires](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_1558708810.html).
8
13
  Rules work on both AMD `define()` and ES `import`/`export` source, in `.js` and `.ts`.
9
14
 
10
15
  ## Install
@@ -15,17 +20,33 @@ npm i -D @biomejs/biome biome-plugin-suitescript
15
20
 
16
21
  ## Use
17
22
 
23
+ Extend one of the three configs. Each turns on the matching plugin preset, Biome's own recommended rules and the
24
+ native Biome rules that Oracle's documentation or agent skills ask for, and declares the SuiteScript globals
25
+ (`define`, `require`, `log`, `util`):
26
+
18
27
  ```jsonc
19
28
  // biome.json
20
29
  {
21
- "extends": ["biome-plugin-suitescript/configs/suitescript2"], // globals: define, require, log, util
30
+ "extends": ["biome-plugin-suitescript/configs/recommended"],
22
31
  "plugins": [
23
- "biome-plugin-suitescript/presets/recommended",
24
- "biome-plugin-suitescript/noLogModule" // add individual rules on top of a preset
32
+ "biome-plugin-suitescript/noLogModule" // add individual rules on top
25
33
  ]
26
34
  }
27
35
  ```
28
36
 
37
+ | Config | Enables |
38
+ | ---------------------------------------------- | ------------------------------------------------------------------------------------ |
39
+ | `biome-plugin-suitescript/configs/minimal` | `presets/minimal`, Biome's recommended rules (with `noUnusedFunctionParameters` off) |
40
+ | `biome-plugin-suitescript/configs/recommended` | `presets/recommended`, plus the native rules marked :green_circle: below |
41
+ | `biome-plugin-suitescript/configs/all` | `presets/all`, plus every native rule marked below |
42
+
43
+ The plugin presets (`biome-plugin-suitescript/presets/<name>`) alone only list this package's rules, because a
44
+ Biome plugin preset cannot enable native rules. Use them in `plugins` if you want to choose the native rules
45
+ yourself. `configs/suitescript2` only declares the globals.
46
+
47
+ Biome's `noUnusedFunctionParameters` is off in all three configs: a `define()` callback takes one parameter per
48
+ dependency (`noExtraParams`), so a module loaded for its side effects would otherwise be reported.
49
+
29
50
  Run the tag checker beside `biome check` (see [Tag checker](#tag-checker)):
30
51
 
31
52
  ```sh
@@ -36,86 +57,137 @@ npx biome-plugin-suitescript check
36
57
 
37
58
  Each rule has one fixed severity: `error` for things that fail at runtime or on upload, `warn` for conventions.
38
59
  Biome plugins cannot be reconfigured per project, so you choose by enabling rules, scoping them, or generating one.
39
-
40
- | Rule | Severity | Description | ESLint equivalent |
41
- | ------------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------- | --------------------------- |
42
- | `noAmdName` (fixable) | error | No module id as the first `define` argument | `no-amd-name` |
43
- | `noExtraModules` | error | Dependency array and callback parameters must match in count | `no-extra-modules` |
44
- | `noInvalidModules` | error | Unknown `N/...` modules | `no-invalid-modules` |
45
- | `noModuleExtensions` (fixable) | error | No `.js` in `define` dependency names | `no-module-extensions` |
46
- | `moduleVars` | warn | Conventional identifier per `N/` module (`N/record` → `record`) | `module-vars` |
47
- | `logRequireTitle` | warn | `log.debug/audit/error/emergency({...})` needs `title` | `log-args` `requireTitle` |
48
- | `logRequireDetails` | warn | ...needs `details` | `log-args` `requireDetails` |
49
- | `noLogModule` | warn | Opt-in opposite of `noMissingModules`: use the global `log`, not `N/log` | `no-log-module` |
50
- | `entryPoints` | error | Returned entry points must belong to one script type and include its required ones | `entry-points` |
51
- | `noMissingModules` | error | A module used (`record.load()`, `log.debug()`) must be loaded in `define`/`import` | none (from Oracle's skills) |
52
- | `noGlobalShadowing` | warn | No variable named `log` or `util` other than the N/log, N/util module itself | none (from Oracle's skills) |
53
- | `noEval` | error | No `eval`, `new Function` or string-form `setTimeout`/`setInterval` | none (OWASP skill) |
54
- | `noDynamicSuiteQL` | warn | `runSuiteQL` queries use `?` placeholders, not concatenation or template literals | none (OWASP skill) |
55
- | `noSuiteScript1Api` | error | No `nlapi*`/`nlobj*` (SuiteScript 1.0) leftovers | none (upgrade skill) |
56
- | `noModuleScopeApiCalls` | warn | No `N/` API calls while the `define` callback runs; initialize from entry points | none (SAFE guide) |
57
- | `noRecordInLoop` | warn | No `record.load`/`record.create` inside loops or `forEach`/`each`/`map` callbacks | none (SAFE guide) |
58
- | `noNativeIterators` | warn | No generator functions or `Symbol.iterator` (GraalJS) | none (SAFE guide) |
59
- | `noWrongScriptTypeModules` | error | No client-only module (`N/currentRecord`, `N/ui/dialog`) in a server script, or a server-only one in a client script | none (Oracle docs) |
60
- | `noBrowserGlobals` | error | No `document`, `window`, `localStorage`, `XMLHttpRequest`, `alert` or `navigator` in server scripts | none (Oracle docs) |
61
- | `noBeforeSubmitRecordReload` | error | No `record.load`/`submitFields`/`delete` of `context.newRecord.id` inside `beforeSubmit` | none (Oracle docs) |
62
- | `noEmptyEntryPoints` | warn | A returned entry point with an empty body | none (Oracle docs) |
63
- | `noMapReduceSearchResults` | warn | `getInputData` returns the search object, not `getRange()` results | none (Oracle docs) |
64
- | `noHardcodedSecrets` | error | Opt-in only: no string literal assigned to `password`, `secret`, `apiKey`, ... | none (Oracle docs) |
65
- | `noUserEventContextSave` | error | No `.save()` on `context.newRecord` or `context.oldRecord` in user events | none (Oracle docs) |
66
- | `noInsecureHttpsUrl` | error | No `http://` URL passed to `https.*` (N/https refuses plain HTTP) | none (Oracle docs) |
67
- | `requireCustpagePrefix` | warn | The `id` of a UI object added to a form (`addField`, `addTab`, ...) starts with `custpage` | none (Oracle docs) |
68
- | `noHeaderUnderscores` | warn | No underscore in a custom `https.*` header name | none (Oracle docs) |
69
- | `noQueryColumnFieldAndFormula` | error | A query column cannot set both `fieldId` and `formula` | none (Oracle docs) |
70
- | `requireClientValidateReturn` | warn | `saveRecord` and `validate*` return a value (only a body with no `return <value>` is flagged) | none (Oracle docs) |
71
- | `noClientLog` | warn | `log.*` is ignored in client scripts attached to a form; use `console` | none (Oracle docs) |
72
-
73
- `noAmdName`, `noExtraModules` and `noModuleExtensions` only apply to `define()`. The rest cover `import` too.
74
-
75
- | Preset | Contents |
76
- | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
77
- | `presets/recommended` | every rule above except the opt-in `noLogModule` and `noHardcodedSecrets` and the ones `presets/all` adds |
78
- | `presets/minimal` | `noExtraModules`, `noInvalidModules` |
79
- | `presets/all` | `recommended` plus `entryPoints`, `noRecordInLoop`, `noNativeIterators`, `noBeforeSubmitRecordReload`, `noEmptyEntryPoints`, `noMapReduceSearchResults`, `requireCustpagePrefix`, `noHeaderUnderscores`, `noQueryColumnFieldAndFormula`, `requireClientValidateReturn` and `noClientLog` (never `noLogModule` or `noHardcodedSecrets`) |
60
+ Rules are sorted by source tier, then severity, then preset, then name. How a rule's tier and place in a preset are
61
+ decided is in [docs/rule-priority.md](docs/rule-priority.md).
62
+
63
+ <!-- rules-table:start -->
64
+
65
+ | Rule | Severity | Presets | Description | Rule source |
66
+ | ---------------------------------------- | -------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
67
+ | `noBrowserGlobals` | 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) |
68
+ | `noGlobalShadowing` | 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) |
69
+ | `noMissingModules` | 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-log-is-a-global-with-two-opposite-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) |
70
+ | `noModuleScopeApiCalls` | 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) |
71
+ | `noNativeIterators` | 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) |
72
+ | `noSuiteQLTypeInSearch` | 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) |
73
+ | `noSuiteScript1Api` | 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) |
74
+ | `noHardcodedSecrets` | 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) |
75
+ | `noDynamicRequire` | 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: [acdvs `no-dynamic-require`](https://github.com/acdvs/eslint-plugin-amd/blob/main/lib/rules/no-dynamic-require.ts) |
76
+ | `noDynamicSuiteQL` | 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) |
77
+ | `noInnerHtml` | 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) |
78
+ | `noPostMessageWildcard` | 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) |
79
+ | `noWildcardCors` | 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) |
80
+ | `requireEachReturn` | 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) |
81
+ | `noRecordInLoop` | 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) |
82
+ | `requireLogModule` | 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-log-is-a-global-with-two-opposite-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) |
83
+ | `noInsecureCookie` | 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) |
84
+ | `noLocationHeader` | 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) |
85
+ | `noPublicFileUpload` | 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) |
86
+ | `noSensitiveBrowserStorage` | 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) |
87
+ | `noStackInResponse` | 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) |
88
+ | `noSuiteQLSelectStar` | 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) |
89
+ | `noSuiteQLSystemNoteJoin` | 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) |
90
+ | `noWeakHash` | 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) |
91
+ | `noAuthorizationHeaderInInternalRequest` | 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) |
92
+ | `noPositionalOptions` | 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) |
93
+ | `noSecretKeyGuidAndSecret` | 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) |
94
+ | `noUnknownModuleMember` | 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) |
95
+ | `noUnsupportedServerWidgetFieldType` | 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) |
96
+ | `requireOptions` | 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) |
97
+ | `requireQueryOptions` | 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) |
98
+ | `requireServerResponseOptions` | 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) |
99
+ | `requireServerWidgetOptions` | 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) |
100
+ | `noInvalidEnumMember` | 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) |
101
+ | `noUnknownOptions` | 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) |
102
+ | `entryPoints` | 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) |
103
+ | `noBeforeSubmitRecordReload` | 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) |
104
+ | `noBlockedResponseHeader` | 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) |
105
+ | `noConflictingClientScriptProps` | 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) |
106
+ | `noInsecureHttpsUrl` | 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) |
107
+ | `noMultipleDefine` | 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) |
108
+ | `noQueryColumnFieldAndFormula` | 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) |
109
+ | `noRequireInsteadOfDefine` | 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) |
110
+ | `noWrongScriptTypeModules` | 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) |
111
+ | `logRequireTitle` | 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) |
112
+ | `noBlockedHttpsHeader` | 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) |
113
+ | `noEmptyEntryPoints` | 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) |
114
+ | `noHardcodedAuthPasswords` | 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) |
115
+ | `noHeaderUnderscores` | 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) |
116
+ | `requireClientValidateReturn` | 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) |
117
+ | `requireCustpagePrefix` | 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) |
118
+ | `requireOrderByForPagedSuiteQL` | 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) |
119
+ | `noClientLog` | 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) |
120
+ | `noMapReduceSearchResults` | 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) |
121
+ | `noLogModule` | 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-log-is-a-global-with-two-opposite-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) |
122
+ | `noExtraParams` | 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), [acdvs `no-extra-params`](https://github.com/acdvs/eslint-plugin-amd/blob/main/lib/rules/no-extra-params.ts) |
123
+ | `noInvalidModules` | 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) |
124
+ | `noAmdName` | 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), [acdvs `no-name`](https://github.com/acdvs/eslint-plugin-amd/blob/main/lib/rules/no-name.ts) |
125
+ | `noInvalidDefine` | 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: [acdvs `no-invalid-define`](https://github.com/acdvs/eslint-plugin-amd/blob/main/lib/rules/no-invalid-define.ts) |
126
+ | `noModuleExtensions` | 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), [acdvs `no-module-extensions`](https://github.com/acdvs/eslint-plugin-amd/blob/main/lib/rules/no-module-extensions.ts) |
127
+ | `noExtraDeps` | 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), [acdvs `no-extra-deps`](https://github.com/acdvs/eslint-plugin-amd/blob/main/lib/rules/no-extra-deps.ts) |
128
+ | `moduleVars` | 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) |
129
+ | `logRequireDetails` | 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) |
130
+ | `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/) |
131
+ | `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/) |
132
+ | `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/) |
133
+ | `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/) |
134
+ | `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/) |
135
+ | `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/) |
136
+ | `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/) |
137
+
138
+ <!-- rules-table:end -->
139
+
140
+ Presets: :white_circle: `minimal` · :green_circle: `recommended` · :large_blue_circle: `all` (a superset of
141
+ `recommended`) · :key: in no preset, add the rule yourself (see [Opt-in rules](#opt-in-rules-and-typings)).
142
+
143
+ Rule source tiers: **Tier 0** a decision recorded as an ADR that overrides every source (it does not change where a
144
+ rule sorts) · **Tier 1** Oracle's SuiteCloud SDK agent skills · **Tier 2** type definitions · **Tier 3** Oracle
145
+ documentation · **Tier 4** SuiteScript-specific rules from other sources · **Tier 5** generic JavaScript rules (the native
146
+ Biome rules above). When sources conflict, the lower tier number wins. The table lists only the native Biome rules that a
147
+ Tier 1, 2 or 3 source asks for; Biome's other recommended rules are enabled by the configs too.
148
+
149
+ `noAmdName`, `noExtraDeps`, `noExtraParams`, `noModuleExtensions`, `noInvalidDefine` and `noDynamicRequire` only look
150
+ at `define()` and `require()` calls. The rest cover `import` too.
80
151
 
81
152
  Suppress a diagnostic with `// biome-ignore lint/plugin: reason`.
82
153
 
83
154
  ### Opt-in rules and typings
84
155
 
85
- `noHardcodedSecrets` is in no preset. It guesses from names (`password`, `passwd`, `pwd`, `secret`, `apiKey`,
86
- `clientSecret`, `tokenSecret`, `consumerSecret`, exact match only) rather than knowing a value is a credential, so it
87
- can misfire on look-alike names, and you should decide whether that trade is worth it: add
88
- `"biome-plugin-suitescript/noHardcodedSecrets"` to `plugins` yourself. Oracle says "Do not hard-code any passwords
89
- in scripts."
90
-
91
- The rules assume you type-check against [`@hitc/netsuite-types`](https://github.com/headintheclouddev/typings-suitescript-2.0),
92
- which already rejects wrong option names, missing parameters and positional-argument calls to N/ methods (their
93
- signatures take an options object). Plain JavaScript projects get none of that from these rules.
156
+ The rules marked :key: are in no preset, because each one guesses and may misfire. Add the ones you want to
157
+ `plugins` yourself, for example `"biome-plugin-suitescript/noHardcodedSecrets"`. The reason each is left out is in
158
+ the table. `noHardcodedSecrets` matches names (`password`, `passwd`, `pwd`, `secret`, `apiKey`, `clientSecret`,
159
+ `tokenSecret`, `consumerSecret`, exact match only) rather than knowing a value is a credential. Oracle says "Don't
160
+ hard-code any passwords in scripts." Biome's `noSecrets`, which matches known token formats and high-entropy strings
161
+ instead, is not enabled in any config: on a real-world SuiteScript codebase (286 files) it reported 282 times, nearly
162
+ all false positives (log messages, NetSuite URLs, minified vendor files). Enable it yourself if you want it.
163
+
164
+ The rules assume you type-check against [`@hitc/netsuite-types`](https://github.com/headintheclouddev/typings-suitescript-2.0)
165
+ (the repository is named `typings-suitescript-2.0` but the typings are written for SuiteScript 2.1; the repository was
166
+ never renamed). They are credited as Tier 2 where a rule follows them, and they already reject wrong option names, missing parameters and positional-argument calls to N/ methods (their
167
+ signatures take an options object). For plain JavaScript projects, which get nothing from the typings, five rules
168
+ generated from the typings cover module-level functions and enums: `requireOptions`, `noUnknownOptions`,
169
+ `noPositionalOptions`, `noInvalidEnumMember` and `noUnknownModuleMember`. They resolve `record` in `record.load(...)`
170
+ from the `define()` or `import` binding, so they only fire on an `N/` module you loaded. Instance methods
171
+ (`rec.setValue`, `form.addField`) need the receiver's type, so only the hand-written `require*Options` rules look at
172
+ those, by name. The typings can lag a NetSuite release, so `noUnknownModuleMember` can report a member Oracle added
173
+ recently; refresh the data with `npm run sync-typings` after updating `@hitc/netsuite-types`.
94
174
 
95
175
  ### Logging and `N/log`
96
176
 
97
- Oracle's own agent skills disagree on whether `N/log` must be loaded: one says the global exists but loading is
98
- recommended, another says it is not global. This package takes the consistent position that a module you use
99
- should be loaded, so `noMissingModules` requires `N/log` when `log.` is called. `noLogModule` is the opposite style
100
- for projects that prefer the global, so enable only one of the two (`presets/all` omits `noLogModule`).
101
-
102
- ### Rules drawn from Oracle's documentation
103
-
104
- `noWrongScriptTypeModules`, `noBrowserGlobals`, `noBeforeSubmitRecordReload`, `noEmptyEntryPoints` and
105
- `noMapReduceSearchResults`, plus the later additions listed in the table above marked Oracle docs, come from Oracle's SuiteScript help-center pages and cover what type checking against
106
- `@hitc/netsuite-types` cannot (the typings accept every signature involved). The client-only and server-only module
107
- lists are copied from Oracle's module reference table, and `noWrongScriptTypeModules` and `noBrowserGlobals` only fire
108
- when the script type can be inferred from the returned entry points. `noBeforeSubmitRecordReload` only matches calls
109
- whose `id` is `...newRecord.id`. `noMapReduceSearchResults` only detects `.getRange()` results.
177
+ Oracle's help center says "NetSuite loads the log Object by default for all script types. You do not need to load it
178
+ manually", so `noMissingModules` leaves `log` (and `util`) alone. Oracle's two skills disagree: the upgrade skill says
179
+ the global exists but loading it is recommended, the learning skill says it is not global (the higher-ranked upgrade
180
+ skill and the documentation win). Two opposite styles are therefore available, and you should enable only one:
181
+ `requireLogModule` (in `all`) wants `N/log` loaded whenever `log.*` is used, following the upgrade skill's
182
+ recommendation; `noLogModule` (opt-in) wants the global.
110
183
 
111
- ### Rules drawn from Oracle's agent skills
184
+ ### Native Biome rules
112
185
 
113
- `noMissingModules`, `noGlobalShadowing`, `noEval`, `noDynamicSuiteQL`, `noSuiteScript1Api`, `noModuleScopeApiCalls`,
114
- `noRecordInLoop` and `noNativeIterators` come from Oracle's
115
- [agent skills](https://github.com/oracle/netsuite-suitecloud-sdk/tree/master/packages/agent-skills) (SuiteScript
116
- upgrade, OWASP secure coding and the SAFE guide). `noMissingModules` leaves `util` alone because it is a global.
117
- `noDynamicSuiteQL` does not flag a bare variable, whose contents it cannot see. `noRecordInLoop` is opt-in because
118
- a Map/Reduce `map` entry point legitimately handles one record per call.
186
+ Rules Biome already ships are not reimplemented. `configs/recommended` enables `noGlobalEval` and `noImpliedEval` for
187
+ Oracle's OWASP skill, and `configs/all` adds the "Modernize JavaScript" rules from the upgrade skill
188
+ (`noVar`, `useConst`, `useTemplate`, `useIncludes`, `useForOf`). `noImpliedEval` and `useIncludes` are Biome nursery
189
+ rules, which may change or be renamed in a Biome release. `noBrowserGlobals` flags `alert` only in server scripts,
190
+ which Biome's `noAlert` cannot do because it has no notion of a script type.
119
191
 
120
192
  ### `entryPoints` and script types
121
193
 
@@ -123,15 +195,18 @@ Biome plugins cannot read comments, so the script type is **inferred from what t
123
195
  (`beforeLoad` → User Event, `onRequest` → Suitelet, ...). The rule reports mixed entry points
124
196
  (`pageInit` with `execute`) and missing required ones (Map/Reduce needs `getInputData` and `map` or `reduce`).
125
197
  Modules that return no distinctive entry point are libraries and are ignored. Generic names such as
126
- `get`, `post`, `map` and `run` alone never make a module a script.
198
+ `get`, `post`, `map` and `run` alone never make a module a script. `noWrongScriptTypeModules` follows Oracle's
199
+ "Supported Script Types" table, except for `N/ui/message`: the table lists it as client-only, but Suitelets and user
200
+ event `beforeLoad` scripts load it to build the message `Form.addPageInitMessage` shows, as Oracle's own FAQ sample
201
+ does.
127
202
 
128
203
  ### Choosing what to enforce
129
204
 
130
205
  - **Pick rules.** `log-args`' two options are the separate `logRequireTitle` / `logRequireDetails` rules.
131
- - **Scope with `includes`.** For example, the global `log` is not available in client scripts:
206
+ - **Scope with `includes`.** For example, keep `noRecordInLoop` away from Map/Reduce `map` files:
132
207
 
133
208
  ```jsonc
134
- { "path": "biome-plugin-suitescript/noLogModule", "includes": ["src/**", "!src/**/client/**"] }
209
+ { "path": "biome-plugin-suitescript/noRecordInLoop", "includes": ["src/**", "!src/**/map-reduce/**"] }
135
210
  ```
136
211
  - **Custom module identifiers.** Generate a rule with your own mapping (and omit `moduleVars`):
137
212
 
@@ -152,7 +227,8 @@ npx biome-plugin-suitescript check "src/**/*.ts" # or explicit globs
152
227
  npx biome-plugin-suitescript check --format github # text (default) | json | github
153
228
  ```
154
229
 
155
- It reports:
230
+ It reports (the `@NApiVersion` and `@NScriptType` checks follow the `api-version` and `script-type` rules of both
231
+ [acdvs](https://github.com/acdvs/eslint-plugin-suitescript) and [btonasse](https://github.com/btonasse/eslint-plugin-suitescript)'s ESLint plugins):
156
232
 
157
233
  - `@NApiVersion` other than `2.1` (2.0, 2.x, 1.0 and invalid values), or missing when the file is a script.
158
234
  - `@NScriptType` with no or an invalid value, or missing when the returned entry points imply one.