@gallop.software/canon 1.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/audit.d.ts +7 -0
- package/dist/cli/commands/audit.js +172 -0
- package/dist/cli/commands/generate.d.ts +6 -0
- package/dist/cli/commands/generate.js +170 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +104 -0
- package/dist/eslint/configs/recommended.d.ts +13 -0
- package/dist/eslint/configs/recommended.js +15 -0
- package/dist/eslint/configs/speedwell.d.ts +13 -0
- package/dist/eslint/configs/speedwell.js +17 -0
- package/dist/eslint/index.d.ts +37 -0
- package/dist/eslint/index.js +24 -0
- package/dist/eslint/rules/no-client-blocks.d.ts +5 -0
- package/dist/eslint/rules/no-client-blocks.js +45 -0
- package/dist/eslint/rules/no-container-in-section.d.ts +5 -0
- package/dist/eslint/rules/no-container-in-section.js +50 -0
- package/dist/eslint/rules/prefer-component-props.d.ts +5 -0
- package/dist/eslint/rules/prefer-component-props.js +100 -0
- package/dist/eslint/rules/prefer-typography-components.d.ts +3 -0
- package/dist/eslint/rules/prefer-typography-components.js +76 -0
- package/dist/eslint/utils/canon.d.ts +25 -0
- package/dist/eslint/utils/canon.js +50 -0
- package/dist/index.js +28 -42
- package/package.json +33 -3
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
declare const plugin: {
|
|
2
|
+
meta: {
|
|
3
|
+
name: string;
|
|
4
|
+
version: string;
|
|
5
|
+
};
|
|
6
|
+
rules: {
|
|
7
|
+
'no-client-blocks': import("@typescript-eslint/utils/ts-eslint").RuleModule<"noClientBlocks", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
8
|
+
name: string;
|
|
9
|
+
};
|
|
10
|
+
'no-container-in-section': import("@typescript-eslint/utils/ts-eslint").RuleModule<"noContainerInSection", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
11
|
+
name: string;
|
|
12
|
+
};
|
|
13
|
+
'prefer-component-props': import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferComponentProps", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
14
|
+
name: string;
|
|
15
|
+
};
|
|
16
|
+
'prefer-typography-components': import("eslint").Rule.RuleModule;
|
|
17
|
+
};
|
|
18
|
+
configs: {
|
|
19
|
+
speedwell: {
|
|
20
|
+
plugins: string[];
|
|
21
|
+
rules: {
|
|
22
|
+
'gallop/no-client-blocks': string;
|
|
23
|
+
'gallop/no-container-in-section': string;
|
|
24
|
+
'gallop/prefer-component-props': string;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
recommended: {
|
|
28
|
+
plugins: string[];
|
|
29
|
+
rules: {
|
|
30
|
+
'gallop/no-client-blocks': string;
|
|
31
|
+
'gallop/no-container-in-section': string;
|
|
32
|
+
'gallop/prefer-component-props': string;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
export default plugin;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import noClientBlocks from './rules/no-client-blocks.js';
|
|
2
|
+
import noContainerInSection from './rules/no-container-in-section.js';
|
|
3
|
+
import preferComponentProps from './rules/prefer-component-props.js';
|
|
4
|
+
import preferTypographyComponents from './rules/prefer-typography-components.js';
|
|
5
|
+
import speedwellConfig from './configs/speedwell.js';
|
|
6
|
+
import recommendedConfig from './configs/recommended.js';
|
|
7
|
+
const plugin = {
|
|
8
|
+
meta: {
|
|
9
|
+
name: 'eslint-plugin-gallop',
|
|
10
|
+
version: '1.0.1',
|
|
11
|
+
},
|
|
12
|
+
rules: {
|
|
13
|
+
'no-client-blocks': noClientBlocks,
|
|
14
|
+
'no-container-in-section': noContainerInSection,
|
|
15
|
+
'prefer-component-props': preferComponentProps,
|
|
16
|
+
'prefer-typography-components': preferTypographyComponents,
|
|
17
|
+
},
|
|
18
|
+
configs: {
|
|
19
|
+
speedwell: speedwellConfig,
|
|
20
|
+
recommended: recommendedConfig,
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
export default plugin;
|
|
24
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvZXNsaW50L2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sY0FBYyxNQUFNLDZCQUE2QixDQUFBO0FBQ3hELE9BQU8sb0JBQW9CLE1BQU0sb0NBQW9DLENBQUE7QUFDckUsT0FBTyxvQkFBb0IsTUFBTSxtQ0FBbUMsQ0FBQTtBQUNwRSxPQUFPLDBCQUEwQixNQUFNLHlDQUF5QyxDQUFBO0FBQ2hGLE9BQU8sZUFBZSxNQUFNLHdCQUF3QixDQUFBO0FBQ3BELE9BQU8saUJBQWlCLE1BQU0sMEJBQTBCLENBQUE7QUFFeEQsTUFBTSxNQUFNLEdBQUc7SUFDYixJQUFJLEVBQUU7UUFDSixJQUFJLEVBQUUsc0JBQXNCO1FBQzVCLE9BQU8sRUFBRSxPQUFPO0tBQ2pCO0lBQ0QsS0FBSyxFQUFFO1FBQ0wsa0JBQWtCLEVBQUUsY0FBYztRQUNsQyx5QkFBeUIsRUFBRSxvQkFBb0I7UUFDL0Msd0JBQXdCLEVBQUUsb0JBQW9CO1FBQzlDLDhCQUE4QixFQUFFLDBCQUEwQjtLQUMzRDtJQUNELE9BQU8sRUFBRTtRQUNQLFNBQVMsRUFBRSxlQUFlO1FBQzFCLFdBQVcsRUFBRSxpQkFBaUI7S0FDL0I7Q0FDRixDQUFBO0FBRUQsZUFBZSxNQUFNLENBQUEiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgbm9DbGllbnRCbG9ja3MgZnJvbSAnLi9ydWxlcy9uby1jbGllbnQtYmxvY2tzLmpzJ1xuaW1wb3J0IG5vQ29udGFpbmVySW5TZWN0aW9uIGZyb20gJy4vcnVsZXMvbm8tY29udGFpbmVyLWluLXNlY3Rpb24uanMnXG5pbXBvcnQgcHJlZmVyQ29tcG9uZW50UHJvcHMgZnJvbSAnLi9ydWxlcy9wcmVmZXItY29tcG9uZW50LXByb3BzLmpzJ1xuaW1wb3J0IHByZWZlclR5cG9ncmFwaHlDb21wb25lbnRzIGZyb20gJy4vcnVsZXMvcHJlZmVyLXR5cG9ncmFwaHktY29tcG9uZW50cy5qcydcbmltcG9ydCBzcGVlZHdlbGxDb25maWcgZnJvbSAnLi9jb25maWdzL3NwZWVkd2VsbC5qcydcbmltcG9ydCByZWNvbW1lbmRlZENvbmZpZyBmcm9tICcuL2NvbmZpZ3MvcmVjb21tZW5kZWQuanMnXG5cbmNvbnN0IHBsdWdpbiA9IHtcbiAgbWV0YToge1xuICAgIG5hbWU6ICdlc2xpbnQtcGx1Z2luLWdhbGxvcCcsXG4gICAgdmVyc2lvbjogJzEuMC4xJyxcbiAgfSxcbiAgcnVsZXM6IHtcbiAgICAnbm8tY2xpZW50LWJsb2Nrcyc6IG5vQ2xpZW50QmxvY2tzLFxuICAgICduby1jb250YWluZXItaW4tc2VjdGlvbic6IG5vQ29udGFpbmVySW5TZWN0aW9uLFxuICAgICdwcmVmZXItY29tcG9uZW50LXByb3BzJzogcHJlZmVyQ29tcG9uZW50UHJvcHMsXG4gICAgJ3ByZWZlci10eXBvZ3JhcGh5LWNvbXBvbmVudHMnOiBwcmVmZXJUeXBvZ3JhcGh5Q29tcG9uZW50cyxcbiAgfSxcbiAgY29uZmlnczoge1xuICAgIHNwZWVkd2VsbDogc3BlZWR3ZWxsQ29uZmlnLFxuICAgIHJlY29tbWVuZGVkOiByZWNvbW1lbmRlZENvbmZpZyxcbiAgfSxcbn1cblxuZXhwb3J0IGRlZmF1bHQgcGx1Z2luXG4iXX0=
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
+
import { getCanonUrl, getCanonPattern } from '../utils/canon.js';
|
|
3
|
+
const RULE_NAME = 'no-client-blocks';
|
|
4
|
+
const pattern = getCanonPattern(RULE_NAME);
|
|
5
|
+
const createRule = ESLintUtils.RuleCreator(() => getCanonUrl(RULE_NAME));
|
|
6
|
+
export default createRule({
|
|
7
|
+
name: RULE_NAME,
|
|
8
|
+
meta: {
|
|
9
|
+
type: 'suggestion',
|
|
10
|
+
docs: {
|
|
11
|
+
description: pattern?.summary || 'Blocks must be server components',
|
|
12
|
+
},
|
|
13
|
+
messages: {
|
|
14
|
+
noClientBlocks: `[Canon ${pattern?.id || '001'}] Block "{{blockName}}" uses 'use client'. Extract hooks and client-side logic into a component in src/components/, then import it here. See: ${pattern?.title || 'Server-First Blocks'} pattern.`,
|
|
15
|
+
},
|
|
16
|
+
schema: [],
|
|
17
|
+
},
|
|
18
|
+
defaultOptions: [],
|
|
19
|
+
create(context) {
|
|
20
|
+
const filename = context.filename || context.getFilename();
|
|
21
|
+
// Only check files in src/blocks/
|
|
22
|
+
if (!filename.includes('/blocks/') && !filename.includes('\\blocks\\')) {
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
// Check for 'use client' directive at the top of the file
|
|
27
|
+
ExpressionStatement(node) {
|
|
28
|
+
if (node.expression.type === 'Literal' &&
|
|
29
|
+
node.expression.value === 'use client') {
|
|
30
|
+
// Extract block name from filename
|
|
31
|
+
const match = filename.match(/([^/\\]+)\.tsx?$/);
|
|
32
|
+
const blockName = match ? match[1] : 'unknown';
|
|
33
|
+
context.report({
|
|
34
|
+
node,
|
|
35
|
+
messageId: 'noClientBlocks',
|
|
36
|
+
data: {
|
|
37
|
+
blockName,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibm8tY2xpZW50LWJsb2Nrcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9lc2xpbnQvcnVsZXMvbm8tY2xpZW50LWJsb2Nrcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sMEJBQTBCLENBQUE7QUFDdEQsT0FBTyxFQUFFLFdBQVcsRUFBRSxlQUFlLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQTtBQUVoRSxNQUFNLFNBQVMsR0FBRyxrQkFBa0IsQ0FBQTtBQUNwQyxNQUFNLE9BQU8sR0FBRyxlQUFlLENBQUMsU0FBUyxDQUFDLENBQUE7QUFFMUMsTUFBTSxVQUFVLEdBQUcsV0FBVyxDQUFDLFdBQVcsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxXQUFXLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQTtBQUl4RSxlQUFlLFVBQVUsQ0FBaUI7SUFDeEMsSUFBSSxFQUFFLFNBQVM7SUFDZixJQUFJLEVBQUU7UUFDSixJQUFJLEVBQUUsWUFBWTtRQUNsQixJQUFJLEVBQUU7WUFDSixXQUFXLEVBQUUsT0FBTyxFQUFFLE9BQU8sSUFBSSxrQ0FBa0M7U0FDcEU7UUFDRCxRQUFRLEVBQUU7WUFDUixjQUFjLEVBQUUsVUFBVSxPQUFPLEVBQUUsRUFBRSxJQUFJLEtBQUssaUpBQWlKLE9BQU8sRUFBRSxLQUFLLElBQUkscUJBQXFCLFdBQVc7U0FDbFA7UUFDRCxNQUFNLEVBQUUsRUFBRTtLQUNYO0lBQ0QsY0FBYyxFQUFFLEVBQUU7SUFDbEIsTUFBTSxDQUFDLE9BQU87UUFDWixNQUFNLFFBQVEsR0FBRyxPQUFPLENBQUMsUUFBUSxJQUFJLE9BQU8sQ0FBQyxXQUFXLEVBQUUsQ0FBQTtRQUUxRCxrQ0FBa0M7UUFDbEMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxRQUFRLENBQUMsVUFBVSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDLFlBQVksQ0FBQyxFQUFFLENBQUM7WUFDdkUsT0FBTyxFQUFFLENBQUE7UUFDWCxDQUFDO1FBRUQsT0FBTztZQUNMLDBEQUEwRDtZQUMxRCxtQkFBbUIsQ0FBQyxJQUFJO2dCQUN0QixJQUNFLElBQUksQ0FBQyxVQUFVLENBQUMsSUFBSSxLQUFLLFNBQVM7b0JBQ2xDLElBQUksQ0FBQyxVQUFVLENBQUMsS0FBSyxLQUFLLFlBQVksRUFDdEMsQ0FBQztvQkFDRCxtQ0FBbUM7b0JBQ25DLE1BQU0sS0FBSyxHQUFHLFFBQVEsQ0FBQyxLQUFLLENBQUMsa0JBQWtCLENBQUMsQ0FBQTtvQkFDaEQsTUFBTSxTQUFTLEdBQUcsS0FBSyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQTtvQkFFOUMsT0FBTyxDQUFDLE1BQU0sQ0FBQzt3QkFDYixJQUFJO3dCQUNKLFNBQVMsRUFBRSxnQkFBZ0I7d0JBQzNCLElBQUksRUFBRTs0QkFDSixTQUFTO3lCQUNWO3FCQUNGLENBQUMsQ0FBQTtnQkFDSixDQUFDO1lBQ0gsQ0FBQztTQUNGLENBQUE7SUFDSCxDQUFDO0NBQ0YsQ0FBQyxDQUFBIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgRVNMaW50VXRpbHMgfSBmcm9tICdAdHlwZXNjcmlwdC1lc2xpbnQvdXRpbHMnXG5pbXBvcnQgeyBnZXRDYW5vblVybCwgZ2V0Q2Fub25QYXR0ZXJuIH0gZnJvbSAnLi4vdXRpbHMvY2Fub24uanMnXG5cbmNvbnN0IFJVTEVfTkFNRSA9ICduby1jbGllbnQtYmxvY2tzJ1xuY29uc3QgcGF0dGVybiA9IGdldENhbm9uUGF0dGVybihSVUxFX05BTUUpXG5cbmNvbnN0IGNyZWF0ZVJ1bGUgPSBFU0xpbnRVdGlscy5SdWxlQ3JlYXRvcigoKSA9PiBnZXRDYW5vblVybChSVUxFX05BTUUpKVxuXG50eXBlIE1lc3NhZ2VJZHMgPSAnbm9DbGllbnRCbG9ja3MnXG5cbmV4cG9ydCBkZWZhdWx0IGNyZWF0ZVJ1bGU8W10sIE1lc3NhZ2VJZHM+KHtcbiAgbmFtZTogUlVMRV9OQU1FLFxuICBtZXRhOiB7XG4gICAgdHlwZTogJ3N1Z2dlc3Rpb24nLFxuICAgIGRvY3M6IHtcbiAgICAgIGRlc2NyaXB0aW9uOiBwYXR0ZXJuPy5zdW1tYXJ5IHx8ICdCbG9ja3MgbXVzdCBiZSBzZXJ2ZXIgY29tcG9uZW50cycsXG4gICAgfSxcbiAgICBtZXNzYWdlczoge1xuICAgICAgbm9DbGllbnRCbG9ja3M6IGBbQ2Fub24gJHtwYXR0ZXJuPy5pZCB8fCAnMDAxJ31dIEJsb2NrIFwie3tibG9ja05hbWV9fVwiIHVzZXMgJ3VzZSBjbGllbnQnLiBFeHRyYWN0IGhvb2tzIGFuZCBjbGllbnQtc2lkZSBsb2dpYyBpbnRvIGEgY29tcG9uZW50IGluIHNyYy9jb21wb25lbnRzLywgdGhlbiBpbXBvcnQgaXQgaGVyZS4gU2VlOiAke3BhdHRlcm4/LnRpdGxlIHx8ICdTZXJ2ZXItRmlyc3QgQmxvY2tzJ30gcGF0dGVybi5gLFxuICAgIH0sXG4gICAgc2NoZW1hOiBbXSxcbiAgfSxcbiAgZGVmYXVsdE9wdGlvbnM6IFtdLFxuICBjcmVhdGUoY29udGV4dCkge1xuICAgIGNvbnN0IGZpbGVuYW1lID0gY29udGV4dC5maWxlbmFtZSB8fCBjb250ZXh0LmdldEZpbGVuYW1lKClcblxuICAgIC8vIE9ubHkgY2hlY2sgZmlsZXMgaW4gc3JjL2Jsb2Nrcy9cbiAgICBpZiAoIWZpbGVuYW1lLmluY2x1ZGVzKCcvYmxvY2tzLycpICYmICFmaWxlbmFtZS5pbmNsdWRlcygnXFxcXGJsb2Nrc1xcXFwnKSkge1xuICAgICAgcmV0dXJuIHt9XG4gICAgfVxuXG4gICAgcmV0dXJuIHtcbiAgICAgIC8vIENoZWNrIGZvciAndXNlIGNsaWVudCcgZGlyZWN0aXZlIGF0IHRoZSB0b3Agb2YgdGhlIGZpbGVcbiAgICAgIEV4cHJlc3Npb25TdGF0ZW1lbnQobm9kZSkge1xuICAgICAgICBpZiAoXG4gICAgICAgICAgbm9kZS5leHByZXNzaW9uLnR5cGUgPT09ICdMaXRlcmFsJyAmJlxuICAgICAgICAgIG5vZGUuZXhwcmVzc2lvbi52YWx1ZSA9PT0gJ3VzZSBjbGllbnQnXG4gICAgICAgICkge1xuICAgICAgICAgIC8vIEV4dHJhY3QgYmxvY2sgbmFtZSBmcm9tIGZpbGVuYW1lXG4gICAgICAgICAgY29uc3QgbWF0Y2ggPSBmaWxlbmFtZS5tYXRjaCgvKFteL1xcXFxdKylcXC50c3g/JC8pXG4gICAgICAgICAgY29uc3QgYmxvY2tOYW1lID0gbWF0Y2ggPyBtYXRjaFsxXSA6ICd1bmtub3duJ1xuXG4gICAgICAgICAgY29udGV4dC5yZXBvcnQoe1xuICAgICAgICAgICAgbm9kZSxcbiAgICAgICAgICAgIG1lc3NhZ2VJZDogJ25vQ2xpZW50QmxvY2tzJyxcbiAgICAgICAgICAgIGRhdGE6IHtcbiAgICAgICAgICAgICAgYmxvY2tOYW1lLFxuICAgICAgICAgICAgfSxcbiAgICAgICAgICB9KVxuICAgICAgICB9XG4gICAgICB9LFxuICAgIH1cbiAgfSxcbn0pXG4iXX0=
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
+
import { getCanonUrl, getCanonPattern } from '../utils/canon.js';
|
|
3
|
+
const RULE_NAME = 'no-container-in-section';
|
|
4
|
+
const pattern = getCanonPattern(RULE_NAME);
|
|
5
|
+
const createRule = ESLintUtils.RuleCreator(() => getCanonUrl(RULE_NAME));
|
|
6
|
+
export default createRule({
|
|
7
|
+
name: RULE_NAME,
|
|
8
|
+
meta: {
|
|
9
|
+
type: 'suggestion',
|
|
10
|
+
docs: {
|
|
11
|
+
description: pattern?.summary || 'No Container inside Section',
|
|
12
|
+
},
|
|
13
|
+
messages: {
|
|
14
|
+
noContainerInSection: `[Canon ${pattern?.id || '002'}] Container inside Section is redundant. Section already provides max-width containment. Use Section's innerAlign prop or a plain div instead.`,
|
|
15
|
+
},
|
|
16
|
+
schema: [],
|
|
17
|
+
},
|
|
18
|
+
defaultOptions: [],
|
|
19
|
+
create(context) {
|
|
20
|
+
// Track if we're inside a Section component
|
|
21
|
+
let sectionDepth = 0;
|
|
22
|
+
function isJSXElementNamed(node, name) {
|
|
23
|
+
return (node.name.type === 'JSXIdentifier' &&
|
|
24
|
+
node.name.name === name);
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
JSXOpeningElement(node) {
|
|
28
|
+
// Check if entering a Section
|
|
29
|
+
if (isJSXElementNamed(node, 'Section')) {
|
|
30
|
+
sectionDepth++;
|
|
31
|
+
}
|
|
32
|
+
// Check if we're inside a Section and found a Container
|
|
33
|
+
if (sectionDepth > 0 && isJSXElementNamed(node, 'Container')) {
|
|
34
|
+
context.report({
|
|
35
|
+
node,
|
|
36
|
+
messageId: 'noContainerInSection',
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
JSXClosingElement(node) {
|
|
41
|
+
// Check if leaving a Section
|
|
42
|
+
if (node.name.type === 'JSXIdentifier' &&
|
|
43
|
+
node.name.name === 'Section') {
|
|
44
|
+
sectionDepth--;
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibm8tY29udGFpbmVyLWluLXNlY3Rpb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvZXNsaW50L3J1bGVzL25vLWNvbnRhaW5lci1pbi1zZWN0aW9uLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxXQUFXLEVBQVksTUFBTSwwQkFBMEIsQ0FBQTtBQUNoRSxPQUFPLEVBQUUsV0FBVyxFQUFFLGVBQWUsRUFBRSxNQUFNLG1CQUFtQixDQUFBO0FBRWhFLE1BQU0sU0FBUyxHQUFHLHlCQUF5QixDQUFBO0FBQzNDLE1BQU0sT0FBTyxHQUFHLGVBQWUsQ0FBQyxTQUFTLENBQUMsQ0FBQTtBQUUxQyxNQUFNLFVBQVUsR0FBRyxXQUFXLENBQUMsV0FBVyxDQUFDLEdBQUcsRUFBRSxDQUFDLFdBQVcsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFBO0FBSXhFLGVBQWUsVUFBVSxDQUFpQjtJQUN4QyxJQUFJLEVBQUUsU0FBUztJQUNmLElBQUksRUFBRTtRQUNKLElBQUksRUFBRSxZQUFZO1FBQ2xCLElBQUksRUFBRTtZQUNKLFdBQVcsRUFBRSxPQUFPLEVBQUUsT0FBTyxJQUFJLDZCQUE2QjtTQUMvRDtRQUNELFFBQVEsRUFBRTtZQUNSLG9CQUFvQixFQUFFLFVBQVUsT0FBTyxFQUFFLEVBQUUsSUFBSSxLQUFLLGdKQUFnSjtTQUNyTTtRQUNELE1BQU0sRUFBRSxFQUFFO0tBQ1g7SUFDRCxjQUFjLEVBQUUsRUFBRTtJQUNsQixNQUFNLENBQUMsT0FBTztRQUNaLDRDQUE0QztRQUM1QyxJQUFJLFlBQVksR0FBRyxDQUFDLENBQUE7UUFFcEIsU0FBUyxpQkFBaUIsQ0FDeEIsSUFBZ0MsRUFDaEMsSUFBWTtZQUVaLE9BQU8sQ0FDTCxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksS0FBSyxlQUFlO2dCQUNsQyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksS0FBSyxJQUFJLENBQ3hCLENBQUE7UUFDSCxDQUFDO1FBRUQsT0FBTztZQUNMLGlCQUFpQixDQUFDLElBQUk7Z0JBQ3BCLDhCQUE4QjtnQkFDOUIsSUFBSSxpQkFBaUIsQ0FBQyxJQUFJLEVBQUUsU0FBUyxDQUFDLEVBQUUsQ0FBQztvQkFDdkMsWUFBWSxFQUFFLENBQUE7Z0JBQ2hCLENBQUM7Z0JBRUQsd0RBQXdEO2dCQUN4RCxJQUFJLFlBQVksR0FBRyxDQUFDLElBQUksaUJBQWlCLENBQUMsSUFBSSxFQUFFLFdBQVcsQ0FBQyxFQUFFLENBQUM7b0JBQzdELE9BQU8sQ0FBQyxNQUFNLENBQUM7d0JBQ2IsSUFBSTt3QkFDSixTQUFTLEVBQUUsc0JBQXNCO3FCQUNsQyxDQUFDLENBQUE7Z0JBQ0osQ0FBQztZQUNILENBQUM7WUFFRCxpQkFBaUIsQ0FBQyxJQUFJO2dCQUNwQiw2QkFBNkI7Z0JBQzdCLElBQ0UsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEtBQUssZUFBZTtvQkFDbEMsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEtBQUssU0FBUyxFQUM1QixDQUFDO29CQUNELFlBQVksRUFBRSxDQUFBO2dCQUNoQixDQUFDO1lBQ0gsQ0FBQztTQUNGLENBQUE7SUFDSCxDQUFDO0NBQ0YsQ0FBQyxDQUFBIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgRVNMaW50VXRpbHMsIFRTRVNUcmVlIH0gZnJvbSAnQHR5cGVzY3JpcHQtZXNsaW50L3V0aWxzJ1xuaW1wb3J0IHsgZ2V0Q2Fub25VcmwsIGdldENhbm9uUGF0dGVybiB9IGZyb20gJy4uL3V0aWxzL2Nhbm9uLmpzJ1xuXG5jb25zdCBSVUxFX05BTUUgPSAnbm8tY29udGFpbmVyLWluLXNlY3Rpb24nXG5jb25zdCBwYXR0ZXJuID0gZ2V0Q2Fub25QYXR0ZXJuKFJVTEVfTkFNRSlcblxuY29uc3QgY3JlYXRlUnVsZSA9IEVTTGludFV0aWxzLlJ1bGVDcmVhdG9yKCgpID0+IGdldENhbm9uVXJsKFJVTEVfTkFNRSkpXG5cbnR5cGUgTWVzc2FnZUlkcyA9ICdub0NvbnRhaW5lckluU2VjdGlvbidcblxuZXhwb3J0IGRlZmF1bHQgY3JlYXRlUnVsZTxbXSwgTWVzc2FnZUlkcz4oe1xuICBuYW1lOiBSVUxFX05BTUUsXG4gIG1ldGE6IHtcbiAgICB0eXBlOiAnc3VnZ2VzdGlvbicsXG4gICAgZG9jczoge1xuICAgICAgZGVzY3JpcHRpb246IHBhdHRlcm4/LnN1bW1hcnkgfHwgJ05vIENvbnRhaW5lciBpbnNpZGUgU2VjdGlvbicsXG4gICAgfSxcbiAgICBtZXNzYWdlczoge1xuICAgICAgbm9Db250YWluZXJJblNlY3Rpb246IGBbQ2Fub24gJHtwYXR0ZXJuPy5pZCB8fCAnMDAyJ31dIENvbnRhaW5lciBpbnNpZGUgU2VjdGlvbiBpcyByZWR1bmRhbnQuIFNlY3Rpb24gYWxyZWFkeSBwcm92aWRlcyBtYXgtd2lkdGggY29udGFpbm1lbnQuIFVzZSBTZWN0aW9uJ3MgaW5uZXJBbGlnbiBwcm9wIG9yIGEgcGxhaW4gZGl2IGluc3RlYWQuYCxcbiAgICB9LFxuICAgIHNjaGVtYTogW10sXG4gIH0sXG4gIGRlZmF1bHRPcHRpb25zOiBbXSxcbiAgY3JlYXRlKGNvbnRleHQpIHtcbiAgICAvLyBUcmFjayBpZiB3ZSdyZSBpbnNpZGUgYSBTZWN0aW9uIGNvbXBvbmVudFxuICAgIGxldCBzZWN0aW9uRGVwdGggPSAwXG5cbiAgICBmdW5jdGlvbiBpc0pTWEVsZW1lbnROYW1lZChcbiAgICAgIG5vZGU6IFRTRVNUcmVlLkpTWE9wZW5pbmdFbGVtZW50LFxuICAgICAgbmFtZTogc3RyaW5nXG4gICAgKTogYm9vbGVhbiB7XG4gICAgICByZXR1cm4gKFxuICAgICAgICBub2RlLm5hbWUudHlwZSA9PT0gJ0pTWElkZW50aWZpZXInICYmXG4gICAgICAgIG5vZGUubmFtZS5uYW1lID09PSBuYW1lXG4gICAgICApXG4gICAgfVxuXG4gICAgcmV0dXJuIHtcbiAgICAgIEpTWE9wZW5pbmdFbGVtZW50KG5vZGUpIHtcbiAgICAgICAgLy8gQ2hlY2sgaWYgZW50ZXJpbmcgYSBTZWN0aW9uXG4gICAgICAgIGlmIChpc0pTWEVsZW1lbnROYW1lZChub2RlLCAnU2VjdGlvbicpKSB7XG4gICAgICAgICAgc2VjdGlvbkRlcHRoKytcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIENoZWNrIGlmIHdlJ3JlIGluc2lkZSBhIFNlY3Rpb24gYW5kIGZvdW5kIGEgQ29udGFpbmVyXG4gICAgICAgIGlmIChzZWN0aW9uRGVwdGggPiAwICYmIGlzSlNYRWxlbWVudE5hbWVkKG5vZGUsICdDb250YWluZXInKSkge1xuICAgICAgICAgIGNvbnRleHQucmVwb3J0KHtcbiAgICAgICAgICAgIG5vZGUsXG4gICAgICAgICAgICBtZXNzYWdlSWQ6ICdub0NvbnRhaW5lckluU2VjdGlvbicsXG4gICAgICAgICAgfSlcbiAgICAgICAgfVxuICAgICAgfSxcblxuICAgICAgSlNYQ2xvc2luZ0VsZW1lbnQobm9kZSkge1xuICAgICAgICAvLyBDaGVjayBpZiBsZWF2aW5nIGEgU2VjdGlvblxuICAgICAgICBpZiAoXG4gICAgICAgICAgbm9kZS5uYW1lLnR5cGUgPT09ICdKU1hJZGVudGlmaWVyJyAmJlxuICAgICAgICAgIG5vZGUubmFtZS5uYW1lID09PSAnU2VjdGlvbidcbiAgICAgICAgKSB7XG4gICAgICAgICAgc2VjdGlvbkRlcHRoLS1cbiAgICAgICAgfVxuICAgICAgfSxcbiAgICB9XG4gIH0sXG59KVxuIl19
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
+
import { getCanonUrl, getCanonPattern } from '../utils/canon.js';
|
|
3
|
+
const RULE_NAME = 'prefer-component-props';
|
|
4
|
+
const pattern = getCanonPattern(RULE_NAME);
|
|
5
|
+
const createRule = ESLintUtils.RuleCreator(() => getCanonUrl(RULE_NAME));
|
|
6
|
+
// Map of component names to their style props and corresponding Tailwind patterns
|
|
7
|
+
const componentPropMappings = {
|
|
8
|
+
Paragraph: {
|
|
9
|
+
margin: /^m([by])?-/, // m- (all), mb- (bottom), my- (y-axis) - all affect bottom margin
|
|
10
|
+
color: /^text-(body|contrast|accent|white|black)/,
|
|
11
|
+
fontSize: /^text-(xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl|7xl|8xl|9xl)$/,
|
|
12
|
+
lineHeight: /^leading-/,
|
|
13
|
+
textAlign: /^text-(left|center|right|justify)$/,
|
|
14
|
+
fontWeight: /^font-(thin|extralight|light|normal|medium|semibold|bold|extrabold|black)$/,
|
|
15
|
+
},
|
|
16
|
+
Heading: {
|
|
17
|
+
margin: /^m([by])?-/, // m- (all), mb- (bottom), my- (y-axis) - all affect bottom margin
|
|
18
|
+
color: /^text-(body|contrast|accent|white|black)/,
|
|
19
|
+
fontSize: /^text-(xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl|7xl|8xl|9xl)$/,
|
|
20
|
+
lineHeight: /^leading-/,
|
|
21
|
+
textAlign: /^text-(left|center|right|justify)$/,
|
|
22
|
+
fontWeight: /^font-(thin|extralight|light|normal|medium|semibold|bold|extrabold|black)$/,
|
|
23
|
+
},
|
|
24
|
+
Accent: {
|
|
25
|
+
margin: /^m([by])?-/, // m- (all), mb- (bottom), my- (y-axis) - all affect bottom margin
|
|
26
|
+
color: /^text-(body|contrast|accent|white|black)/,
|
|
27
|
+
size: /^text-(xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl|7xl|8xl|9xl)$/,
|
|
28
|
+
textAlign: /^text-(left|center|right|justify)$/,
|
|
29
|
+
},
|
|
30
|
+
Button: {
|
|
31
|
+
margin: /^m([by])?-/, // m- (all), mb- (bottom), my- (y-axis) - all affect bottom margin
|
|
32
|
+
},
|
|
33
|
+
Label: {
|
|
34
|
+
margin: /^m([by])?-/, // m- (all), mb- (bottom), my- (y-axis) - all affect bottom margin
|
|
35
|
+
color: /^text-(body|contrast|accent|white|black)/,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
export default createRule({
|
|
39
|
+
name: RULE_NAME,
|
|
40
|
+
meta: {
|
|
41
|
+
type: 'suggestion',
|
|
42
|
+
docs: {
|
|
43
|
+
description: pattern?.summary || 'Use props over className for supported styles',
|
|
44
|
+
},
|
|
45
|
+
messages: {
|
|
46
|
+
preferComponentProps: `[Canon ${pattern?.id || '004'}] "{{className}}" in className should use the "{{propName}}" prop instead. Replace className="{{className}}" with {{propName}}="{{className}}".`,
|
|
47
|
+
},
|
|
48
|
+
schema: [],
|
|
49
|
+
},
|
|
50
|
+
defaultOptions: [],
|
|
51
|
+
create(context) {
|
|
52
|
+
return {
|
|
53
|
+
JSXOpeningElement(node) {
|
|
54
|
+
// Get the component name
|
|
55
|
+
if (node.name.type !== 'JSXIdentifier')
|
|
56
|
+
return;
|
|
57
|
+
const componentName = node.name.name;
|
|
58
|
+
// Check if this component has prop mappings
|
|
59
|
+
const propMappings = componentPropMappings[componentName];
|
|
60
|
+
if (!propMappings)
|
|
61
|
+
return;
|
|
62
|
+
// Find the className attribute
|
|
63
|
+
const classNameAttr = node.attributes.find((attr) => attr.type === 'JSXAttribute' &&
|
|
64
|
+
attr.name.type === 'JSXIdentifier' &&
|
|
65
|
+
attr.name.name === 'className');
|
|
66
|
+
if (!classNameAttr || !classNameAttr.value)
|
|
67
|
+
return;
|
|
68
|
+
// Extract class string value
|
|
69
|
+
let classValue = null;
|
|
70
|
+
if (classNameAttr.value.type === 'Literal') {
|
|
71
|
+
classValue = String(classNameAttr.value.value);
|
|
72
|
+
}
|
|
73
|
+
else if (classNameAttr.value.type === 'JSXExpressionContainer' &&
|
|
74
|
+
classNameAttr.value.expression.type === 'Literal') {
|
|
75
|
+
classValue = String(classNameAttr.value.expression.value);
|
|
76
|
+
}
|
|
77
|
+
if (!classValue)
|
|
78
|
+
return;
|
|
79
|
+
// Split into individual classes and check each
|
|
80
|
+
const classes = classValue.split(/\s+/).filter(Boolean);
|
|
81
|
+
for (const cls of classes) {
|
|
82
|
+
for (const [propName, pattern] of Object.entries(propMappings)) {
|
|
83
|
+
if (pattern.test(cls)) {
|
|
84
|
+
context.report({
|
|
85
|
+
node: classNameAttr,
|
|
86
|
+
messageId: 'preferComponentProps',
|
|
87
|
+
data: {
|
|
88
|
+
className: cls,
|
|
89
|
+
propName,
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
break; // Only report once per class
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJlZmVyLWNvbXBvbmVudC1wcm9wcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9lc2xpbnQvcnVsZXMvcHJlZmVyLWNvbXBvbmVudC1wcm9wcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsV0FBVyxFQUFZLE1BQU0sMEJBQTBCLENBQUE7QUFDaEUsT0FBTyxFQUFFLFdBQVcsRUFBRSxlQUFlLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQTtBQUVoRSxNQUFNLFNBQVMsR0FBRyx3QkFBd0IsQ0FBQTtBQUMxQyxNQUFNLE9BQU8sR0FBRyxlQUFlLENBQUMsU0FBUyxDQUFDLENBQUE7QUFFMUMsTUFBTSxVQUFVLEdBQUcsV0FBVyxDQUFDLFdBQVcsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxXQUFXLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQTtBQUl4RSxrRkFBa0Y7QUFDbEYsTUFBTSxxQkFBcUIsR0FBMkM7SUFDcEUsU0FBUyxFQUFFO1FBQ1QsTUFBTSxFQUFFLFlBQVksRUFBRSxrRUFBa0U7UUFDeEYsS0FBSyxFQUFFLDBDQUEwQztRQUNqRCxRQUFRLEVBQUUsMkRBQTJEO1FBQ3JFLFVBQVUsRUFBRSxXQUFXO1FBQ3ZCLFNBQVMsRUFBRSxvQ0FBb0M7UUFDL0MsVUFBVSxFQUFFLDRFQUE0RTtLQUN6RjtJQUNELE9BQU8sRUFBRTtRQUNQLE1BQU0sRUFBRSxZQUFZLEVBQUUsa0VBQWtFO1FBQ3hGLEtBQUssRUFBRSwwQ0FBMEM7UUFDakQsUUFBUSxFQUFFLDJEQUEyRDtRQUNyRSxVQUFVLEVBQUUsV0FBVztRQUN2QixTQUFTLEVBQUUsb0NBQW9DO1FBQy9DLFVBQVUsRUFBRSw0RUFBNEU7S0FDekY7SUFDRCxNQUFNLEVBQUU7UUFDTixNQUFNLEVBQUUsWUFBWSxFQUFFLGtFQUFrRTtRQUN4RixLQUFLLEVBQUUsMENBQTBDO1FBQ2pELElBQUksRUFBRSwyREFBMkQ7UUFDakUsU0FBUyxFQUFFLG9DQUFvQztL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ckMsQ0FBQztvQkFDSCxDQUFDO2dCQUNILENBQUM7WUFDSCxDQUFDO1NBQ0YsQ0FBQTtJQUNILENBQUM7Q0FDRixDQUFDLENBQUEiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBFU0xpbnRVdGlscywgVFNFU1RyZWUgfSBmcm9tICdAdHlwZXNjcmlwdC1lc2xpbnQvdXRpbHMnXG5pbXBvcnQgeyBnZXRDYW5vblVybCwgZ2V0Q2Fub25QYXR0ZXJuIH0gZnJvbSAnLi4vdXRpbHMvY2Fub24uanMnXG5cbmNvbnN0IFJVTEVfTkFNRSA9ICdwcmVmZXItY29tcG9uZW50LXByb3BzJ1xuY29uc3QgcGF0dGVybiA9IGdldENhbm9uUGF0dGVybihSVUxFX05BTUUpXG5cbmNvbnN0IGNyZWF0ZVJ1bGUgPSBFU0xpbnRVdGlscy5SdWxlQ3JlYXRvcigoKSA9PiBnZXRDYW5vblVybChSVUxFX05BTUUpKVxuXG50eXBlIE1lc3NhZ2VJZHMgPSAncHJlZmVyQ29tcG9uZW50UHJvcHMnXG5cbi8vIE1hcCBvZiBjb21wb25lbnQgbmFtZXMgdG8gdGhlaXIgc3R5bGUgcHJvcHMgYW5kIGNvcnJlc3BvbmRpbmcgVGFpbHdpbmQgcGF0dGVybnNcbmNvbnN0IGNvbXBvbmVudFByb3BNYXBwaW5nczogUmVjb3JkPHN0cmluZywgUmVjb3JkPHN0cmluZywgUmVnRXhwPj4gPSB7XG4gIFBhcmFncmFwaDoge1xuICAgIG1hcmdpbjogL15tKFtieV0pPy0vLCAvLyBtLSAoYWxsKSwgbWItIChib3R0b20pLCBteS0gKHktYXhpcykgLSBhbGwgYWZmZWN0IGJvdHRvbSBtYXJnaW5cbiAgICBjb2xvcjogL150ZXh0LShib2R5fGNvbnRyYXN0fGFjY2VudHx3aGl0ZXxibGFjaykvLFxuICAgIGZvbnRTaXplOiAvXnRleHQtKHhzfHNtfGJhc2V8bGd8eGx8MnhsfDN4bHw0eGx8NXhsfDZ4bHw3eGx8OHhsfDl4bCkkLyxcbiAgICBsaW5lSGVpZ2h0OiAvXmxlYWRpbmctLyxcbiAgICB0ZXh0QWxpZ246IC9edGV4dC0obGVmdHxjZW50ZXJ8cmlnaHR8anVzdGlmeSkkLyxcbiAgICBmb250V2VpZ2h0OiAvXmZvbnQtKHRoaW58ZXh0cmFsaWdodHxsaWdodHxub3JtYWx8bWVkaXVtfHNlbWlib2xkfGJvbGR8ZXh0cmFib2xkfGJsYWNrKSQvLFxuICB9LFxuICBIZWFkaW5nOiB7XG4gICAgbWFyZ2luOiAvXm0oW2J5XSk/LS8sIC8vIG0tIChhbGwpLCBtYi0gKGJvdHRvbSksIG15LSAoeS1heGlzKSAtIGFsbCBhZmZlY3QgYm90dG9tIG1hcmdpblxuICAgIGNvbG9yOiAvXnRleHQtKGJvZHl8Y29udHJhc3R8YWNjZW50fHdoaXRlfGJsYWNrKS8sXG4gICAgZm9udFNpemU6IC9edGV4dC0oeHN8c218YmFzZXxsZ3x4bHwyeGx8M3hsfDR4bHw1eGx8NnhsfDd4bHw4eGx8OXhsKSQvLFxuICAgIGxpbmVIZWlnaHQ6IC9ebGVhZGluZy0vLFxuICAgIHRleHRBbGlnbjogL150ZXh0LShsZWZ0fGNlbnRlcnxyaWdodHxqdXN0aWZ5KSQvLFxuICAgIGZvbnRXZWlnaHQ6IC9eZm9udC0odGhpbnxleHRyYWxpZ2h0fGxpZ2h0fG5vcm1hbHxtZWRpdW18c2VtaWJvbGR8Ym9sZHxleHRyYWJvbGR8YmxhY2spJC8sXG4gIH0sXG4gIEFjY2VudDoge1xuICAgIG1hcmdpbjogL15tKFtieV0pPy0vLCAvLyBtLSAoYWxsKSwgbWItIChib3R0b20pLCBteS0gKHktYXhpcykgLSBhbGwgYWZmZWN0IGJvdHRvbSBtYXJnaW5cbiAgICBjb2xvcjogL150ZXh0LShib2R5fGNvbnRyYXN0fGFjY2VudHx3aGl0ZXxibGFjaykvLFxuICAgIHNpemU6IC9edGV4dC0oeHN8c218YmFzZXxsZ3x4bHwyeGx8M3hsfDR4bHw1eGx8NnhsfDd4bHw4eGx8OXhsKSQvLFxuICAgIHRleHRBbGlnbjogL150ZXh0LShsZWZ0fGNlbnRlcnxyaWdodHxqdXN0aWZ5KSQvLFxuICB9LFxuICBCdXR0b246IHtcbiAgICBtYXJnaW46IC9ebShbYnldKT8tLywgLy8gbS0gKGFsbCksIG1iLSAoYm90dG9tKSwgbXktICh5LWF4aXMpIC0gYWxsIGFmZmVjdCBib3R0b20gbWFyZ2luXG4gIH0sXG4gIExhYmVsOiB7XG4gICAgbWFyZ2luOiAvXm0oW2J5XSk/LS8sIC8vIG0tIChhbGwpLCBtYi0gKGJvdHRvbSksIG15LSAoeS1heGlzKSAtIGFsbCBhZmZlY3QgYm90dG9tIG1hcmdpblxuICAgIGNvbG9yOiAvXnRleHQtKGJvZHl8Y29udHJhc3R8YWNjZW50fHdoaXRlfGJsYWNrKS8sXG4gIH0sXG59XG5cbmV4cG9ydCBkZWZhdWx0IGNyZWF0ZVJ1bGU8W10sIE1lc3NhZ2VJZHM+KHtcbiAgbmFtZTogUlVMRV9OQU1FLFxuICBtZXRhOiB7XG4gICAgdHlwZTogJ3N1Z2dlc3Rpb24nLFxuICAgIGRvY3M6IHtcbiAgICAgIGRlc2NyaXB0aW9uOiBwYXR0ZXJuPy5zdW1tYXJ5IHx8ICdVc2UgcHJvcHMgb3ZlciBjbGFzc05hbWUgZm9yIHN1cHBvcnRlZCBzdHlsZXMnLFxuICAgIH0sXG4gICAgbWVzc2FnZXM6IHtcbiAgICAgIHByZWZlckNvbXBvbmVudFByb3BzOiBgW0Nhbm9uICR7cGF0dGVybj8uaWQgfHwgJzAwNCd9XSBcInt7Y2xhc3NOYW1lfX1cIiBpbiBjbGFzc05hbWUgc2hvdWxkIHVzZSB0aGUgXCJ7e3Byb3BOYW1lfX1cIiBwcm9wIGluc3RlYWQuIFJlcGxhY2UgY2xhc3NOYW1lPVwie3tjbGFzc05hbWV9fVwiIHdpdGgge3twcm9wTmFtZX19PVwie3tjbGFzc05hbWV9fVwiLmAsXG4gICAgfSxcbiAgICBzY2hlbWE6IFtdLFxuICB9LFxuICBkZWZhdWx0T3B0aW9uczogW10sXG4gIGNyZWF0ZShjb250ZXh0KSB7XG4gICAgcmV0dXJuIHtcbiAgICAgIEpTWE9wZW5pbmdFbGVtZW50KG5vZGUpIHtcbiAgICAgICAgLy8gR2V0IHRoZSBjb21wb25lbnQgbmFtZVxuICAgICAgICBpZiAobm9kZS5uYW1lLnR5cGUgIT09ICdKU1hJZGVudGlmaWVyJykgcmV0dXJuXG4gICAgICAgIGNvbnN0IGNvbXBvbmVudE5hbWUgPSBub2RlLm5hbWUubmFtZVxuXG4gICAgICAgIC8vIENoZWNrIGlmIHRoaXMgY29tcG9uZW50IGhhcyBwcm9wIG1hcHBpbmdzXG4gICAgICAgIGNvbnN0IHByb3BNYXBwaW5ncyA9IGNvbXBvbmVudFByb3BNYXBwaW5nc1tjb21wb25lbnROYW1lXVxuICAgICAgICBpZiAoIXByb3BNYXBwaW5ncykgcmV0dXJuXG5cbiAgICAgICAgLy8gRmluZCB0aGUgY2xhc3NOYW1lIGF0dHJpYnV0ZVxuICAgICAgICBjb25zdCBjbGFzc05hbWVBdHRyID0gbm9kZS5hdHRyaWJ1dGVzLmZpbmQoXG4gICAgICAgICAgKGF0dHIpOiBhdHRyIGlzIFRTRVNUcmVlLkpTWEF0dHJpYnV0ZSA9PlxuICAgICAgICAgICAgYXR0ci50eXBlID09PSAnSlNYQXR0cmlidXRlJyAmJlxuICAgICAgICAgICAgYXR0ci5uYW1lLnR5cGUgPT09ICdKU1hJZGVudGlmaWVyJyAmJlxuICAgICAgICAgICAgYXR0ci5uYW1lLm5hbWUgPT09ICdjbGFzc05hbWUnXG4gICAgICAgIClcblxuICAgICAgICBpZiAoIWNsYXNzTmFtZUF0dHIgfHwgIWNsYXNzTmFtZUF0dHIudmFsdWUpIHJldHVyblxuXG4gICAgICAgIC8vIEV4dHJhY3QgY2xhc3Mgc3RyaW5nIHZhbHVlXG4gICAgICAgIGxldCBjbGFzc1ZhbHVlOiBzdHJpbmcgfCBudWxsID0gbnVsbFxuXG4gICAgICAgIGlmIChjbGFzc05hbWVBdHRyLnZhbHVlLnR5cGUgPT09ICdMaXRlcmFsJykge1xuICAgICAgICAgIGNsYXNzVmFsdWUgPSBTdHJpbmcoY2xhc3NOYW1lQXR0ci52YWx1ZS52YWx1ZSlcbiAgICAgICAgfSBlbHNlIGlmIChcbiAgICAgICAgICBjbGFzc05hbWVBdHRyLnZhbHVlLnR5cGUgPT09ICdKU1hFeHByZXNzaW9uQ29udGFpbmVyJyAmJlxuICAgICAgICAgIGNsYXNzTmFtZUF0dHIudmFsdWUuZXhwcmVzc2lvbi50eXBlID09PSAnTGl0ZXJhbCdcbiAgICAgICAgKSB7XG4gICAgICAgICAgY2xhc3NWYWx1ZSA9IFN0cmluZyhjbGFzc05hbWVBdHRyLnZhbHVlLmV4cHJlc3Npb24udmFsdWUpXG4gICAgICAgIH1cblxuICAgICAgICBpZiAoIWNsYXNzVmFsdWUpIHJldHVyblxuXG4gICAgICAgIC8vIFNwbGl0IGludG8gaW5kaXZpZHVhbCBjbGFzc2VzIGFuZCBjaGVjayBlYWNoXG4gICAgICAgIGNvbnN0IGNsYXNzZXMgPSBjbGFzc1ZhbHVlLnNwbGl0KC9cXHMrLykuZmlsdGVyKEJvb2xlYW4pXG5cbiAgICAgICAgZm9yIChjb25zdCBjbHMgb2YgY2xhc3Nlcykge1xuICAgICAgICAgIGZvciAoY29uc3QgW3Byb3BOYW1lLCBwYXR0ZXJuXSBvZiBPYmplY3QuZW50cmllcyhwcm9wTWFwcGluZ3MpKSB7XG4gICAgICAgICAgICBpZiAocGF0dGVybi50ZXN0KGNscykpIHtcbiAgICAgICAgICAgICAgY29udGV4dC5yZXBvcnQoe1xuICAgICAgICAgICAgICAgIG5vZGU6IGNsYXNzTmFtZUF0dHIsXG4gICAgICAgICAgICAgICAgbWVzc2FnZUlkOiAncHJlZmVyQ29tcG9uZW50UHJvcHMnLFxuICAgICAgICAgICAgICAgIGRhdGE6IHtcbiAgICAgICAgICAgICAgICAgIGNsYXNzTmFtZTogY2xzLFxuICAgICAgICAgICAgICAgICAgcHJvcE5hbWUsXG4gICAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgICAgfSlcbiAgICAgICAgICAgICAgYnJlYWsgLy8gT25seSByZXBvcnQgb25jZSBwZXIgY2xhc3NcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH0sXG4gICAgfVxuICB9LFxufSlcbiJdfQ==
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { getCanonUrl, getCanonPattern } from '../utils/canon.js';
|
|
2
|
+
const RULE_NAME = 'prefer-typography-components';
|
|
3
|
+
const pattern = getCanonPattern(RULE_NAME);
|
|
4
|
+
const rule = {
|
|
5
|
+
meta: {
|
|
6
|
+
type: 'suggestion',
|
|
7
|
+
docs: {
|
|
8
|
+
description: pattern?.summary || 'Use Paragraph/Span, not raw tags',
|
|
9
|
+
recommended: true,
|
|
10
|
+
url: getCanonUrl(RULE_NAME),
|
|
11
|
+
},
|
|
12
|
+
messages: {
|
|
13
|
+
useParagraph: `[Canon ${pattern?.id || '003'}] Use the Paragraph component instead of <p>. Import: import { Paragraph } from "@/components"`,
|
|
14
|
+
useSpan: `[Canon ${pattern?.id || '003'}] Use the Span component instead of <span> for text content. Import: import { Span } from "@/components"`,
|
|
15
|
+
},
|
|
16
|
+
schema: [],
|
|
17
|
+
},
|
|
18
|
+
create(context) {
|
|
19
|
+
const filename = context.filename || context.getFilename();
|
|
20
|
+
// Only apply to block files
|
|
21
|
+
if (!filename.includes('/blocks/')) {
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
JSXOpeningElement(node) {
|
|
26
|
+
const elementName = node.name?.name;
|
|
27
|
+
if (elementName === 'p') {
|
|
28
|
+
context.report({
|
|
29
|
+
node,
|
|
30
|
+
messageId: 'useParagraph',
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
if (elementName === 'span') {
|
|
34
|
+
// Skip spans that are inside typography components (Heading, Paragraph, Label, etc.)
|
|
35
|
+
// These are used for inline styling effects like gradient text, emphasis, etc.
|
|
36
|
+
const typographyComponents = ['Heading', 'Paragraph', 'Label', 'Span', 'Quote', 'Subheading', 'Accent'];
|
|
37
|
+
let parent = node.parent;
|
|
38
|
+
while (parent) {
|
|
39
|
+
if (parent.type === 'JSXElement' &&
|
|
40
|
+
parent.openingElement?.name?.name &&
|
|
41
|
+
typographyComponents.includes(parent.openingElement.name.name)) {
|
|
42
|
+
// Span is inside a typography component, skip warning
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
parent = parent.parent;
|
|
46
|
+
}
|
|
47
|
+
// Check className for text-related classes and gradient text
|
|
48
|
+
let hasTextClasses = false;
|
|
49
|
+
let isGradientText = false;
|
|
50
|
+
node.attributes?.forEach((attr) => {
|
|
51
|
+
if (attr.type === 'JSXAttribute' && attr.name?.name === 'className') {
|
|
52
|
+
const value = attr.value?.value || '';
|
|
53
|
+
// Check for text-related Tailwind classes
|
|
54
|
+
if (/\b(text-|font-|leading-|tracking-)/.test(value)) {
|
|
55
|
+
hasTextClasses = true;
|
|
56
|
+
}
|
|
57
|
+
// Skip gradient text spans (bg-clip-text is used for gradient text effects)
|
|
58
|
+
if (/\bbg-clip-text\b/.test(value)) {
|
|
59
|
+
isGradientText = true;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
// Only warn if the span has text styling and is not gradient text
|
|
64
|
+
if (hasTextClasses && !isGradientText) {
|
|
65
|
+
context.report({
|
|
66
|
+
node,
|
|
67
|
+
messageId: 'useSpan',
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
export default rule;
|
|
76
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJlZmVyLXR5cG9ncmFwaHktY29tcG9uZW50cy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9lc2xpbnQvcnVsZXMvcHJlZmVyLXR5cG9ncmFwaHktY29tcG9uZW50cy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLEVBQUUsV0FBVyxFQUFFLGVBQWUsRUFBRSxNQUFNLG1CQUFtQixDQUFBO0FBRWhFLE1BQU0sU0FBUyxHQUFHLDhCQUE4QixDQUFBO0FBQ2hELE1BQU0sT0FBTyxHQUFHLGVBQWUsQ0FBQyxTQUFTLENBQUMsQ0FBQTtBQUUxQyxNQUFNLElBQUksR0FBb0I7SUFDNUIsSUFBSSxFQUFFO1FBQ0osSUFBSSxFQUFFLFlBQVk7UUFDbEIsSUFBSSxFQUFFO1lBQ0osV0FBVyxFQUFFLE9BQU8sRUFBRSxPQUFPLElBQUksa0NBQWtDO1lBQ25FLFdBQVcsRUFBRSxJQUFJO1lBQ2pCLEdBQUcsRUFBRSxXQUFXLENBQUMsU0FBUyxDQUFDO1NBQzVCO1FBQ0QsUUFBUSxFQUFFO1lBQ1IsWUFBWSxFQUFFLFVBQVUsT0FBTyxFQUFFLEVBQUUsSUFBSSxLQUFLLGdHQUFnRztZQUM1SSxPQUFPLEVBQUUsVUFBVSxPQUFPLEVBQUUsRUFBRSxJQUFJLEtBQUssMEdBQTBHO1NBQ2xKO1FBQ0QsTUFBTSxFQUFFLEVBQUU7S0FDWDtJQUVELE1BQU0sQ0FBQyxPQUFPO1FBQ1osTUFBTSxRQUFRLEdBQUcsT0FBTyxDQUFDLFFBQVEsSUFBSSxPQUFPLENBQUMsV0FBVyxFQUFFLENBQUE7UUFFMUQsNEJBQTRCO1FBQzVCLElBQUksQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDLFVBQVUsQ0FBQyxFQUFFLENBQUM7WUFDbkMsT0FBTyxFQUFFLENBQUE7UUFDWCxDQUFDO1FBRUQsT0FBTztZQUNMLGlCQUFpQixDQUFDLElBQVM7Z0JBQ3pCLE1BQU0sV0FBVyxHQUFHLElBQUksQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFBO2dCQUVuQyxJQUFJLFdBQVcsS0FBSyxHQUFHLEVBQUUsQ0FBQztvQkFDeEIsT0FBTyxDQUFDLE1BQU0sQ0FBQzt3QkFDYixJQUFJO3dCQUNKLFNBQVMsRUFBRSxjQUFjO3FCQUMxQixDQUFDLENBQUE7Z0JBQ0osQ0FBQztnQkFFRCxJQUFJLFdBQVcsS0FBSyxNQUFNLEVBQUUsQ0FBQztvQkFDM0IscUZBQXFGO29CQUNyRiwrRUFBK0U7b0JBQy9FLE1BQU0sb0JBQW9CLEdBQUcsQ0FBQyxTQUFTLEVBQUUsV0FBVyxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsT0FBTyxFQUFFLFlBQVksRUFBRSxRQUFRLENBQUMsQ0FBQTtvQkFFdkcsSUFBSSxNQUFNLEdBQUcsSUFBSSxDQUFDLE1BQU0sQ0FBQTtvQkFDeEIsT0FBTyxNQUFNLEVBQUUsQ0FBQzt3QkFDZCxJQUNFLE1BQU0sQ0FBQyxJQUFJLEtBQUssWUFBWTs0QkFDNUIsTUFBTSxDQUFDLGNBQWMsRUFBRSxJQUFJLEVBQUUsSUFBSTs0QkFDakMsb0JBQW9CLENBQUMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxjQUFjLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUM5RCxDQUFDOzRCQUNELHNEQUFzRDs0QkFDdEQsT0FBTTt3QkFDUixDQUFDO3dCQUNELE1BQU0sR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFBO29CQUN4QixDQUFDO29CQUVELDZEQUE2RDtvQkFDN0QsSUFBSSxjQUFjLEdBQUcsS0FBSyxDQUFBO29CQUMxQixJQUFJLGNBQWMsR0FBRyxLQUFLLENBQUE7b0JBRTFCLElBQUksQ0FBQyxVQUFVLEVBQUUsT0FBTyxDQUFDLENBQUMsSUFBUyxFQUFFLEVBQUU7d0JBQ3JDLElBQUksSUFBSSxDQUFDLElBQUksS0FBSyxjQUFjLElBQUksSUFBSSxDQUFDLElBQUksRUFBRSxJQUFJLEtBQUssV0FBVyxFQUFFLENBQUM7NEJBQ3BFLE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxLQUFLLEVBQUUsS0FBSyxJQUFJLEVBQUUsQ0FBQTs0QkFDckMsMENBQTBDOzRCQUMxQyxJQUFJLG9DQUFvQyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDO2dDQUNyRCxjQUFjLEdBQUcsSUFBSSxDQUFBOzRCQUN2QixDQUFDOzRCQUNELDRFQUE0RTs0QkFDNUUsSUFBSSxrQkFBa0IsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQztnQ0FDbkMsY0FBYyxHQUFHLElBQUksQ0FBQTs0QkFDdkIsQ0FBQzt3QkFDSCxDQUFDO29CQUNILENBQUMsQ0FBQyxDQUFBO29CQUVGLGtFQUFrRTtvQkFDbEUsSUFBSSxjQUFjLElBQUksQ0FBQyxjQUFjLEVBQUUsQ0FBQzt3QkFDdEMsT0FBTyxDQUFDLE1BQU0sQ0FBQzs0QkFDYixJQUFJOzRCQUNKLFNBQVMsRUFBRSxTQUFTO3lCQUNyQixDQUFDLENBQUE7b0JBQ0osQ0FBQztnQkFDSCxDQUFDO1lBQ0gsQ0FBQztTQUNGLENBQUE7SUFDSCxDQUFDO0NBQ0YsQ0FBQTtBQUVELGVBQWUsSUFBSSxDQUFBIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHR5cGUgeyBSdWxlIH0gZnJvbSAnZXNsaW50J1xuaW1wb3J0IHsgZ2V0Q2Fub25VcmwsIGdldENhbm9uUGF0dGVybiB9IGZyb20gJy4uL3V0aWxzL2Nhbm9uLmpzJ1xuXG5jb25zdCBSVUxFX05BTUUgPSAncHJlZmVyLXR5cG9ncmFwaHktY29tcG9uZW50cydcbmNvbnN0IHBhdHRlcm4gPSBnZXRDYW5vblBhdHRlcm4oUlVMRV9OQU1FKVxuXG5jb25zdCBydWxlOiBSdWxlLlJ1bGVNb2R1bGUgPSB7XG4gIG1ldGE6IHtcbiAgICB0eXBlOiAnc3VnZ2VzdGlvbicsXG4gICAgZG9jczoge1xuICAgICAgZGVzY3JpcHRpb246IHBhdHRlcm4/LnN1bW1hcnkgfHwgJ1VzZSBQYXJhZ3JhcGgvU3Bhbiwgbm90IHJhdyB0YWdzJyxcbiAgICAgIHJlY29tbWVuZGVkOiB0cnVlLFxuICAgICAgdXJsOiBnZXRDYW5vblVybChSVUxFX05BTUUpLFxuICAgIH0sXG4gICAgbWVzc2FnZXM6IHtcbiAgICAgIHVzZVBhcmFncmFwaDogYFtDYW5vbiAke3BhdHRlcm4/LmlkIHx8ICcwMDMnfV0gVXNlIHRoZSBQYXJhZ3JhcGggY29tcG9uZW50IGluc3RlYWQgb2YgPHA+LiBJbXBvcnQ6IGltcG9ydCB7IFBhcmFncmFwaCB9IGZyb20gXCJAL2NvbXBvbmVudHNcImAsXG4gICAgICB1c2VTcGFuOiBgW0Nhbm9uICR7cGF0dGVybj8uaWQgfHwgJzAwMyd9XSBVc2UgdGhlIFNwYW4gY29tcG9uZW50IGluc3RlYWQgb2YgPHNwYW4+IGZvciB0ZXh0IGNvbnRlbnQuIEltcG9ydDogaW1wb3J0IHsgU3BhbiB9IGZyb20gXCJAL2NvbXBvbmVudHNcImAsXG4gICAgfSxcbiAgICBzY2hlbWE6IFtdLFxuICB9LFxuXG4gIGNyZWF0ZShjb250ZXh0KSB7XG4gICAgY29uc3QgZmlsZW5hbWUgPSBjb250ZXh0LmZpbGVuYW1lIHx8IGNvbnRleHQuZ2V0RmlsZW5hbWUoKVxuXG4gICAgLy8gT25seSBhcHBseSB0byBibG9jayBmaWxlc1xuICAgIGlmICghZmlsZW5hbWUuaW5jbHVkZXMoJy9ibG9ja3MvJykpIHtcbiAgICAgIHJldHVybiB7fVxuICAgIH1cblxuICAgIHJldHVybiB7XG4gICAgICBKU1hPcGVuaW5nRWxlbWVudChub2RlOiBhbnkpIHtcbiAgICAgICAgY29uc3QgZWxlbWVudE5hbWUgPSBub2RlLm5hbWU/Lm5hbWVcblxuICAgICAgICBpZiAoZWxlbWVudE5hbWUgPT09ICdwJykge1xuICAgICAgICAgIGNvbnRleHQucmVwb3J0KHtcbiAgICAgICAgICAgIG5vZGUsXG4gICAgICAgICAgICBtZXNzYWdlSWQ6ICd1c2VQYXJhZ3JhcGgnLFxuICAgICAgICAgIH0pXG4gICAgICAgIH1cblxuICAgICAgICBpZiAoZWxlbWVudE5hbWUgPT09ICdzcGFuJykge1xuICAgICAgICAgIC8vIFNraXAgc3BhbnMgdGhhdCBhcmUgaW5zaWRlIHR5cG9ncmFwaHkgY29tcG9uZW50cyAoSGVhZGluZywgUGFyYWdyYXBoLCBMYWJlbCwgZXRjLilcbiAgICAgICAgICAvLyBUaGVzZSBhcmUgdXNlZCBmb3IgaW5saW5lIHN0eWxpbmcgZWZmZWN0cyBsaWtlIGdyYWRpZW50IHRleHQsIGVtcGhhc2lzLCBldGMuXG4gICAgICAgICAgY29uc3QgdHlwb2dyYXBoeUNvbXBvbmVudHMgPSBbJ0hlYWRpbmcnLCAnUGFyYWdyYXBoJywgJ0xhYmVsJywgJ1NwYW4nLCAnUXVvdGUnLCAnU3ViaGVhZGluZycsICdBY2NlbnQnXVxuICAgICAgICAgIFxuICAgICAgICAgIGxldCBwYXJlbnQgPSBub2RlLnBhcmVudFxuICAgICAgICAgIHdoaWxlIChwYXJlbnQpIHtcbiAgICAgICAgICAgIGlmIChcbiAgICAgICAgICAgICAgcGFyZW50LnR5cGUgPT09ICdKU1hFbGVtZW50JyAmJlxuICAgICAgICAgICAgICBwYXJlbnQub3BlbmluZ0VsZW1lbnQ/Lm5hbWU/Lm5hbWUgJiZcbiAgICAgICAgICAgICAgdHlwb2dyYXBoeUNvbXBvbmVudHMuaW5jbHVkZXMocGFyZW50Lm9wZW5pbmdFbGVtZW50Lm5hbWUubmFtZSlcbiAgICAgICAgICAgICkge1xuICAgICAgICAgICAgICAvLyBTcGFuIGlzIGluc2lkZSBhIHR5cG9ncmFwaHkgY29tcG9uZW50LCBza2lwIHdhcm5pbmdcbiAgICAgICAgICAgICAgcmV0dXJuXG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBwYXJlbnQgPSBwYXJlbnQucGFyZW50XG4gICAgICAgICAgfVxuXG4gICAgICAgICAgLy8gQ2hlY2sgY2xhc3NOYW1lIGZvciB0ZXh0LXJlbGF0ZWQgY2xhc3NlcyBhbmQgZ3JhZGllbnQgdGV4dFxuICAgICAgICAgIGxldCBoYXNUZXh0Q2xhc3NlcyA9IGZhbHNlXG4gICAgICAgICAgbGV0IGlzR3JhZGllbnRUZXh0ID0gZmFsc2VcblxuICAgICAgICAgIG5vZGUuYXR0cmlidXRlcz8uZm9yRWFjaCgoYXR0cjogYW55KSA9PiB7XG4gICAgICAgICAgICBpZiAoYXR0ci50eXBlID09PSAnSlNYQXR0cmlidXRlJyAmJiBhdHRyLm5hbWU/Lm5hbWUgPT09ICdjbGFzc05hbWUnKSB7XG4gICAgICAgICAgICAgIGNvbnN0IHZhbHVlID0gYXR0ci52YWx1ZT8udmFsdWUgfHwgJydcbiAgICAgICAgICAgICAgLy8gQ2hlY2sgZm9yIHRleHQtcmVsYXRlZCBUYWlsd2luZCBjbGFzc2VzXG4gICAgICAgICAgICAgIGlmICgvXFxiKHRleHQtfGZvbnQtfGxlYWRpbmctfHRyYWNraW5nLSkvLnRlc3QodmFsdWUpKSB7XG4gICAgICAgICAgICAgICAgaGFzVGV4dENsYXNzZXMgPSB0cnVlXG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgLy8gU2tpcCBncmFkaWVudCB0ZXh0IHNwYW5zIChiZy1jbGlwLXRleHQgaXMgdXNlZCBmb3IgZ3JhZGllbnQgdGV4dCBlZmZlY3RzKVxuICAgICAgICAgICAgICBpZiAoL1xcYmJnLWNsaXAtdGV4dFxcYi8udGVzdCh2YWx1ZSkpIHtcbiAgICAgICAgICAgICAgICBpc0dyYWRpZW50VGV4dCA9IHRydWVcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0pXG5cbiAgICAgICAgICAvLyBPbmx5IHdhcm4gaWYgdGhlIHNwYW4gaGFzIHRleHQgc3R5bGluZyBhbmQgaXMgbm90IGdyYWRpZW50IHRleHRcbiAgICAgICAgICBpZiAoaGFzVGV4dENsYXNzZXMgJiYgIWlzR3JhZGllbnRUZXh0KSB7XG4gICAgICAgICAgICBjb250ZXh0LnJlcG9ydCh7XG4gICAgICAgICAgICAgIG5vZGUsXG4gICAgICAgICAgICAgIG1lc3NhZ2VJZDogJ3VzZVNwYW4nLFxuICAgICAgICAgICAgfSlcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH0sXG4gICAgfVxuICB9LFxufVxuXG5leHBvcnQgZGVmYXVsdCBydWxlXG4iXX0=
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type Pattern } from '../../index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Get Canon pattern info for an ESLint rule
|
|
4
|
+
*/
|
|
5
|
+
export declare function getCanonPattern(ruleName: string): Pattern | undefined;
|
|
6
|
+
/**
|
|
7
|
+
* Format a message with Canon pattern prefix
|
|
8
|
+
*/
|
|
9
|
+
export declare function formatCanonMessage(ruleName: string, message: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Get Canon docs metadata for a rule
|
|
12
|
+
*/
|
|
13
|
+
export declare function getCanonDocs(ruleName: string): {
|
|
14
|
+
canon?: undefined;
|
|
15
|
+
} | {
|
|
16
|
+
canon: {
|
|
17
|
+
pattern: string;
|
|
18
|
+
title: string;
|
|
19
|
+
url: string;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Create a URL for the Canon pattern docs
|
|
24
|
+
*/
|
|
25
|
+
export declare function getCanonUrl(ruleName: string): string;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { patterns } from '../../index.js';
|
|
2
|
+
// Build a lookup map from rule name to pattern
|
|
3
|
+
const ruleToPatternMap = new Map();
|
|
4
|
+
for (const pattern of patterns) {
|
|
5
|
+
if (pattern.rule) {
|
|
6
|
+
ruleToPatternMap.set(pattern.rule, pattern);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Get Canon pattern info for an ESLint rule
|
|
11
|
+
*/
|
|
12
|
+
export function getCanonPattern(ruleName) {
|
|
13
|
+
return ruleToPatternMap.get(`gallop/${ruleName}`);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Format a message with Canon pattern prefix
|
|
17
|
+
*/
|
|
18
|
+
export function formatCanonMessage(ruleName, message) {
|
|
19
|
+
const pattern = getCanonPattern(ruleName);
|
|
20
|
+
if (pattern) {
|
|
21
|
+
return `[Canon ${pattern.id}] ${message}`;
|
|
22
|
+
}
|
|
23
|
+
return message;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Get Canon docs metadata for a rule
|
|
27
|
+
*/
|
|
28
|
+
export function getCanonDocs(ruleName) {
|
|
29
|
+
const pattern = getCanonPattern(ruleName);
|
|
30
|
+
if (!pattern)
|
|
31
|
+
return {};
|
|
32
|
+
return {
|
|
33
|
+
canon: {
|
|
34
|
+
pattern: pattern.id,
|
|
35
|
+
title: pattern.title,
|
|
36
|
+
url: `https://github.com/gallop-software/canon/blob/main/${pattern.file}`,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Create a URL for the Canon pattern docs
|
|
42
|
+
*/
|
|
43
|
+
export function getCanonUrl(ruleName) {
|
|
44
|
+
const pattern = getCanonPattern(ruleName);
|
|
45
|
+
if (pattern) {
|
|
46
|
+
return `https://github.com/gallop-software/canon/blob/main/${pattern.file}`;
|
|
47
|
+
}
|
|
48
|
+
return `https://github.com/gallop-software/canon`;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2Fub24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvZXNsaW50L3V0aWxzL2Nhbm9uLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxRQUFRLEVBQWdCLE1BQU0sZ0JBQWdCLENBQUE7QUFFdkQsK0NBQStDO0FBQy9DLE1BQU0sZ0JBQWdCLEdBQUcsSUFBSSxHQUFHLEVBQW1CLENBQUE7QUFDbkQsS0FBSyxNQUFNLE9BQU8sSUFBSSxRQUFRLEVBQUUsQ0FBQztJQUMvQixJQUFJLE9BQU8sQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUNqQixnQkFBZ0IsQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLElBQUksRUFBRSxPQUFPLENBQUMsQ0FBQTtJQUM3QyxDQUFDO0FBQ0gsQ0FBQztBQUVEOztHQUVHO0FBQ0gsTUFBTSxVQUFVLGVBQWUsQ0FBQyxRQUFnQjtJQUM5QyxPQUFPLGdCQUFnQixDQUFDLEdBQUcsQ0FBQyxVQUFVLFFBQVEsRUFBRSxDQUFDLENBQUE7QUFDbkQsQ0FBQztBQUVEOztHQUVHO0FBQ0gsTUFBTSxVQUFVLGtCQUFrQixDQUFDLFFBQWdCLEVBQUUsT0FBZTtJQUNsRSxNQUFNLE9BQU8sR0FBRyxlQUFlLENBQUMsUUFBUSxDQUFDLENBQUE7SUFDekMsSUFBSSxPQUFPLEVBQUUsQ0FBQztRQUNaLE9BQU8sVUFBVSxPQUFPLENBQUMsRUFBRSxLQUFLLE9BQU8sRUFBRSxDQUFBO0lBQzNDLENBQUM7SUFDRCxPQUFPLE9BQU8sQ0FBQTtBQUNoQixDQUFDO0FBRUQ7O0dBRUc7QUFDSCxNQUFNLFVBQVUsWUFBWSxDQUFDLFFBQWdCO0lBQzNDLE1BQU0sT0FBTyxHQUFHLGVBQWUsQ0FBQyxRQUFRLENBQUMsQ0FBQTtJQUN6QyxJQUFJLENBQUMsT0FBTztRQUFFLE9BQU8sRUFBRSxDQUFBO0lBRXZCLE9BQU87UUFDTCxLQUFLLEVBQUU7WUFDTCxPQUFPLEVBQUUsT0FBTyxDQUFDLEVBQUU7WUFDbkIsS0FBSyxFQUFFLE9BQU8sQ0FBQyxLQUFLO1lBQ3BCLEdBQUcsRUFBRSxzREFBc0QsT0FBTyxDQUFDLElBQUksRUFBRTtTQUMxRTtLQUNGLENBQUE7QUFDSCxDQUFDO0FBRUQ7O0dBRUc7QUFDSCxNQUFNLFVBQVUsV0FBVyxDQUFDLFFBQWdCO0lBQzFDLE1BQU0sT0FBTyxHQUFHLGVBQWUsQ0FBQyxRQUFRLENBQUMsQ0FBQTtJQUN6QyxJQUFJLE9BQU8sRUFBRSxDQUFDO1FBQ1osT0FBTyxzREFBc0QsT0FBTyxDQUFDLElBQUksRUFBRSxDQUFBO0lBQzdFLENBQUM7SUFDRCxPQUFPLDBDQUEwQyxDQUFBO0FBQ25ELENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBwYXR0ZXJucywgdHlwZSBQYXR0ZXJuIH0gZnJvbSAnLi4vLi4vaW5kZXguanMnXG5cbi8vIEJ1aWxkIGEgbG9va3VwIG1hcCBmcm9tIHJ1bGUgbmFtZSB0byBwYXR0ZXJuXG5jb25zdCBydWxlVG9QYXR0ZXJuTWFwID0gbmV3IE1hcDxzdHJpbmcsIFBhdHRlcm4+KClcbmZvciAoY29uc3QgcGF0dGVybiBvZiBwYXR0ZXJucykge1xuICBpZiAocGF0dGVybi5ydWxlKSB7XG4gICAgcnVsZVRvUGF0dGVybk1hcC5zZXQocGF0dGVybi5ydWxlLCBwYXR0ZXJuKVxuICB9XG59XG5cbi8qKlxuICogR2V0IENhbm9uIHBhdHRlcm4gaW5mbyBmb3IgYW4gRVNMaW50IHJ1bGVcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGdldENhbm9uUGF0dGVybihydWxlTmFtZTogc3RyaW5nKTogUGF0dGVybiB8IHVuZGVmaW5lZCB7XG4gIHJldHVybiBydWxlVG9QYXR0ZXJuTWFwLmdldChgZ2FsbG9wLyR7cnVsZU5hbWV9YClcbn1cblxuLyoqXG4gKiBGb3JtYXQgYSBtZXNzYWdlIHdpdGggQ2Fub24gcGF0dGVybiBwcmVmaXhcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGZvcm1hdENhbm9uTWVzc2FnZShydWxlTmFtZTogc3RyaW5nLCBtZXNzYWdlOiBzdHJpbmcpOiBzdHJpbmcge1xuICBjb25zdCBwYXR0ZXJuID0gZ2V0Q2Fub25QYXR0ZXJuKHJ1bGVOYW1lKVxuICBpZiAocGF0dGVybikge1xuICAgIHJldHVybiBgW0Nhbm9uICR7cGF0dGVybi5pZH1dICR7bWVzc2FnZX1gXG4gIH1cbiAgcmV0dXJuIG1lc3NhZ2Vcbn1cblxuLyoqXG4gKiBHZXQgQ2Fub24gZG9jcyBtZXRhZGF0YSBmb3IgYSBydWxlXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBnZXRDYW5vbkRvY3MocnVsZU5hbWU6IHN0cmluZykge1xuICBjb25zdCBwYXR0ZXJuID0gZ2V0Q2Fub25QYXR0ZXJuKHJ1bGVOYW1lKVxuICBpZiAoIXBhdHRlcm4pIHJldHVybiB7fVxuICBcbiAgcmV0dXJuIHtcbiAgICBjYW5vbjoge1xuICAgICAgcGF0dGVybjogcGF0dGVybi5pZCxcbiAgICAgIHRpdGxlOiBwYXR0ZXJuLnRpdGxlLFxuICAgICAgdXJsOiBgaHR0cHM6Ly9naXRodWIuY29tL2dhbGxvcC1zb2Z0d2FyZS9jYW5vbi9ibG9iL21haW4vJHtwYXR0ZXJuLmZpbGV9YCxcbiAgICB9LFxuICB9XG59XG5cbi8qKlxuICogQ3JlYXRlIGEgVVJMIGZvciB0aGUgQ2Fub24gcGF0dGVybiBkb2NzXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBnZXRDYW5vblVybChydWxlTmFtZTogc3RyaW5nKTogc3RyaW5nIHtcbiAgY29uc3QgcGF0dGVybiA9IGdldENhbm9uUGF0dGVybihydWxlTmFtZSlcbiAgaWYgKHBhdHRlcm4pIHtcbiAgICByZXR1cm4gYGh0dHBzOi8vZ2l0aHViLmNvbS9nYWxsb3Atc29mdHdhcmUvY2Fub24vYmxvYi9tYWluLyR7cGF0dGVybi5maWxlfWBcbiAgfVxuICByZXR1cm4gYGh0dHBzOi8vZ2l0aHViLmNvbS9nYWxsb3Atc29mdHdhcmUvY2Fub25gXG59XG4iXX0=
|