@gallop.software/canon 2.33.0 → 2.34.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.
package/dist/eslint/index.d.ts
CHANGED
|
@@ -33,6 +33,7 @@ declare const plugin: {
|
|
|
33
33
|
'no-classnames-package': import("eslint").Rule.RuleModule;
|
|
34
34
|
'prefer-alias-imports': import("eslint").Rule.RuleModule;
|
|
35
35
|
'no-inline-svg': import("eslint").Rule.RuleModule;
|
|
36
|
+
'no-next-image': import("eslint").Rule.RuleModule;
|
|
36
37
|
};
|
|
37
38
|
/**
|
|
38
39
|
* Recommended rule configurations - spread into your ESLint config
|
|
@@ -56,6 +57,7 @@ declare const plugin: {
|
|
|
56
57
|
readonly 'gallop/no-classnames-package': "warn";
|
|
57
58
|
readonly 'gallop/prefer-alias-imports': "warn";
|
|
58
59
|
readonly 'gallop/no-inline-svg': "warn";
|
|
60
|
+
readonly 'gallop/no-next-image': "warn";
|
|
59
61
|
};
|
|
60
62
|
};
|
|
61
63
|
export default plugin;
|
package/dist/eslint/index.js
CHANGED
|
@@ -15,6 +15,7 @@ import requireCanonSetup from './rules/require-canon-setup.js';
|
|
|
15
15
|
import noClassnamesPackage from './rules/no-classnames-package.js';
|
|
16
16
|
import preferAliasImports from './rules/prefer-alias-imports.js';
|
|
17
17
|
import noInlineSvg from './rules/no-inline-svg.js';
|
|
18
|
+
import noNextImage from './rules/no-next-image.js';
|
|
18
19
|
/**
|
|
19
20
|
* All Canon ESLint rules with recommended severity levels
|
|
20
21
|
*/
|
|
@@ -36,6 +37,7 @@ const recommended = {
|
|
|
36
37
|
'gallop/no-classnames-package': 'warn',
|
|
37
38
|
'gallop/prefer-alias-imports': 'warn',
|
|
38
39
|
'gallop/no-inline-svg': 'warn',
|
|
40
|
+
'gallop/no-next-image': 'warn',
|
|
39
41
|
};
|
|
40
42
|
const plugin = {
|
|
41
43
|
meta: {
|
|
@@ -60,6 +62,7 @@ const plugin = {
|
|
|
60
62
|
'no-classnames-package': noClassnamesPackage,
|
|
61
63
|
'prefer-alias-imports': preferAliasImports,
|
|
62
64
|
'no-inline-svg': noInlineSvg,
|
|
65
|
+
'no-next-image': noNextImage,
|
|
63
66
|
},
|
|
64
67
|
/**
|
|
65
68
|
* Recommended rule configurations - spread into your ESLint config
|
|
@@ -68,4 +71,4 @@ const plugin = {
|
|
|
68
71
|
recommended,
|
|
69
72
|
};
|
|
70
73
|
export default plugin;
|
|
71
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
74
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvZXNsaW50L2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sY0FBYyxNQUFNLDZCQUE2QixDQUFBO0FBQ3hELE9BQU8sb0JBQW9CLE1BQU0sb0NBQW9DLENBQUE7QUFDckUsT0FBTyxvQkFBb0IsTUFBTSxtQ0FBbUMsQ0FBQTtBQUNwRSxPQUFPLDBCQUEwQixNQUFNLHlDQUF5QyxDQUFBO0FBQ2hGLE9BQU8sc0JBQXNCLE1BQU0scUNBQXFDLENBQUE7QUFDeEUsT0FBTyxpQkFBaUIsTUFBTSxnQ0FBZ0MsQ0FBQTtBQUM5RCxPQUFPLFdBQVcsTUFBTSwwQkFBMEIsQ0FBQTtBQUNsRCxPQUFPLGtCQUFrQixNQUFNLGtDQUFrQyxDQUFBO0FBQ2pFLE9BQU8sNEJBQTRCLE1BQU0sNENBQTRDLENBQUE7QUFDckYsT0FBTyxtQkFBbUIsTUFBTSxtQ0FBbUMsQ0FBQTtBQUNuRSxPQUFPLG9CQUFvQixNQUFNLG1DQUFtQyxDQUFBO0FBQ3BFLE9BQU8sWUFBWSxNQUFNLDJCQUEyQixDQUFBO0FBQ3BELE9BQU8scUJBQXFCLE1BQU0sb0NBQW9DLENBQUE7QUFDdEUsT0FBTyxpQkFBaUIsTUFBTSxnQ0FBZ0MsQ0FBQTtBQUM5RCxPQUFPLG1CQUFtQixNQUFNLGtDQUFrQyxDQUFBO0FBQ2xFLE9BQU8sa0JBQWtCLE1BQU0saUNBQWlDLENBQUE7QUFDaEUsT0FBTyxXQUFXLE1BQU0sMEJBQTBCLENBQUE7QUFDbEQsT0FBTyxXQUFXLE1BQU0sMEJBQTBCLENBQUE7QUFFbEQ7O0dBRUc7QUFDSCxNQUFNLFdBQVcsR0FBRztJQUNsQix5QkFBeUIsRUFBRSxNQUFNO0lBQ2pDLGdDQUFnQyxFQUFFLE1BQU07SUFDeEMsZ0NBQWdDLEVBQUUsTUFBTTtJQUN4QywrQkFBK0IsRUFBRSxNQUFNO0lBQ3ZDLHFDQUFxQyxFQUFFLE1BQU07SUFDN0MsaUNBQWlDLEVBQUUsTUFBTTtJQUN6Qyw0QkFBNEIsRUFBRSxNQUFNO0lBQ3BDLHNCQUFzQixFQUFFLE1BQU07SUFDOUIsOEJBQThCLEVBQUUsTUFBTTtJQUN0Qyx3Q0FBd0MsRUFBRSxNQUFNO0lBQ2hELCtCQUErQixFQUFFLE1BQU07SUFDdkMsK0JBQStCLEVBQUUsTUFBTTtJQUN2Qyx1QkFBdUIsRUFBRSxNQUFNO0lBQy9CLDRCQUE0QixFQUFFLE1BQU07SUFDcEMsOEJBQThCLEVBQUUsTUFBTTtJQUN0Qyw2QkFBNkIsRUFBRSxNQUFNO0lBQ3JDLHNCQUFzQixFQUFFLE1BQU07SUFDOUIsc0JBQXNCLEVBQUUsTUFBTTtDQUN0QixDQUFBO0FBRVYsTUFBTSxNQUFNLEdBQUc7SUFDYixJQUFJLEVBQUU7UUFDSixJQUFJLEVBQUUsc0JBQXNCO1FBQzVCLE9BQU8sRUFBRSxRQUFRO0tBQ2xCO0lBQ0QsS0FBSyxFQUFFO1FBQ0wsa0JBQWtCLEVBQUUsY0FBYztRQUNsQyx5QkFBeUIsRUFBRSxxQkFBcUI7UUFDaEQseUJBQXlCLEVBQUUsb0JBQW9CO1FBQy9DLHdCQUF3QixFQUFFLG9CQUFvQjtRQUM5Qyw4QkFBOEIsRUFBRSwwQkFBMEI7UUFDMUQsMEJBQTBCLEVBQUUsc0JBQXNCO1FBQ2xELHFCQUFxQixFQUFFLGlCQUFpQjtRQUN4QyxlQUFlLEVBQUUsV0FBVztRQUM1Qix1QkFBdUIsRUFBRSxrQkFBa0I7UUFDM0MsaUNBQWlDLEVBQUUsNEJBQTRCO1FBQy9ELHdCQUF3QixFQUFFLG1CQUFtQjtRQUM3Qyx3QkFBd0IsRUFBRSxvQkFBb0I7UUFDOUMsZ0JBQWdCLEVBQUUsWUFBWTtRQUM5QixxQkFBcUIsRUFBRSxpQkFBaUI7UUFDeEMsdUJBQXVCLEVBQUUsbUJBQW1CO1FBQzVDLHNCQUFzQixFQUFFLGtCQUFrQjtRQUMxQyxlQUFlLEVBQUUsV0FBVztRQUM1QixlQUFlLEVBQUUsV0FBVztLQUM3QjtJQUNEOzs7T0FHRztJQUNILFdBQVc7Q0FDWixDQUFBO0FBRUQsZUFBZSxNQUFNLENBQUEiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgbm9DbGllbnRCbG9ja3MgZnJvbSAnLi9ydWxlcy9uby1jbGllbnQtYmxvY2tzLmpzJ1xuaW1wb3J0IG5vQ29udGFpbmVySW5TZWN0aW9uIGZyb20gJy4vcnVsZXMvbm8tY29udGFpbmVyLWluLXNlY3Rpb24uanMnXG5pbXBvcnQgcHJlZmVyQ29tcG9uZW50UHJvcHMgZnJvbSAnLi9ydWxlcy9wcmVmZXItY29tcG9uZW50LXByb3BzLmpzJ1xuaW1wb3J0IHByZWZlclR5cG9ncmFwaHlDb21wb25lbnRzIGZyb20gJy4vcnVsZXMvcHJlZmVyLXR5cG9ncmFwaHktY29tcG9uZW50cy5qcydcbmltcG9ydCBwcmVmZXJMYXlvdXRDb21wb25lbnRzIGZyb20gJy4vcnVsZXMvcHJlZmVyLWxheW91dC1jb21wb25lbnRzLmpzJ1xuaW1wb3J0IG5vQXJiaXRyYXJ5Q29sb3JzIGZyb20gJy4vcnVsZXMvbm8tYXJiaXRyYXJ5LWNvbG9ycy5qcydcbmltcG9ydCBub1Jhd0NvbG9ycyBmcm9tICcuL3J1bGVzL25vLXJhdy1jb2xvcnMuanMnXG5pbXBvcnQgbm9Dcm9zc1pvbmVJbXBvcnRzIGZyb20gJy4vcnVsZXMvbm8tY3Jvc3Mtem9uZS1pbXBvcnRzLmpzJ1xuaW1wb3J0IG5vTmF0aXZlSW50ZXJzZWN0aW9uT2JzZXJ2ZXIgZnJvbSAnLi9ydWxlcy9uby1uYXRpdmUtaW50ZXJzZWN0aW9uLW9ic2VydmVyLmpzJ1xuaW1wb3J0IG5vQ29tcG9uZW50SW5CbG9ja3MgZnJvbSAnLi9ydWxlcy9uby1jb21wb25lbnQtaW4tYmxvY2tzLmpzJ1xuaW1wb3J0IHByZWZlckxpc3RDb21wb25lbnRzIGZyb20gJy4vcnVsZXMvcHJlZmVyLWxpc3QtY29tcG9uZW50cy5qcydcbmltcG9ydCBub05hdGl2ZURhdGUgZnJvbSAnLi9ydWxlcy9uby1uYXRpdmUtZGF0ZS5qcydcbmltcG9ydCBibG9ja05hbWluZ0NvbnZlbnRpb24gZnJvbSAnLi9ydWxlcy9ibG9jay1uYW1pbmctY29udmVudGlvbi5qcydcbmltcG9ydCByZXF1aXJlQ2Fub25TZXR1cCBmcm9tICcuL3J1bGVzL3JlcXVpcmUtY2Fub24tc2V0dXAuanMnXG5pbXBvcnQgbm9DbGFzc25hbWVzUGFja2FnZSBmcm9tICcuL3J1bGVzL25vLWNsYXNzbmFtZXMtcGFja2FnZS5qcydcbmltcG9ydCBwcmVmZXJBbGlhc0ltcG9ydHMgZnJvbSAnLi9ydWxlcy9wcmVmZXItYWxpYXMtaW1wb3J0cy5qcydcbmltcG9ydCBub0lubGluZVN2ZyBmcm9tICcuL3J1bGVzL25vLWlubGluZS1zdmcuanMnXG5pbXBvcnQgbm9OZXh0SW1hZ2UgZnJvbSAnLi9ydWxlcy9uby1uZXh0LWltYWdlLmpzJ1xuXG4vKipcbiAqIEFsbCBDYW5vbiBFU0xpbnQgcnVsZXMgd2l0aCByZWNvbW1lbmRlZCBzZXZlcml0eSBsZXZlbHNcbiAqL1xuY29uc3QgcmVjb21tZW5kZWQgPSB7XG4gICdnYWxsb3Avbm8tY2xpZW50LWJsb2Nrcyc6ICd3YXJuJyxcbiAgJ2dhbGxvcC9ibG9jay1uYW1pbmctY29udmVudGlvbic6ICd3YXJuJyxcbiAgJ2dhbGxvcC9uby1jb250YWluZXItaW4tc2VjdGlvbic6ICd3YXJuJyxcbiAgJ2dhbGxvcC9wcmVmZXItY29tcG9uZW50LXByb3BzJzogJ3dhcm4nLFxuICAnZ2FsbG9wL3ByZWZlci10eXBvZ3JhcGh5LWNvbXBvbmVudHMnOiAnd2FybicsXG4gICdnYWxsb3AvcHJlZmVyLWxheW91dC1jb21wb25lbnRzJzogJ3dhcm4nLFxuICAnZ2FsbG9wL25vLWFyYml0cmFyeS1jb2xvcnMnOiAnd2FybicsXG4gICdnYWxsb3Avbm8tcmF3LWNvbG9ycyc6ICd3YXJuJyxcbiAgJ2dhbGxvcC9uby1jcm9zcy16b25lLWltcG9ydHMnOiAnd2FybicsXG4gICdnYWxsb3Avbm8tbmF0aXZlLWludGVyc2VjdGlvbi1vYnNlcnZlcic6ICd3YXJuJyxcbiAgJ2dhbGxvcC9uby1jb21wb25lbnQtaW4tYmxvY2tzJzogJ3dhcm4nLFxuICAnZ2FsbG9wL3ByZWZlci1saXN0LWNvbXBvbmVudHMnOiAnd2FybicsXG4gICdnYWxsb3Avbm8tbmF0aXZlLWRhdGUnOiAnd2FybicsXG4gICdnYWxsb3AvcmVxdWlyZS1jYW5vbi1zZXR1cCc6ICd3YXJuJyxcbiAgJ2dhbGxvcC9uby1jbGFzc25hbWVzLXBhY2thZ2UnOiAnd2FybicsXG4gICdnYWxsb3AvcHJlZmVyLWFsaWFzLWltcG9ydHMnOiAnd2FybicsXG4gICdnYWxsb3Avbm8taW5saW5lLXN2Zyc6ICd3YXJuJyxcbiAgJ2dhbGxvcC9uby1uZXh0LWltYWdlJzogJ3dhcm4nLFxufSBhcyBjb25zdFxuXG5jb25zdCBwbHVnaW4gPSB7XG4gIG1ldGE6IHtcbiAgICBuYW1lOiAnZXNsaW50LXBsdWdpbi1nYWxsb3AnLFxuICAgIHZlcnNpb246ICcyLjMxLjAnLFxuICB9LFxuICBydWxlczoge1xuICAgICduby1jbGllbnQtYmxvY2tzJzogbm9DbGllbnRCbG9ja3MsXG4gICAgJ2Jsb2NrLW5hbWluZy1jb252ZW50aW9uJzogYmxvY2tOYW1pbmdDb252ZW50aW9uLFxuICAgICduby1jb250YWluZXItaW4tc2VjdGlvbic6IG5vQ29udGFpbmVySW5TZWN0aW9uLFxuICAgICdwcmVmZXItY29tcG9uZW50LXByb3BzJzogcHJlZmVyQ29tcG9uZW50UHJvcHMsXG4gICAgJ3ByZWZlci10eXBvZ3JhcGh5LWNvbXBvbmVudHMnOiBwcmVmZXJUeXBvZ3JhcGh5Q29tcG9uZW50cyxcbiAgICAncHJlZmVyLWxheW91dC1jb21wb25lbnRzJzogcHJlZmVyTGF5b3V0Q29tcG9uZW50cyxcbiAgICAnbm8tYXJiaXRyYXJ5LWNvbG9ycyc6IG5vQXJiaXRyYXJ5Q29sb3JzLFxuICAgICduby1yYXctY29sb3JzJzogbm9SYXdDb2xvcnMsXG4gICAgJ25vLWNyb3NzLXpvbmUtaW1wb3J0cyc6IG5vQ3Jvc3Nab25lSW1wb3J0cyxcbiAgICAnbm8tbmF0aXZlLWludGVyc2VjdGlvbi1vYnNlcnZlcic6IG5vTmF0aXZlSW50ZXJzZWN0aW9uT2JzZXJ2ZXIsXG4gICAgJ25vLWNvbXBvbmVudC1pbi1ibG9ja3MnOiBub0NvbXBvbmVudEluQmxvY2tzLFxuICAgICdwcmVmZXItbGlzdC1jb21wb25lbnRzJzogcHJlZmVyTGlzdENvbXBvbmVudHMsXG4gICAgJ25vLW5hdGl2ZS1kYXRlJzogbm9OYXRpdmVEYXRlLFxuICAgICdyZXF1aXJlLWNhbm9uLXNldHVwJzogcmVxdWlyZUNhbm9uU2V0dXAsXG4gICAgJ25vLWNsYXNzbmFtZXMtcGFja2FnZSc6IG5vQ2xhc3NuYW1lc1BhY2thZ2UsXG4gICAgJ3ByZWZlci1hbGlhcy1pbXBvcnRzJzogcHJlZmVyQWxpYXNJbXBvcnRzLFxuICAgICduby1pbmxpbmUtc3ZnJzogbm9JbmxpbmVTdmcsXG4gICAgJ25vLW5leHQtaW1hZ2UnOiBub05leHRJbWFnZSxcbiAgfSxcbiAgLyoqXG4gICAqIFJlY29tbWVuZGVkIHJ1bGUgY29uZmlndXJhdGlvbnMgLSBzcHJlYWQgaW50byB5b3VyIEVTTGludCBjb25maWdcbiAgICogQGV4YW1wbGUgcnVsZXM6IHsgLi4uZ2FsbG9wLnJlY29tbWVuZGVkIH1cbiAgICovXG4gIHJlY29tbWVuZGVkLFxufVxuXG5leHBvcnQgZGVmYXVsdCBwbHVnaW5cbiJdfQ==
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { getCanonUrl, getCanonPattern } from '../utils/canon.js';
|
|
2
|
+
const RULE_NAME = 'no-next-image';
|
|
3
|
+
const pattern = getCanonPattern(RULE_NAME);
|
|
4
|
+
const NEXT_IMAGE_SOURCES = ['next/image', 'next/legacy/image'];
|
|
5
|
+
const rule = {
|
|
6
|
+
meta: {
|
|
7
|
+
type: 'suggestion',
|
|
8
|
+
docs: {
|
|
9
|
+
description: pattern?.summary || 'Use custom Image component instead of next/image',
|
|
10
|
+
recommended: true,
|
|
11
|
+
url: getCanonUrl(RULE_NAME),
|
|
12
|
+
},
|
|
13
|
+
messages: {
|
|
14
|
+
noNextImage: `[Canon ${pattern?.id || '028'}] Use the custom Image component instead of next/image. Import: import Image from "@/components/image"`,
|
|
15
|
+
},
|
|
16
|
+
schema: [],
|
|
17
|
+
},
|
|
18
|
+
create(context) {
|
|
19
|
+
return {
|
|
20
|
+
ImportDeclaration(node) {
|
|
21
|
+
const source = node.source?.value;
|
|
22
|
+
if (typeof source === 'string' &&
|
|
23
|
+
NEXT_IMAGE_SOURCES.includes(source)) {
|
|
24
|
+
context.report({
|
|
25
|
+
node,
|
|
26
|
+
messageId: 'noNextImage',
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
CallExpression(node) {
|
|
31
|
+
if (node.callee?.name === 'require' &&
|
|
32
|
+
node.arguments?.length === 1 &&
|
|
33
|
+
node.arguments[0]?.type === 'Literal' &&
|
|
34
|
+
typeof node.arguments[0].value === 'string' &&
|
|
35
|
+
NEXT_IMAGE_SOURCES.includes(node.arguments[0].value)) {
|
|
36
|
+
context.report({
|
|
37
|
+
node,
|
|
38
|
+
messageId: 'noNextImage',
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
export default rule;
|
|
46
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibm8tbmV4dC1pbWFnZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9lc2xpbnQvcnVsZXMvbm8tbmV4dC1pbWFnZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLEVBQUUsV0FBVyxFQUFFLGVBQWUsRUFBRSxNQUFNLG1CQUFtQixDQUFBO0FBRWhFLE1BQU0sU0FBUyxHQUFHLGVBQWUsQ0FBQTtBQUNqQyxNQUFNLE9BQU8sR0FBRyxlQUFlLENBQUMsU0FBUyxDQUFDLENBQUE7QUFFMUMsTUFBTSxrQkFBa0IsR0FBRyxDQUFDLFlBQVksRUFBRSxtQkFBbUIsQ0FBQyxDQUFBO0FBRTlELE1BQU0sSUFBSSxHQUFvQjtJQUM1QixJQUFJLEVBQUU7UUFDSixJQUFJLEVBQUUsWUFBWTtRQUNsQixJQUFJLEVBQUU7WUFDSixXQUFXLEVBQ1QsT0FBTyxFQUFFLE9BQU8sSUFBSSxrREFBa0Q7WUFDeEUsV0FBVyxFQUFFLElBQUk7WUFDakIsR0FBRyxFQUFFLFdBQVcsQ0FBQyxTQUFTLENBQUM7U0FDNUI7UUFDRCxRQUFRLEVBQUU7WUFDUixXQUFXLEVBQUUsVUFBVSxPQUFPLEVBQUUsRUFBRSxJQUFJLEtBQUssd0dBQXdHO1NBQ3BKO1FBQ0QsTUFBTSxFQUFFLEVBQUU7S0FDWDtJQUVELE1BQU0sQ0FBQyxPQUFPO1FBQ1osT0FBTztZQUNMLGlCQUFpQixDQUFDLElBQVM7Z0JBQ3pCLE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyxNQUFNLEVBQUUsS0FBSyxDQUFBO2dCQUNqQyxJQUNFLE9BQU8sTUFBTSxLQUFLLFFBQVE7b0JBQzFCLGtCQUFrQixDQUFDLFFBQVEsQ0FBQyxNQUFNLENBQUMsRUFDbkMsQ0FBQztvQkFDRCxPQUFPLENBQUMsTUFBTSxDQUFDO3dCQUNiLElBQUk7d0JBQ0osU0FBUyxFQUFFLGFBQWE7cUJBQ3pCLENBQUMsQ0FBQTtnQkFDSixDQUFDO1lBQ0gsQ0FBQztZQUNELGNBQWMsQ0FBQyxJQUFTO2dCQUN0QixJQUNFLElBQUksQ0FBQyxNQUFNLEVBQUUsSUFBSSxLQUFLLFNBQVM7b0JBQy9CLElBQUksQ0FBQyxTQUFTLEVBQUUsTUFBTSxLQUFLLENBQUM7b0JBQzVCLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLEVBQUUsSUFBSSxLQUFLLFNBQVM7b0JBQ3JDLE9BQU8sSUFBSSxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLEtBQUssUUFBUTtvQkFDM0Msa0JBQWtCLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLEVBQ3BELENBQUM7b0JBQ0QsT0FBTyxDQUFDLE1BQU0sQ0FBQzt3QkFDYixJQUFJO3dCQUNKLFNBQVMsRUFBRSxhQUFhO3FCQUN6QixDQUFDLENBQUE7Z0JBQ0osQ0FBQztZQUNILENBQUM7U0FDRixDQUFBO0lBQ0gsQ0FBQztDQUNGLENBQUE7QUFFRCxlQUFlLElBQUksQ0FBQSIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB0eXBlIHsgUnVsZSB9IGZyb20gJ2VzbGludCdcbmltcG9ydCB7IGdldENhbm9uVXJsLCBnZXRDYW5vblBhdHRlcm4gfSBmcm9tICcuLi91dGlscy9jYW5vbi5qcydcblxuY29uc3QgUlVMRV9OQU1FID0gJ25vLW5leHQtaW1hZ2UnXG5jb25zdCBwYXR0ZXJuID0gZ2V0Q2Fub25QYXR0ZXJuKFJVTEVfTkFNRSlcblxuY29uc3QgTkVYVF9JTUFHRV9TT1VSQ0VTID0gWyduZXh0L2ltYWdlJywgJ25leHQvbGVnYWN5L2ltYWdlJ11cblxuY29uc3QgcnVsZTogUnVsZS5SdWxlTW9kdWxlID0ge1xuICBtZXRhOiB7XG4gICAgdHlwZTogJ3N1Z2dlc3Rpb24nLFxuICAgIGRvY3M6IHtcbiAgICAgIGRlc2NyaXB0aW9uOlxuICAgICAgICBwYXR0ZXJuPy5zdW1tYXJ5IHx8ICdVc2UgY3VzdG9tIEltYWdlIGNvbXBvbmVudCBpbnN0ZWFkIG9mIG5leHQvaW1hZ2UnLFxuICAgICAgcmVjb21tZW5kZWQ6IHRydWUsXG4gICAgICB1cmw6IGdldENhbm9uVXJsKFJVTEVfTkFNRSksXG4gICAgfSxcbiAgICBtZXNzYWdlczoge1xuICAgICAgbm9OZXh0SW1hZ2U6IGBbQ2Fub24gJHtwYXR0ZXJuPy5pZCB8fCAnMDI4J31dIFVzZSB0aGUgY3VzdG9tIEltYWdlIGNvbXBvbmVudCBpbnN0ZWFkIG9mIG5leHQvaW1hZ2UuIEltcG9ydDogaW1wb3J0IEltYWdlIGZyb20gXCJAL2NvbXBvbmVudHMvaW1hZ2VcImAsXG4gICAgfSxcbiAgICBzY2hlbWE6IFtdLFxuICB9LFxuXG4gIGNyZWF0ZShjb250ZXh0KSB7XG4gICAgcmV0dXJuIHtcbiAgICAgIEltcG9ydERlY2xhcmF0aW9uKG5vZGU6IGFueSkge1xuICAgICAgICBjb25zdCBzb3VyY2UgPSBub2RlLnNvdXJjZT8udmFsdWVcbiAgICAgICAgaWYgKFxuICAgICAgICAgIHR5cGVvZiBzb3VyY2UgPT09ICdzdHJpbmcnICYmXG4gICAgICAgICAgTkVYVF9JTUFHRV9TT1VSQ0VTLmluY2x1ZGVzKHNvdXJjZSlcbiAgICAgICAgKSB7XG4gICAgICAgICAgY29udGV4dC5yZXBvcnQoe1xuICAgICAgICAgICAgbm9kZSxcbiAgICAgICAgICAgIG1lc3NhZ2VJZDogJ25vTmV4dEltYWdlJyxcbiAgICAgICAgICB9KVxuICAgICAgICB9XG4gICAgICB9LFxuICAgICAgQ2FsbEV4cHJlc3Npb24obm9kZTogYW55KSB7XG4gICAgICAgIGlmIChcbiAgICAgICAgICBub2RlLmNhbGxlZT8ubmFtZSA9PT0gJ3JlcXVpcmUnICYmXG4gICAgICAgICAgbm9kZS5hcmd1bWVudHM/Lmxlbmd0aCA9PT0gMSAmJlxuICAgICAgICAgIG5vZGUuYXJndW1lbnRzWzBdPy50eXBlID09PSAnTGl0ZXJhbCcgJiZcbiAgICAgICAgICB0eXBlb2Ygbm9kZS5hcmd1bWVudHNbMF0udmFsdWUgPT09ICdzdHJpbmcnICYmXG4gICAgICAgICAgTkVYVF9JTUFHRV9TT1VSQ0VTLmluY2x1ZGVzKG5vZGUuYXJndW1lbnRzWzBdLnZhbHVlKVxuICAgICAgICApIHtcbiAgICAgICAgICBjb250ZXh0LnJlcG9ydCh7XG4gICAgICAgICAgICBub2RlLFxuICAgICAgICAgICAgbWVzc2FnZUlkOiAnbm9OZXh0SW1hZ2UnLFxuICAgICAgICAgIH0pXG4gICAgICAgIH1cbiAgICAgIH0sXG4gICAgfVxuICB9LFxufVxuXG5leHBvcnQgZGVmYXVsdCBydWxlXG4iXX0=
|
package/package.json
CHANGED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Pattern 028: Custom Image Component
|
|
2
|
+
|
|
3
|
+
**Canon Version:** 1.0
|
|
4
|
+
**Status:** Stable
|
|
5
|
+
**Category:** Components
|
|
6
|
+
**Enforcement:** ESLint (`gallop/no-next-image`)
|
|
7
|
+
|
|
8
|
+
## Decision
|
|
9
|
+
|
|
10
|
+
Use the project's custom `Image` component from `@/components/image`. Do not use `next/image` directly.
|
|
11
|
+
|
|
12
|
+
## Rationale
|
|
13
|
+
|
|
14
|
+
1. **Studio integration** — Automatically resolves image metadata and dimensions via `getStudioImage()`, handling responsive sizing without manual width/height
|
|
15
|
+
2. **Consistent API** — Unified interface with project-specific props: size variants, captions, rounded corners, media links, lightbox support
|
|
16
|
+
3. **Standard defaults** — Enforces project defaults for lazy loading, rounded corners, and aspect ratios
|
|
17
|
+
|
|
18
|
+
## Examples
|
|
19
|
+
|
|
20
|
+
### Good
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
import Image from '@/components/image'
|
|
24
|
+
|
|
25
|
+
<Image src="/images/hero.jpg" alt="Hero banner" size="large" />
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
import Image from '@/components/image'
|
|
30
|
+
|
|
31
|
+
<Image
|
|
32
|
+
src="/images/photo.jpg"
|
|
33
|
+
alt="Gallery item"
|
|
34
|
+
size="medium"
|
|
35
|
+
caption="Figure 1"
|
|
36
|
+
mediaLink
|
|
37
|
+
/>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Bad
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import Image from 'next/image'
|
|
44
|
+
|
|
45
|
+
<Image src="/images/hero.jpg" alt="Hero banner" width={1200} height={600} />
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
import Image from 'next/legacy/image'
|
|
50
|
+
|
|
51
|
+
<Image src="/images/hero.jpg" alt="Hero banner" layout="fill" />
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Enforcement
|
|
55
|
+
|
|
56
|
+
- **ESLint Rule:** `gallop/no-next-image`
|
|
57
|
+
- Flags `import` or `require()` of `next/image` or `next/legacy/image`
|
|
58
|
+
|
|
59
|
+
## References
|
|
60
|
+
|
|
61
|
+
- Custom Image component: `src/components/image.tsx`
|
|
62
|
+
- Studio helpers: `src/utils/studio-helpers.ts`
|
package/schema.json
CHANGED
|
@@ -300,6 +300,16 @@
|
|
|
300
300
|
"enforcement": "eslint",
|
|
301
301
|
"rule": "gallop/no-native-date",
|
|
302
302
|
"summary": "Use Luxon DateTime, not native JavaScript Date"
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
"id": "028",
|
|
306
|
+
"title": "Custom Image Component",
|
|
307
|
+
"file": "patterns/028-custom-image-component.md",
|
|
308
|
+
"category": "components",
|
|
309
|
+
"status": "stable",
|
|
310
|
+
"enforcement": "eslint",
|
|
311
|
+
"rule": "gallop/no-next-image",
|
|
312
|
+
"summary": "Use custom Image component, not next/image"
|
|
303
313
|
}
|
|
304
314
|
],
|
|
305
315
|
"guarantees": [
|