From 99d4737177fa76cc4e9886641cc7cfe39192c83a Mon Sep 17 00:00:00 2001 From: Achintya Rai Date: Tue, 10 Feb 2026 13:15:56 -0800 Subject: [PATCH 1/4] test: added test cases for prefer-ideal-image.ts --- .../__tests__/prefer-ideal-image.test.ts | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 packages/eslint-plugin/src/rules/__tests__/prefer-ideal-image.test.ts diff --git a/packages/eslint-plugin/src/rules/__tests__/prefer-ideal-image.test.ts b/packages/eslint-plugin/src/rules/__tests__/prefer-ideal-image.test.ts new file mode 100644 index 000000000000..bf7a0b77529c --- /dev/null +++ b/packages/eslint-plugin/src/rules/__tests__/prefer-ideal-image.test.ts @@ -0,0 +1,62 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import rule from '../prefer-ideal-image'; +import {RuleTester} from './testUtils'; + +const errorMsg = [{messageId: 'idealImageError'}] as const; +const errorWarning = [{messageId: 'idealImageWarning'}] as const; + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, +}); + +ruleTester.run('prefer-ideal-image', rule, { + valid: [ + { + code: "", + }, + { + code: "", + }, + { + code: "", + }, + { + code: '', + }, + { + code: '', + }, + { + code: '', + }, + ], + invalid: [ + { + code: "", + errors: errorMsg, + }, + { + code: "", + errors: errorWarning, + }, + { + code: "", + errors: errorWarning, + }, + { + code: "", + errors: errorWarning, + }, + ], +}); From dc5ba8e9991f8315942ece687fa92f8a36ac6213 Mon Sep 17 00:00:00 2001 From: Achintya Rai Date: Tue, 10 Feb 2026 20:59:20 -0800 Subject: [PATCH 2/4] test: added test cases for unsupported types (.svg) --- .../src/rules/__tests__/prefer-ideal-image.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/eslint-plugin/src/rules/__tests__/prefer-ideal-image.test.ts b/packages/eslint-plugin/src/rules/__tests__/prefer-ideal-image.test.ts index bf7a0b77529c..6949f92cb86d 100644 --- a/packages/eslint-plugin/src/rules/__tests__/prefer-ideal-image.test.ts +++ b/packages/eslint-plugin/src/rules/__tests__/prefer-ideal-image.test.ts @@ -40,6 +40,9 @@ ruleTester.run('prefer-ideal-image', rule, { { code: '', }, + { + code: "", + }, ], invalid: [ { From f06cabf1f663f72f3f58dd6c0b0dcc47ac45a1f5 Mon Sep 17 00:00:00 2001 From: Achintya Rai Date: Tue, 10 Feb 2026 20:59:41 -0800 Subject: [PATCH 3/4] feat: added prefer-ideal-image plugin --- .../src/rules/prefer-ideal-image.ts | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 packages/eslint-plugin/src/rules/prefer-ideal-image.ts diff --git a/packages/eslint-plugin/src/rules/prefer-ideal-image.ts b/packages/eslint-plugin/src/rules/prefer-ideal-image.ts new file mode 100644 index 000000000000..f727261683a1 --- /dev/null +++ b/packages/eslint-plugin/src/rules/prefer-ideal-image.ts @@ -0,0 +1,95 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {createRule} from '../util'; +import type {TSESTree} from '@typescript-eslint/types/dist/ts-estree'; + +type Options = []; +type MessageIds = 'idealImageError' | 'idealImageWarning'; + +const docsUrl = + 'https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-ideal-image'; + +export default createRule({ + name: 'prefer-ideal-image', + meta: { + type: 'problem', + docs: { + description: + 'enforce using Docusaurus IdealImage plugin component instead of tags', + recommended: false, + }, + schema: [], + messages: { + idealImageError: `Do not use an \`\` element to embed images. Use the \`\` component from \`@theme/IdealImage\` instead. See ${docsUrl}`, + idealImageWarning: `If this is a local file do not use an \`\` element to embed images. Use the \`\` component from \`@theme/IdealImage\` instead. See ${docsUrl}`, + }, + }, + defaultOptions: [], + + create(context) { + return { + JSXOpeningElement(node) { + const elementName = (node.name as TSESTree.JSXIdentifier).name; + + if (elementName !== 'img') { + return; + } + + const srcAttr = node.attributes.find( + (attr): attr is TSESTree.JSXAttribute => + attr.type === 'JSXAttribute' && attr.name.name === 'src', + ); + + const value = srcAttr?.value; + + if (!value) { + return; + } + + if (value.type === 'Literal' && typeof value.value === 'string') { + const val = value.value; + + if (val.toLowerCase().endsWith('.svg')) { + return; + } + + if (val.startsWith('http') || val.startsWith('//')) { + return; + } + if ( + val.startsWith('./') || + val.startsWith('../') || + val.startsWith('/') + ) { + context.report({node: value, messageId: 'idealImageWarning'}); + } + return; + } + + if (value.type === 'JSXExpressionContainer') { + const expr = value.expression; + + if (expr.type === 'TemplateLiteral') { + const firstPart = expr.quasis[0]?.value.raw; + if (firstPart?.startsWith('http') || firstPart?.startsWith('//')) { + return; + } + } + + if ( + expr.type === 'CallExpression' && + expr.callee.type === 'Identifier' && + expr.callee.name === 'require' + ) { + context.report({node, messageId: 'idealImageError'}); + } + } + }, + }; + }, +}); From 27a484a8c05cbfb62e852257349ba28ba2169213 Mon Sep 17 00:00:00 2001 From: Achintya Rai Date: Tue, 10 Feb 2026 20:59:57 -0800 Subject: [PATCH 4/4] doc: updated documentation for prefer-ideal-image --- .../docs/api/misc/eslint-plugin/README.mdx | 1 + .../misc/eslint-plugin/prefer-ideal-image.mdx | 50 +++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 website/docs/api/misc/eslint-plugin/prefer-ideal-image.mdx diff --git a/website/docs/api/misc/eslint-plugin/README.mdx b/website/docs/api/misc/eslint-plugin/README.mdx index a0d41ee4d458..bdf355ec555c 100644 --- a/website/docs/api/misc/eslint-plugin/README.mdx +++ b/website/docs/api/misc/eslint-plugin/README.mdx @@ -54,6 +54,7 @@ For more fine-grained control, you can also enable the plugin manually and confi | [`@docusaurus/string-literal-i18n-messages`](./string-literal-i18n-messages.mdx) | Enforce translate APIs to be called on plain text labels | ✅ | | [`@docusaurus/no-html-links`](./no-html-links.mdx) | Ensures @docusaurus/Link is used instead of `` tags | ✅ | | [`@docusaurus/prefer-docusaurus-heading`](./prefer-docusaurus-heading.mdx) | Ensures @theme/Heading is used instead of `` tags for headings | ✅ | +| [`@docusaurus/prefer-ideal-image`](./prefer-ideal-image.mdx) | Ensures @theme/IdealImage is used instead of `` tags for embedding images | | ✅ = recommended diff --git a/website/docs/api/misc/eslint-plugin/prefer-ideal-image.mdx b/website/docs/api/misc/eslint-plugin/prefer-ideal-image.mdx new file mode 100644 index 000000000000..f2d306879b28 --- /dev/null +++ b/website/docs/api/misc/eslint-plugin/prefer-ideal-image.mdx @@ -0,0 +1,50 @@ +--- +slug: /api/misc/@docusaurus/eslint-plugin/prefer-ideal-image +--- + +# prefer-ideal-image + +Ensure that the `` component provided by the [`@docusaurus/plugin-ideal-image`](../../plugins/plugin-ideal-image.mdx) plugin is used instead of standard `` tags for local assets. + +The `@theme/IdealImage` component automatically generates responsive images, provides lazy-loading, and adds low-quality image placeholders (LQIP) to improve LCP and user experience. + +## Rule Details {#details} + +This rule flags standard HTML `` tags that point to local files, suggesting the use of `IdealImage` for better optimization. + +Examples of **incorrect** code for this rule: + +```jsx +// Error: Definitely a local image via require() + + +// Warning: Likely a local image via relative path + + +// Warning: Root-relative path usually points to the static folder + +``` + +Examples of **correct** code for this rule: + +```jsx +// Optimized using the IdealImage component +import IdealImage from '@theme/IdealImage'; + + + +// External images are ignored + + +// SVGs are ignored as IdealImage does not support them + +``` + +## When to not use it + +If you have not installed or do not plan to use @docusaurus/plugin-ideal-image, you should keep this rule disabled. + +## Further Reading {#further-reading} + +- https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-ideal-image +- https://web.dev/articles/browser-level-image-lazy-loading