@gallop.software/canon 2.0.2 → 2.2.0

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.
@@ -8,6 +8,8 @@ declare const recommendedConfig: {
8
8
  'gallop/no-client-blocks': string;
9
9
  'gallop/no-container-in-section': string;
10
10
  'gallop/prefer-component-props': string;
11
+ 'gallop/prefer-layout-components': string;
12
+ 'gallop/background-image-rounded': string;
11
13
  };
12
14
  };
13
15
  export default recommendedConfig;
@@ -9,7 +9,9 @@ const recommendedConfig = {
9
9
  'gallop/no-client-blocks': 'warn',
10
10
  'gallop/no-container-in-section': 'warn',
11
11
  'gallop/prefer-component-props': 'warn',
12
+ 'gallop/prefer-layout-components': 'warn',
13
+ 'gallop/background-image-rounded': 'warn',
12
14
  },
13
15
  };
14
16
  export default recommendedConfig;
15
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVjb21tZW5kZWQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvZXNsaW50L2NvbmZpZ3MvcmVjb21tZW5kZWQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7OztHQUdHO0FBQ0gsTUFBTSxpQkFBaUIsR0FBRztJQUN4QixPQUFPLEVBQUUsQ0FBQyxRQUFRLENBQUM7SUFDbkIsS0FBSyxFQUFFO1FBQ0wsMENBQTBDO1FBQzFDLHlCQUF5QixFQUFFLE1BQU07UUFDakMsZ0NBQWdDLEVBQUUsTUFBTTtRQUN4QywrQkFBK0IsRUFBRSxNQUFNO0tBQ3hDO0NBQ0YsQ0FBQTtBQUVELGVBQWUsaUJBQWlCLENBQUEiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIFJlY29tbWVuZGVkIGNvbmZpZ3VyYXRpb25cbiAqIEEgc2Vuc2libGUgZGVmYXVsdCBmb3IgYW55IEdhbGxvcC1iYXNlZCB0ZW1wbGF0ZVxuICovXG5jb25zdCByZWNvbW1lbmRlZENvbmZpZyA9IHtcbiAgcGx1Z2luczogWydnYWxsb3AnXSxcbiAgcnVsZXM6IHtcbiAgICAvLyBDb3JlIHJ1bGVzIHRoYXQgYXBwbHkgdG8gbW9zdCB0ZW1wbGF0ZXNcbiAgICAnZ2FsbG9wL25vLWNsaWVudC1ibG9ja3MnOiAnd2FybicsXG4gICAgJ2dhbGxvcC9uby1jb250YWluZXItaW4tc2VjdGlvbic6ICd3YXJuJyxcbiAgICAnZ2FsbG9wL3ByZWZlci1jb21wb25lbnQtcHJvcHMnOiAnd2FybicsXG4gIH0sXG59XG5cbmV4cG9ydCBkZWZhdWx0IHJlY29tbWVuZGVkQ29uZmlnXG4iXX0=
17
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVjb21tZW5kZWQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvZXNsaW50L2NvbmZpZ3MvcmVjb21tZW5kZWQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7OztHQUdHO0FBQ0gsTUFBTSxpQkFBaUIsR0FBRztJQUN4QixPQUFPLEVBQUUsQ0FBQyxRQUFRLENBQUM7SUFDbkIsS0FBSyxFQUFFO1FBQ0wsMENBQTBDO1FBQzFDLHlCQUF5QixFQUFFLE1BQU07UUFDakMsZ0NBQWdDLEVBQUUsTUFBTTtRQUN4QywrQkFBK0IsRUFBRSxNQUFNO1FBQ3ZDLGlDQUFpQyxFQUFFLE1BQU07UUFDekMsaUNBQWlDLEVBQUUsTUFBTTtLQUMxQztDQUNGLENBQUE7QUFFRCxlQUFlLGlCQUFpQixDQUFBIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBSZWNvbW1lbmRlZCBjb25maWd1cmF0aW9uXG4gKiBBIHNlbnNpYmxlIGRlZmF1bHQgZm9yIGFueSBHYWxsb3AtYmFzZWQgdGVtcGxhdGVcbiAqL1xuY29uc3QgcmVjb21tZW5kZWRDb25maWcgPSB7XG4gIHBsdWdpbnM6IFsnZ2FsbG9wJ10sXG4gIHJ1bGVzOiB7XG4gICAgLy8gQ29yZSBydWxlcyB0aGF0IGFwcGx5IHRvIG1vc3QgdGVtcGxhdGVzXG4gICAgJ2dhbGxvcC9uby1jbGllbnQtYmxvY2tzJzogJ3dhcm4nLFxuICAgICdnYWxsb3Avbm8tY29udGFpbmVyLWluLXNlY3Rpb24nOiAnd2FybicsXG4gICAgJ2dhbGxvcC9wcmVmZXItY29tcG9uZW50LXByb3BzJzogJ3dhcm4nLFxuICAgICdnYWxsb3AvcHJlZmVyLWxheW91dC1jb21wb25lbnRzJzogJ3dhcm4nLFxuICAgICdnYWxsb3AvYmFja2dyb3VuZC1pbWFnZS1yb3VuZGVkJzogJ3dhcm4nLFxuICB9LFxufVxuXG5leHBvcnQgZGVmYXVsdCByZWNvbW1lbmRlZENvbmZpZ1xuIl19
@@ -8,6 +8,8 @@ declare const speedwellConfig: {
8
8
  'gallop/no-client-blocks': string;
9
9
  'gallop/no-container-in-section': string;
10
10
  'gallop/prefer-component-props': string;
11
+ 'gallop/prefer-layout-components': string;
12
+ 'gallop/background-image-rounded': string;
11
13
  };
12
14
  };
13
15
  export default speedwellConfig;
@@ -11,7 +11,11 @@ const speedwellConfig = {
11
11
  'gallop/no-container-in-section': 'warn',
12
12
  // Use component props instead of className for style values
13
13
  'gallop/prefer-component-props': 'warn',
14
+ // Use Grid/Columns instead of raw div with grid classes
15
+ 'gallop/prefer-layout-components': 'warn',
16
+ // Background images must have rounded="rounded-none"
17
+ 'gallop/background-image-rounded': 'warn',
14
18
  },
15
19
  };
16
20
  export default speedwellConfig;
17
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3BlZWR3ZWxsLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2VzbGludC9jb25maWdzL3NwZWVkd2VsbC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7O0dBR0c7QUFDSCxNQUFNLGVBQWUsR0FBRztJQUN0QixPQUFPLEVBQUUsQ0FBQyxRQUFRLENBQUM7SUFDbkIsS0FBSyxFQUFFO1FBQ0wsMEVBQTBFO1FBQzFFLHlCQUF5QixFQUFFLE1BQU07UUFFakMsdUNBQXVDO1FBQ3ZDLGdDQUFnQyxFQUFFLE1BQU07UUFFeEMsNERBQTREO1FBQzVELCtCQUErQixFQUFFLE1BQU07S0FDeEM7Q0FDRixDQUFBO0FBRUQsZUFBZSxlQUFlLENBQUEiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIFNwZWVkd2VsbCB0ZW1wbGF0ZSBjb25maWd1cmF0aW9uXG4gKiBFbmFibGVzIGFsbCBHYWxsb3AgcnVsZXMgcmVsZXZhbnQgdG8gdGhlIFNwZWVkd2VsbCBhcmNoaXRlY3R1cmVcbiAqL1xuY29uc3Qgc3BlZWR3ZWxsQ29uZmlnID0ge1xuICBwbHVnaW5zOiBbJ2dhbGxvcCddLFxuICBydWxlczoge1xuICAgIC8vIEJsb2NrcyBzaG91bGQgYmUgc2VydmVyIGNvbXBvbmVudHMgLSBleHRyYWN0IGNsaWVudCBsb2dpYyB0byBjb21wb25lbnRzXG4gICAgJ2dhbGxvcC9uby1jbGllbnQtYmxvY2tzJzogJ3dhcm4nLFxuXG4gICAgLy8gU2VjdGlvbiBhbHJlYWR5IHByb3ZpZGVzIGNvbnRhaW5tZW50XG4gICAgJ2dhbGxvcC9uby1jb250YWluZXItaW4tc2VjdGlvbic6ICd3YXJuJyxcblxuICAgIC8vIFVzZSBjb21wb25lbnQgcHJvcHMgaW5zdGVhZCBvZiBjbGFzc05hbWUgZm9yIHN0eWxlIHZhbHVlc1xuICAgICdnYWxsb3AvcHJlZmVyLWNvbXBvbmVudC1wcm9wcyc6ICd3YXJuJyxcbiAgfSxcbn1cblxuZXhwb3J0IGRlZmF1bHQgc3BlZWR3ZWxsQ29uZmlnXG4iXX0=
21
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3BlZWR3ZWxsLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2VzbGludC9jb25maWdzL3NwZWVkd2VsbC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7O0dBR0c7QUFDSCxNQUFNLGVBQWUsR0FBRztJQUN0QixPQUFPLEVBQUUsQ0FBQyxRQUFRLENBQUM7SUFDbkIsS0FBSyxFQUFFO1FBQ0wsMEVBQTBFO1FBQzFFLHlCQUF5QixFQUFFLE1BQU07UUFFakMsdUNBQXVDO1FBQ3ZDLGdDQUFnQyxFQUFFLE1BQU07UUFFeEMsNERBQTREO1FBQzVELCtCQUErQixFQUFFLE1BQU07UUFFdkMsd0RBQXdEO1FBQ3hELGlDQUFpQyxFQUFFLE1BQU07UUFFekMscURBQXFEO1FBQ3JELGlDQUFpQyxFQUFFLE1BQU07S0FDMUM7Q0FDRixDQUFBO0FBRUQsZUFBZSxlQUFlLENBQUEiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIFNwZWVkd2VsbCB0ZW1wbGF0ZSBjb25maWd1cmF0aW9uXG4gKiBFbmFibGVzIGFsbCBHYWxsb3AgcnVsZXMgcmVsZXZhbnQgdG8gdGhlIFNwZWVkd2VsbCBhcmNoaXRlY3R1cmVcbiAqL1xuY29uc3Qgc3BlZWR3ZWxsQ29uZmlnID0ge1xuICBwbHVnaW5zOiBbJ2dhbGxvcCddLFxuICBydWxlczoge1xuICAgIC8vIEJsb2NrcyBzaG91bGQgYmUgc2VydmVyIGNvbXBvbmVudHMgLSBleHRyYWN0IGNsaWVudCBsb2dpYyB0byBjb21wb25lbnRzXG4gICAgJ2dhbGxvcC9uby1jbGllbnQtYmxvY2tzJzogJ3dhcm4nLFxuXG4gICAgLy8gU2VjdGlvbiBhbHJlYWR5IHByb3ZpZGVzIGNvbnRhaW5tZW50XG4gICAgJ2dhbGxvcC9uby1jb250YWluZXItaW4tc2VjdGlvbic6ICd3YXJuJyxcblxuICAgIC8vIFVzZSBjb21wb25lbnQgcHJvcHMgaW5zdGVhZCBvZiBjbGFzc05hbWUgZm9yIHN0eWxlIHZhbHVlc1xuICAgICdnYWxsb3AvcHJlZmVyLWNvbXBvbmVudC1wcm9wcyc6ICd3YXJuJyxcblxuICAgIC8vIFVzZSBHcmlkL0NvbHVtbnMgaW5zdGVhZCBvZiByYXcgZGl2IHdpdGggZ3JpZCBjbGFzc2VzXG4gICAgJ2dhbGxvcC9wcmVmZXItbGF5b3V0LWNvbXBvbmVudHMnOiAnd2FybicsXG5cbiAgICAvLyBCYWNrZ3JvdW5kIGltYWdlcyBtdXN0IGhhdmUgcm91bmRlZD1cInJvdW5kZWQtbm9uZVwiXG4gICAgJ2dhbGxvcC9iYWNrZ3JvdW5kLWltYWdlLXJvdW5kZWQnOiAnd2FybicsXG4gIH0sXG59XG5cbmV4cG9ydCBkZWZhdWx0IHNwZWVkd2VsbENvbmZpZ1xuIl19
@@ -14,6 +14,8 @@ declare const plugin: {
14
14
  name: string;
15
15
  };
16
16
  'prefer-typography-components': import("eslint").Rule.RuleModule;
17
+ 'prefer-layout-components': import("eslint").Rule.RuleModule;
18
+ 'background-image-rounded': import("eslint").Rule.RuleModule;
17
19
  };
18
20
  configs: {
19
21
  speedwell: {
@@ -22,6 +24,8 @@ declare const plugin: {
22
24
  'gallop/no-client-blocks': string;
23
25
  'gallop/no-container-in-section': string;
24
26
  'gallop/prefer-component-props': string;
27
+ 'gallop/prefer-layout-components': string;
28
+ 'gallop/background-image-rounded': string;
25
29
  };
26
30
  };
27
31
  recommended: {
@@ -30,6 +34,8 @@ declare const plugin: {
30
34
  'gallop/no-client-blocks': string;
31
35
  'gallop/no-container-in-section': string;
32
36
  'gallop/prefer-component-props': string;
37
+ 'gallop/prefer-layout-components': string;
38
+ 'gallop/background-image-rounded': string;
33
39
  };
34
40
  };
35
41
  };
@@ -2,6 +2,8 @@ import noClientBlocks from './rules/no-client-blocks.js';
2
2
  import noContainerInSection from './rules/no-container-in-section.js';
3
3
  import preferComponentProps from './rules/prefer-component-props.js';
4
4
  import preferTypographyComponents from './rules/prefer-typography-components.js';
5
+ import preferLayoutComponents from './rules/prefer-layout-components.js';
6
+ import backgroundImageRounded from './rules/background-image-rounded.js';
5
7
  import speedwellConfig from './configs/speedwell.js';
6
8
  import recommendedConfig from './configs/recommended.js';
7
9
  const plugin = {
@@ -14,6 +16,8 @@ const plugin = {
14
16
  'no-container-in-section': noContainerInSection,
15
17
  'prefer-component-props': preferComponentProps,
16
18
  'prefer-typography-components': preferTypographyComponents,
19
+ 'prefer-layout-components': preferLayoutComponents,
20
+ 'background-image-rounded': backgroundImageRounded,
17
21
  },
18
22
  configs: {
19
23
  speedwell: speedwellConfig,
@@ -21,4 +25,4 @@ const plugin = {
21
25
  },
22
26
  };
23
27
  export default plugin;
24
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvZXNsaW50L2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sY0FBYyxNQUFNLDZCQUE2QixDQUFBO0FBQ3hELE9BQU8sb0JBQW9CLE1BQU0sb0NBQW9DLENBQUE7QUFDckUsT0FBTyxvQkFBb0IsTUFBTSxtQ0FBbUMsQ0FBQTtBQUNwRSxPQUFPLDBCQUEwQixNQUFNLHlDQUF5QyxDQUFBO0FBQ2hGLE9BQU8sZUFBZSxNQUFNLHdCQUF3QixDQUFBO0FBQ3BELE9BQU8saUJBQWlCLE1BQU0sMEJBQTBCLENBQUE7QUFFeEQsTUFBTSxNQUFNLEdBQUc7SUFDYixJQUFJLEVBQUU7UUFDSixJQUFJLEVBQUUsc0JBQXNCO1FBQzVCLE9BQU8sRUFBRSxPQUFPO0tBQ2pCO0lBQ0QsS0FBSyxFQUFFO1FBQ0wsa0JBQWtCLEVBQUUsY0FBYztRQUNsQyx5QkFBeUIsRUFBRSxvQkFBb0I7UUFDL0Msd0JBQXdCLEVBQUUsb0JBQW9CO1FBQzlDLDhCQUE4QixFQUFFLDBCQUEwQjtLQUMzRDtJQUNELE9BQU8sRUFBRTtRQUNQLFNBQVMsRUFBRSxlQUFlO1FBQzFCLFdBQVcsRUFBRSxpQkFBaUI7S0FDL0I7Q0FDRixDQUFBO0FBRUQsZUFBZSxNQUFNLENBQUEiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgbm9DbGllbnRCbG9ja3MgZnJvbSAnLi9ydWxlcy9uby1jbGllbnQtYmxvY2tzLmpzJ1xuaW1wb3J0IG5vQ29udGFpbmVySW5TZWN0aW9uIGZyb20gJy4vcnVsZXMvbm8tY29udGFpbmVyLWluLXNlY3Rpb24uanMnXG5pbXBvcnQgcHJlZmVyQ29tcG9uZW50UHJvcHMgZnJvbSAnLi9ydWxlcy9wcmVmZXItY29tcG9uZW50LXByb3BzLmpzJ1xuaW1wb3J0IHByZWZlclR5cG9ncmFwaHlDb21wb25lbnRzIGZyb20gJy4vcnVsZXMvcHJlZmVyLXR5cG9ncmFwaHktY29tcG9uZW50cy5qcydcbmltcG9ydCBzcGVlZHdlbGxDb25maWcgZnJvbSAnLi9jb25maWdzL3NwZWVkd2VsbC5qcydcbmltcG9ydCByZWNvbW1lbmRlZENvbmZpZyBmcm9tICcuL2NvbmZpZ3MvcmVjb21tZW5kZWQuanMnXG5cbmNvbnN0IHBsdWdpbiA9IHtcbiAgbWV0YToge1xuICAgIG5hbWU6ICdlc2xpbnQtcGx1Z2luLWdhbGxvcCcsXG4gICAgdmVyc2lvbjogJzEuMC4xJyxcbiAgfSxcbiAgcnVsZXM6IHtcbiAgICAnbm8tY2xpZW50LWJsb2Nrcyc6IG5vQ2xpZW50QmxvY2tzLFxuICAgICduby1jb250YWluZXItaW4tc2VjdGlvbic6IG5vQ29udGFpbmVySW5TZWN0aW9uLFxuICAgICdwcmVmZXItY29tcG9uZW50LXByb3BzJzogcHJlZmVyQ29tcG9uZW50UHJvcHMsXG4gICAgJ3ByZWZlci10eXBvZ3JhcGh5LWNvbXBvbmVudHMnOiBwcmVmZXJUeXBvZ3JhcGh5Q29tcG9uZW50cyxcbiAgfSxcbiAgY29uZmlnczoge1xuICAgIHNwZWVkd2VsbDogc3BlZWR3ZWxsQ29uZmlnLFxuICAgIHJlY29tbWVuZGVkOiByZWNvbW1lbmRlZENvbmZpZyxcbiAgfSxcbn1cblxuZXhwb3J0IGRlZmF1bHQgcGx1Z2luXG4iXX0=
28
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvZXNsaW50L2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sY0FBYyxNQUFNLDZCQUE2QixDQUFBO0FBQ3hELE9BQU8sb0JBQW9CLE1BQU0sb0NBQW9DLENBQUE7QUFDckUsT0FBTyxvQkFBb0IsTUFBTSxtQ0FBbUMsQ0FBQTtBQUNwRSxPQUFPLDBCQUEwQixNQUFNLHlDQUF5QyxDQUFBO0FBQ2hGLE9BQU8sc0JBQXNCLE1BQU0scUNBQXFDLENBQUE7QUFDeEUsT0FBTyxzQkFBc0IsTUFBTSxxQ0FBcUMsQ0FBQTtBQUN4RSxPQUFPLGVBQWUsTUFBTSx3QkFBd0IsQ0FBQTtBQUNwRCxPQUFPLGlCQUFpQixNQUFNLDBCQUEwQixDQUFBO0FBRXhELE1BQU0sTUFBTSxHQUFHO0lBQ2IsSUFBSSxFQUFFO1FBQ0osSUFBSSxFQUFFLHNCQUFzQjtRQUM1QixPQUFPLEVBQUUsT0FBTztLQUNqQjtJQUNELEtBQUssRUFBRTtRQUNMLGtCQUFrQixFQUFFLGNBQWM7UUFDbEMseUJBQXlCLEVBQUUsb0JBQW9CO1FBQy9DLHdCQUF3QixFQUFFLG9CQUFvQjtRQUM5Qyw4QkFBOEIsRUFBRSwwQkFBMEI7UUFDMUQsMEJBQTBCLEVBQUUsc0JBQXNCO1FBQ2xELDBCQUEwQixFQUFFLHNCQUFzQjtLQUNuRDtJQUNELE9BQU8sRUFBRTtRQUNQLFNBQVMsRUFBRSxlQUFlO1FBQzFCLFdBQVcsRUFBRSxpQkFBaUI7S0FDL0I7Q0FDRixDQUFBO0FBRUQsZUFBZSxNQUFNLENBQUEiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgbm9DbGllbnRCbG9ja3MgZnJvbSAnLi9ydWxlcy9uby1jbGllbnQtYmxvY2tzLmpzJ1xuaW1wb3J0IG5vQ29udGFpbmVySW5TZWN0aW9uIGZyb20gJy4vcnVsZXMvbm8tY29udGFpbmVyLWluLXNlY3Rpb24uanMnXG5pbXBvcnQgcHJlZmVyQ29tcG9uZW50UHJvcHMgZnJvbSAnLi9ydWxlcy9wcmVmZXItY29tcG9uZW50LXByb3BzLmpzJ1xuaW1wb3J0IHByZWZlclR5cG9ncmFwaHlDb21wb25lbnRzIGZyb20gJy4vcnVsZXMvcHJlZmVyLXR5cG9ncmFwaHktY29tcG9uZW50cy5qcydcbmltcG9ydCBwcmVmZXJMYXlvdXRDb21wb25lbnRzIGZyb20gJy4vcnVsZXMvcHJlZmVyLWxheW91dC1jb21wb25lbnRzLmpzJ1xuaW1wb3J0IGJhY2tncm91bmRJbWFnZVJvdW5kZWQgZnJvbSAnLi9ydWxlcy9iYWNrZ3JvdW5kLWltYWdlLXJvdW5kZWQuanMnXG5pbXBvcnQgc3BlZWR3ZWxsQ29uZmlnIGZyb20gJy4vY29uZmlncy9zcGVlZHdlbGwuanMnXG5pbXBvcnQgcmVjb21tZW5kZWRDb25maWcgZnJvbSAnLi9jb25maWdzL3JlY29tbWVuZGVkLmpzJ1xuXG5jb25zdCBwbHVnaW4gPSB7XG4gIG1ldGE6IHtcbiAgICBuYW1lOiAnZXNsaW50LXBsdWdpbi1nYWxsb3AnLFxuICAgIHZlcnNpb246ICcxLjAuMScsXG4gIH0sXG4gIHJ1bGVzOiB7XG4gICAgJ25vLWNsaWVudC1ibG9ja3MnOiBub0NsaWVudEJsb2NrcyxcbiAgICAnbm8tY29udGFpbmVyLWluLXNlY3Rpb24nOiBub0NvbnRhaW5lckluU2VjdGlvbixcbiAgICAncHJlZmVyLWNvbXBvbmVudC1wcm9wcyc6IHByZWZlckNvbXBvbmVudFByb3BzLFxuICAgICdwcmVmZXItdHlwb2dyYXBoeS1jb21wb25lbnRzJzogcHJlZmVyVHlwb2dyYXBoeUNvbXBvbmVudHMsXG4gICAgJ3ByZWZlci1sYXlvdXQtY29tcG9uZW50cyc6IHByZWZlckxheW91dENvbXBvbmVudHMsXG4gICAgJ2JhY2tncm91bmQtaW1hZ2Utcm91bmRlZCc6IGJhY2tncm91bmRJbWFnZVJvdW5kZWQsXG4gIH0sXG4gIGNvbmZpZ3M6IHtcbiAgICBzcGVlZHdlbGw6IHNwZWVkd2VsbENvbmZpZyxcbiAgICByZWNvbW1lbmRlZDogcmVjb21tZW5kZWRDb25maWcsXG4gIH0sXG59XG5cbmV4cG9ydCBkZWZhdWx0IHBsdWdpblxuIl19
@@ -0,0 +1,3 @@
1
+ import type { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
@@ -0,0 +1,122 @@
1
+ import { getCanonUrl, getCanonPattern } from '../utils/canon.js';
2
+ const RULE_NAME = 'background-image-rounded';
3
+ const pattern = getCanonPattern(RULE_NAME);
4
+ const rule = {
5
+ meta: {
6
+ type: 'suggestion',
7
+ docs: {
8
+ description: pattern?.summary || 'Background images must have rounded="rounded-none"',
9
+ recommended: true,
10
+ url: getCanonUrl(RULE_NAME),
11
+ },
12
+ messages: {
13
+ requireRoundedNone: `[Canon ${pattern?.id || '019'}] Background Image components (with absolute inset-0) must have rounded="rounded-none" to prevent corner clipping.`,
14
+ },
15
+ schema: [],
16
+ },
17
+ create(context) {
18
+ const filename = context.filename || context.getFilename();
19
+ // Only apply to block files
20
+ if (!filename.includes('/blocks/')) {
21
+ return {};
22
+ }
23
+ return {
24
+ JSXOpeningElement(node) {
25
+ const elementName = node.name?.name;
26
+ // Only check Image components
27
+ if (elementName !== 'Image') {
28
+ return;
29
+ }
30
+ // Check if className contains 'absolute' and 'inset-0'
31
+ const classNameAttr = node.attributes?.find((attr) => attr.type === 'JSXAttribute' &&
32
+ attr.name?.name === 'className');
33
+ if (!classNameAttr) {
34
+ return;
35
+ }
36
+ const classValue = getClassNameValue(classNameAttr);
37
+ if (!classValue) {
38
+ return;
39
+ }
40
+ // Check if this is a background image pattern
41
+ if (!isBackgroundImage(classValue)) {
42
+ return;
43
+ }
44
+ // Check if rounded="rounded-none" is set
45
+ const roundedAttr = node.attributes?.find((attr) => attr.type === 'JSXAttribute' &&
46
+ attr.name?.name === 'rounded');
47
+ if (!roundedAttr) {
48
+ context.report({
49
+ node,
50
+ messageId: 'requireRoundedNone',
51
+ });
52
+ return;
53
+ }
54
+ // Check the value of rounded prop
55
+ const roundedValue = getRoundedValue(roundedAttr);
56
+ if (roundedValue !== 'rounded-none') {
57
+ context.report({
58
+ node,
59
+ messageId: 'requireRoundedNone',
60
+ });
61
+ }
62
+ },
63
+ };
64
+ },
65
+ };
66
+ /**
67
+ * Extract className value from attribute
68
+ */
69
+ function getClassNameValue(attr) {
70
+ // Handle string literal
71
+ if (attr.value?.type === 'Literal' && typeof attr.value.value === 'string') {
72
+ return attr.value.value;
73
+ }
74
+ // Handle JSX expression container with template literal
75
+ if (attr.value?.type === 'JSXExpressionContainer') {
76
+ const expr = attr.value.expression;
77
+ if (expr.type === 'TemplateLiteral') {
78
+ // Combine all quasis
79
+ return expr.quasis?.map((q) => q.value?.raw || '').join(' ') || null;
80
+ }
81
+ // Handle clsx or other function calls - extract string arguments
82
+ if (expr.type === 'CallExpression') {
83
+ const strings = [];
84
+ for (const arg of expr.arguments || []) {
85
+ if (arg.type === 'Literal' && typeof arg.value === 'string') {
86
+ strings.push(arg.value);
87
+ }
88
+ if (arg.type === 'TemplateLiteral') {
89
+ strings.push(arg.quasis?.map((q) => q.value?.raw || '').join(' ') || '');
90
+ }
91
+ }
92
+ return strings.join(' ');
93
+ }
94
+ }
95
+ return null;
96
+ }
97
+ /**
98
+ * Check if className indicates a background image pattern
99
+ */
100
+ function isBackgroundImage(classValue) {
101
+ const classes = classValue.split(/\s+/);
102
+ const hasAbsolute = classes.includes('absolute');
103
+ const hasInset0 = classes.includes('inset-0');
104
+ return hasAbsolute && hasInset0;
105
+ }
106
+ /**
107
+ * Extract rounded prop value
108
+ */
109
+ function getRoundedValue(attr) {
110
+ if (attr.value?.type === 'Literal' && typeof attr.value.value === 'string') {
111
+ return attr.value.value;
112
+ }
113
+ if (attr.value?.type === 'JSXExpressionContainer') {
114
+ const expr = attr.value.expression;
115
+ if (expr.type === 'Literal' && typeof expr.value === 'string') {
116
+ return expr.value;
117
+ }
118
+ }
119
+ return null;
120
+ }
121
+ export default rule;
122
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYmFja2dyb3VuZC1pbWFnZS1yb3VuZGVkLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2VzbGludC9ydWxlcy9iYWNrZ3JvdW5kLWltYWdlLXJvdW5kZWQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0EsT0FBTyxFQUFFLFdBQVcsRUFBRSxlQUFlLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQTtBQUVoRSxNQUFNLFNBQVMsR0FBRywwQkFBMEIsQ0FBQTtBQUM1QyxNQUFNLE9BQU8sR0FBRyxlQUFlLENBQUMsU0FBUyxDQUFDLENBQUE7QUFFMUMsTUFBTSxJQUFJLEdBQW9CO0lBQzVCLElBQUksRUFBRTtRQUNKLElBQUksRUFBRSxZQUFZO1FBQ2xCLElBQUksRUFBRTtZQUNKLFdBQVcsRUFBRSxPQUFPLEVBQUUsT0FBTyxJQUFJLG9EQUFvRDtZQUNyRixXQUFXLEVBQUUsSUFBSTtZQUNqQixHQUFHLEVBQUUsV0FBVyxDQUFDLFNBQVMsQ0FBQztTQUM1QjtRQUNELFFBQVEsRUFBRTtZQUNSLGtCQUFrQixFQUFFLFVBQVUsT0FBTyxFQUFFLEVBQUUsSUFBSSxLQUFLLG9IQUFvSDtTQUN2SztRQUNELE1BQU0sRUFBRSxFQUFFO0tBQ1g7SUFFRCxNQUFNLENBQUMsT0FBTztRQUNaLE1BQU0sUUFBUSxHQUFHLE9BQU8sQ0FBQyxRQUFRLElBQUksT0FBTyxDQUFDLFdBQVcsRUFBRSxDQUFBO1FBRTFELDRCQUE0QjtRQUM1QixJQUFJLENBQUMsUUFBUSxDQUFDLFFBQVEsQ0FBQyxVQUFVLENBQUMsRUFBRSxDQUFDO1lBQ25DLE9BQU8sRUFBRSxDQUFBO1FBQ1gsQ0FBQztRQUVELE9BQU87WUFDTCxpQkFBaUIsQ0FBQyxJQUFTO2dCQUN6QixNQUFNLFdBQVcsR0FBRyxJQUFJLENBQUMsSUFBSSxFQUFFLElBQUksQ0FBQTtnQkFFbkMsOEJBQThCO2dCQUM5QixJQUFJLFdBQVcsS0FBSyxPQUFPLEVBQUUsQ0FBQztvQkFDNUIsT0FBTTtn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QVMsSUFBSSxPQUFPLElBQUksQ0FBQyxLQUFLLEtBQUssUUFBUSxFQUFFLENBQUM7WUFDOUQsT0FBTyxJQUFJLENBQUMsS0FBSyxDQUFBO1FBQ25CLENBQUM7SUFDSCxDQUFDO0lBRUQsT0FBTyxJQUFJLENBQUE7QUFDYixDQUFDO0FBRUQsZUFBZSxJQUFJLENBQUEiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgdHlwZSB7IFJ1bGUgfSBmcm9tICdlc2xpbnQnXG5pbXBvcnQgeyBnZXRDYW5vblVybCwgZ2V0Q2Fub25QYXR0ZXJuIH0gZnJvbSAnLi4vdXRpbHMvY2Fub24uanMnXG5cbmNvbnN0IFJVTEVfTkFNRSA9ICdiYWNrZ3JvdW5kLWltYWdlLXJvdW5kZWQnXG5jb25zdCBwYXR0ZXJuID0gZ2V0Q2Fub25QYXR0ZXJuKFJVTEVfTkFNRSlcblxuY29uc3QgcnVsZTogUnVsZS5SdWxlTW9kdWxlID0ge1xuICBtZXRhOiB7XG4gICAgdHlwZTogJ3N1Z2dlc3Rpb24nLFxuICAgIGRvY3M6IHtcbiAgICAgIGRlc2NyaXB0aW9uOiBwYXR0ZXJuPy5zdW1tYXJ5IHx8ICdCYWNrZ3JvdW5kIGltYWdlcyBtdXN0IGhhdmUgcm91bmRlZD1cInJvdW5kZWQtbm9uZVwiJyxcbiAgICAgIHJlY29tbWVuZGVkOiB0cnVlLFxuICAgICAgdXJsOiBnZXRDYW5vblVybChSVUxFX05BTUUpLFxuICAgIH0sXG4gICAgbWVzc2FnZXM6IHtcbiAgICAgIHJlcXVpcmVSb3VuZGVkTm9uZTogYFtDYW5vbiAke3BhdHRlcm4/LmlkIHx8ICcwMTknfV0gQmFja2dyb3VuZCBJbWFnZSBjb21wb25lbnRzICh3aXRoIGFic29sdXRlIGluc2V0LTApIG11c3QgaGF2ZSByb3VuZGVkPVwicm91bmRlZC1ub25lXCIgdG8gcHJldmVudCBjb3JuZXIgY2xpcHBpbmcuYCxcbiAgICB9LFxuICAgIHNjaGVtYTogW10sXG4gIH0sXG5cbiAgY3JlYXRlKGNvbnRleHQpIHtcbiAgICBjb25zdCBmaWxlbmFtZSA9IGNvbnRleHQuZmlsZW5hbWUgfHwgY29udGV4dC5nZXRGaWxlbmFtZSgpXG5cbiAgICAvLyBPbmx5IGFwcGx5IHRvIGJsb2NrIGZpbGVzXG4gICAgaWYgKCFmaWxlbmFtZS5pbmNsdWRlcygnL2Jsb2Nrcy8nKSkge1xuICAgICAgcmV0dXJuIHt9XG4gICAgfVxuXG4gICAgcmV0dXJuIHtcbiAgICAgIEpTWE9wZW5pbmdFbGVtZW50KG5vZGU6IGFueSkge1xuICAgICAgICBjb25zdCBlbGVtZW50TmFtZSA9IG5vZGUubmFtZT8ubmFtZVxuXG4gICAgICAgIC8vIE9ubHkgY2hlY2sgSW1hZ2UgY29tcG9uZW50c1xuICAgICAgICBpZiAoZWxlbWVudE5hbWUgIT09ICdJbWFnZScpIHtcbiAgICAgICAgICByZXR1cm5cbiAgICAgICAgfVxuXG4gICAgICAgIC8vIENoZWNrIGlmIGNsYXNzTmFtZSBjb250YWlucyAnYWJzb2x1dGUnIGFuZCAnaW5zZXQtMCdcbiAgICAgICAgY29uc3QgY2xhc3NOYW1lQXR0ciA9IG5vZGUuYXR0cmlidXRlcz8uZmluZChcbiAgICAgICAgICAoYXR0cjogYW55KSA9PlxuICAgICAgICAgICAgYXR0ci50eXBlID09PSAnSlNYQXR0cmlidXRlJyAmJlxuICAgICAgICAgICAgYXR0ci5uYW1lPy5uYW1lID09PSAnY2xhc3NOYW1lJ1xuICAgICAgICApXG5cbiAgICAgICAgaWYgKCFjbGFzc05hbWVBdHRyKSB7XG4gICAgICAgICAgcmV0dXJuXG4gICAgICAgIH1cblxuICAgICAgICBjb25zdCBjbGFzc1ZhbHVlID0gZ2V0Q2xhc3NOYW1lVmFsdWUoY2xhc3NOYW1lQXR0cilcbiAgICAgICAgaWYgKCFjbGFzc1ZhbHVlKSB7XG4gICAgICAgICAgcmV0dXJuXG4gICAgICAgIH1cblxuICAgICAgICAvLyBDaGVjayBpZiB0aGlzIGlzIGEgYmFja2dyb3VuZCBpbWFnZSBwYXR0ZXJuXG4gICAgICAgIGlmICghaXNCYWNrZ3JvdW5kSW1hZ2UoY2xhc3NWYWx1ZSkpIHtcbiAgICAgICAgICByZXR1cm5cbiAgICAgICAgfVxuXG4gICAgICAgIC8vIENoZWNrIGlmIHJvdW5kZWQ9XCJyb3VuZGVkLW5vbmVcIiBpcyBzZXRcbiAgICAgICAgY29uc3Qgcm91bmRlZEF0dHIgPSBub2RlLmF0dHJpYnV0ZXM/LmZpbmQoXG4gICAgICAgICAgKGF0dHI6IGFueSkgPT5cbiAgICAgICAgICAgIGF0dHIudHlwZSA9PT0gJ0pTWEF0dHJpYnV0ZScgJiZcbiAgICAgICAgICAgIGF0dHIubmFtZT8ubmFtZSA9PT0gJ3JvdW5kZWQnXG4gICAgICAgIClcblxuICAgICAgICBpZiAoIXJvdW5kZWRBdHRyKSB7XG4gICAgICAgICAgY29udGV4dC5yZXBvcnQoe1xuICAgICAgICAgICAgbm9kZSxcbiAgICAgICAgICAgIG1lc3NhZ2VJZDogJ3JlcXVpcmVSb3VuZGVkTm9uZScsXG4gICAgICAgICAgfSlcbiAgICAgICAgICByZXR1cm5cbiAgICAgICAgfVxuXG4gICAgICAgIC8vIENoZWNrIHRoZSB2YWx1ZSBvZiByb3VuZGVkIHByb3BcbiAgICAgICAgY29uc3Qgcm91bmRlZFZhbHVlID0gZ2V0Um91bmRlZFZhbHVlKHJvdW5kZWRBdHRyKVxuICAgICAgICBpZiAocm91bmRlZFZhbHVlICE9PSAncm91bmRlZC1ub25lJykge1xuICAgICAgICAgIGNvbnRleHQucmVwb3J0KHtcbiAgICAgICAgICAgIG5vZGUsXG4gICAgICAgICAgICBtZXNzYWdlSWQ6ICdyZXF1aXJlUm91bmRlZE5vbmUnLFxuICAgICAgICAgIH0pXG4gICAgICAgIH1cbiAgICAgIH0sXG4gICAgfVxuICB9LFxufVxuXG4vKipcbiAqIEV4dHJhY3QgY2xhc3NOYW1lIHZhbHVlIGZyb20gYXR0cmlidXRlXG4gKi9cbmZ1bmN0aW9uIGdldENsYXNzTmFtZVZhbHVlKGF0dHI6IGFueSk6IHN0cmluZyB8IG51bGwge1xuICAvLyBIYW5kbGUgc3RyaW5nIGxpdGVyYWxcbiAgaWYgKGF0dHIudmFsdWU/LnR5cGUgPT09ICdMaXRlcmFsJyAmJiB0eXBlb2YgYXR0ci52YWx1ZS52YWx1ZSA9PT0gJ3N0cmluZycpIHtcbiAgICByZXR1cm4gYXR0ci52YWx1ZS52YWx1ZVxuICB9XG5cbiAgLy8gSGFuZGxlIEpTWCBleHByZXNzaW9uIGNvbnRhaW5lciB3aXRoIHRlbXBsYXRlIGxpdGVyYWxcbiAgaWYgKGF0dHIudmFsdWU/LnR5cGUgPT09ICdKU1hFeHByZXNzaW9uQ29udGFpbmVyJykge1xuICAgIGNvbnN0IGV4cHIgPSBhdHRyLnZhbHVlLmV4cHJlc3Npb25cblxuICAgIGlmIChleHByLnR5cGUgPT09ICdUZW1wbGF0ZUxpdGVyYWwnKSB7XG4gICAgICAvLyBDb21iaW5lIGFsbCBxdWFzaXNcbiAgICAgIHJldHVybiBleHByLnF1YXNpcz8ubWFwKChxOiBhbnkpID0+IHEudmFsdWU/LnJhdyB8fCAnJykuam9pbignICcpIHx8IG51bGxcbiAgICB9XG5cbiAgICAvLyBIYW5kbGUgY2xzeCBvciBvdGhlciBmdW5jdGlvbiBjYWxscyAtIGV4dHJhY3Qgc3RyaW5nIGFyZ3VtZW50c1xuICAgIGlmIChleHByLnR5cGUgPT09ICdDYWxsRXhwcmVzc2lvbicpIHtcbiAgICAgIGNvbnN0IHN0cmluZ3M6IHN0cmluZ1tdID0gW11cbiAgICAgIGZvciAoY29uc3QgYXJnIG9mIGV4cHIuYXJndW1lbnRzIHx8IFtdKSB7XG4gICAgICAgIGlmIChhcmcudHlwZSA9PT0gJ0xpdGVyYWwnICYmIHR5cGVvZiBhcmcudmFsdWUgPT09ICdzdHJpbmcnKSB7XG4gICAgICAgICAgc3RyaW5ncy5wdXNoKGFyZy52YWx1ZSlcbiAgICAgICAgfVxuICAgICAgICBpZiAoYXJnLnR5cGUgPT09ICdUZW1wbGF0ZUxpdGVyYWwnKSB7XG4gICAgICAgICAgc3RyaW5ncy5wdXNoKGFyZy5xdWFzaXM/Lm1hcCgocTogYW55KSA9PiBxLnZhbHVlPy5yYXcgfHwgJycpLmpvaW4oJyAnKSB8fCAnJylcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgcmV0dXJuIHN0cmluZ3Muam9pbignICcpXG4gICAgfVxuICB9XG5cbiAgcmV0dXJuIG51bGxcbn1cblxuLyoqXG4gKiBDaGVjayBpZiBjbGFzc05hbWUgaW5kaWNhdGVzIGEgYmFja2dyb3VuZCBpbWFnZSBwYXR0ZXJuXG4gKi9cbmZ1bmN0aW9uIGlzQmFja2dyb3VuZEltYWdlKGNsYXNzVmFsdWU6IHN0cmluZyk6IGJvb2xlYW4ge1xuICBjb25zdCBjbGFzc2VzID0gY2xhc3NWYWx1ZS5zcGxpdCgvXFxzKy8pXG4gIGNvbnN0IGhhc0Fic29sdXRlID0gY2xhc3Nlcy5pbmNsdWRlcygnYWJzb2x1dGUnKVxuICBjb25zdCBoYXNJbnNldDAgPSBjbGFzc2VzLmluY2x1ZGVzKCdpbnNldC0wJylcbiAgXG4gIHJldHVybiBoYXNBYnNvbHV0ZSAmJiBoYXNJbnNldDBcbn1cblxuLyoqXG4gKiBFeHRyYWN0IHJvdW5kZWQgcHJvcCB2YWx1ZVxuICovXG5mdW5jdGlvbiBnZXRSb3VuZGVkVmFsdWUoYXR0cjogYW55KTogc3RyaW5nIHwgbnVsbCB7XG4gIGlmIChhdHRyLnZhbHVlPy50eXBlID09PSAnTGl0ZXJhbCcgJiYgdHlwZW9mIGF0dHIudmFsdWUudmFsdWUgPT09ICdzdHJpbmcnKSB7XG4gICAgcmV0dXJuIGF0dHIudmFsdWUudmFsdWVcbiAgfVxuXG4gIGlmIChhdHRyLnZhbHVlPy50eXBlID09PSAnSlNYRXhwcmVzc2lvbkNvbnRhaW5lcicpIHtcbiAgICBjb25zdCBleHByID0gYXR0ci52YWx1ZS5leHByZXNzaW9uXG4gICAgaWYgKGV4cHIudHlwZSA9PT0gJ0xpdGVyYWwnICYmIHR5cGVvZiBleHByLnZhbHVlID09PSAnc3RyaW5nJykge1xuICAgICAgcmV0dXJuIGV4cHIudmFsdWVcbiAgICB9XG4gIH1cblxuICByZXR1cm4gbnVsbFxufVxuXG5leHBvcnQgZGVmYXVsdCBydWxlXG4iXX0=
@@ -0,0 +1,3 @@
1
+ import type { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
@@ -0,0 +1,113 @@
1
+ import { getCanonUrl, getCanonPattern } from '../utils/canon.js';
2
+ const RULE_NAME = 'prefer-layout-components';
3
+ const pattern = getCanonPattern(RULE_NAME);
4
+ const rule = {
5
+ meta: {
6
+ type: 'suggestion',
7
+ docs: {
8
+ description: pattern?.summary || 'Use Grid/Columns, not raw div with grid',
9
+ recommended: true,
10
+ url: getCanonUrl(RULE_NAME),
11
+ },
12
+ messages: {
13
+ useLayoutComponent: `[Canon ${pattern?.id || '018'}] Use the Grid or Columns component instead of <div className="grid ...">. Import: import { Grid, Columns, Column } from "@/components"`,
14
+ },
15
+ schema: [],
16
+ },
17
+ create(context) {
18
+ const filename = context.filename || context.getFilename();
19
+ // Only apply to block files
20
+ if (!filename.includes('/blocks/')) {
21
+ return {};
22
+ }
23
+ return {
24
+ JSXOpeningElement(node) {
25
+ const elementName = node.name?.name;
26
+ // Only check div elements
27
+ if (elementName !== 'div') {
28
+ return;
29
+ }
30
+ // Check if className contains 'grid'
31
+ const classNameAttr = node.attributes?.find((attr) => attr.type === 'JSXAttribute' &&
32
+ attr.name?.name === 'className');
33
+ if (!classNameAttr) {
34
+ return;
35
+ }
36
+ // Handle string literal className
37
+ if (classNameAttr.value?.type === 'Literal') {
38
+ const classValue = classNameAttr.value.value;
39
+ if (typeof classValue === 'string' && hasGridClass(classValue)) {
40
+ context.report({
41
+ node,
42
+ messageId: 'useLayoutComponent',
43
+ });
44
+ }
45
+ return;
46
+ }
47
+ // Handle template literal className
48
+ if (classNameAttr.value?.type === 'JSXExpressionContainer') {
49
+ const expr = classNameAttr.value.expression;
50
+ // Direct template literal: className={`grid ...`}
51
+ if (expr.type === 'TemplateLiteral') {
52
+ const quasis = expr.quasis || [];
53
+ for (const quasi of quasis) {
54
+ if (quasi.value?.raw && hasGridClass(quasi.value.raw)) {
55
+ context.report({
56
+ node,
57
+ messageId: 'useLayoutComponent',
58
+ });
59
+ return;
60
+ }
61
+ }
62
+ }
63
+ // clsx call: className={clsx('grid', ...)}
64
+ if (expr.type === 'CallExpression') {
65
+ const args = expr.arguments || [];
66
+ for (const arg of args) {
67
+ if (arg.type === 'Literal' && typeof arg.value === 'string') {
68
+ if (hasGridClass(arg.value)) {
69
+ context.report({
70
+ node,
71
+ messageId: 'useLayoutComponent',
72
+ });
73
+ return;
74
+ }
75
+ }
76
+ // Check template literals in clsx args
77
+ if (arg.type === 'TemplateLiteral') {
78
+ const quasis = arg.quasis || [];
79
+ for (const quasi of quasis) {
80
+ if (quasi.value?.raw && hasGridClass(quasi.value.raw)) {
81
+ context.report({
82
+ node,
83
+ messageId: 'useLayoutComponent',
84
+ });
85
+ return;
86
+ }
87
+ }
88
+ }
89
+ }
90
+ }
91
+ }
92
+ },
93
+ };
94
+ },
95
+ };
96
+ /**
97
+ * Check if a className string contains grid classes
98
+ * Matches: 'grid', 'grid-cols-', etc.
99
+ * Does NOT match: 'grid-area', component names with 'grid' in them
100
+ */
101
+ function hasGridClass(classString) {
102
+ // Split by whitespace and check each class
103
+ const classes = classString.split(/\s+/);
104
+ for (const cls of classes) {
105
+ // Match standalone 'grid' or 'grid-cols-*' patterns
106
+ if (cls === 'grid' || cls.startsWith('grid-cols-')) {
107
+ return true;
108
+ }
109
+ }
110
+ return false;
111
+ }
112
+ export default rule;
113
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJlZmVyLWxheW91dC1jb21wb25lbnRzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2VzbGludC9ydWxlcy9wcmVmZXItbGF5b3V0LWNvbXBvbmVudHMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0EsT0FBTyxFQUFFLFdBQVcsRUFBRSxlQUFlLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQTtBQUVoRSxNQUFNLFNBQVMsR0FBRywwQkFBMEIsQ0FBQTtBQUM1QyxNQUFNLE9BQU8sR0FBRyxlQUFlLENBQUMsU0FBUyxDQUFDLENBQUE7QUFFMUMsTUFBTSxJQUFJLEdBQW9CO0lBQzVCLElBQUksRUFBRTtRQUNKLElBQUksRUFBRSxZQUFZO1FBQ2xCLElBQUksRUFBRTtZQUNKLFdBQVcsRUFBRSxPQUFPLEVBQUUsT0FBTyxJQUFJLHlDQUF5QztZQUMxRSxXQUFXLEVBQUUsSUFBSTtZQUNqQixHQUFHLEVBQUUsV0FBVyxDQUFDLFNBQVMsQ0FBQztTQUM1QjtRQUNELFFBQVEsRUFBRTtZQUNSLGtCQUFrQixFQUFFLFVBQVUsT0FBTyxFQUFFLEVBQUUsSUFBSSxLQUFLLHlJQUF5STtTQUM1TDtRQUNELE1BQU0sRUFBRSxFQUFFO0tBQ1g7SUFFRCxNQUFNLENBQUMsT0FBTztRQUNaLE1BQU0sUUFBUSxHQUFHLE9BQU8sQ0FBQyxRQUFRLElBQUksT0FBTyxDQUFDLFdBQVcsRUFBRSxDQUFBO1FBRTFELDRCQUE0QjtRQUM1QixJQUFJLENBQUMsUUFBUSxDQUFDLFFBQVEsQ0FBQyxVQUFVLENBQUMsRUFBRSxDQUFDO1lBQ25DLE9BQU8sRUFBRSxDQUFBO1FBQ1gsQ0FBQztRQUVELE9BQU87WUFDTCxpQkFBaUIsQ0FBQyxJQUFTO2dCQUN6QixNQUFNLFdBQVcsR0FBRyxJQUFJLENBQUMsSUFBSSxFQUFFLElBQUksQ0FBQTtnQkFFbkMsMEJBQTBCO2dCQUMxQixJQUFJLFdBQVcsS0FBSyxLQUFLLEVBQUUsQ0FBQztvQkFDMUIsT0FBTTtn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QkFDSCxDQUFDO29CQUNILENBQUM7Z0JBQ0gsQ0FBQztZQUNILENBQUM7U0FDRixDQUFBO0lBQ0gsQ0FBQztDQUNGLENBQUE7QUFFRDs7OztHQUlHO0FBQ0gsU0FBUyxZQUFZLENBQUMsV0FBbUI7SUFDdkMsMkNBQTJDO0lBQzNDLE1BQU0sT0FBTyxHQUFHLFdBQVcsQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLENBQUE7SUFFeEMsS0FBSyxNQUFNLEdBQUcsSUFBSSxPQUFPLEVBQUUsQ0FBQztRQUMxQixvREFBb0Q7UUFDcEQsSUFBSSxHQUFHLEtBQUssTUFBTSxJQUFJLEdBQUcsQ0FBQyxVQUFVLENBQUMsWUFBWSxDQUFDLEVBQUUsQ0FBQztZQUNuRCxPQUFPLElBQUksQ0FBQTtRQUNiLENBQUM7SUFDSCxDQUFDO0lBRUQsT0FBTyxLQUFLLENBQUE7QUFDZCxDQUFDO0FBRUQsZUFBZSxJQUFJLENBQUEiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgdHlwZSB7IFJ1bGUgfSBmcm9tICdlc2xpbnQnXG5pbXBvcnQgeyBnZXRDYW5vblVybCwgZ2V0Q2Fub25QYXR0ZXJuIH0gZnJvbSAnLi4vdXRpbHMvY2Fub24uanMnXG5cbmNvbnN0IFJVTEVfTkFNRSA9ICdwcmVmZXItbGF5b3V0LWNvbXBvbmVudHMnXG5jb25zdCBwYXR0ZXJuID0gZ2V0Q2Fub25QYXR0ZXJuKFJVTEVfTkFNRSlcblxuY29uc3QgcnVsZTogUnVsZS5SdWxlTW9kdWxlID0ge1xuICBtZXRhOiB7XG4gICAgdHlwZTogJ3N1Z2dlc3Rpb24nLFxuICAgIGRvY3M6IHtcbiAgICAgIGRlc2NyaXB0aW9uOiBwYXR0ZXJuPy5zdW1tYXJ5IHx8ICdVc2UgR3JpZC9Db2x1bW5zLCBub3QgcmF3IGRpdiB3aXRoIGdyaWQnLFxuICAgICAgcmVjb21tZW5kZWQ6IHRydWUsXG4gICAgICB1cmw6IGdldENhbm9uVXJsKFJVTEVfTkFNRSksXG4gICAgfSxcbiAgICBtZXNzYWdlczoge1xuICAgICAgdXNlTGF5b3V0Q29tcG9uZW50OiBgW0Nhbm9uICR7cGF0dGVybj8uaWQgfHwgJzAxOCd9XSBVc2UgdGhlIEdyaWQgb3IgQ29sdW1ucyBjb21wb25lbnQgaW5zdGVhZCBvZiA8ZGl2IGNsYXNzTmFtZT1cImdyaWQgLi4uXCI+LiBJbXBvcnQ6IGltcG9ydCB7IEdyaWQsIENvbHVtbnMsIENvbHVtbiB9IGZyb20gXCJAL2NvbXBvbmVudHNcImAsXG4gICAgfSxcbiAgICBzY2hlbWE6IFtdLFxuICB9LFxuXG4gIGNyZWF0ZShjb250ZXh0KSB7XG4gICAgY29uc3QgZmlsZW5hbWUgPSBjb250ZXh0LmZpbGVuYW1lIHx8IGNvbnRleHQuZ2V0RmlsZW5hbWUoKVxuXG4gICAgLy8gT25seSBhcHBseSB0byBibG9jayBmaWxlc1xuICAgIGlmICghZmlsZW5hbWUuaW5jbHVkZXMoJy9ibG9ja3MvJykpIHtcbiAgICAgIHJldHVybiB7fVxuICAgIH1cblxuICAgIHJldHVybiB7XG4gICAgICBKU1hPcGVuaW5nRWxlbWVudChub2RlOiBhbnkpIHtcbiAgICAgICAgY29uc3QgZWxlbWVudE5hbWUgPSBub2RlLm5hbWU/Lm5hbWVcblxuICAgICAgICAvLyBPbmx5IGNoZWNrIGRpdiBlbGVtZW50c1xuICAgICAgICBpZiAoZWxlbWVudE5hbWUgIT09ICdkaXYnKSB7XG4gICAgICAgICAgcmV0dXJuXG4gICAgICAgIH1cblxuICAgICAgICAvLyBDaGVjayBpZiBjbGFzc05hbWUgY29udGFpbnMgJ2dyaWQnXG4gICAgICAgIGNvbnN0IGNsYXNzTmFtZUF0dHIgPSBub2RlLmF0dHJpYnV0ZXM/LmZpbmQoXG4gICAgICAgICAgKGF0dHI6IGFueSkgPT5cbiAgICAgICAgICAgIGF0dHIudHlwZSA9PT0gJ0pTWEF0dHJpYnV0ZScgJiZcbiAgICAgICAgICAgIGF0dHIubmFtZT8ubmFtZSA9PT0gJ2NsYXNzTmFtZSdcbiAgICAgICAgKVxuXG4gICAgICAgIGlmICghY2xhc3NOYW1lQXR0cikge1xuICAgICAgICAgIHJldHVyblxuICAgICAgICB9XG5cbiAgICAgICAgLy8gSGFuZGxlIHN0cmluZyBsaXRlcmFsIGNsYXNzTmFtZVxuICAgICAgICBpZiAoY2xhc3NOYW1lQXR0ci52YWx1ZT8udHlwZSA9PT0gJ0xpdGVyYWwnKSB7XG4gICAgICAgICAgY29uc3QgY2xhc3NWYWx1ZSA9IGNsYXNzTmFtZUF0dHIudmFsdWUudmFsdWVcbiAgICAgICAgICBpZiAodHlwZW9mIGNsYXNzVmFsdWUgPT09ICdzdHJpbmcnICYmIGhhc0dyaWRDbGFzcyhjbGFzc1ZhbHVlKSkge1xuICAgICAgICAgICAgY29udGV4dC5yZXBvcnQoe1xuICAgICAgICAgICAgICBub2RlLFxuICAgICAgICAgICAgICBtZXNzYWdlSWQ6ICd1c2VMYXlvdXRDb21wb25lbnQnLFxuICAgICAgICAgICAgfSlcbiAgICAgICAgICB9XG4gICAgICAgICAgcmV0dXJuXG4gICAgICAgIH1cblxuICAgICAgICAvLyBIYW5kbGUgdGVtcGxhdGUgbGl0ZXJhbCBjbGFzc05hbWVcbiAgICAgICAgaWYgKGNsYXNzTmFtZUF0dHIudmFsdWU/LnR5cGUgPT09ICdKU1hFeHByZXNzaW9uQ29udGFpbmVyJykge1xuICAgICAgICAgIGNvbnN0IGV4cHIgPSBjbGFzc05hbWVBdHRyLnZhbHVlLmV4cHJlc3Npb25cblxuICAgICAgICAgIC8vIERpcmVjdCB0ZW1wbGF0ZSBsaXRlcmFsOiBjbGFzc05hbWU9e2BncmlkIC4uLmB9XG4gICAgICAgICAgaWYgKGV4cHIudHlwZSA9PT0gJ1RlbXBsYXRlTGl0ZXJhbCcpIHtcbiAgICAgICAgICAgIGNvbnN0IHF1YXNpcyA9IGV4cHIucXVhc2lzIHx8IFtdXG4gICAgICAgICAgICBmb3IgKGNvbnN0IHF1YXNpIG9mIHF1YXNpcykge1xuICAgICAgICAgICAgICBpZiAocXVhc2kudmFsdWU/LnJhdyAmJiBoYXNHcmlkQ2xhc3MocXVhc2kudmFsdWUucmF3KSkge1xuICAgICAgICAgICAgICAgIGNvbnRleHQucmVwb3J0KHtcbiAgICAgICAgICAgICAgICAgIG5vZGUsXG4gICAgICAgICAgICAgICAgICBtZXNzYWdlSWQ6ICd1c2VMYXlvdXRDb21wb25lbnQnLFxuICAgICAgICAgICAgICAgIH0pXG4gICAgICAgICAgICAgICAgcmV0dXJuXG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG5cbiAgICAgICAgICAvLyBjbHN4IGNhbGw6IGNsYXNzTmFtZT17Y2xzeCgnZ3JpZCcsIC4uLil9XG4gICAgICAgICAgaWYgKGV4cHIudHlwZSA9PT0gJ0NhbGxFeHByZXNzaW9uJykge1xuICAgICAgICAgICAgY29uc3QgYXJncyA9IGV4cHIuYXJndW1lbnRzIHx8IFtdXG4gICAgICAgICAgICBmb3IgKGNvbnN0IGFyZyBvZiBhcmdzKSB7XG4gICAgICAgICAgICAgIGlmIChhcmcudHlwZSA9PT0gJ0xpdGVyYWwnICYmIHR5cGVvZiBhcmcudmFsdWUgPT09ICdzdHJpbmcnKSB7XG4gICAgICAgICAgICAgICAgaWYgKGhhc0dyaWRDbGFzcyhhcmcudmFsdWUpKSB7XG4gICAgICAgICAgICAgICAgICBjb250ZXh0LnJlcG9ydCh7XG4gICAgICAgICAgICAgICAgICAgIG5vZGUsXG4gICAgICAgICAgICAgICAgICAgIG1lc3NhZ2VJZDogJ3VzZUxheW91dENvbXBvbmVudCcsXG4gICAgICAgICAgICAgICAgICB9KVxuICAgICAgICAgICAgICAgICAgcmV0dXJuXG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIC8vIENoZWNrIHRlbXBsYXRlIGxpdGVyYWxzIGluIGNsc3ggYXJnc1xuICAgICAgICAgICAgICBpZiAoYXJnLnR5cGUgPT09ICdUZW1wbGF0ZUxpdGVyYWwnKSB7XG4gICAgICAgICAgICAgICAgY29uc3QgcXVhc2lzID0gYXJnLnF1YXNpcyB8fCBbXVxuICAgICAgICAgICAgICAgIGZvciAoY29uc3QgcXVhc2kgb2YgcXVhc2lzKSB7XG4gICAgICAgICAgICAgICAgICBpZiAocXVhc2kudmFsdWU/LnJhdyAmJiBoYXNHcmlkQ2xhc3MocXVhc2kudmFsdWUucmF3KSkge1xuICAgICAgICAgICAgICAgICAgICBjb250ZXh0LnJlcG9ydCh7XG4gICAgICAgICAgICAgICAgICAgICAgbm9kZSxcbiAgICAgICAgICAgICAgICAgICAgICBtZXNzYWdlSWQ6ICd1c2VMYXlvdXRDb21wb25lbnQnLFxuICAgICAgICAgICAgICAgICAgICB9KVxuICAgICAgICAgICAgICAgICAgICByZXR1cm5cbiAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH0sXG4gICAgfVxuICB9LFxufVxuXG4vKipcbiAqIENoZWNrIGlmIGEgY2xhc3NOYW1lIHN0cmluZyBjb250YWlucyBncmlkIGNsYXNzZXNcbiAqIE1hdGNoZXM6ICdncmlkJywgJ2dyaWQtY29scy0nLCBldGMuXG4gKiBEb2VzIE5PVCBtYXRjaDogJ2dyaWQtYXJlYScsIGNvbXBvbmVudCBuYW1lcyB3aXRoICdncmlkJyBpbiB0aGVtXG4gKi9cbmZ1bmN0aW9uIGhhc0dyaWRDbGFzcyhjbGFzc1N0cmluZzogc3RyaW5nKTogYm9vbGVhbiB7XG4gIC8vIFNwbGl0IGJ5IHdoaXRlc3BhY2UgYW5kIGNoZWNrIGVhY2ggY2xhc3NcbiAgY29uc3QgY2xhc3NlcyA9IGNsYXNzU3RyaW5nLnNwbGl0KC9cXHMrLylcbiAgXG4gIGZvciAoY29uc3QgY2xzIG9mIGNsYXNzZXMpIHtcbiAgICAvLyBNYXRjaCBzdGFuZGFsb25lICdncmlkJyBvciAnZ3JpZC1jb2xzLSonIHBhdHRlcm5zXG4gICAgaWYgKGNscyA9PT0gJ2dyaWQnIHx8IGNscy5zdGFydHNXaXRoKCdncmlkLWNvbHMtJykpIHtcbiAgICAgIHJldHVybiB0cnVlXG4gICAgfVxuICB9XG4gIFxuICByZXR1cm4gZmFsc2Vcbn1cblxuZXhwb3J0IGRlZmF1bHQgcnVsZVxuIl19
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gallop.software/canon",
3
- "version": "2.0.2",
3
+ "version": "2.2.0",
4
4
  "type": "module",
5
5
  "description": "Gallop Canon - Architecture patterns, ESLint plugin, and CLI for template governance",
6
6
  "main": "dist/index.js",
@@ -0,0 +1,85 @@
1
+ # 018: Layout Components
2
+
3
+ **Category:** Layout
4
+ **Status:** Stable
5
+ **Enforcement:** ESLint (`gallop/prefer-layout-components`)
6
+
7
+ ## Summary
8
+
9
+ Use the `Grid` or `Columns` component instead of raw `<div>` elements with grid classes.
10
+
11
+ ## Rationale
12
+
13
+ Raw `<div>` elements with grid classes bypass the design system's layout abstractions. The `Grid` and `Columns` components provide:
14
+
15
+ - **Consistent defaults** for gaps, columns, and alignment
16
+ - **Semantic intent** - code is more readable when intent is clear
17
+ - **Centralized updates** - layout defaults can be updated in one place
18
+ - **Reduced errors** - no need to remember all required grid classes
19
+
20
+ ## Bad
21
+
22
+ ```tsx
23
+ <div className="grid grid-cols-3 gap-6">
24
+ <Card>...</Card>
25
+ <Card>...</Card>
26
+ <Card>...</Card>
27
+ </div>
28
+
29
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-8 items-center">
30
+ <div>Left content</div>
31
+ <div>Right content</div>
32
+ </div>
33
+ ```
34
+
35
+ ## Good
36
+
37
+ ```tsx
38
+ import { Grid, Columns, Column } from '@/components'
39
+
40
+ <Grid cols="grid-cols-3" gap="gap-6">
41
+ <Card>...</Card>
42
+ <Card>...</Card>
43
+ <Card>...</Card>
44
+ </Grid>
45
+
46
+ <Columns>
47
+ <Column>Left content</Column>
48
+ <Column>Right content</Column>
49
+ </Columns>
50
+ ```
51
+
52
+ ## Component Reference
53
+
54
+ ### Grid
55
+
56
+ For multi-item grid layouts (3+ columns, card grids, galleries):
57
+
58
+ ```tsx
59
+ <Grid
60
+ cols="grid-cols-1 lg:grid-cols-3" // optional, defaults to 1 → 3
61
+ gap="gap-6" // optional, has sensible default
62
+ >
63
+ {children}
64
+ </Grid>
65
+ ```
66
+
67
+ ### Columns
68
+
69
+ For two-column layouts with optional reversal:
70
+
71
+ ```tsx
72
+ <Columns
73
+ cols="grid-cols-1 lg:grid-cols-2" // optional
74
+ gap="gap-8" // optional
75
+ align="items-center" // optional
76
+ reverseColumns={true} // swap order on lg+
77
+ >
78
+ <Column>Left</Column>
79
+ <Column>Right</Column>
80
+ </Columns>
81
+ ```
82
+
83
+ ## Exceptions
84
+
85
+ The rule only applies to files in `/blocks/`. Component files may use raw divs with grid when building the layout primitives themselves.
@@ -0,0 +1,52 @@
1
+ # 019: Background Image Rounded
2
+
3
+ **Category:** Components
4
+ **Status:** Stable
5
+ **Enforcement:** ESLint (`gallop/background-image-rounded`)
6
+
7
+ ## Summary
8
+
9
+ When using the `Image` component as a background image (with `absolute inset-0` positioning), always set `rounded="rounded-none"`.
10
+
11
+ ## Rationale
12
+
13
+ The Image component defaults to `rounded-lg` for rounded corners. When used as a full-bleed background image, these rounded corners:
14
+
15
+ - **Cause visual artifacts** - corners get clipped unexpectedly
16
+ - **Conflict with container rounding** - the parent container should control edge styling
17
+ - **Create inconsistent edges** - background images should fill their container completely
18
+
19
+ ## Bad
20
+
21
+ ```tsx
22
+ {/* Background image without rounded prop - uses default rounded-lg */}
23
+ <Image
24
+ src="/images/hero-bg.jpg"
25
+ alt="Background"
26
+ className="absolute inset-0 w-full h-full object-cover"
27
+ />
28
+ ```
29
+
30
+ ## Good
31
+
32
+ ```tsx
33
+ {/* Background image with explicit rounded-none */}
34
+ <Image
35
+ src="/images/hero-bg.jpg"
36
+ alt="Background"
37
+ className="absolute inset-0 w-full h-full object-cover"
38
+ rounded="rounded-none"
39
+ />
40
+ ```
41
+
42
+ ## Detection
43
+
44
+ The rule identifies background images by checking for:
45
+ - `Image` component usage
46
+ - `className` containing both `absolute` and `inset-0`
47
+
48
+ When detected, the rule requires `rounded="rounded-none"` to be explicitly set.
49
+
50
+ ## Exceptions
51
+
52
+ This rule only applies to files in `/blocks/`. Component files that implement background image patterns internally are not flagged.
package/schema.json CHANGED
@@ -210,6 +210,26 @@
210
210
  "enforcement": "documentation",
211
211
  "rule": null,
212
212
  "summary": "PageMetadata structure, structured data"
213
+ },
214
+ {
215
+ "id": "018",
216
+ "title": "Layout Components",
217
+ "file": "patterns/018-layout-components.md",
218
+ "category": "layout",
219
+ "status": "stable",
220
+ "enforcement": "eslint",
221
+ "rule": "gallop/prefer-layout-components",
222
+ "summary": "Use Grid/Columns, not raw div with grid"
223
+ },
224
+ {
225
+ "id": "019",
226
+ "title": "Background Image Rounded",
227
+ "file": "patterns/019-background-image-rounded.md",
228
+ "category": "components",
229
+ "status": "stable",
230
+ "enforcement": "eslint",
231
+ "rule": "gallop/background-image-rounded",
232
+ "summary": "Background images must have rounded=\"rounded-none\""
213
233
  }
214
234
  ],
215
235
  "guarantees": [
@@ -239,7 +259,7 @@
239
259
  "name": "Design System Compliance",
240
260
  "since": "1.0.0",
241
261
  "status": "stable",
242
- "patterns": ["003", "004", "009", "010", "011"]
262
+ "patterns": ["003", "004", "009", "010", "011", "018", "019"]
243
263
  }
244
264
  ]
245
265
  }