Skip to content

Commit 8d23d8d

Browse files
committed
fix(linter/plugins): deep freeze options
1 parent 3226864 commit 8d23d8d

File tree

3 files changed

+51
-5
lines changed

3 files changed

+51
-5
lines changed

apps/oxlint/src-js/plugins/load.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { createContext } from "./context.js";
2+
import { deepFreezeJsonArray } from "./json.js";
23
import { DEFAULT_OPTIONS } from "./options.js";
34
import { getErrorMessage } from "../utils/utils.js";
45

@@ -166,6 +167,7 @@ function registerPlugin(plugin: Plugin, packageName: string | null): PluginDetai
166167
if (!isArray(inputDefaultOptions)) {
167168
throw new TypeError("`rule.meta.defaultOptions` must be an array if provided");
168169
}
170+
deepFreezeJsonArray(inputDefaultOptions);
169171
defaultOptions = inputDefaultOptions;
170172
}
171173

apps/oxlint/test/fixtures/options/output.snap.md

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,40 @@
33

44
# stdout
55
```
6-
x options-plugin(default-options): options: ["string",123,true,{"toBe":false,"notToBe":true}]
6+
x options-plugin(default-options):
7+
| options: [
8+
| "string",
9+
| 123,
10+
| true,
11+
| {
12+
| "toBe": false,
13+
| "notToBe": true
14+
| },
15+
| {
16+
| "deep": [
17+
| {
18+
| "deeper": {
19+
| "evenDeeper": [
20+
| {
21+
| "soDeep": {
22+
| "soSoDeep": true
23+
| }
24+
| }
25+
| ]
26+
| }
27+
| }
28+
| ]
29+
| }
30+
| ]
31+
| isDeepFrozen: true
732
,-[files/index.js:1:1]
833
1 | debugger;
934
: ^
1035
`----
1136
12-
x options-plugin(options): options: []
37+
x options-plugin(options):
38+
| options: []
39+
| isDeepFrozen: true
1340
,-[files/index.js:1:1]
1441
1 | debugger;
1542
: ^

apps/oxlint/test/fixtures/options/plugin.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,29 @@ const plugin: Plugin = {
1818
options: {
1919
create(context) {
2020
context.report({
21-
message: `options: ${JSON.stringify(context.options)}`,
21+
message:
22+
`\noptions: ${JSON.stringify(context.options, null, 2)}\n` +
23+
`isDeepFrozen: ${isDeepFrozen(context.options)}`,
2224
node: SPAN,
2325
});
2426
return {};
2527
},
2628
},
2729
"default-options": {
2830
meta: {
29-
defaultOptions: ["string", 123, true, { toBe: false, notToBe: true }],
31+
defaultOptions: [
32+
"string",
33+
123,
34+
true,
35+
{ toBe: false, notToBe: true },
36+
{ deep: [{ deeper: { evenDeeper: [{ soDeep: { soSoDeep: true } }] } }] },
37+
],
3038
},
3139
create(context) {
3240
context.report({
33-
message: `options: ${JSON.stringify(context.options)}`,
41+
message:
42+
`\noptions: ${JSON.stringify(context.options, null, 2)}\n` +
43+
`isDeepFrozen: ${isDeepFrozen(context.options)}`,
3444
node: SPAN,
3545
});
3646
return {};
@@ -40,3 +50,10 @@ const plugin: Plugin = {
4050
};
4151

4252
export default plugin;
53+
54+
function isDeepFrozen(value: unknown): boolean {
55+
if (value === null || typeof value !== "object") return true;
56+
if (!Object.isFrozen(value)) return false;
57+
if (Array.isArray(value)) return value.every(isDeepFrozen);
58+
return Object.values(value).every(isDeepFrozen);
59+
}

0 commit comments

Comments
 (0)