@gallop.software/canon 2.15.0 → 2.15.2
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/index.js
CHANGED
|
File without changes
|
|
@@ -2,12 +2,12 @@ const rule = {
|
|
|
2
2
|
meta: {
|
|
3
3
|
type: 'suggestion',
|
|
4
4
|
docs: {
|
|
5
|
-
description: 'Prevent
|
|
5
|
+
description: 'Prevent exporting component functions from block files - reusable components should be in the components folder',
|
|
6
6
|
category: 'Best Practices',
|
|
7
7
|
recommended: true,
|
|
8
8
|
},
|
|
9
9
|
messages: {
|
|
10
|
-
noComponentInBlocks: '[Canon 025]
|
|
10
|
+
noComponentInBlocks: '[Canon 025] Exported component functions should not be defined in block files. Move this component to src/components/ and import it. Non-exported content components are allowed.',
|
|
11
11
|
},
|
|
12
12
|
schema: [],
|
|
13
13
|
},
|
|
@@ -20,6 +20,8 @@ const rule = {
|
|
|
20
20
|
}
|
|
21
21
|
// Track the default export name to allow it
|
|
22
22
|
let defaultExportName = null;
|
|
23
|
+
// Track named exports
|
|
24
|
+
const namedExports = new Set();
|
|
23
25
|
return {
|
|
24
26
|
// Track the default export
|
|
25
27
|
ExportDefaultDeclaration(node) {
|
|
@@ -30,6 +32,34 @@ const rule = {
|
|
|
30
32
|
defaultExportName = node.declaration.name;
|
|
31
33
|
}
|
|
32
34
|
},
|
|
35
|
+
// Track named exports: export function Foo() {} or export const Foo = () => {}
|
|
36
|
+
ExportNamedDeclaration(node) {
|
|
37
|
+
if (node.declaration) {
|
|
38
|
+
if (node.declaration.type === 'FunctionDeclaration' && node.declaration.id) {
|
|
39
|
+
namedExports.add(node.declaration.id.name);
|
|
40
|
+
}
|
|
41
|
+
else if (node.declaration.type === 'VariableDeclaration') {
|
|
42
|
+
for (const declarator of node.declaration.declarations) {
|
|
43
|
+
if (declarator.id.type === 'Identifier') {
|
|
44
|
+
namedExports.add(declarator.id.name);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Handle: export { Foo, Bar }
|
|
50
|
+
if (node.specifiers) {
|
|
51
|
+
for (const specifier of node.specifiers) {
|
|
52
|
+
if (specifier.exported.type === 'Identifier') {
|
|
53
|
+
namedExports.add(specifier.exported.name);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
// Check for exported function declarations that look like components (PascalCase)
|
|
59
|
+
'Program:exit'() {
|
|
60
|
+
// Now check all named exports that are PascalCase
|
|
61
|
+
// The actual flagging happens in the ExportNamedDeclaration handler
|
|
62
|
+
},
|
|
33
63
|
// Check for function declarations that look like components (PascalCase)
|
|
34
64
|
FunctionDeclaration(node) {
|
|
35
65
|
if (!node.id)
|
|
@@ -38,6 +68,10 @@ const rule = {
|
|
|
38
68
|
// Skip the default export (the block itself)
|
|
39
69
|
if (name === defaultExportName)
|
|
40
70
|
return;
|
|
71
|
+
// Only flag if it's exported (named export)
|
|
72
|
+
// Non-exported content components are allowed
|
|
73
|
+
if (!namedExports.has(name))
|
|
74
|
+
return;
|
|
41
75
|
// Check if it's PascalCase (likely a component)
|
|
42
76
|
if (/^[A-Z]/.test(name)) {
|
|
43
77
|
context.report({
|
|
@@ -57,6 +91,10 @@ const rule = {
|
|
|
57
91
|
// Skip the default export
|
|
58
92
|
if (name === defaultExportName)
|
|
59
93
|
return;
|
|
94
|
+
// Only flag if it's exported (named export)
|
|
95
|
+
// Non-exported content components are allowed
|
|
96
|
+
if (!namedExports.has(name))
|
|
97
|
+
return;
|
|
60
98
|
// Check if it's PascalCase (likely a component)
|
|
61
99
|
if (/^[A-Z]/.test(name)) {
|
|
62
100
|
context.report({
|
|
@@ -71,4 +109,4 @@ const rule = {
|
|
|
71
109
|
},
|
|
72
110
|
};
|
|
73
111
|
export default rule;
|
|
74
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
112
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibm8tY29tcG9uZW50LWluLWJsb2Nrcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9lc2xpbnQvcnVsZXMvbm8tY29tcG9uZW50LWluLWJsb2Nrcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFFQSxNQUFNLElBQUksR0FBb0I7SUFDNUIsSUFBSSxFQUFFO1FBQ0osSUFBSSxFQUFFLFlBQVk7UUFDbEIsSUFBSSxFQUFFO1lBQ0osV0FBVyxFQUNULGlIQUFpSDtZQUNuSCxRQUFRLEVBQUUsZ0JBQWdCO1lBQzFCLFdBQVcsRUFBRSxJQUFJO1NBQ2xCO1FBQ0QsUUFBUSxFQUFFO1lBQ1IsbUJBQW1CLEVBQ2pCLG1MQUFtTDtTQUN0TDtRQUNELE1BQU0sRUFBRSxFQUFFO0tBQ1g7SUFDRCxNQUFNLENBQUMsT0FBeUI7UUFDOUIsTUFBTSxRQUFRLEdBQUcsT0FBTyxDQUFDLFFBQVEsSUFBSSxPQUFPLENBQUMsV0FBVyxFQUFFLENBQUE7UUFFMUQseUJBQXlCO1FBQ3pCLE1BQU0sT0FBTyxHQUFHLFFBQVEsQ0FBQyxRQUFRLENBQUMsVUFBVSxDQUFDLElBQUksUUFBUSxDQUFDLFFBQVEsQ0FBQyxZQUFZLENBQUMsQ0FBQTtRQUNoRixJQUFJLENBQUMsT0FBTyxFQUFFLENBQUM7WUFDYixPQUFPLEVBQUUsQ0FBQTtRQUNYLENBQUM7UUFFRCw0Q0FBNEM7UUFDNUMsSUFBSSxpQkFBaUIsR0FBa0IsSUFBSSxDQUFBO1FBQzNDLHNCQUFzQjtRQUN0QixNQUFNLFlBQVksR0FBRyxJQUFJLEdBQUcsRUFBVSxDQUFBO1FBRXRDLE9BQU87WUFDTCwyQkFBMkI7WUFDM0Isd0JBQXdCLENBQUMsSUFBSTtnQkFDM0IsSUFBSSxJQUFJLENBQUMsV0FBVyxDQUFDLElBQUksS0FBSyxxQkFBcUIsSUFBSSxJQUFJLENBQUMsV0FBVyxDQUFDLEVBQUUsRUFBRSxDQUFDO29CQUMzRSxpQkFBaUIsR0FBRyxJQUFJLENBQUMsV0FBVyxDQUFDLEVBQUUs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Qjs0QkFBRSxPQUFNO3dCQUV0Qyw0Q0FBNEM7d0JBQzVDLDhDQUE4Qzt3QkFDOUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDOzRCQUFFLE9BQU07d0JBRW5DLGdEQUFnRDt3QkFDaEQsSUFBSSxRQUFRLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUM7NEJBQ3hCLE9BQU8sQ0FBQyxNQUFNLENBQUM7Z0NBQ2IsSUFBSSxFQUFFLFVBQVU7Z0NBQ2hCLFNBQVMsRUFBRSxxQkFBcUI7NkJBQ2pDLENBQUMsQ0FBQTt3QkFDSixDQUFDO29CQUNILENBQUM7Z0JBQ0gsQ0FBQztZQUNILENBQUM7U0FDRixDQUFBO0lBQ0gsQ0FBQztDQUNGLENBQUE7QUFFRCxlQUFlLElBQUksQ0FBQSIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IFJ1bGUgfSBmcm9tICdlc2xpbnQnXG5cbmNvbnN0IHJ1bGU6IFJ1bGUuUnVsZU1vZHVsZSA9IHtcbiAgbWV0YToge1xuICAgIHR5cGU6ICdzdWdnZXN0aW9uJyxcbiAgICBkb2NzOiB7XG4gICAgICBkZXNjcmlwdGlvbjpcbiAgICAgICAgJ1ByZXZlbnQgZXhwb3J0aW5nIGNvbXBvbmVudCBmdW5jdGlvbnMgZnJvbSBibG9jayBmaWxlcyAtIHJldXNhYmxlIGNvbXBvbmVudHMgc2hvdWxkIGJlIGluIHRoZSBjb21wb25lbnRzIGZvbGRlcicsXG4gICAgICBjYXRlZ29yeTogJ0Jlc3QgUHJhY3RpY2VzJyxcbiAgICAgIHJlY29tbWVuZGVkOiB0cnVlLFxuICAgIH0sXG4gICAgbWVzc2FnZXM6IHtcbiAgICAgIG5vQ29tcG9uZW50SW5CbG9ja3M6XG4gICAgICAgICdbQ2Fub24gMDI1XSBFeHBvcnRlZCBjb21wb25lbnQgZnVuY3Rpb25zIHNob3VsZCBub3QgYmUgZGVmaW5lZCBpbiBibG9jayBmaWxlcy4gTW92ZSB0aGlzIGNvbXBvbmVudCB0byBzcmMvY29tcG9uZW50cy8gYW5kIGltcG9ydCBpdC4gTm9uLWV4cG9ydGVkIGNvbnRlbnQgY29tcG9uZW50cyBhcmUgYWxsb3dlZC4nLFxuICAgIH0sXG4gICAgc2NoZW1hOiBbXSxcbiAgfSxcbiAgY3JlYXRlKGNvbnRleHQ6IFJ1bGUuUnVsZUNvbnRleHQpIHtcbiAgICBjb25zdCBmaWxlbmFtZSA9IGNvbnRleHQuZmlsZW5hbWUgfHwgY29udGV4dC5nZXRGaWxlbmFtZSgpXG5cbiAgICAvLyBPbmx5IGNoZWNrIGJsb2NrIGZpbGVzXG4gICAgY29uc3QgaXNCbG9jayA9IGZpbGVuYW1lLmluY2x1ZGVzKCcvYmxvY2tzLycpIHx8IGZpbGVuYW1lLmluY2x1ZGVzKCdcXFxcYmxvY2tzXFxcXCcpXG4gICAgaWYgKCFpc0Jsb2NrKSB7XG4gICAgICByZXR1cm4ge31cbiAgICB9XG5cbiAgICAvLyBUcmFjayB0aGUgZGVmYXVsdCBleHBvcnQgbmFtZSB0byBhbGxvdyBpdFxuICAgIGxldCBkZWZhdWx0RXhwb3J0TmFtZTogc3RyaW5nIHwgbnVsbCA9IG51bGxcbiAgICAvLyBUcmFjayBuYW1lZCBleHBvcnRzXG4gICAgY29uc3QgbmFtZWRFeHBvcnRzID0gbmV3IFNldDxzdHJpbmc+KClcblxuICAgIHJldHVybiB7XG4gICAgICAvLyBUcmFjayB0aGUgZGVmYXVsdCBleHBvcnRcbiAgICAgIEV4cG9ydERlZmF1bHREZWNsYXJhdGlvbihub2RlKSB7XG4gICAgICAgIGlmIChub2RlLmRlY2xhcmF0aW9uLnR5cGUgPT09ICdGdW5jdGlvbkRlY2xhcmF0aW9uJyAmJiBub2RlLmRlY2xhcmF0aW9uLmlkKSB7XG4gICAgICAgICAgZGVmYXVsdEV4cG9ydE5hbWUgPSBub2RlLmRlY2xhcmF0aW9uLmlkLm5hbWVcbiAgICAgICAgfSBlbHNlIGlmIChub2RlLmRlY2xhcmF0aW9uLnR5cGUgPT09ICdJZGVudGlmaWVyJykge1xuICAgICAgICAgIGRlZmF1bHRFeHBvcnROYW1lID0gbm9kZS5kZWNsYXJhdGlvbi5uYW1lXG4gICAgICAgIH1cbiAgICAgIH0sXG5cbiAgICAgIC8vIFRyYWNrIG5hbWVkIGV4cG9ydHM6IGV4cG9ydCBmdW5jdGlvbiBGb28oKSB7fSBvciBleHBvcnQgY29uc3QgRm9vID0gKCkgPT4ge31cbiAgICAgIEV4cG9ydE5hbWVkRGVjbGFyYXRpb24obm9kZSkge1xuICAgICAgICBpZiAobm9kZS5kZWNsYXJhdGlvbikge1xuICAgICAgICAgIGlmIChub2RlLmRlY2xhcmF0aW9uLnR5cGUgPT09ICdGdW5jdGlvbkRlY2xhcmF0aW9uJyAmJiBub2RlLmRlY2xhcmF0aW9uLmlkKSB7XG4gICAgICAgICAgICBuYW1lZEV4cG9ydHMuYWRkKG5vZGUuZGVjbGFyYXRpb24uaWQubmFtZSlcbiAgICAgICAgICB9IGVsc2UgaWYgKG5vZGUuZGVjbGFyYXRpb24udHlwZSA9PT0gJ1ZhcmlhYmxlRGVjbGFyYXRpb24nKSB7XG4gICAgICAgICAgICBmb3IgKGNvbnN0IGRlY2xhcmF0b3Igb2Ygbm9kZS5kZWNsYXJhdGlvbi5kZWNsYXJhdGlvbnMpIHtcbiAgICAgICAgICAgICAgaWYgKGRlY2xhcmF0b3IuaWQudHlwZSA9PT0gJ0lkZW50aWZpZXInKSB7XG4gICAgICAgICAgICAgICAgbmFtZWRFeHBvcnRzLmFkZChkZWNsYXJhdG9yLmlkLm5hbWUpXG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgLy8gSGFuZGxlOiBleHBvcnQgeyBGb28sIEJhciB9XG4gICAgICAgIGlmIChub2RlLnNwZWNpZmllcnMpIHtcbiAgICAgICAgICBmb3IgKGNvbnN0IHNwZWNpZmllciBvZiBub2RlLnNwZWNpZmllcnMpIHtcbiAgICAgICAgICAgIGlmIChzcGVjaWZpZXIuZXhwb3J0ZWQudHlwZSA9PT0gJ0lkZW50aWZpZXInKSB7XG4gICAgICAgICAgICAgIG5hbWVkRXhwb3J0cy5hZGQoc3BlY2lmaWVyLmV4cG9ydGVkLm5hbWUpXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9LFxuXG4gICAgICAvLyBDaGVjayBmb3IgZXhwb3J0ZWQgZnVuY3Rpb24gZGVjbGFyYXRpb25zIHRoYXQgbG9vayBsaWtlIGNvbXBvbmVudHMgKFBhc2NhbENhc2UpXG4gICAgICAnUHJvZ3JhbTpleGl0JygpIHtcbiAgICAgICAgLy8gTm93IGNoZWNrIGFsbCBuYW1lZCBleHBvcnRzIHRoYXQgYXJlIFBhc2NhbENhc2VcbiAgICAgICAgLy8gVGhlIGFjdHVhbCBmbGFnZ2luZyBoYXBwZW5zIGluIHRoZSBFeHBvcnROYW1lZERlY2xhcmF0aW9uIGhhbmRsZXJcbiAgICAgIH0sXG5cbiAgICAgIC8vIENoZWNrIGZvciBmdW5jdGlvbiBkZWNsYXJhdGlvbnMgdGhhdCBsb29rIGxpa2UgY29tcG9uZW50cyAoUGFzY2FsQ2FzZSlcbiAgICAgIEZ1bmN0aW9uRGVjbGFyYXRpb24obm9kZSkge1xuICAgICAgICBpZiAoIW5vZGUuaWQpIHJldHVyblxuXG4gICAgICAgIGNvbnN0IG5hbWUgPSBub2RlLmlkLm5hbWVcblxuICAgICAgICAvLyBTa2lwIHRoZSBkZWZhdWx0IGV4cG9ydCAodGhlIGJsb2NrIGl0c2VsZilcbiAgICAgICAgaWYgKG5hbWUgPT09IGRlZmF1bHRFeHBvcnROYW1lKSByZXR1cm5cblxuICAgICAgICAvLyBPbmx5IGZsYWcgaWYgaXQncyBleHBvcnRlZCAobmFtZWQgZXhwb3J0KVxuICAgICAgICAvLyBOb24tZXhwb3J0ZWQgY29udGVudCBjb21wb25lbnRzIGFyZSBhbGxvd2VkXG4gICAgICAgIGlmICghbmFtZWRFeHBvcnRzLmhhcyhuYW1lKSkgcmV0dXJuXG5cbiAgICAgICAgLy8gQ2hlY2sgaWYgaXQncyBQYXNjYWxDYXNlIChsaWtlbHkgYSBjb21wb25lbnQpXG4gICAgICAgIGlmICgvXltBLVpdLy50ZXN0KG5hbWUpKSB7XG4gICAgICAgICAgY29udGV4dC5yZXBvcnQoe1xuICAgICAgICAgICAgbm9kZSxcbiAgICAgICAgICAgIG1lc3NhZ2VJZDogJ25vQ29tcG9uZW50SW5CbG9ja3MnLFxuICAgICAgICAgIH0pXG4gICAgICAgIH1cbiAgICAgIH0sXG5cbiAgICAgIC8vIENoZWNrIGZvciBhcnJvdyBmdW5jdGlvbiBjb21wb25lbnRzOiBjb25zdCBNeUNvbXBvbmVudCA9ICgpID0+IHt9XG4gICAgICBWYXJpYWJsZURlY2xhcmF0aW9uKG5vZGUpIHtcbiAgICAgICAgZm9yIChjb25zdCBkZWNsYXJhdG9yIG9mIG5vZGUuZGVjbGFyYXRpb25zKSB7XG4gICAgICAgICAgaWYgKFxuICAgICAgICAgICAgZGVjbGFyYXRvci5pZC50eXBlID09PSAnSWRlbnRpZmllcicgJiZcbiAgICAgICAgICAgIGRlY2xhcmF0b3IuaW5pdCAmJlxuICAgICAgICAgICAgKGRlY2xhcmF0b3IuaW5pdC50eXBlID09PSAnQXJyb3dGdW5jdGlvbkV4cHJlc3Npb24nIHx8XG4gICAgICAgICAgICAgIGRlY2xhcmF0b3IuaW5pdC50eXBlID09PSAnRnVuY3Rpb25FeHByZXNzaW9uJylcbiAgICAgICAgICApIHtcbiAgICAgICAgICAgIGNvbnN0IG5hbWUgPSBkZWNsYXJhdG9yLmlkLm5hbWVcblxuICAgICAgICAgICAgLy8gU2tpcCB0aGUgZGVmYXVsdCBleHBvcnRcbiAgICAgICAgICAgIGlmIChuYW1lID09PSBkZWZhdWx0RXhwb3J0TmFtZSkgcmV0dXJuXG5cbiAgICAgICAgICAgIC8vIE9ubHkgZmxhZyBpZiBpdCdzIGV4cG9ydGVkIChuYW1lZCBleHBvcnQpXG4gICAgICAgICAgICAvLyBOb24tZXhwb3J0ZWQgY29udGVudCBjb21wb25lbnRzIGFyZSBhbGxvd2VkXG4gICAgICAgICAgICBpZiAoIW5hbWVkRXhwb3J0cy5oYXMobmFtZSkpIHJldHVyblxuXG4gICAgICAgICAgICAvLyBDaGVjayBpZiBpdCdzIFBhc2NhbENhc2UgKGxpa2VseSBhIGNvbXBvbmVudClcbiAgICAgICAgICAgIGlmICgvXltBLVpdLy50ZXN0KG5hbWUpKSB7XG4gICAgICAgICAgICAgIGNvbnRleHQucmVwb3J0KHtcbiAgICAgICAgICAgICAgICBub2RlOiBkZWNsYXJhdG9yLFxuICAgICAgICAgICAgICAgIG1lc3NhZ2VJZDogJ25vQ29tcG9uZW50SW5CbG9ja3MnLFxuICAgICAgICAgICAgICB9KVxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfSxcbiAgICB9XG4gIH0sXG59XG5cbmV4cG9ydCBkZWZhdWx0IHJ1bGVcbiJdfQ==
|
package/package.json
CHANGED
|
@@ -24,6 +24,7 @@ Use dedicated props instead of `className` for styling that components support.
|
|
|
24
24
|
<Heading className="text-center mb-6 text-accent">Title</Heading>
|
|
25
25
|
<Paragraph className="text-lg mb-4">Text content</Paragraph>
|
|
26
26
|
<Label className="text-sm font-semibold">Category</Label>
|
|
27
|
+
<Image src="/photo.jpg" className="rounded-lg aspect-4/5" />
|
|
27
28
|
```
|
|
28
29
|
|
|
29
30
|
### Good
|
|
@@ -38,6 +39,7 @@ Use dedicated props instead of `className` for styling that components support.
|
|
|
38
39
|
<Label fontSize="text-sm" fontWeight="font-semibold">
|
|
39
40
|
Category
|
|
40
41
|
</Label>
|
|
42
|
+
<Image src="/photo.jpg" rounded="rounded-lg" aspect="aspect-4/5" />
|
|
41
43
|
```
|
|
42
44
|
|
|
43
45
|
## Supported Props by Component
|
|
@@ -71,6 +73,10 @@ Use dedicated props instead of `className` for styling that components support.
|
|
|
71
73
|
### Button
|
|
72
74
|
- `margin` — Bottom margin
|
|
73
75
|
|
|
76
|
+
### Image
|
|
77
|
+
- `rounded` — Border radius (e.g., `rounded-lg`, `rounded-none`, `rounded-full`)
|
|
78
|
+
- `aspect` — Aspect ratio (e.g., `aspect-4/5`, `aspect-square`, `aspect-video`)
|
|
79
|
+
|
|
74
80
|
## When to Use className
|
|
75
81
|
|
|
76
82
|
Use `className` for styles that are NOT covered by props:
|
|
@@ -100,3 +106,4 @@ If a component doesn't support a prop you need:
|
|
|
100
106
|
- `src/components/heading.tsx` — Heading with prop overrides
|
|
101
107
|
- `src/components/paragraph.tsx` — Paragraph with prop overrides
|
|
102
108
|
- `src/components/label.tsx` — Label with prop overrides
|
|
109
|
+
- `src/components/image.tsx` — Image with prop overrides
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# 025: No Components in Blocks
|
|
2
|
+
|
|
3
|
+
**Category:** Structure
|
|
4
|
+
**Status:** Stable
|
|
5
|
+
**Enforcement:** ESLint (`gallop/no-component-in-blocks`)
|
|
6
|
+
|
|
7
|
+
## Summary
|
|
8
|
+
|
|
9
|
+
Exported component functions must be in the components folder, not defined in block files. Non-exported content components are allowed.
|
|
10
|
+
|
|
11
|
+
## Rationale
|
|
12
|
+
|
|
13
|
+
Blocks should be self-contained page sections that import reusable components. When you define and export a component inside a block file, it:
|
|
14
|
+
|
|
15
|
+
1. **Breaks reusability** - The component can't be easily shared across other blocks
|
|
16
|
+
2. **Violates separation of concerns** - Blocks assemble components, components are building blocks
|
|
17
|
+
3. **Makes the codebase harder to navigate** - Components should live in `/components`
|
|
18
|
+
|
|
19
|
+
However, **content components** (non-exported helper functions that render specific content for that block) are allowed. These are internal to the block and not meant to be reused elsewhere.
|
|
20
|
+
|
|
21
|
+
## Bad
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
// src/blocks/hero-1.tsx
|
|
25
|
+
export function FeatureCard({ title }: { title: string }) {
|
|
26
|
+
// ❌ Exported component - should be in components folder
|
|
27
|
+
return <div>{title}</div>
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default function Hero1() {
|
|
31
|
+
return <FeatureCard title="Hello" />
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Good
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
// src/blocks/hero-1.tsx
|
|
39
|
+
import { FeatureCard } from '@/components'
|
|
40
|
+
|
|
41
|
+
export default function Hero1() {
|
|
42
|
+
return <FeatureCard title="Hello" />
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
// src/blocks/sidebar-1.tsx
|
|
48
|
+
|
|
49
|
+
// ✅ Non-exported content component - allowed
|
|
50
|
+
function Demo1() {
|
|
51
|
+
return (
|
|
52
|
+
<div className="space-y-6">
|
|
53
|
+
<Heading as="h2">Panel 1 Content</Heading>
|
|
54
|
+
<Paragraph>This content is specific to this block...</Paragraph>
|
|
55
|
+
</div>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ✅ Non-exported content component - allowed
|
|
60
|
+
function Demo2() {
|
|
61
|
+
return (
|
|
62
|
+
<div className="space-y-6">
|
|
63
|
+
<Heading as="h2">Panel 2 Content</Heading>
|
|
64
|
+
<Paragraph>More specific content...</Paragraph>
|
|
65
|
+
</div>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const panels = {
|
|
70
|
+
demo1: <Demo1 />,
|
|
71
|
+
demo2: <Demo2 />,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export default function Sidebar1() {
|
|
75
|
+
return (
|
|
76
|
+
<SidebarStackProvider>
|
|
77
|
+
<Section>...</Section>
|
|
78
|
+
<SidebarPanels panels={panels} />
|
|
79
|
+
</SidebarStackProvider>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Exceptions
|
|
85
|
+
|
|
86
|
+
- **Non-exported functions** - Internal content components that render block-specific content are allowed
|
|
87
|
+
- **The default export** - The block's main component is always allowed
|
|
88
|
+
- **Non-PascalCase functions** - Utility functions (camelCase) are not checked by this rule
|
package/schema.json
CHANGED
|
@@ -289,7 +289,7 @@
|
|
|
289
289
|
"status": "stable",
|
|
290
290
|
"enforcement": "eslint",
|
|
291
291
|
"rule": "gallop/no-component-in-blocks",
|
|
292
|
-
"summary": "
|
|
292
|
+
"summary": "Exported component functions must be in components folder; non-exported content components are allowed in blocks"
|
|
293
293
|
},
|
|
294
294
|
{
|
|
295
295
|
"id": "026",
|