biome-plugin-suitescript 0.1.0 → 0.2.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 (36) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +208 -149
  3. package/biome-manifest.json +95 -3
  4. package/configs/suitescript2.json +5 -5
  5. package/dist/bin/biome-plugin-suitescript.js +23 -23
  6. package/dist/cli/analyze.js +33 -3
  7. package/dist/lib/module-script-types.js +70 -0
  8. package/dist/lib/script-side.js +83 -0
  9. package/dist/lib/script-types.js +12 -0
  10. package/dist/lib/suitescript.js +23 -0
  11. package/dist/rules/noAmdName.grit +9 -9
  12. package/dist/rules/noBeforeSubmitRecordReload.js +19 -0
  13. package/dist/rules/noBrowserGlobals.js +23 -0
  14. package/dist/rules/noClientLog.js +14 -0
  15. package/dist/rules/noDynamicSuiteQL.js +24 -0
  16. package/dist/rules/noEmptyEntryPoints.js +19 -0
  17. package/dist/rules/noEval.js +21 -0
  18. package/dist/rules/noGlobalShadowing.js +34 -0
  19. package/dist/rules/noHardcodedSecrets.js +38 -0
  20. package/dist/rules/noHeaderUnderscores.js +23 -0
  21. package/dist/rules/noInsecureHttpsUrl.js +20 -0
  22. package/dist/rules/noLogModule.js +4 -1
  23. package/dist/rules/noMapReduceSearchResults.js +33 -0
  24. package/dist/rules/noMissingModules.js +26 -0
  25. package/dist/rules/noModuleExtensions.grit +12 -12
  26. package/dist/rules/noModuleScopeApiCalls.js +39 -0
  27. package/dist/rules/noNativeIterators.js +16 -0
  28. package/dist/rules/noQueryColumnFieldAndFormula.js +19 -0
  29. package/dist/rules/noRecordInLoop.js +31 -0
  30. package/dist/rules/noSuiteScript1Api.js +10 -0
  31. package/dist/rules/noUserEventContextSave.js +12 -0
  32. package/dist/rules/noWrongScriptTypeModules.js +28 -0
  33. package/dist/rules/requireClientValidateReturn.js +27 -0
  34. package/dist/rules/requireCustpagePrefix.js +22 -0
  35. package/package.json +83 -83
  36. package/schema.json +21 -21
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Eric Nemchik
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Eric Nemchik
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,149 +1,208 @@
1
- # biome-plugin-suitescript
2
-
3
- [Biome](https://biomejs.dev) lint plugins and a tag checker for NetSuite **SuiteScript 2.1**, ported from
4
- [acdvs/eslint-plugin-suitescript](https://github.com/acdvs/eslint-plugin-suitescript) and
5
- [btonasse/eslint-plugin-suitescript](https://github.com/btonasse/eslint-plugin-suitescript).
6
-
7
- Requires Biome **>= 2.6.4** (npm-distributed plugins use `biome-manifest.json`) and Node >= 22 for the CLI.
8
- Rules work on both AMD `define()` and ES `import`/`export` source, in `.js` and `.ts`.
9
-
10
- ## Install
11
-
12
- ```sh
13
- npm i -D @biomejs/biome biome-plugin-suitescript
14
- ```
15
-
16
- ## Use
17
-
18
- ```jsonc
19
- // biome.json
20
- {
21
- "extends": ["biome-plugin-suitescript/configs/suitescript2"], // globals: define, require, log, util
22
- "plugins": [
23
- "biome-plugin-suitescript/presets/recommended",
24
- "biome-plugin-suitescript/noLogModule" // add individual rules on top of a preset
25
- ]
26
- }
27
- ```
28
-
29
- Run the tag checker beside `biome check` (see [Tag checker](#tag-checker)):
30
-
31
- ```sh
32
- npx biome-plugin-suitescript check
33
- ```
34
-
35
- ## Rules
36
-
37
- Each rule has one fixed severity: `error` for things that fail at runtime or on upload, `warn` for conventions.
38
- 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 | 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
-
52
- `noAmdName`, `noExtraModules` and `noModuleExtensions` only apply to `define()`. The rest cover `import` too.
53
-
54
- | Preset | Contents |
55
- | --------------------- | ----------------------------------------------------------------------------------------------------------- |
56
- | `presets/recommended` | every rule above except `noLogModule` and `entryPoints` (no false positives on libraries or client scripts) |
57
- | `presets/minimal` | `noExtraModules`, `noInvalidModules` |
58
- | `presets/all` | `recommended` + `noLogModule` + `entryPoints` |
59
-
60
- Suppress a diagnostic with `// biome-ignore lint/plugin: reason`.
61
-
62
- ### `entryPoints` and script types
63
-
64
- Biome plugins cannot read comments, so the script type is **inferred from what the module returns**
65
- (`beforeLoad` → User Event, `onRequest` → Suitelet, ...). The rule reports mixed entry points
66
- (`pageInit` with `execute`) and missing required ones (Map/Reduce needs `getInputData` and `map` or `reduce`).
67
- Modules that return no distinctive entry point are libraries and are ignored. Generic names such as
68
- `get`, `post`, `map` and `run` alone never make a module a script.
69
-
70
- ### Choosing what to enforce
71
-
72
- - **Pick rules.** `log-args`' two options are the separate `logRequireTitle` / `logRequireDetails` rules.
73
- - **Scope with `includes`.** For example, the global `log` is not available in client scripts:
74
-
75
- ```jsonc
76
- { "path": "biome-plugin-suitescript/noLogModule", "includes": ["src/**", "!src/**/client/**"] }
77
- ```
78
- - **Custom module identifiers.** Generate a rule with your own mapping (and omit `moduleVars`):
79
-
80
- ```sh
81
- npx biome-plugin-suitescript generate module-vars --set N/record=nRecord --out plugins/module-vars.js
82
- ```
83
-
84
- then add `"./plugins/module-vars.js"` to `plugins`.
85
-
86
- ## Tag checker
87
-
88
- `@NApiVersion` and `@NScriptType` live in comments, which no Biome plugin can read, so a small CLI checks them.
89
- It is **report-only**: it never edits files.
90
-
91
- ```sh
92
- npx biome-plugin-suitescript check # uses biome-plugin-suitescript.json
93
- npx biome-plugin-suitescript check "src/**/*.ts" # or explicit globs
94
- npx biome-plugin-suitescript check --format github # text (default) | json | github
95
- ```
96
-
97
- It reports:
98
-
99
- - `@NApiVersion` other than `2.1` (2.0, 2.x, 1.0 and invalid values), or missing when the file is a script.
100
- - `@NScriptType` with no or an invalid value, or missing when the returned entry points imply one.
101
- - A declared script type that does not match the returned entry points, or lacks its required ones.
102
-
103
- Files with no tags and no implied entry points (libraries) are skipped. For scripts still on 1.0, 2.0 or 2.x, the
104
- output points to Oracle's
105
- [`netsuite-suitescript-upgrade`](https://github.com/oracle/netsuite-suitecloud-sdk/tree/master/packages/agent-skills/netsuite-suitescript-upgrade)
106
- agent skill for the conversion.
107
-
108
- Exit codes: `0` clean, `1` problems found, `2` usage or configuration error.
109
-
110
- ### Configuration
111
-
112
- List only the **script sources**: files deployed to the file cabinet, or the sources they are transpiled from (not
113
- the transpiled output, and not build files such as `swc.mts`). The CLI refuses to run without a list rather than
114
- guessing from `deploy.xml`.
115
-
116
- ```jsonc
117
- // biome-plugin-suitescript.json
118
- {
119
- "$schema": "./node_modules/biome-plugin-suitescript/schema.json",
120
- "includes": ["src/TypeScripts/**/*.{js,ts}", "!**/*.d.ts", "!**/*.test.*"]
121
- }
122
- ```
123
-
124
- `!` negation works as in Biome's `includes`. Reuse the same globs in the plugin `includes` in `biome.json` to scope the
125
- Biome rules identically.
126
-
127
- ## Development
128
-
129
- The source is TypeScript under `src/`, compiled by `tsc` to `dist/` (the only code that is published). Scripts and
130
- tests are TypeScript too and run directly on Node. Node and npm versions come from `devEngines` in `package.json`
131
- (also in `.nvmrc` and `.tool-versions`).
132
-
133
- ```sh
134
- npm install
135
- npm run build # tsc -> dist/, copies GritQL rules, regenerates biome-manifest.json
136
- npm run typecheck # tsc --noEmit over src, scripts and tests
137
- npm run check # biome check (npm run format applies fixes)
138
- npm run sync-modules # refreshes the N/ module list from @hitc/netsuite-types
139
- npm test # builds first, then tests the built package
140
- ```
141
-
142
- Git hooks (lefthook) run Biome on staged files before commit and reinstall dependencies after checkout or merge.
143
-
144
- Commits follow [Conventional Commits](https://www.conventionalcommits.org); release-please opens a release PR and
145
- publishes to npm when it is merged, using npm trusted publishing (OIDC, no token; the package settings on npmjs.com
146
- allow this repo and `release.yml`). Dependencies are managed by Renovate: dev
147
- dependencies are pinned, the `@biomejs/biome` peer range stays a caret, and Biome and `@biomejs/runtime` update together.
148
-
149
- MIT licensed. Module and script-type data and rule behavior derive from the MIT-licensed ESLint plugins above.
1
+ # biome-plugin-suitescript
2
+
3
+ [Biome](https://biomejs.dev) lint plugins and a tag checker for NetSuite **SuiteScript 2.1**, ported from
4
+ [acdvs/eslint-plugin-suitescript](https://github.com/acdvs/eslint-plugin-suitescript) and
5
+ [btonasse/eslint-plugin-suitescript](https://github.com/btonasse/eslint-plugin-suitescript).
6
+
7
+ Requires Biome **>= 2.6.4** (npm-distributed plugins use `biome-manifest.json`) and Node >= 22 for the CLI.
8
+ Rules work on both AMD `define()` and ES `import`/`export` source, in `.js` and `.ts`.
9
+
10
+ ## Install
11
+
12
+ ```sh
13
+ npm i -D @biomejs/biome biome-plugin-suitescript
14
+ ```
15
+
16
+ ## Use
17
+
18
+ ```jsonc
19
+ // biome.json
20
+ {
21
+ "extends": ["biome-plugin-suitescript/configs/suitescript2"], // globals: define, require, log, util
22
+ "plugins": [
23
+ "biome-plugin-suitescript/presets/recommended",
24
+ "biome-plugin-suitescript/noLogModule" // add individual rules on top of a preset
25
+ ]
26
+ }
27
+ ```
28
+
29
+ Run the tag checker beside `biome check` (see [Tag checker](#tag-checker)):
30
+
31
+ ```sh
32
+ npx biome-plugin-suitescript check
33
+ ```
34
+
35
+ ## Rules
36
+
37
+ Each rule has one fixed severity: `error` for things that fail at runtime or on upload, `warn` for conventions.
38
+ 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`) |
80
+
81
+ Suppress a diagnostic with `// biome-ignore lint/plugin: reason`.
82
+
83
+ ### Opt-in rules and typings
84
+
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.
94
+
95
+ ### Logging and `N/log`
96
+
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.
110
+
111
+ ### Rules drawn from Oracle's agent skills
112
+
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.
119
+
120
+ ### `entryPoints` and script types
121
+
122
+ Biome plugins cannot read comments, so the script type is **inferred from what the module returns**
123
+ (`beforeLoad` → User Event, `onRequest` → Suitelet, ...). The rule reports mixed entry points
124
+ (`pageInit` with `execute`) and missing required ones (Map/Reduce needs `getInputData` and `map` or `reduce`).
125
+ 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.
127
+
128
+ ### Choosing what to enforce
129
+
130
+ - **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:
132
+
133
+ ```jsonc
134
+ { "path": "biome-plugin-suitescript/noLogModule", "includes": ["src/**", "!src/**/client/**"] }
135
+ ```
136
+ - **Custom module identifiers.** Generate a rule with your own mapping (and omit `moduleVars`):
137
+
138
+ ```sh
139
+ npx biome-plugin-suitescript generate module-vars --set N/record=nRecord --out plugins/module-vars.js
140
+ ```
141
+
142
+ then add `"./plugins/module-vars.js"` to `plugins`.
143
+
144
+ ## Tag checker
145
+
146
+ `@NApiVersion` and `@NScriptType` live in comments, which no Biome plugin can read, so a small CLI checks them.
147
+ It is **report-only**: it never edits files.
148
+
149
+ ```sh
150
+ npx biome-plugin-suitescript check # uses biome-plugin-suitescript.json
151
+ npx biome-plugin-suitescript check "src/**/*.ts" # or explicit globs
152
+ npx biome-plugin-suitescript check --format github # text (default) | json | github
153
+ ```
154
+
155
+ It reports:
156
+
157
+ - `@NApiVersion` other than `2.1` (2.0, 2.x, 1.0 and invalid values), or missing when the file is a script.
158
+ - `@NScriptType` with no or an invalid value, or missing when the returned entry points imply one.
159
+ - A module from the wrong side of the declared script type (for example `N/file` in a client script), or one the declared type does not support at all (`N/http`, `N/https`, `N/llm` and `N/sftp` in a custom tool; custom modules in a bundle installation script).
160
+ - A declared script type that does not match the returned entry points, or lacks its required ones.
161
+
162
+ Files with no tags and no implied entry points (libraries) are skipped. For scripts still on 1.0, 2.0 or 2.x, the
163
+ output points to Oracle's
164
+ [`netsuite-suitescript-upgrade`](https://github.com/oracle/netsuite-suitecloud-sdk/tree/master/packages/agent-skills/netsuite-suitescript-upgrade)
165
+ agent skill for the conversion.
166
+
167
+ Exit codes: `0` clean, `1` problems found, `2` usage or configuration error.
168
+
169
+ ### Configuration
170
+
171
+ List only the **script sources**: files deployed to the file cabinet, or the sources they are transpiled from (not
172
+ the transpiled output, and not build files such as `swc.mts`). The CLI refuses to run without a list rather than
173
+ guessing from `deploy.xml`.
174
+
175
+ ```jsonc
176
+ // biome-plugin-suitescript.json
177
+ {
178
+ "$schema": "./node_modules/biome-plugin-suitescript/schema.json",
179
+ "includes": ["src/TypeScripts/**/*.{js,ts}", "!**/*.d.ts", "!**/*.test.*"]
180
+ }
181
+ ```
182
+
183
+ `!` negation works as in Biome's `includes`. Reuse the same globs in the plugin `includes` in `biome.json` to scope the
184
+ Biome rules identically.
185
+
186
+ ## Development
187
+
188
+ The source is TypeScript under `src/`, compiled by `tsc` to `dist/` (the only code that is published). Scripts and
189
+ tests are TypeScript too and run directly on Node. Node and npm versions come from `devEngines` in `package.json`
190
+ (also in `.nvmrc` and `.tool-versions`).
191
+
192
+ ```sh
193
+ npm install
194
+ npm run build # tsc -> dist/, copies GritQL rules, regenerates biome-manifest.json
195
+ npm run typecheck # tsc --noEmit over src, scripts and tests
196
+ npm run check # biome check (npm run format applies fixes)
197
+ npm run sync-modules # refreshes the N/ module list from @hitc/netsuite-types
198
+ npm test # builds first, then tests the built package
199
+ ```
200
+
201
+ Git hooks (lefthook) run Biome on staged files before commit and reinstall dependencies after checkout or merge.
202
+
203
+ Commits follow [Conventional Commits](https://www.conventionalcommits.org); release-please opens a release PR and
204
+ publishes to npm when it is merged, using npm trusted publishing (OIDC, no token; the package settings on npmjs.com
205
+ allow this repo and `release.yml`). Dependencies are managed by Renovate: dev
206
+ dependencies are pinned, the `@biomejs/biome` peer range stays a caret, and Biome and `@biomejs/runtime` update together.
207
+
208
+ MIT licensed. Module and script-type data and rule behavior derive from the MIT-licensed ESLint plugins above.
@@ -15,8 +15,27 @@
15
15
  "moduleVars",
16
16
  "logRequireTitle",
17
17
  "logRequireDetails",
18
- "noLogModule",
19
- "entryPoints"
18
+ "noMissingModules",
19
+ "noGlobalShadowing",
20
+ "noEval",
21
+ "noDynamicSuiteQL",
22
+ "noSuiteScript1Api",
23
+ "noModuleScopeApiCalls",
24
+ "noWrongScriptTypeModules",
25
+ "noBrowserGlobals",
26
+ "noUserEventContextSave",
27
+ "noInsecureHttpsUrl",
28
+ "entryPoints",
29
+ "noRecordInLoop",
30
+ "noNativeIterators",
31
+ "noBeforeSubmitRecordReload",
32
+ "noEmptyEntryPoints",
33
+ "noMapReduceSearchResults",
34
+ "requireCustpagePrefix",
35
+ "noHeaderUnderscores",
36
+ "noQueryColumnFieldAndFormula",
37
+ "requireClientValidateReturn",
38
+ "noClientLog"
20
39
  ],
21
40
  "minimal": ["noExtraModules", "noInvalidModules"],
22
41
  "recommended": [
@@ -26,7 +45,17 @@
26
45
  "noModuleExtensions",
27
46
  "moduleVars",
28
47
  "logRequireTitle",
29
- "logRequireDetails"
48
+ "logRequireDetails",
49
+ "noMissingModules",
50
+ "noGlobalShadowing",
51
+ "noEval",
52
+ "noDynamicSuiteQL",
53
+ "noSuiteScript1Api",
54
+ "noModuleScopeApiCalls",
55
+ "noWrongScriptTypeModules",
56
+ "noBrowserGlobals",
57
+ "noUserEventContextSave",
58
+ "noInsecureHttpsUrl"
30
59
  ]
31
60
  },
32
61
  "rules": [
@@ -45,17 +74,80 @@
45
74
  {
46
75
  "noAmdName": "./dist/rules/noAmdName.grit"
47
76
  },
77
+ {
78
+ "noBeforeSubmitRecordReload": "./dist/rules/noBeforeSubmitRecordReload.js"
79
+ },
80
+ {
81
+ "noBrowserGlobals": "./dist/rules/noBrowserGlobals.js"
82
+ },
83
+ {
84
+ "noClientLog": "./dist/rules/noClientLog.js"
85
+ },
86
+ {
87
+ "noDynamicSuiteQL": "./dist/rules/noDynamicSuiteQL.js"
88
+ },
89
+ {
90
+ "noEmptyEntryPoints": "./dist/rules/noEmptyEntryPoints.js"
91
+ },
92
+ {
93
+ "noEval": "./dist/rules/noEval.js"
94
+ },
48
95
  {
49
96
  "noExtraModules": "./dist/rules/noExtraModules.js"
50
97
  },
98
+ {
99
+ "noGlobalShadowing": "./dist/rules/noGlobalShadowing.js"
100
+ },
101
+ {
102
+ "noHardcodedSecrets": "./dist/rules/noHardcodedSecrets.js"
103
+ },
104
+ {
105
+ "noHeaderUnderscores": "./dist/rules/noHeaderUnderscores.js"
106
+ },
107
+ {
108
+ "noInsecureHttpsUrl": "./dist/rules/noInsecureHttpsUrl.js"
109
+ },
51
110
  {
52
111
  "noInvalidModules": "./dist/rules/noInvalidModules.js"
53
112
  },
54
113
  {
55
114
  "noLogModule": "./dist/rules/noLogModule.js"
56
115
  },
116
+ {
117
+ "noMapReduceSearchResults": "./dist/rules/noMapReduceSearchResults.js"
118
+ },
119
+ {
120
+ "noMissingModules": "./dist/rules/noMissingModules.js"
121
+ },
57
122
  {
58
123
  "noModuleExtensions": "./dist/rules/noModuleExtensions.grit"
124
+ },
125
+ {
126
+ "noModuleScopeApiCalls": "./dist/rules/noModuleScopeApiCalls.js"
127
+ },
128
+ {
129
+ "noNativeIterators": "./dist/rules/noNativeIterators.js"
130
+ },
131
+ {
132
+ "noQueryColumnFieldAndFormula": "./dist/rules/noQueryColumnFieldAndFormula.js"
133
+ },
134
+ {
135
+ "noRecordInLoop": "./dist/rules/noRecordInLoop.js"
136
+ },
137
+ {
138
+ "noSuiteScript1Api": "./dist/rules/noSuiteScript1Api.js"
139
+ },
140
+ {
141
+ "noUserEventContextSave": "./dist/rules/noUserEventContextSave.js"
142
+ },
143
+ {
144
+ "noWrongScriptTypeModules": "./dist/rules/noWrongScriptTypeModules.js"
145
+ },
146
+ {
147
+ "requireClientValidateReturn": "./dist/rules/requireClientValidateReturn.js"
148
+ },
149
+ {
150
+ "requireCustpagePrefix": "./dist/rules/requireCustpagePrefix.js"
59
151
  }
60
152
  ]
61
153
  },
@@ -1,5 +1,5 @@
1
- {
2
- "javascript": {
3
- "globals": ["define", "require", "log", "util"]
4
- }
5
- }
1
+ {
2
+ "javascript": {
3
+ "globals": ["define", "require", "log", "util"]
4
+ }
5
+ }
@@ -5,11 +5,11 @@ import { glob } from "tinyglobby";
5
5
  import { analyzeSource } from "../cli/analyze.js";
6
6
  import { format } from "../cli/format.js";
7
7
  import { MODULES } from "../lib/modules.js";
8
- const USAGE = `Usage:
9
- biome-plugin-suitescript check [globs...] [--config file] [--format text|json|github]
10
- biome-plugin-suitescript generate module-vars [--set N/module=identifier]... [--out file.js]
11
-
12
- check reads "includes" from biome-plugin-suitescript.json unless globs are given.
8
+ const USAGE = `Usage:
9
+ biome-plugin-suitescript check [globs...] [--config file] [--format text|json|github]
10
+ biome-plugin-suitescript generate module-vars [--set N/module=identifier]... [--out file.js]
11
+
12
+ check reads "includes" from biome-plugin-suitescript.json unless globs are given.
13
13
  Exit codes: 0 clean, 1 problems found, 2 usage or configuration error.`;
14
14
  function fail(message) {
15
15
  console.error(`${message}\n\n${USAGE}`);
@@ -99,24 +99,24 @@ function generate(args) {
99
99
  map[name] = id;
100
100
  }
101
101
  const out = flags.out?.[0] ?? "suitescript-module-vars.js";
102
- writeFileSync(out, `// Generated by biome-plugin-suitescript. Add to biome.json "plugins".
103
- import { defineRule, ast, registerDiagnostic } from "@biomejs/runtime/plugin";
104
-
105
- ${inline("define.js")}
106
- ${inline("biome-modules.js")}
107
- const MODULES = ${JSON.stringify(map, null, 2)};
108
-
109
- export const moduleVars = defineRule({
110
- \tquery: ast("JS_CALL_EXPRESSION", "JS_IMPORT"),
111
- \trun(node) {
112
- \t\tfor (const { name, param } of dependencies(node)) {
113
- \t\t\tconst expected = name && Object.hasOwn(MODULES, name) ? MODULES[name] : undefined;
114
- \t\t\tif (expected && param && param.text !== expected) {
115
- \t\t\t\tregisterDiagnostic(param, "warning", \`Identifier for \${name} should be '\${expected}'.\`);
116
- \t\t\t}
117
- \t\t}
118
- \t},
119
- });
102
+ writeFileSync(out, `// Generated by biome-plugin-suitescript. Add to biome.json "plugins".
103
+ import { defineRule, ast, registerDiagnostic } from "@biomejs/runtime/plugin";
104
+
105
+ ${inline("define.js")}
106
+ ${inline("biome-modules.js")}
107
+ const MODULES = ${JSON.stringify(map, null, 2)};
108
+
109
+ export const moduleVars = defineRule({
110
+ \tquery: ast("JS_CALL_EXPRESSION", "JS_IMPORT"),
111
+ \trun(node) {
112
+ \t\tfor (const { name, param } of dependencies(node)) {
113
+ \t\t\tconst expected = name && Object.hasOwn(MODULES, name) ? MODULES[name] : undefined;
114
+ \t\t\tif (expected && param && param.text !== expected) {
115
+ \t\t\t\tregisterDiagnostic(param, "warning", \`Identifier for \${name} should be '\${expected}'.\`);
116
+ \t\t\t}
117
+ \t\t}
118
+ \t},
119
+ });
120
120
  `);
121
121
  console.log(`Wrote ${out}`);
122
122
  }