biome-plugin-suitescript 0.3.0 → 0.5.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 (2) hide show
  1. package/README.md +86 -74
  2. package/package.json +10 -1
package/README.md CHANGED
@@ -1,11 +1,18 @@
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`.
14
+ Coming from the ESLint plugins? See [Migrating from the ESLint plugins](docs/migrating-from-eslint.md).
15
+ Want to see it run first? Open the [example project](https://github.com/nemchik/biome-plugin-suitescript-example) in GitHub Codespaces, or use it as a template for your own.
9
16
 
10
17
  ## Install
11
18
 
@@ -57,78 +64,78 @@ decided is in [docs/rule-priority.md](docs/rule-priority.md).
57
64
 
58
65
  <!-- rules-table:start -->
59
66
 
60
- | Rule | Severity | Presets | Description | Rule source |
61
- | ---------------------------------------- | -------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
62
- | `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/oracle/netsuite-suitecloud-sdk/blob/master/packages/agent-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) |
63
- | `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/oracle/netsuite-suitecloud-sdk/blob/master/packages/agent-skills/netsuite-suitescript-upgrade/SKILL.md) |
64
- | `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/oracle/netsuite-suitecloud-sdk/blob/master/packages/agent-skills/netsuite-suitescript-upgrade/SKILL.md), [learning skill](https://github.com/oracle/netsuite-suitecloud-sdk/blob/master/packages/agent-skills/netsuite-suitescript-learning/SKILL.md) |
65
- | `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/oracle/netsuite-suitecloud-sdk/blob/master/packages/agent-skills/netsuite-sdf-safe-guide/SKILL.md) |
66
- | `noNativeIterators` | error | :green_circle::large_blue_circle: | No generator functions or `Symbol.iterator` (GraalJS) | Tier 1: [SAFE guide](https://github.com/oracle/netsuite-suitecloud-sdk/blob/master/packages/agent-skills/netsuite-sdf-safe-guide/SKILL.md) |
67
- | `noSuiteQLTypeInSearch` | error | :green_circle::large_blue_circle: | No SuiteQL abbreviation (`PurchOrd`) as the `type` of `search.create` | Tier 1: [SAFE guide](https://github.com/oracle/netsuite-suitecloud-sdk/blob/master/packages/agent-skills/netsuite-sdf-safe-guide/SKILL.md) |
68
- | `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/oracle/netsuite-suitecloud-sdk/blob/master/packages/agent-skills/netsuite-suitescript-upgrade/SKILL.md) |
69
- | `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/oracle/netsuite-suitecloud-sdk/blob/master/packages/agent-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) |
70
- | `noDynamicRequire` | warn | :green_circle::large_blue_circle: | `require()` takes string literal module names, not expressions | Tier 1: [OWASP skill](https://github.com/oracle/netsuite-suitecloud-sdk/blob/master/packages/agent-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) |
71
- | `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/oracle/netsuite-suitecloud-sdk/blob/master/packages/agent-skills/netsuite-owasp-secure-coding/SKILL.md) |
72
- | `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/oracle/netsuite-suitecloud-sdk/blob/master/packages/agent-skills/netsuite-owasp-secure-coding/SKILL.md) |
73
- | `noPostMessageWildcard` | warn | :green_circle::large_blue_circle: | No `'*'` target origin in `postMessage` | Tier 1: [OWASP skill](https://github.com/oracle/netsuite-suitecloud-sdk/blob/master/packages/agent-skills/netsuite-owasp-secure-coding/SKILL.md) |
74
- | `noWildcardCors` | warn | :green_circle::large_blue_circle: | No `Access-Control-Allow-Origin: *` response header | Tier 1: [OWASP skill](https://github.com/oracle/netsuite-suitecloud-sdk/blob/master/packages/agent-skills/netsuite-owasp-secure-coding/SKILL.md) |
75
- | `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/oracle/netsuite-suitecloud-sdk/blob/master/packages/agent-skills/netsuite-suitescript-upgrade/SKILL.md), [learning skill](https://github.com/oracle/netsuite-suitecloud-sdk/blob/master/packages/agent-skills/netsuite-suitescript-learning/SKILL.md) |
76
- | `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/oracle/netsuite-suitecloud-sdk/blob/master/packages/agent-skills/netsuite-sdf-safe-guide/SKILL.md) |
77
- | `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/oracle/netsuite-suitecloud-sdk/blob/master/packages/agent-skills/netsuite-suitescript-upgrade/SKILL.md), [learning skill](https://github.com/oracle/netsuite-suitecloud-sdk/blob/master/packages/agent-skills/netsuite-suitescript-learning/SKILL.md) |
78
- | `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/oracle/netsuite-suitecloud-sdk/blob/master/packages/agent-skills/netsuite-owasp-secure-coding/SKILL.md) |
79
- | `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/oracle/netsuite-suitecloud-sdk/blob/master/packages/agent-skills/netsuite-owasp-secure-coding/SKILL.md) |
80
- | `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/oracle/netsuite-suitecloud-sdk/blob/master/packages/agent-skills/netsuite-owasp-secure-coding/references/07-file-upload-download.md) |
81
- | `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/oracle/netsuite-suitecloud-sdk/blob/master/packages/agent-skills/netsuite-owasp-secure-coding/SKILL.md) |
82
- | `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/oracle/netsuite-suitecloud-sdk/blob/master/packages/agent-skills/netsuite-owasp-secure-coding/SKILL.md) |
83
- | `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/oracle/netsuite-suitecloud-sdk/blob/master/packages/agent-skills/netsuite-sdf-safe-guide/references/03-performance-optimization.md) |
84
- | `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/oracle/netsuite-suitecloud-sdk/blob/master/packages/agent-skills/netsuite-sdf-safe-guide/SKILL.md) |
85
- | `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/oracle/netsuite-suitecloud-sdk/blob/master/packages/agent-skills/netsuite-owasp-secure-coding/SKILL.md) |
86
- | `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) |
87
- | `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) |
88
- | `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) |
89
- | `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) |
90
- | `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) |
91
- | `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) |
92
- | `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) |
93
- | `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) |
94
- | `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) |
95
- | `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) |
96
- | `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) |
97
- | `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) |
98
- | `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) |
99
- | `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) |
100
- | `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) |
101
- | `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) |
102
- | `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) |
103
- | `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) |
104
- | `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) |
105
- | `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) |
106
- | `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) |
107
- | `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) |
108
- | `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) |
109
- | `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) |
110
- | `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) |
111
- | `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) |
112
- | `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) |
113
- | `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) |
114
- | `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) |
115
- | `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) |
116
- | `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) |
117
- | `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) |
118
- | `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) |
119
- | `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) |
120
- | `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) |
121
- | `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) |
122
- | `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) |
123
- | `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) |
124
- | `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) |
125
- | `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/) |
126
- | `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/) |
127
- | `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/) |
128
- | `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/) |
129
- | `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/) |
130
- | `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/) |
131
- | `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/) |
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-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) |
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
+ | [`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-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) |
85
+ | [`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) |
86
+ | [`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) |
87
+ | [`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) |
88
+ | [`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) |
89
+ | [`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) |
90
+ | [`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) |
91
+ | [`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) |
92
+ | [`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) |
93
+ | [`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) |
94
+ | [`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) |
95
+ | [`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) |
96
+ | [`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) |
97
+ | [`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) |
98
+ | [`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) |
99
+ | [`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) |
100
+ | [`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) |
101
+ | [`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) |
102
+ | [`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) |
103
+ | [`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) |
104
+ | [`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) |
105
+ | [`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) |
106
+ | [`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) |
107
+ | [`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) |
108
+ | [`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) |
109
+ | [`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) |
110
+ | [`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) |
111
+ | [`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) |
112
+ | [`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) |
113
+ | [`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) |
114
+ | [`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) |
115
+ | [`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) |
116
+ | [`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) |
117
+ | [`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) |
118
+ | [`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) |
119
+ | [`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) |
120
+ | [`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) |
121
+ | [`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) |
122
+ | [`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) |
123
+ | [`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-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) |
124
+ | [`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) |
125
+ | [`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) |
126
+ | [`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) |
127
+ | [`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) |
128
+ | [`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) |
129
+ | [`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) |
130
+ | [`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) |
131
+ | [`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) |
132
+ | `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/) |
133
+ | `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/) |
134
+ | `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/) |
135
+ | `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/) |
136
+ | `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/) |
137
+ | `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/) |
138
+ | `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/) |
132
139
 
133
140
  <!-- rules-table:end -->
134
141
 
@@ -213,7 +220,7 @@ does.
213
220
 
214
221
  ## Tag checker
215
222
 
216
- `@NApiVersion` and `@NScriptType` live in comments, which no Biome plugin can read, so a small CLI checks them.
223
+ `@NApiVersion` and `@NScriptType` live in comments, which no Biome plugin can read (we have [asked Biome for comment access](https://github.com/biomejs/biome/discussions/12939); this CLI goes away if it ships), so a small CLI checks them.
217
224
  It is **report-only**: it never edits files.
218
225
 
219
226
  ```sh
@@ -256,6 +263,8 @@ Biome rules identically.
256
263
 
257
264
  ## Development
258
265
 
266
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for how to report bugs, propose rules and open pull requests.
267
+
259
268
  The source is TypeScript under `src/`, compiled by `tsc` to `dist/` (the only code that is published). Scripts and
260
269
  tests are TypeScript too and run directly on Node. Node and npm versions come from `devEngines` in `package.json`
261
270
  (also in `.nvmrc` and `.tool-versions`).
@@ -265,7 +274,10 @@ npm install
265
274
  npm run build # tsc -> dist/, copies GritQL rules, regenerates biome-manifest.json
266
275
  npm run typecheck # tsc --noEmit over src, scripts and tests
267
276
  npm run check # biome check (npm run format applies fixes)
277
+ npm run new-rule -- noFooBar --tier 3 # scaffolds a rule, fixture, test case and sources entry (see CONTRIBUTING.md)
268
278
  npm run sync-modules # refreshes the N/ module list from @hitc/netsuite-types
279
+ npm run readme # regenerates the rules table above (after npm run build)
280
+ npm run docs # regenerates the per-rule pages in docs/rules/ and the migration table (after npm run build)
269
281
  npm test # builds first, then tests the built package
270
282
  ```
271
283
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "biome-plugin-suitescript",
3
- "version": "0.3.0",
3
+ "version": "0.5.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",
@@ -37,11 +37,19 @@
37
37
  "prebuild": "node -e \"require(\\\"node:fs\\\").rmSync(\\\"dist\\\",{recursive:true,force:true})\"",
38
38
  "build": "tsc -p tsconfig.build.json && node scripts/build-manifest.ts",
39
39
  "check": "biome check",
40
+ "docs": "node scripts/rule-docs.ts && node scripts/migration-table.ts",
40
41
  "preformat": "npm run typecheck",
41
42
  "format": "biome check --write",
42
43
  "lint": "biome lint",
44
+ "new-rule": "node scripts/new-rule.ts",
43
45
  "prepack": "npm run build",
44
46
  "readme": "node scripts/rules-table.ts",
47
+ "skills": "skills",
48
+ "skills:add": "skills add --skill \"*\" --agent universal --agent claude-code --yes",
49
+ "skills:check": "skills check",
50
+ "skills:install": "skills experimental_install && node scripts/link-claude-skills.ts",
51
+ "skills:list": "skills list",
52
+ "skills:update": "skills update --project --yes && node scripts/link-claude-skills.ts",
45
53
  "sync-modules": "node scripts/sync-modules.ts",
46
54
  "sync-typings": "node scripts/sync-typings.ts",
47
55
  "pretest": "npm run build",
@@ -59,6 +67,7 @@
59
67
  "@types/node": "24.19.2",
60
68
  "git-pull-run": "2.1.0",
61
69
  "lefthook": "2.2.2",
70
+ "skills": "1.7.6",
62
71
  "typescript": "7.0.2"
63
72
  },
64
73
  "peerDependencies": {