Skip to content

Commit 4f35132

Browse files
committed
fix(linter/plugins): definePlugin apply defineRule to rules
1 parent d5643a1 commit 4f35132

File tree

11 files changed

+658
-11
lines changed

11 files changed

+658
-11
lines changed

apps/oxlint/src-js/index.ts

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,58 @@
11
import type { Context } from './plugins/context.ts';
22
import type { CreateOnceRule, Plugin, Rule } from './plugins/load.ts';
3-
import type { BeforeHook, Visitor } from './plugins/types.ts';
3+
import type { BeforeHook, Visitor, VisitorWithHooks } from './plugins/types.ts';
44

5-
const { defineProperty, getPrototypeOf, setPrototypeOf } = Object;
5+
const { defineProperty, getPrototypeOf, hasOwn, setPrototypeOf } = Object;
66

77
const dummyOptions: unknown[] = [],
88
dummyReport = () => {};
99

10-
// Define a plugin.
10+
/**
11+
* Define a plugin.
12+
*
13+
* Converts any rules with `createOnce` method to have an ESLint-compatible `create` method.
14+
*
15+
* The `plugin` object passed in is mutated in-place.
16+
*
17+
* @param plugin - Plugin to define
18+
* @returns Plugin with all rules having `create` method
19+
* @throws {Error} If `plugin` is not an object, or `plugin.rules` is not an object
20+
*/
1121
export function definePlugin(plugin: Plugin): Plugin {
22+
// Validate type of `plugin`
23+
if (plugin === null || typeof plugin !== 'object') throw new Error('Plugin must be an object');
24+
25+
const { rules } = plugin;
26+
if (rules === null || typeof rules !== 'object') throw new Error('Plugin must have an object as `rules` property');
27+
28+
// Make each rule in the plugin ESLint-compatible by calling `defineRule` on it
29+
for (const ruleName in rules) {
30+
if (hasOwn(rules, ruleName)) {
31+
rules[ruleName] = defineRule(rules[ruleName]);
32+
}
33+
}
34+
1235
return plugin;
1336
}
1437

15-
// Define a rule.
16-
// If rule has `createOnce` method, add an ESLint-compatible `create` method which delegates to `createOnce`.
38+
/**
39+
* Define a rule.
40+
*
41+
* If rules does not already have a `create` method, create an ESLint-compatible `create` method
42+
* which delegates to `createOnce`.
43+
*
44+
* The `rule` object passed in is mutated in-place.
45+
*
46+
* @param rule - Rule to define
47+
* @returns Rule with `create` method
48+
* @throws {Error} If `rule` is not an object
49+
*/
1750
export function defineRule(rule: Rule): Rule {
18-
if (!('createOnce' in rule)) return rule;
19-
if ('create' in rule) throw new Error('Rules must define only `create` or `createOnce` methods, not both');
51+
// Validate type of `rule`
52+
if (rule === null || typeof rule !== 'object') throw new Error('Rule must be an object');
53+
54+
// If rule already has `create` method, return it as is
55+
if ('create' in rule) return rule;
2056

2157
// Add `create` function to `rule`
2258
let context: Context = null, visitor: Visitor, beforeHook: BeforeHook | null;
@@ -59,6 +95,11 @@ function createContextAndVisitor(rule: CreateOnceRule): {
5995
visitor: Visitor;
6096
beforeHook: BeforeHook | null;
6197
} {
98+
// Validate type of `createOnce`
99+
const { createOnce } = rule;
100+
if (createOnce == null) throw new Error('Rules must define either a `create` or `createOnce` method');
101+
if (typeof createOnce !== 'function') throw new Error('Rule `createOnce` property must be a function');
102+
62103
// Call `createOnce` with empty context object.
63104
// Really, `context` should be an instance of `Context`, which would throw error on accessing e.g. `id`
64105
// in body of `createOnce`. But any such bugs should have been caught when testing the rule in Oxlint,
@@ -69,7 +110,7 @@ function createContextAndVisitor(rule: CreateOnceRule): {
69110
report: { value: dummyReport, enumerable: true, configurable: true },
70111
});
71112

72-
let { before: beforeHook, after: afterHook, ...visitor } = rule.createOnce(context);
113+
let { before: beforeHook, after: afterHook, ...visitor } = createOnce.call(rule, context) as VisitorWithHooks;
73114

74115
if (beforeHook === void 0) {
75116
beforeHook = null;

apps/oxlint/test/__snapshots__/e2e.test.ts.snap

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -896,6 +896,267 @@ Found 0 warnings and 42 errors.
896896
Finished in Xms on 2 files using X threads."
897897
`;
898898
899+
exports[`oxlint CLI > should support \`definePlugin\` 1`] = `
900+
"
901+
x define-plugin-plugin(create): create body:
902+
| this === rule: true
903+
,-[files/1.js:1:1]
904+
1 | let a, b;
905+
: ^
906+
\`----
907+
908+
x define-plugin-plugin(create-once): before hook:
909+
| createOnce call count: 1
910+
| this === rule: true
911+
| filename: files/1.js
912+
,-[files/1.js:1:1]
913+
1 | let a, b;
914+
: ^
915+
\`----
916+
917+
x define-plugin-plugin(create-once): after hook:
918+
| identNum: 2
919+
| filename: files/1.js
920+
,-[files/1.js:1:1]
921+
1 | let a, b;
922+
: ^
923+
\`----
924+
925+
x define-plugin-plugin(create-once-after-only): after hook:
926+
| filename: files/1.js
927+
,-[files/1.js:1:1]
928+
1 | let a, b;
929+
: ^
930+
\`----
931+
932+
x define-plugin-plugin(create-once-before-false): before hook:
933+
| filename: files/1.js
934+
,-[files/1.js:1:1]
935+
1 | let a, b;
936+
: ^
937+
\`----
938+
939+
x define-plugin-plugin(create-once-before-only): before hook:
940+
| filename: files/1.js
941+
,-[files/1.js:1:1]
942+
1 | let a, b;
943+
: ^
944+
\`----
945+
946+
x define-plugin-plugin(create): ident visit fn "a":
947+
| filename: files/1.js
948+
,-[files/1.js:1:5]
949+
1 | let a, b;
950+
: ^
951+
\`----
952+
953+
x define-plugin-plugin(create-once): ident visit fn "a":
954+
| identNum: 1
955+
| filename: files/1.js
956+
,-[files/1.js:1:5]
957+
1 | let a, b;
958+
: ^
959+
\`----
960+
961+
x define-plugin-plugin(create-once-after-only): ident visit fn "a":
962+
| filename: files/1.js
963+
,-[files/1.js:1:5]
964+
1 | let a, b;
965+
: ^
966+
\`----
967+
968+
x define-plugin-plugin(create-once-before-only): ident visit fn "a":
969+
| filename: files/1.js
970+
,-[files/1.js:1:5]
971+
1 | let a, b;
972+
: ^
973+
\`----
974+
975+
x define-plugin-plugin(create-once-no-hooks): ident visit fn "a":
976+
| filename: files/1.js
977+
,-[files/1.js:1:5]
978+
1 | let a, b;
979+
: ^
980+
\`----
981+
982+
x define-plugin-plugin(create): ident visit fn "b":
983+
| filename: files/1.js
984+
,-[files/1.js:1:8]
985+
1 | let a, b;
986+
: ^
987+
\`----
988+
989+
x define-plugin-plugin(create-once): ident visit fn "b":
990+
| identNum: 2
991+
| filename: files/1.js
992+
,-[files/1.js:1:8]
993+
1 | let a, b;
994+
: ^
995+
\`----
996+
997+
x define-plugin-plugin(create-once-after-only): ident visit fn "b":
998+
| filename: files/1.js
999+
,-[files/1.js:1:8]
1000+
1 | let a, b;
1001+
: ^
1002+
\`----
1003+
1004+
x define-plugin-plugin(create-once-before-only): ident visit fn "b":
1005+
| filename: files/1.js
1006+
,-[files/1.js:1:8]
1007+
1 | let a, b;
1008+
: ^
1009+
\`----
1010+
1011+
x define-plugin-plugin(create-once-no-hooks): ident visit fn "b":
1012+
| filename: files/1.js
1013+
,-[files/1.js:1:8]
1014+
1 | let a, b;
1015+
: ^
1016+
\`----
1017+
1018+
x define-plugin-plugin(create): create body:
1019+
| this === rule: true
1020+
,-[files/2.js:1:1]
1021+
1 | let c, d;
1022+
: ^
1023+
\`----
1024+
1025+
x define-plugin-plugin(create-once): before hook:
1026+
| createOnce call count: 1
1027+
| this === rule: true
1028+
| filename: files/2.js
1029+
,-[files/2.js:1:1]
1030+
1 | let c, d;
1031+
: ^
1032+
\`----
1033+
1034+
x define-plugin-plugin(create-once): after hook:
1035+
| identNum: 2
1036+
| filename: files/2.js
1037+
,-[files/2.js:1:1]
1038+
1 | let c, d;
1039+
: ^
1040+
\`----
1041+
1042+
x define-plugin-plugin(create-once-after-only): after hook:
1043+
| filename: files/2.js
1044+
,-[files/2.js:1:1]
1045+
1 | let c, d;
1046+
: ^
1047+
\`----
1048+
1049+
x define-plugin-plugin(create-once-before-false): before hook:
1050+
| filename: files/2.js
1051+
,-[files/2.js:1:1]
1052+
1 | let c, d;
1053+
: ^
1054+
\`----
1055+
1056+
x define-plugin-plugin(create-once-before-false): after hook:
1057+
| filename: files/2.js
1058+
,-[files/2.js:1:1]
1059+
1 | let c, d;
1060+
: ^
1061+
\`----
1062+
1063+
x define-plugin-plugin(create-once-before-only): before hook:
1064+
| filename: files/2.js
1065+
,-[files/2.js:1:1]
1066+
1 | let c, d;
1067+
: ^
1068+
\`----
1069+
1070+
x define-plugin-plugin(create): ident visit fn "c":
1071+
| filename: files/2.js
1072+
,-[files/2.js:1:5]
1073+
1 | let c, d;
1074+
: ^
1075+
\`----
1076+
1077+
x define-plugin-plugin(create-once): ident visit fn "c":
1078+
| identNum: 1
1079+
| filename: files/2.js
1080+
,-[files/2.js:1:5]
1081+
1 | let c, d;
1082+
: ^
1083+
\`----
1084+
1085+
x define-plugin-plugin(create-once-after-only): ident visit fn "c":
1086+
| filename: files/2.js
1087+
,-[files/2.js:1:5]
1088+
1 | let c, d;
1089+
: ^
1090+
\`----
1091+
1092+
x define-plugin-plugin(create-once-before-false): ident visit fn "c":
1093+
| filename: files/2.js
1094+
,-[files/2.js:1:5]
1095+
1 | let c, d;
1096+
: ^
1097+
\`----
1098+
1099+
x define-plugin-plugin(create-once-before-only): ident visit fn "c":
1100+
| filename: files/2.js
1101+
,-[files/2.js:1:5]
1102+
1 | let c, d;
1103+
: ^
1104+
\`----
1105+
1106+
x define-plugin-plugin(create-once-no-hooks): ident visit fn "c":
1107+
| filename: files/2.js
1108+
,-[files/2.js:1:5]
1109+
1 | let c, d;
1110+
: ^
1111+
\`----
1112+
1113+
x define-plugin-plugin(create): ident visit fn "d":
1114+
| filename: files/2.js
1115+
,-[files/2.js:1:8]
1116+
1 | let c, d;
1117+
: ^
1118+
\`----
1119+
1120+
x define-plugin-plugin(create-once): ident visit fn "d":
1121+
| identNum: 2
1122+
| filename: files/2.js
1123+
,-[files/2.js:1:8]
1124+
1 | let c, d;
1125+
: ^
1126+
\`----
1127+
1128+
x define-plugin-plugin(create-once-after-only): ident visit fn "d":
1129+
| filename: files/2.js
1130+
,-[files/2.js:1:8]
1131+
1 | let c, d;
1132+
: ^
1133+
\`----
1134+
1135+
x define-plugin-plugin(create-once-before-false): ident visit fn "d":
1136+
| filename: files/2.js
1137+
,-[files/2.js:1:8]
1138+
1 | let c, d;
1139+
: ^
1140+
\`----
1141+
1142+
x define-plugin-plugin(create-once-before-only): ident visit fn "d":
1143+
| filename: files/2.js
1144+
,-[files/2.js:1:8]
1145+
1 | let c, d;
1146+
: ^
1147+
\`----
1148+
1149+
x define-plugin-plugin(create-once-no-hooks): ident visit fn "d":
1150+
| filename: files/2.js
1151+
,-[files/2.js:1:8]
1152+
1 | let c, d;
1153+
: ^
1154+
\`----
1155+
1156+
Found 0 warnings and 35 errors.
1157+
Finished in Xms on 2 files using X threads."
1158+
`;
1159+
8991160
exports[`oxlint CLI > should support \`defineRule\` 1`] = `
9001161
"
9011162
x define-rule-plugin(create): create body:

0 commit comments

Comments
 (0)