Skip to content

Commit 314c27d

Browse files
committed
fix(linter/plugins): definePlugin apply defineRule to rules (#14065)
`definePlugin` previously passed the input through unchanged. This necessitated wrapping every rule in `defineRule` too. Instead, make `definePlugin` call `defineRule` for every rule. Before: ```js import { definePlugin, defineRule } from 'oxlint'; export default definePlugin({ meta: { name: 'my-plugin' }, rules: { 'my-rule': defineRule({ createOnce: (context) => ({ /* visitor */ }), }), 'my-other-rule': defineRule({ createOnce: (context) => ({ /* visitor */ }), }), }, }); ``` After: ```js import { definePlugin } from 'oxlint'; export default definePlugin({ meta: { name: 'my-plugin' }, rules: { 'my-rule': { createOnce: (context) => ({ /* visitor */ }), }, 'my-other-rule': { createOnce: (context) => ({ /* visitor */ }), }, }, }); ```
1 parent 2fd4b1e commit 314c27d

File tree

16 files changed

+1263
-11
lines changed

16 files changed

+1263
-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;

0 commit comments

Comments
 (0)