@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 defining component functions inside block files - components should be in the components folder',
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] Component functions should not be defined in block files. Move this component to src/components/ and import it.',
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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibm8tY29tcG9uZW50LWluLWJsb2Nrcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9lc2xpbnQvcnVsZXMvbm8tY29tcG9uZW50LWluLWJsb2Nrcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFFQSxNQUFNLElBQUksR0FBb0I7SUFDNUIsSUFBSSxFQUFFO1FBQ0osSUFBSSxFQUFFLFlBQVk7UUFDbEIsSUFBSSxFQUFFO1lBQ0osV0FBVyxFQUNULHlHQUF5RztZQUMzRyxRQUFRLEVBQUUsZ0JBQWdCO1lBQzFCLFdBQVcsRUFBRSxJQUFJO1NBQ2xCO1FBQ0QsUUFBUSxFQUFFO1lBQ1IsbUJBQW1CLEVBQ2pCLDZIQUE2SDtTQUNoSTtRQUNELE1BQU0sRUFBRSxFQUFFO0tBQ1g7SUFDRCxNQUFNLENBQUMsT0FBeUI7UUFDOUIsTUFBTSxRQUFRLEdBQUcsT0FBTyxDQUFDLFFBQVEsSUFBSSxPQUFPLENBQUMsV0FBVyxFQUFFLENBQUE7UUFFMUQseUJBQXlCO1FBQ3pCLE1BQU0sT0FBTyxHQUFHLFFBQVEsQ0FBQyxRQUFRLENBQUMsVUFBVSxDQUFDLElBQUksUUFBUSxDQUFDLFFBQVEsQ0FBQyxZQUFZLENBQUMsQ0FBQTtRQUNoRixJQUFJLENBQUMsT0FBTyxFQUFFLENBQUM7WUFDYixPQUFPLEVBQUUsQ0FBQTtRQUNYLENBQUM7UUFFRCw0Q0FBNEM7UUFDNUMsSUFBSSxpQkFBaUIsR0FBa0IsSUFBSSxDQUFBO1FBRTNDLE9BQU87WUFDTCwyQkFBMkI7WUFDM0Isd0JBQXdCLENBQUMsSUFBSTtnQkFDM0IsSUFBSSxJQUFJLENBQUMsV0FBVyxDQUFDLElBQUksS0FBSyxxQkFBcUIsSUFBSSxJQUFJLENBQUMsV0FBVyxDQUFDLEVBQUUsRUFBRSxDQUFDO29CQUMzRSxpQkFBaUIsR0FBRyxJQUFJLENBQUMsV0FBVyxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUE7Z0JBQzlDLENBQUM7cUJBQU0sSUFBSSxJQUFJLENBQUMsV0FBVyxDQUFDLElBQUksS0FBSyxZQUFZLEVBQUUsQ0FBQztvQkFDbEQsaUJBQWlCLEdBQUcsSUFBSSxDQUFDLFdBQVcsQ0FBQyxJQUFJLENBQUE7Z0JBQzNDLENBQUM7WUFDSCxDQUFDO1lBRUQseUVBQXlFO1lBQ3pFLG1CQUFtQixDQUFDLElBQUk7Z0JBQ3RCLElBQUksQ0FBQyxJQUFJLENBQUMsRUFBRTtvQkFBRSxPQUFNO2dCQUVwQixNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQTtnQkFFekIsNkNBQTZDO2dCQUM3QyxJQUFJLElBQUksS0FBSyxpQkFBaUI7b0JBQUUsT0FBTTtnQkFFdEMsZ0RBQWdEO2dCQUNoRCxJQUFJLFFBQVEsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQztvQkFDeEIsT0FBTyxDQUFDLE1BQU0sQ0FBQzt3QkFDYixJQUFJO3dCQUNKLFNBQVMsRUFBRSxxQkFBcUI7cUJBQ2pDLENBQUMsQ0FBQTtnQkFDSixDQUFDO1lBQ0gsQ0FBQztZQUVELG9FQUFvRTtZQUNwRSxtQkFBbUIsQ0FBQyxJQUFJO2dCQUN0QixLQUFLLE1BQU0sVUFBVSxJQUFJLElBQUksQ0FBQyxZQUFZLEVBQUUsQ0FBQztvQkFDM0MsSUFDRSxVQUFVLENBQUMsRUFBRSxDQUFDLElBQUksS0FBSyxZQUFZO3dCQUNuQyxVQUFVLENBQUMsSUFBSTt3QkFDZixDQUFDLFVBQVUsQ0FBQyxJQUFJLENBQUMsSUFBSSxLQUFLLHlCQUF5Qjs0QkFDakQsVUFBVSxDQUFDLElBQUksQ0FBQyxJQUFJLEtBQUssb0JBQW9CLENBQUMsRUFDaEQsQ0FBQzt3QkFDRCxNQUFNLElBQUksR0FBRyxVQUFVLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQTt3QkFFL0IsMEJBQTBCO3dCQUMxQixJQUFJLElBQUksS0FBSyxpQkFBaUI7NEJBQUUsT0FBTTt3QkFFdEMsZ0RBQWdEO3dCQUNoRCxJQUFJLFFBQVEsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQzs0QkFDeEIsT0FBTyxDQUFDLE1BQU0sQ0FBQztnQ0FDYixJQUFJLEVBQUUsVUFBVTtnQ0FDaEIsU0FBUyxFQUFFLHFCQUFxQjs2QkFDakMsQ0FBQyxDQUFBO3dCQUNKLENBQUM7b0JBQ0gsQ0FBQztnQkFDSCxDQUFDO1lBQ0gsQ0FBQztTQUNGLENBQUE7SUFDSCxDQUFDO0NBQ0YsQ0FBQTtBQUVELGVBQWUsSUFBSSxDQUFBIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgUnVsZSB9IGZyb20gJ2VzbGludCdcblxuY29uc3QgcnVsZTogUnVsZS5SdWxlTW9kdWxlID0ge1xuICBtZXRhOiB7XG4gICAgdHlwZTogJ3N1Z2dlc3Rpb24nLFxuICAgIGRvY3M6IHtcbiAgICAgIGRlc2NyaXB0aW9uOlxuICAgICAgICAnUHJldmVudCBkZWZpbmluZyBjb21wb25lbnQgZnVuY3Rpb25zIGluc2lkZSBibG9jayBmaWxlcyAtIGNvbXBvbmVudHMgc2hvdWxkIGJlIGluIHRoZSBjb21wb25lbnRzIGZvbGRlcicsXG4gICAgICBjYXRlZ29yeTogJ0Jlc3QgUHJhY3RpY2VzJyxcbiAgICAgIHJlY29tbWVuZGVkOiB0cnVlLFxuICAgIH0sXG4gICAgbWVzc2FnZXM6IHtcbiAgICAgIG5vQ29tcG9uZW50SW5CbG9ja3M6XG4gICAgICAgICdbQ2Fub24gMDI1XSBDb21wb25lbnQgZnVuY3Rpb25zIHNob3VsZCBub3QgYmUgZGVmaW5lZCBpbiBibG9jayBmaWxlcy4gTW92ZSB0aGlzIGNvbXBvbmVudCB0byBzcmMvY29tcG9uZW50cy8gYW5kIGltcG9ydCBpdC4nLFxuICAgIH0sXG4gICAgc2NoZW1hOiBbXSxcbiAgfSxcbiAgY3JlYXRlKGNvbnRleHQ6IFJ1bGUuUnVsZUNvbnRleHQpIHtcbiAgICBjb25zdCBmaWxlbmFtZSA9IGNvbnRleHQuZmlsZW5hbWUgfHwgY29udGV4dC5nZXRGaWxlbmFtZSgpXG5cbiAgICAvLyBPbmx5IGNoZWNrIGJsb2NrIGZpbGVzXG4gICAgY29uc3QgaXNCbG9jayA9IGZpbGVuYW1lLmluY2x1ZGVzKCcvYmxvY2tzLycpIHx8IGZpbGVuYW1lLmluY2x1ZGVzKCdcXFxcYmxvY2tzXFxcXCcpXG4gICAgaWYgKCFpc0Jsb2NrKSB7XG4gICAgICByZXR1cm4ge31cbiAgICB9XG5cbiAgICAvLyBUcmFjayB0aGUgZGVmYXVsdCBleHBvcnQgbmFtZSB0byBhbGxvdyBpdFxuICAgIGxldCBkZWZhdWx0RXhwb3J0TmFtZTogc3RyaW5nIHwgbnVsbCA9IG51bGxcblxuICAgIHJldHVybiB7XG4gICAgICAvLyBUcmFjayB0aGUgZGVmYXVsdCBleHBvcnRcbiAgICAgIEV4cG9ydERlZmF1bHREZWNsYXJhdGlvbihub2RlKSB7XG4gICAgICAgIGlmIChub2RlLmRlY2xhcmF0aW9uLnR5cGUgPT09ICdGdW5jdGlvbkRlY2xhcmF0aW9uJyAmJiBub2RlLmRlY2xhcmF0aW9uLmlkKSB7XG4gICAgICAgICAgZGVmYXVsdEV4cG9ydE5hbWUgPSBub2RlLmRlY2xhcmF0aW9uLmlkLm5hbWVcbiAgICAgICAgfSBlbHNlIGlmIChub2RlLmRlY2xhcmF0aW9uLnR5cGUgPT09ICdJZGVudGlmaWVyJykge1xuICAgICAgICAgIGRlZmF1bHRFeHBvcnROYW1lID0gbm9kZS5kZWNsYXJhdGlvbi5uYW1lXG4gICAgICAgIH1cbiAgICAgIH0sXG5cbiAgICAgIC8vIENoZWNrIGZvciBmdW5jdGlvbiBkZWNsYXJhdGlvbnMgdGhhdCBsb29rIGxpa2UgY29tcG9uZW50cyAoUGFzY2FsQ2FzZSlcbiAgICAgIEZ1bmN0aW9uRGVjbGFyYXRpb24obm9kZSkge1xuICAgICAgICBpZiAoIW5vZGUuaWQpIHJldHVyblxuXG4gICAgICAgIGNvbnN0IG5hbWUgPSBub2RlLmlkLm5hbWVcblxuICAgICAgICAvLyBTa2lwIHRoZSBkZWZhdWx0IGV4cG9ydCAodGhlIGJsb2NrIGl0c2VsZilcbiAgICAgICAgaWYgKG5hbWUgPT09IGRlZmF1bHRFeHBvcnROYW1lKSByZXR1cm5cblxuICAgICAgICAvLyBDaGVjayBpZiBpdCdzIFBhc2NhbENhc2UgKGxpa2VseSBhIGNvbXBvbmVudClcbiAgICAgICAgaWYgKC9eW0EtWl0vLnRlc3QobmFtZSkpIHtcbiAgICAgICAgICBjb250ZXh0LnJlcG9ydCh7XG4gICAgICAgICAgICBub2RlLFxuICAgICAgICAgICAgbWVzc2FnZUlkOiAnbm9Db21wb25lbnRJbkJsb2NrcycsXG4gICAgICAgICAgfSlcbiAgICAgICAgfVxuICAgICAgfSxcblxuICAgICAgLy8gQ2hlY2sgZm9yIGFycm93IGZ1bmN0aW9uIGNvbXBvbmVudHM6IGNvbnN0IE15Q29tcG9uZW50ID0gKCkgPT4ge31cbiAgICAgIFZhcmlhYmxlRGVjbGFyYXRpb24obm9kZSkge1xuICAgICAgICBmb3IgKGNvbnN0IGRlY2xhcmF0b3Igb2Ygbm9kZS5kZWNsYXJhdGlvbnMpIHtcbiAgICAgICAgICBpZiAoXG4gICAgICAgICAgICBkZWNsYXJhdG9yLmlkLnR5cGUgPT09ICdJZGVudGlmaWVyJyAmJlxuICAgICAgICAgICAgZGVjbGFyYXRvci5pbml0ICYmXG4gICAgICAgICAgICAoZGVjbGFyYXRvci5pbml0LnR5cGUgPT09ICdBcnJvd0Z1bmN0aW9uRXhwcmVzc2lvbicgfHxcbiAgICAgICAgICAgICAgZGVjbGFyYXRvci5pbml0LnR5cGUgPT09ICdGdW5jdGlvbkV4cHJlc3Npb24nKVxuICAgICAgICAgICkge1xuICAgICAgICAgICAgY29uc3QgbmFtZSA9IGRlY2xhcmF0b3IuaWQubmFtZVxuXG4gICAgICAgICAgICAvLyBTa2lwIHRoZSBkZWZhdWx0IGV4cG9ydFxuICAgICAgICAgICAgaWYgKG5hbWUgPT09IGRlZmF1bHRFeHBvcnROYW1lKSByZXR1cm5cblxuICAgICAgICAgICAgLy8gQ2hlY2sgaWYgaXQncyBQYXNjYWxDYXNlIChsaWtlbHkgYSBjb21wb25lbnQpXG4gICAgICAgICAgICBpZiAoL15bQS1aXS8udGVzdChuYW1lKSkge1xuICAgICAgICAgICAgICBjb250ZXh0LnJlcG9ydCh7XG4gICAgICAgICAgICAgICAgbm9kZTogZGVjbGFyYXRvcixcbiAgICAgICAgICAgICAgICBtZXNzYWdlSWQ6ICdub0NvbXBvbmVudEluQmxvY2tzJyxcbiAgICAgICAgICAgICAgfSlcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH0sXG4gICAgfVxuICB9LFxufVxuXG5leHBvcnQgZGVmYXVsdCBydWxlXG4iXX0=
112
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibm8tY29tcG9uZW50LWluLWJsb2Nrcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9lc2xpbnQvcnVsZXMvbm8tY29tcG9uZW50LWluLWJsb2Nrcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFFQSxNQUFNLElBQUksR0FBb0I7SUFDNUIsSUFBSSxFQUFFO1FBQ0osSUFBSSxFQUFFLFlBQVk7UUFDbEIsSUFBSSxFQUFFO1lBQ0osV0FBVyxFQUNULGlIQUFpSDtZQUNuSCxRQUFRLEVBQUUsZ0JBQWdCO1lBQzFCLFdBQVcsRUFBRSxJQUFJO1NBQ2xCO1FBQ0QsUUFBUSxFQUFFO1lBQ1IsbUJBQW1CLEVBQ2pCLG1MQUFtTDtTQUN0TDtRQUNELE1BQU0sRUFBRSxFQUFFO0tBQ1g7SUFDRCxNQUFNLENBQUMsT0FBeUI7UUFDOUIsTUFBTSxRQUFRLEdBQUcsT0FBTyxDQUFDLFFBQVEsSUFBSSxPQUFPLENBQUMsV0FBVyxFQUFFLENBQUE7UUFFMUQseUJBQXlCO1FBQ3pCLE1BQU0sT0FBTyxHQUFHLFFBQVEsQ0FBQyxRQUFRLENBQUMsVUFBVSxDQUFDLElBQUksUUFBUSxDQUFDLFFBQVEsQ0FBQyxZQUFZLENBQUMsQ0FBQTtRQUNoRixJQUFJLENBQUMsT0FBTyxFQUFFLENBQUM7WUFDYixPQUFPLEVBQUUsQ0FBQTtRQUNYLENBQUM7UUFFRCw0Q0FBNEM7UUFDNUMsSUFBSSxpQkFBaUIsR0FBa0IsSUFBSSxDQUFBO1FBQzNDLHNCQUFzQjtRQUN0QixNQUFNLFlBQVksR0FBRyxJQUFJLEdBQUcsRUFBVSxDQUFBO1FBRXRDLE9BQU87WUFDTCwyQkFBMkI7WUFDM0Isd0JBQXdCLENBQUMsSUFBSTtnQkFDM0IsSUFBSSxJQUFJLENBQUMsV0FBVyxDQUFDLElBQUksS0FBSyxxQkFBcUIsSUFBSSxJQUFJLENBQUMsV0FBVyxDQUFDLEVBQUUsRUFBRSxDQUFDO29CQUMzRSxpQkFBaUIsR0FBRyxJQUFJLENBQUMsV0FBVyxDQUFDLEVBQUUs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Qjs0QkFBRSxPQUFNO3dCQUV0Qyw0Q0FBNEM7d0JBQzVDLDhDQUE4Qzt3QkFDOUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDOzRCQUFFLE9BQU07d0JBRW5DLGdEQUFnRDt3QkFDaEQsSUFBSSxRQUFRLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUM7NEJBQ3hCLE9BQU8sQ0FBQyxNQUFNLENBQUM7Z0NBQ2IsSUFBSSxFQUFFLFVBQVU7Z0NBQ2hCLFNBQVMsRUFBRSxxQkFBcUI7NkJBQ2pDLENBQUMsQ0FBQTt3QkFDSixDQUFDO29CQUNILENBQUM7Z0JBQ0gsQ0FBQztZQUNILENBQUM7U0FDRixDQUFBO0lBQ0gsQ0FBQztDQUNGLENBQUE7QUFFRCxlQUFlLElBQUksQ0FBQSIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IFJ1bGUgfSBmcm9tICdlc2xpbnQnXG5cbmNvbnN0IHJ1bGU6IFJ1bGUuUnVsZU1vZHVsZSA9IHtcbiAgbWV0YToge1xuICAgIHR5cGU6ICdzdWdnZXN0aW9uJyxcbiAgICBkb2NzOiB7XG4gICAgICBkZXNjcmlwdGlvbjpcbiAgICAgICAgJ1ByZXZlbnQgZXhwb3J0aW5nIGNvbXBvbmVudCBmdW5jdGlvbnMgZnJvbSBibG9jayBmaWxlcyAtIHJldXNhYmxlIGNvbXBvbmVudHMgc2hvdWxkIGJlIGluIHRoZSBjb21wb25lbnRzIGZvbGRlcicsXG4gICAgICBjYXRlZ29yeTogJ0Jlc3QgUHJhY3RpY2VzJyxcbiAgICAgIHJlY29tbWVuZGVkOiB0cnVlLFxuICAgIH0sXG4gICAgbWVzc2FnZXM6IHtcbiAgICAgIG5vQ29tcG9uZW50SW5CbG9ja3M6XG4gICAgICAgICdbQ2Fub24gMDI1XSBFeHBvcnRlZCBjb21wb25lbnQgZnVuY3Rpb25zIHNob3VsZCBub3QgYmUgZGVmaW5lZCBpbiBibG9jayBmaWxlcy4gTW92ZSB0aGlzIGNvbXBvbmVudCB0byBzcmMvY29tcG9uZW50cy8gYW5kIGltcG9ydCBpdC4gTm9uLWV4cG9ydGVkIGNvbnRlbnQgY29tcG9uZW50cyBhcmUgYWxsb3dlZC4nLFxuICAgIH0sXG4gICAgc2NoZW1hOiBbXSxcbiAgfSxcbiAgY3JlYXRlKGNvbnRleHQ6IFJ1bGUuUnVsZUNvbnRleHQpIHtcbiAgICBjb25zdCBmaWxlbmFtZSA9IGNvbnRleHQuZmlsZW5hbWUgfHwgY29udGV4dC5nZXRGaWxlbmFtZSgpXG5cbiAgICAvLyBPbmx5IGNoZWNrIGJsb2NrIGZpbGVzXG4gICAgY29uc3QgaXNCbG9jayA9IGZpbGVuYW1lLmluY2x1ZGVzKCcvYmxvY2tzLycpIHx8IGZpbGVuYW1lLmluY2x1ZGVzKCdcXFxcYmxvY2tzXFxcXCcpXG4gICAgaWYgKCFpc0Jsb2NrKSB7XG4gICAgICByZXR1cm4ge31cbiAgICB9XG5cbiAgICAvLyBUcmFjayB0aGUgZGVmYXVsdCBleHBvcnQgbmFtZSB0byBhbGxvdyBpdFxuICAgIGxldCBkZWZhdWx0RXhwb3J0TmFtZTogc3RyaW5nIHwgbnVsbCA9IG51bGxcbiAgICAvLyBUcmFjayBuYW1lZCBleHBvcnRzXG4gICAgY29uc3QgbmFtZWRFeHBvcnRzID0gbmV3IFNldDxzdHJpbmc+KClcblxuICAgIHJldHVybiB7XG4gICAgICAvLyBUcmFjayB0aGUgZGVmYXVsdCBleHBvcnRcbiAgICAgIEV4cG9ydERlZmF1bHREZWNsYXJhdGlvbihub2RlKSB7XG4gICAgICAgIGlmIChub2RlLmRlY2xhcmF0aW9uLnR5cGUgPT09ICdGdW5jdGlvbkRlY2xhcmF0aW9uJyAmJiBub2RlLmRlY2xhcmF0aW9uLmlkKSB7XG4gICAgICAgICAgZGVmYXVsdEV4cG9ydE5hbWUgPSBub2RlLmRlY2xhcmF0aW9uLmlkLm5hbWVcbiAgICAgICAgfSBlbHNlIGlmIChub2RlLmRlY2xhcmF0aW9uLnR5cGUgPT09ICdJZGVudGlmaWVyJykge1xuICAgICAgICAgIGRlZmF1bHRFeHBvcnROYW1lID0gbm9kZS5kZWNsYXJhdGlvbi5uYW1lXG4gICAgICAgIH1cbiAgICAgIH0sXG5cbiAgICAgIC8vIFRyYWNrIG5hbWVkIGV4cG9ydHM6IGV4cG9ydCBmdW5jdGlvbiBGb28oKSB7fSBvciBleHBvcnQgY29uc3QgRm9vID0gKCkgPT4ge31cbiAgICAgIEV4cG9ydE5hbWVkRGVjbGFyYXRpb24obm9kZSkge1xuICAgICAgICBpZiAobm9kZS5kZWNsYXJhdGlvbikge1xuICAgICAgICAgIGlmIChub2RlLmRlY2xhcmF0aW9uLnR5cGUgPT09ICdGdW5jdGlvbkRlY2xhcmF0aW9uJyAmJiBub2RlLmRlY2xhcmF0aW9uLmlkKSB7XG4gICAgICAgICAgICBuYW1lZEV4cG9ydHMuYWRkKG5vZGUuZGVjbGFyYXRpb24uaWQubmFtZSlcbiAgICAgICAgICB9IGVsc2UgaWYgKG5vZGUuZGVjbGFyYXRpb24udHlwZSA9PT0gJ1ZhcmlhYmxlRGVjbGFyYXRpb24nKSB7XG4gICAgICAgICAgICBmb3IgKGNvbnN0IGRlY2xhcmF0b3Igb2Ygbm9kZS5kZWNsYXJhdGlvbi5kZWNsYXJhdGlvbnMpIHtcbiAgICAgICAgICAgICAgaWYgKGRlY2xhcmF0b3IuaWQudHlwZSA9PT0gJ0lkZW50aWZpZXInKSB7XG4gICAgICAgICAgICAgICAgbmFtZWRFeHBvcnRzLmFkZChkZWNsYXJhdG9yLmlkLm5hbWUpXG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgLy8gSGFuZGxlOiBleHBvcnQgeyBGb28sIEJhciB9XG4gICAgICAgIGlmIChub2RlLnNwZWNpZmllcnMpIHtcbiAgICAgICAgICBmb3IgKGNvbnN0IHNwZWNpZmllciBvZiBub2RlLnNwZWNpZmllcnMpIHtcbiAgICAgICAgICAgIGlmIChzcGVjaWZpZXIuZXhwb3J0ZWQudHlwZSA9PT0gJ0lkZW50aWZpZXInKSB7XG4gICAgICAgICAgICAgIG5hbWVkRXhwb3J0cy5hZGQoc3BlY2lmaWVyLmV4cG9ydGVkLm5hbWUpXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9LFxuXG4gICAgICAvLyBDaGVjayBmb3IgZXhwb3J0ZWQgZnVuY3Rpb24gZGVjbGFyYXRpb25zIHRoYXQgbG9vayBsaWtlIGNvbXBvbmVudHMgKFBhc2NhbENhc2UpXG4gICAgICAnUHJvZ3JhbTpleGl0JygpIHtcbiAgICAgICAgLy8gTm93IGNoZWNrIGFsbCBuYW1lZCBleHBvcnRzIHRoYXQgYXJlIFBhc2NhbENhc2VcbiAgICAgICAgLy8gVGhlIGFjdHVhbCBmbGFnZ2luZyBoYXBwZW5zIGluIHRoZSBFeHBvcnROYW1lZERlY2xhcmF0aW9uIGhhbmRsZXJcbiAgICAgIH0sXG5cbiAgICAgIC8vIENoZWNrIGZvciBmdW5jdGlvbiBkZWNsYXJhdGlvbnMgdGhhdCBsb29rIGxpa2UgY29tcG9uZW50cyAoUGFzY2FsQ2FzZSlcbiAgICAgIEZ1bmN0aW9uRGVjbGFyYXRpb24obm9kZSkge1xuICAgICAgICBpZiAoIW5vZGUuaWQpIHJldHVyblxuXG4gICAgICAgIGNvbnN0IG5hbWUgPSBub2RlLmlkLm5hbWVcblxuICAgICAgICAvLyBTa2lwIHRoZSBkZWZhdWx0IGV4cG9ydCAodGhlIGJsb2NrIGl0c2VsZilcbiAgICAgICAgaWYgKG5hbWUgPT09IGRlZmF1bHRFeHBvcnROYW1lKSByZXR1cm5cblxuICAgICAgICAvLyBPbmx5IGZsYWcgaWYgaXQncyBleHBvcnRlZCAobmFtZWQgZXhwb3J0KVxuICAgICAgICAvLyBOb24tZXhwb3J0ZWQgY29udGVudCBjb21wb25lbnRzIGFyZSBhbGxvd2VkXG4gICAgICAgIGlmICghbmFtZWRFeHBvcnRzLmhhcyhuYW1lKSkgcmV0dXJuXG5cbiAgICAgICAgLy8gQ2hlY2sgaWYgaXQncyBQYXNjYWxDYXNlIChsaWtlbHkgYSBjb21wb25lbnQpXG4gICAgICAgIGlmICgvXltBLVpdLy50ZXN0KG5hbWUpKSB7XG4gICAgICAgICAgY29udGV4dC5yZXBvcnQoe1xuICAgICAgICAgICAgbm9kZSxcbiAgICAgICAgICAgIG1lc3NhZ2VJZDogJ25vQ29tcG9uZW50SW5CbG9ja3MnLFxuICAgICAgICAgIH0pXG4gICAgICAgIH1cbiAgICAgIH0sXG5cbiAgICAgIC8vIENoZWNrIGZvciBhcnJvdyBmdW5jdGlvbiBjb21wb25lbnRzOiBjb25zdCBNeUNvbXBvbmVudCA9ICgpID0+IHt9XG4gICAgICBWYXJpYWJsZURlY2xhcmF0aW9uKG5vZGUpIHtcbiAgICAgICAgZm9yIChjb25zdCBkZWNsYXJhdG9yIG9mIG5vZGUuZGVjbGFyYXRpb25zKSB7XG4gICAgICAgICAgaWYgKFxuICAgICAgICAgICAgZGVjbGFyYXRvci5pZC50eXBlID09PSAnSWRlbnRpZmllcicgJiZcbiAgICAgICAgICAgIGRlY2xhcmF0b3IuaW5pdCAmJlxuICAgICAgICAgICAgKGRlY2xhcmF0b3IuaW5pdC50eXBlID09PSAnQXJyb3dGdW5jdGlvbkV4cHJlc3Npb24nIHx8XG4gICAgICAgICAgICAgIGRlY2xhcmF0b3IuaW5pdC50eXBlID09PSAnRnVuY3Rpb25FeHByZXNzaW9uJylcbiAgICAgICAgICApIHtcbiAgICAgICAgICAgIGNvbnN0IG5hbWUgPSBkZWNsYXJhdG9yLmlkLm5hbWVcblxuICAgICAgICAgICAgLy8gU2tpcCB0aGUgZGVmYXVsdCBleHBvcnRcbiAgICAgICAgICAgIGlmIChuYW1lID09PSBkZWZhdWx0RXhwb3J0TmFtZSkgcmV0dXJuXG5cbiAgICAgICAgICAgIC8vIE9ubHkgZmxhZyBpZiBpdCdzIGV4cG9ydGVkIChuYW1lZCBleHBvcnQpXG4gICAgICAgICAgICAvLyBOb24tZXhwb3J0ZWQgY29udGVudCBjb21wb25lbnRzIGFyZSBhbGxvd2VkXG4gICAgICAgICAgICBpZiAoIW5hbWVkRXhwb3J0cy5oYXMobmFtZSkpIHJldHVyblxuXG4gICAgICAgICAgICAvLyBDaGVjayBpZiBpdCdzIFBhc2NhbENhc2UgKGxpa2VseSBhIGNvbXBvbmVudClcbiAgICAgICAgICAgIGlmICgvXltBLVpdLy50ZXN0KG5hbWUpKSB7XG4gICAgICAgICAgICAgIGNvbnRleHQucmVwb3J0KHtcbiAgICAgICAgICAgICAgICBub2RlOiBkZWNsYXJhdG9yLFxuICAgICAgICAgICAgICAgIG1lc3NhZ2VJZDogJ25vQ29tcG9uZW50SW5CbG9ja3MnLFxuICAgICAgICAgICAgICB9KVxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfSxcbiAgICB9XG4gIH0sXG59XG5cbmV4cG9ydCBkZWZhdWx0IHJ1bGVcbiJdfQ==
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gallop.software/canon",
3
- "version": "2.15.0",
3
+ "version": "2.15.2",
4
4
  "type": "module",
5
5
  "description": "Gallop Canon - Architecture patterns, ESLint plugin, and CLI for template governance",
6
6
  "main": "dist/index.js",
@@ -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": "Component functions must be in components folder, not blocks"
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",