biome-plugin-suitescript 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +154 -78
  2. package/biome-manifest.json +179 -17
  3. package/configs/all.json +29 -0
  4. package/configs/minimal.json +14 -0
  5. package/configs/recommended.json +20 -0
  6. package/dist/lib/define.js +35 -5
  7. package/dist/lib/headers.js +12 -0
  8. package/dist/lib/module-calls.js +65 -0
  9. package/dist/lib/module-script-types.js +7 -1
  10. package/dist/lib/options.js +35 -0
  11. package/dist/lib/required-options.js +14 -0
  12. package/dist/lib/script-types.js +25 -14
  13. package/dist/lib/suiteql.js +16 -0
  14. package/dist/lib/typings-data.js +4327 -0
  15. package/dist/lib/typings.js +2 -0
  16. package/dist/rules/logRequireDetails.js +4 -0
  17. package/dist/rules/logRequireTitle.js +1 -0
  18. package/dist/rules/noAuthorizationHeaderInInternalRequest.js +24 -0
  19. package/dist/rules/noBlockedHttpsHeader.js +32 -0
  20. package/dist/rules/noBlockedResponseHeader.js +35 -0
  21. package/dist/rules/noBrowserGlobals.js +2 -0
  22. package/dist/rules/noConflictingClientScriptProps.js +36 -0
  23. package/dist/rules/noDynamicRequire.js +23 -0
  24. package/dist/rules/noDynamicSuiteQL.js +11 -6
  25. package/dist/rules/noExtraDeps.js +16 -0
  26. package/dist/rules/noExtraParams.js +17 -0
  27. package/dist/rules/noGlobalShadowing.js +6 -2
  28. package/dist/rules/noHardcodedAuthPasswords.js +27 -0
  29. package/dist/rules/noHardcodedSecrets.js +7 -2
  30. package/dist/rules/noInnerHtml.js +41 -0
  31. package/dist/rules/noInsecureCookie.js +27 -0
  32. package/dist/rules/noInvalidDefine.js +47 -0
  33. package/dist/rules/noInvalidEnumMember.js +24 -0
  34. package/dist/rules/noLocationHeader.js +20 -0
  35. package/dist/rules/noLogModule.js +8 -5
  36. package/dist/rules/noMissingModules.js +6 -3
  37. package/dist/rules/noModuleScopeApiCalls.js +14 -4
  38. package/dist/rules/noMultipleDefine.js +14 -0
  39. package/dist/rules/noNativeIterators.js +8 -5
  40. package/dist/rules/noPositionalOptions.js +26 -0
  41. package/dist/rules/noPostMessageWildcard.js +15 -0
  42. package/dist/rules/noPublicFileUpload.js +20 -0
  43. package/dist/rules/noRecordInLoop.js +8 -5
  44. package/dist/rules/noRequireInsteadOfDefine.js +25 -0
  45. package/dist/rules/noSecretKeyGuidAndSecret.js +16 -0
  46. package/dist/rules/noSensitiveBrowserStorage.js +24 -0
  47. package/dist/rules/noStackInResponse.js +21 -0
  48. package/dist/rules/noSuiteQLSelectStar.js +18 -0
  49. package/dist/rules/noSuiteQLSystemNoteJoin.js +19 -0
  50. package/dist/rules/noSuiteQLTypeInSearch.js +41 -0
  51. package/dist/rules/noSuiteScript1Api.js +77 -3
  52. package/dist/rules/noUnknownModuleMember.js +23 -0
  53. package/dist/rules/noUnknownOptions.js +31 -0
  54. package/dist/rules/noUnsupportedServerWidgetFieldType.js +40 -0
  55. package/dist/rules/noWeakHash.js +17 -0
  56. package/dist/rules/noWildcardCors.js +16 -0
  57. package/dist/rules/noWrongScriptTypeModules.js +3 -1
  58. package/dist/rules/requireCustpagePrefix.js +14 -13
  59. package/dist/rules/requireEachReturn.js +38 -0
  60. package/dist/rules/requireLogModule.js +25 -0
  61. package/dist/rules/requireOptions.js +26 -0
  62. package/dist/rules/requireOrderByForPagedSuiteQL.js +17 -0
  63. package/dist/rules/requireQueryOptions.js +53 -0
  64. package/dist/rules/requireServerResponseOptions.js +33 -0
  65. package/dist/rules/requireServerWidgetOptions.js +37 -0
  66. package/package.json +10 -1
  67. package/dist/rules/noEval.js +0 -21
  68. package/dist/rules/noExtraModules.js +0 -23
  69. package/dist/rules/noUserEventContextSave.js +0 -12
@@ -0,0 +1,17 @@
1
+ import { ast, defineRule, registerDiagnostic } from "@biomejs/runtime/plugin";
2
+ import { staticSuiteql } from "../lib/suiteql.js";
3
+ // Oracle (query.runSuiteQLPaged): "You must specify a sorting order in the query
4
+ // definition when using this method to avoid duplicate or missing results. The
5
+ // query definition must provide a unique and unambiguous sorting order with a
6
+ // specified precedence." Only a query built from literals can be checked.
7
+ export const requireOrderByForPagedSuiteQL = defineRule({
8
+ query: ast("JS_CALL_EXPRESSION"),
9
+ run(node) {
10
+ if (!/(^|\.)runSuiteQLPaged(\.promise)?$/.test(node.callee?.text ?? ""))
11
+ return;
12
+ const sql = staticSuiteql(node);
13
+ if (sql && !/\bORDER\s+BY\b/i.test(sql)) {
14
+ registerDiagnostic(node, "warning", "A paged SuiteQL query needs an ORDER BY to avoid duplicate or missing results.");
15
+ }
16
+ },
17
+ });
@@ -0,0 +1,53 @@
1
+ import { defineRule, registerDiagnostic, semantic, } from "@biomejs/runtime/plugin";
2
+ import { moduleOfReceiver } from "../lib/module-calls.js";
3
+ import { MODULES } from "../lib/modules.js";
4
+ import { definedOptions } from "../lib/required-options.js";
5
+ // Oracle (N/query, Query.createColumn and createCondition): "options.fieldId:
6
+ // required if options.formula is not used" and "options.type: required if
7
+ // options.formula is used"; createCondition's `operator` is required and
8
+ // createSort's `column` is required. The type definitions (N/query.d.ts) outrank
9
+ // the page and give a formula-only condition its own overload without `operator`,
10
+ // so `operator` is required only when there is no `formula`. They type `type?` as
11
+ // optional with the formula, but their own comment says "Required if options.formula
12
+ // is used", which agrees with the page. Only an options object literal without a
13
+ // spread is checked. A receiver bound to another module is skipped:
14
+ // `search.createColumn({ name })` is N/search, which takes different options. When
15
+ // the binding cannot be resolved, a receiver named like another module's
16
+ // conventional identifier (`search`) is skipped too.
17
+ export const requireQueryOptions = defineRule({
18
+ query: semantic("JS_CALL_EXPRESSION"),
19
+ run(node, { model }) {
20
+ const callee = node.callee?.text ?? "";
21
+ const method = /(^|\.)(createColumn|createCondition|createSort)$/.exec(callee)?.[2];
22
+ if (!method)
23
+ return;
24
+ const receiver = node.callee?.children()[0];
25
+ const module = receiver && moduleOfReceiver(receiver, model);
26
+ if (module && module !== "N/query")
27
+ return;
28
+ const conventional = Object.entries(MODULES).find(([, identifier]) => identifier === receiver?.text)?.[0];
29
+ if (!module && conventional && conventional !== "N/query")
30
+ return;
31
+ const defined = definedOptions(node);
32
+ if (!defined)
33
+ return;
34
+ const problems = [];
35
+ if (method === "createSort") {
36
+ if (!defined.has("column"))
37
+ problems.push("needs the 'column' option");
38
+ }
39
+ else {
40
+ if (!defined.has("fieldId") && !defined.has("formula"))
41
+ problems.push("needs a 'fieldId' or a 'formula' option");
42
+ if (defined.has("formula") && !defined.has("type"))
43
+ problems.push("needs the 'type' option when it uses 'formula'");
44
+ if (method === "createCondition" &&
45
+ !defined.has("operator") &&
46
+ !defined.has("formula"))
47
+ problems.push("needs the 'operator' option");
48
+ }
49
+ for (const problem of problems) {
50
+ registerDiagnostic(node, "error", `${method}() ${problem}.`);
51
+ }
52
+ },
53
+ });
@@ -0,0 +1,33 @@
1
+ import { ast, defineRule, registerDiagnostic } from "@biomejs/runtime/plugin";
2
+ import { definedOptions } from "../lib/required-options.js";
3
+ const REQUIRED = {
4
+ addHeader: ["name", "value"],
5
+ getHeader: ["name"],
6
+ renderPdf: ["xmlString"],
7
+ sendRedirect: ["type", "identifier"],
8
+ setHeader: ["name", "value"],
9
+ write: ["output"],
10
+ writeFile: ["file"],
11
+ writeLine: ["output"],
12
+ writePage: ["pageObject"],
13
+ };
14
+ // Oracle (ServerResponse method pages): each method lists its required options and
15
+ // throws SSS_MISSING_REQD_ARGUMENT without them. The type definitions
16
+ // (N/http.d.ts) require the same options. Only calls on a receiver named
17
+ // like a response, with an options object literal and no spread, are checked.
18
+ export const requireServerResponseOptions = defineRule({
19
+ query: ast("JS_CALL_EXPRESSION"),
20
+ run(node) {
21
+ const match = /response\.(\w+)$/i.exec(node.callee?.text ?? "");
22
+ const method = match?.[1];
23
+ const required = method && REQUIRED[method];
24
+ const defined = required && definedOptions(node);
25
+ if (!required || !defined)
26
+ return;
27
+ for (const name of required) {
28
+ if (!defined.has(name)) {
29
+ registerDiagnostic(node, "error", `${method}() needs the '${name}' option.`);
30
+ }
31
+ }
32
+ },
33
+ });
@@ -0,0 +1,37 @@
1
+ import { ast, defineRule, registerDiagnostic } from "@biomejs/runtime/plugin";
2
+ import { definedOptions } from "../lib/required-options.js";
3
+ const LABELLED = ["id", "label"];
4
+ const REQUIRED = {
5
+ addButton: ["label"],
6
+ addColumn: [...LABELLED, "type"],
7
+ addCredentialField: [...LABELLED, "restrictToDomains", "restrictToScriptIds"],
8
+ addField: [...LABELLED, "type"],
9
+ addFieldGroup: LABELLED,
10
+ addSecretKeyField: LABELLED,
11
+ addSublist: [...LABELLED, "type"],
12
+ addSubtab: LABELLED,
13
+ addTab: LABELLED,
14
+ };
15
+ // Oracle (N/ui/serverWidget method pages): each add method lists the options it
16
+ // requires and throws SSS_MISSING_REQD_ARGUMENT without them. The type definitions
17
+ // (N/ui/serverWidget.d.ts) outrank the pages and disagree on two methods: they make
18
+ // addButton's `id` optional and addSecretKeyField's `restrictToScriptIds`
19
+ // optional, where the pages require both, so those two follow the type
20
+ // definitions. The receiver cannot
21
+ // be typed, so a method of the same name on another object can misfire. Only an
22
+ // options object literal without a spread is checked.
23
+ export const requireServerWidgetOptions = defineRule({
24
+ query: ast("JS_CALL_EXPRESSION"),
25
+ run(node) {
26
+ const method = /\.(\w+)$/.exec(node.callee?.text ?? "")?.[1];
27
+ const required = method && REQUIRED[method];
28
+ const defined = required && definedOptions(node);
29
+ if (!required || !defined)
30
+ return;
31
+ for (const name of required) {
32
+ if (!defined.has(name)) {
33
+ registerDiagnostic(node, "error", `${method}() needs the '${name}' option.`);
34
+ }
35
+ }
36
+ },
37
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "biome-plugin-suitescript",
3
- "version": "0.2.0",
3
+ "version": "0.4.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",
@@ -41,7 +41,15 @@
41
41
  "format": "biome check --write",
42
42
  "lint": "biome lint",
43
43
  "prepack": "npm run build",
44
+ "readme": "node scripts/rules-table.ts",
45
+ "skills": "skills",
46
+ "skills:add": "skills add --skill \"*\" --agent universal --agent claude-code --yes",
47
+ "skills:check": "skills check",
48
+ "skills:install": "skills experimental_install && node scripts/link-claude-skills.ts",
49
+ "skills:list": "skills list",
50
+ "skills:update": "skills update --project --yes && node scripts/link-claude-skills.ts",
44
51
  "sync-modules": "node scripts/sync-modules.ts",
52
+ "sync-typings": "node scripts/sync-typings.ts",
45
53
  "pretest": "npm run build",
46
54
  "test": "node --test test/*.test.ts",
47
55
  "typecheck": "tsc"
@@ -57,6 +65,7 @@
57
65
  "@types/node": "24.19.2",
58
66
  "git-pull-run": "2.1.0",
59
67
  "lefthook": "2.2.2",
68
+ "skills": "1.7.6",
60
69
  "typescript": "7.0.2"
61
70
  },
62
71
  "peerDependencies": {
@@ -1,21 +0,0 @@
1
- import { ast, defineRule, registerDiagnostic } from "@biomejs/runtime/plugin";
2
- const TIMERS = ["setTimeout", "setInterval"];
3
- // eval(), new Function() and string-form timers run arbitrary code.
4
- export const noEval = defineRule({
5
- query: ast("JS_CALL_EXPRESSION", "JS_NEW_EXPRESSION"),
6
- run(node) {
7
- const callee = node.callee?.text;
8
- if (!callee)
9
- return;
10
- if (callee === "eval" || callee === "Function") {
11
- registerDiagnostic(node, "error", `Do not use ${callee}().`);
12
- }
13
- else if (node.kind === "JS_CALL_EXPRESSION" && TIMERS.includes(callee)) {
14
- const first = node.arguments?.children()[0]?.children()[0];
15
- if (first?.kind === "JS_STRING_LITERAL_EXPRESSION" ||
16
- first?.kind === "JS_TEMPLATE_EXPRESSION") {
17
- registerDiagnostic(node, "error", `Do not pass a string to ${callee}(); it is evaluated as code.`);
18
- }
19
- }
20
- },
21
- });
@@ -1,23 +0,0 @@
1
- import { ast, defineRule, registerDiagnostic } from "@biomejs/runtime/plugin";
2
- import { parseDefine } from "../lib/define.js";
3
- export const noExtraModules = defineRule({
4
- query: ast("JS_CALL_EXPRESSION"),
5
- run(node) {
6
- const def = parseDefine(node);
7
- if (!def)
8
- return;
9
- const names = def.elements;
10
- const params = def.params;
11
- if (names.length > params.length) {
12
- for (const extra of names.slice(params.length)) {
13
- registerDiagnostic(extra, "error", `Excess module name ${extra.text}.`);
14
- }
15
- }
16
- else if (params.length > names.length) {
17
- for (const extra of params.slice(names.length)) {
18
- if (extra)
19
- registerDiagnostic(extra, "error", `Excess module identifier '${extra.text}'.`);
20
- }
21
- }
22
- },
23
- });
@@ -1,12 +0,0 @@
1
- import { ast, defineRule, registerDiagnostic } from "@biomejs/runtime/plugin";
2
- // Oracle: "You can't use record.Record.save() here" for the newRecord and
3
- // oldRecord of a user event context; load the record to change it instead.
4
- export const noUserEventContextSave = defineRule({
5
- query: ast("JS_CALL_EXPRESSION"),
6
- run(node) {
7
- const callee = node.callee?.text ?? "";
8
- if (/(^|\.)(newRecord|oldRecord)\.save$/.test(callee)) {
9
- registerDiagnostic(node, "error", `${callee}() is not allowed; the context record is read-only.`);
10
- }
11
- },
12
- });