NodeJS 使用eslint强制特定的命名导出?

gojuced7  于 5个月前  发布在  Node.js
关注(0)|答案(1)|浏览(68)

我想使用eslint来要求JavaScript/TypeScript文件导出给定名称的命名导出。
例如,在我的src/pages目录中,我希望导出名为config的命名导出所需的所有文件:

无效示例

src/pages/index.js
export const Page = () => {}

// Error: no export named `config`

字符串

有效示例

src/pages/index.js

export const Page = () => {}

export const config = {}


这在目前是可能的吗?我已经看过各种eslint规则,当然还有eslint-plugin-import,但还没有找到任何适合这个确切目的的东西。
如果有人有任何关于如何在TypeScript中为这个config导出强制特定类型的想法,那就再加一分。

w8biq8rn

w8biq8rn1#

有点晚了,但这里有一些示例代码,我已经验证了工作。我写了基本上是这个确切的请求,需要一个StorybookDocumentation类型的导出变量,基本上是为了同一个最终目标。
该方法的一般要点是编写一个custom rule
1.从Program根节点开始,遍历所有token,寻找与导出相关的token(在我的例子中,我选择使用命名导出,但对下面的逻辑进行轻微调整,您可以针对默认导出)。
1.一旦我将标记缩小到只导出变量及其类型,我就将它们分块到一个[identifier,type]元组数组中。
1.然后我Assert这些元组中至少有一个是具有正确名称和类型的导出变量,否则我报告一个问题。

import { ESLintUtils, TSESTree } from '@typescript-eslint/utils';

const arePriorTokensExportConst = (index: number, tokens: TSESTree.Token[]) => {
    const previousTokenIsConst = tokens[index - 1]?.value === 'const';
    const previousPreviousTokenIsExport = tokens[index - 2]?.value === 'export';
    const isNamedExport = previousTokenIsConst && previousPreviousTokenIsExport;
    return isNamedExport;
};
export const requireStorybookDocumentation = ESLintUtils.RuleCreator.withoutDocs({
    create(context) {
        return {
            Program(program) {
                const namedExports = context.sourceCode.tokensAndComments
                    .filter((token, currentIndex, tokens) => {
                        if (token?.type !== 'Identifier') {
                            return false;
                        }
                        const isNamedExport = arePriorTokensExportConst(currentIndex, tokens);
                        const isTypeForNamedExport =
                            tokens[currentIndex - 1]?.value === ':' &&
                            arePriorTokensExportConst(currentIndex - 2, tokens);
                        return isNamedExport || isTypeForNamedExport;
                    })
                    .reduce<TSESTree.Token[][]>((acc, cur, index) => {
                        if (index % 2 === 0) {
                            acc.push([cur]);
                            return acc;
                        }
                        acc[acc.length - 1].push(cur);
                        return acc;
                    }, []);

                if (
                    !namedExports.some(namedExport => {
                        const [identifier, identifierType] = namedExport;
                        return (
                            identifierType?.value === 'StorybookDocumentation' && identifier?.value === 'Documentation'
                        );
                    })
                ) {
                    context.report({
                        messageId: 'require-storybook-documentation',
                        node: program,
                    });
                }
            },
        };
    },
    meta: {
        docs: {
            description: 'Function Component files should export a Storybook documentation configuration',
        },
        messages: {
            'require-storybook-documentation': 'Define and export a StorybookDocumentation object named Documentation',
        },
        type: 'suggestion',
        schema: [],
    },
    defaultOptions: [],
});

export default {
    'require-storybook-documentation': requireStorybookDocumentation,
} satisfies Record<string, ESLintUtils.RuleModule<any, any>>;

字符串
然后进行一些测试来验证它的行为是否符合预期

import { RuleTester } from '@typescript-eslint/rule-tester';
import { describe, after, it } from 'mocha';
import { requireStorybookDocumentation } from '../rules';
RuleTester.afterAll = after;
RuleTester.describe = describe;
RuleTester.it = it;
RuleTester.itOnly = it.only;

const ruleTester = new RuleTester({
    parser: '@typescript-eslint/parser',
    parserOptions: {
        project: './tsconfig.json',
        ecmaFeatures: { jsx: true },
        tsconfigRootDir: __dirname,
    },
});

ruleTester.run('my-rule', requireStorybookDocumentation, {
    valid: [
        {
            code: `
    import { FunctionComponent } from 'react';
    import { StoryObj, Meta } from '@storybook/react';

    type StorybookDocumentation<TComponent extends FunctionComponent, TDefinedStories> = {
        Meta: Meta<TComponent>,
        Stories: Record<TDefinedStories, StoryObj<TComponent>>
    };

    export const MyComponent: FunctionComponent = () => <h1>Hello World!</h1>;
    export const Documentation: StorybookDocumentation<typeof MyComponent, 'Primary'>  = {
        Meta: {
            component: MyComponent,
        },
        Stories: {
            Primary: {}
        }
    };
`,
        },
    ],
    invalid: [
        {
            code: `
        import { FunctionComponent } from 'react';
        export const MyComponent: FunctionComponent = () => <h1>Hello World!</h1>;
    `,
            errors: [{ messageId: 'require-storybook-documentation' }],
        },
    ],
});

相关问题