Skip to content

Commit

Permalink
feat: add RuleCreator.withoutDocs (#4136)
Browse files Browse the repository at this point in the history
* feat: add RuleCreator.withoutDocs

* fix: docs.test typing
  • Loading branch information
Josh Goldberg committed Nov 14, 2021
1 parent b225da8 commit 87cfc6a
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 28 deletions.
2 changes: 1 addition & 1 deletion packages/eslint-plugin/tests/docs.test.ts
Expand Up @@ -111,7 +111,7 @@ describe('Validating rule metadata', () => {
// validate if rule name is same as url
// there is no way to access this field but its used only in generation of docs url
expect(
rule.meta.docs?.url.endsWith(`rules/${ruleName}.md`),
rule.meta.docs?.url?.endsWith(`rules/${ruleName}.md`),
).toBeTruthy();
});

Expand Down
104 changes: 78 additions & 26 deletions packages/experimental-utils/src/eslint-utils/RuleCreator.ts
Expand Up @@ -8,51 +8,103 @@ import {
import { applyDefault } from './applyDefault';

// we automatically add the url
type CreateRuleMetaDocs = Omit<RuleMetaDataDocs, 'url'>;
type CreateRuleMeta<TMessageIds extends string> = {
docs: CreateRuleMetaDocs;
type NamedCreateRuleMetaDocs = Omit<RuleMetaDataDocs, 'url'>;
type NamedCreateRuleMeta<TMessageIds extends string> = {
docs: NamedCreateRuleMetaDocs;
} & Omit<RuleMetaData<TMessageIds>, 'docs'>;

interface CreateAndOptions<
TOptions extends readonly unknown[],
TMessageIds extends string,
TRuleListener extends RuleListener,
> {
create: (
context: Readonly<RuleContext<TMessageIds, TOptions>>,
optionsWithDefault: Readonly<TOptions>,
) => TRuleListener;
defaultOptions: Readonly<TOptions>;
}

interface RuleWithMeta<
TOptions extends readonly unknown[],
TMessageIds extends string,
TRuleListener extends RuleListener,
> extends CreateAndOptions<TOptions, TMessageIds, TRuleListener> {
meta: RuleMetaData<TMessageIds>;
}

interface RuleWithMetaAndName<
TOptions extends readonly unknown[],
TMessageIds extends string,
TRuleListener extends RuleListener,
> extends CreateAndOptions<TOptions, TMessageIds, TRuleListener> {
meta: NamedCreateRuleMeta<TMessageIds>;
name: string;
}

/**
* Creates reusable function to create rules with default options and docs URLs.
*
* @param urlCreator Creates a documentation URL for a given rule name.
* @returns Function to create a rule with the docs URL format.
*/
function RuleCreator(urlCreator: (ruleName: string) => string) {
// This function will get much easier to call when this is merged https://github.com/Microsoft/TypeScript/pull/26349
// TODO - when the above PR lands; add type checking for the context.report `data` property
return function createRule<
return function createNamedRule<
TOptions extends readonly unknown[],
TMessageIds extends string,
TRuleListener extends RuleListener = RuleListener,
>({
name,
meta,
defaultOptions,
create,
}: Readonly<{
name: string;
meta: CreateRuleMeta<TMessageIds>;
defaultOptions: Readonly<TOptions>;
create: (
context: Readonly<RuleContext<TMessageIds, TOptions>>,
optionsWithDefault: Readonly<TOptions>,
) => TRuleListener;
}>): RuleModule<TMessageIds, TOptions, TRuleListener> {
return {
...rule
}: Readonly<
RuleWithMetaAndName<TOptions, TMessageIds, TRuleListener>
>): RuleModule<TMessageIds, TOptions, TRuleListener> {
return createRule<TOptions, TMessageIds, TRuleListener>({
meta: {
...meta,
docs: {
...meta.docs,
url: urlCreator(name),
},
},
create(
context: Readonly<RuleContext<TMessageIds, TOptions>>,
): TRuleListener {
const optionsWithDefault = applyDefault(
defaultOptions,
context.options,
);
return create(context, optionsWithDefault);
},
};
...rule,
});
};
}

/**
* Creates a well-typed TSESLint custom ESLint rule without a docs URL.
*
* @returns Well-typed TSESLint custom ESLint rule.
* @remarks It is generally better to provide a docs URL function to RuleCreator.
*/
function createRule<
TOptions extends readonly unknown[],
TMessageIds extends string,
TRuleListener extends RuleListener = RuleListener,
>({
create,
defaultOptions,
meta,
}: Readonly<RuleWithMeta<TOptions, TMessageIds, TRuleListener>>): RuleModule<
TMessageIds,
TOptions,
TRuleListener
> {
return {
meta,
create(
context: Readonly<RuleContext<TMessageIds, TOptions>>,
): TRuleListener {
const optionsWithDefault = applyDefault(defaultOptions, context.options);
return create(context, optionsWithDefault);
},
};
}

RuleCreator.withoutDocs = createRule;

export { RuleCreator };
2 changes: 1 addition & 1 deletion packages/experimental-utils/src/ts-eslint/Rule.ts
Expand Up @@ -19,7 +19,7 @@ interface RuleMetaDataDocs {
/**
* The URL of the rule's docs
*/
url: string;
url?: string;
/**
* Specifies whether the rule can return suggestions.
*/
Expand Down

0 comments on commit 87cfc6a

Please sign in to comment.