Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/oxlint/src-js/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { lint } from './bindings.js';
let loadPlugin: typeof loadPluginWrapper | null = null;
let lintFile: typeof lintFileWrapper | null = null;

function loadPluginWrapper(path: string, packageName?: string): Promise<string> {
function loadPluginWrapper(path: string, packageName: string | null): Promise<string> {
if (loadPlugin === null) {
const require = createRequire(import.meta.url);
// `plugins.js` is in root of `dist`. See `tsdown.config.ts`.
Expand Down
51 changes: 39 additions & 12 deletions apps/oxlint/src-js/plugins/load.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,10 @@ interface PluginDetails {
* containing try/catch.
*
* @param path - Absolute path of plugin file
* @param packageName - Optional package name from package.json (fallback if plugin.meta.name is missing)
* @param packageName - Optional package name from `package.json` (fallback if `plugin.meta.name` is not defined)
* @returns JSON result
*/
export async function loadPlugin(path: string, packageName?: string): Promise<string> {
export async function loadPlugin(path: string, packageName: string | null): Promise<string> {
try {
const res = await loadPluginImpl(path, packageName);
return JSON.stringify({ Success: res });
Expand All @@ -118,13 +118,15 @@ export async function loadPlugin(path: string, packageName?: string): Promise<st
* Load a plugin.
*
* @param path - Absolute path of plugin file
* @param packageName - Optional package name from package.json (fallback if plugin.meta.name is missing)
* @param packageName - Optional package name from `package.json` (fallback if `plugin.meta.name` is not defined)
* @returns - Plugin details
* @throws {Error} If plugin has already been registered
* @throws {TypeError} If one of plugin's rules is malformed or its `createOnce` method returns invalid visitor
* @throws {Error} If plugin has no name
* @throws {TypeError} If one of plugin's rules is malformed, or its `createOnce` method returns invalid visitor
* @throws {TypeError} if `plugin.meta.name` is not a string
* @throws {*} If plugin throws an error during import
*/
async function loadPluginImpl(path: string, packageName?: string): Promise<PluginDetails> {
async function loadPluginImpl(path: string, packageName: string | null): Promise<PluginDetails> {
if (registeredPluginPaths.has(path)) {
throw new Error('This plugin has already been registered. This is a bug in Oxlint. Please report it.');
}
Expand All @@ -134,13 +136,9 @@ async function loadPluginImpl(path: string, packageName?: string): Promise<Plugi
registeredPluginPaths.add(path);

// TODO: Use a validation library to assert the shape of the plugin, and of rules
// Get plugin name from plugin.meta.name, or fall back to package name from package.json
const pluginName = plugin.meta?.name ?? packageName;
if (!pluginName) {
throw new TypeError(
'Plugin must have either meta.name or be loaded from an npm package with a name field in package.json',
);
}

const pluginName = getPluginName(plugin, packageName);

const offset = registeredRules.length;
const { rules } = plugin;
const ruleNames = ObjectKeys(rules);
Expand Down Expand Up @@ -223,6 +221,35 @@ async function loadPluginImpl(path: string, packageName?: string): Promise<Plugi
return { name: pluginName, offset, ruleNames };
}

/**
* Get plugin name.
* - If `plugin.meta.name` is defined, return it.
* - Otherwise, fall back to `packageName`, if defined.
* - If neither is defined, throw an error.
*
* @param plugin - Plugin object
* @param packageName - Package name from `package.json`
* @returns Plugin name
* @throws {TypeError} If `plugin.meta.name` is not a string
* @throws {Error} If neither `plugin.meta.name` nor `packageName` are defined
*/
function getPluginName(plugin: Plugin, packageName: string | null): string {
const pluginMeta = plugin.meta;
if (pluginMeta != null) {
const pluginMetaName = pluginMeta.name;
if (pluginMetaName != null) {
if (typeof pluginMetaName !== 'string') throw new TypeError('`plugin.meta.name` must be a string if defined');
return pluginMetaName;
}
}

if (packageName !== null) return packageName;

throw new Error(
'Plugin must either define `meta.name`, or be loaded from an NPM package with a `name` field in `package.json`',
);
}

/**
* Validate and conform `before` / `after` hook function.
* @param hookFn - Hook function, or `null` / `undefined`
Expand Down
Loading