Skip to content

Conversation

@nvrakesh06
Copy link

@nvrakesh06 nvrakesh06 commented Aug 23, 2025

Description

This PR implements plugin cache invalidation for Prettier to fix issue #17260. The implementation uses a simplified ESLint-style approach where plugins export a meta object with name and version information for proper cache invalidation.

Implementation

ESLint-Style Plugin Metadata

  • Plugins export a meta object with name, version, and optionally namespace properties
  • Follows the same pattern as ESLint plugins for consistency and familiarity
  • Enables automatic cache invalidation when plugin versions change
  • Fully backward compatible - plugins without metadata behave identically to before

Plugin Metadata Format

export const meta = {
  name: "prettier-plugin-example",
  version: "1.2.3",
  namespace: "example" // optional
};

Simplified Cache Implementation

  • Cache functions extract plugins from options.plugins internally
  • Preserves original plugin order for deterministic behavior
  • Clean API without redundant parameter passing

Files Modified

  • docs/cli.md - Updated CLI documentation with simplified cache approach
  • docs/plugins.md - Updated plugin documentation with ESLint-style metadata approach
  • src/main/plugins/plugin-signature.js - Simplified plugin signature logic
  • src/cli/format.js - Clean cache function calls
  • src/cli/format-results-cache.js - Extract plugins from options internally
  • tests/integration/__tests__/cache-plugin-invalidation.js - Integration tests
  • tests/unit/format-results-cache.test.js - Cache unit tests
  • tests/unit/plugin-signature.test.js - Plugin signature unit tests

Benefits

  • Automatic Cache Invalidation: Plugin version changes properly invalidate cache
  • Backward Compatibility: Existing plugins continue to work without modifications
  • ESLint-style Approach: Familiar metadata pattern for plugin developers
  • Simplified Implementation: Clean API without complex fallback mechanisms
  • Order Preservation: Plugin order is preserved for deterministic behavior
  • Comprehensive Testing: Full test coverage for all scenarios

Checklist

  • I've added tests to confirm my change works.
  • (If changing the API or CLI) I've documented the changes I've made (in the docs/ directory).
  • (If the change is user-facing) I've added my changes to changelog_unreleased/*/XXXX.md file following changelog_unreleased/TEMPLATE.md.
  • I've read the contributing guidelines.

Fixes: Cache invalidation on plugin upgrade with optional metadata #17260

Implements opt-in plugin metadata support for cache invalidation.
Plugins can export prettierPluginMeta with name/version to enable
proper cache invalidation when plugin versions change.

Fully backward compatible - plugins without metadata behave identically.

Fixes prettier#17260
Extends the plugin cache invalidation to include a package.json fallback
when plugins don't export explicit prettierPluginMeta.

This hybrid approach provides:
1. Explicit metadata (fastest, most reliable)
2. Package.json fallback (automatic for npm plugins)
3. Legacy behavior (unchanged for compatibility)

Most existing npm-published plugins will now get automatic cache
invalidation without any changes required.

- Adds package.json reading with directory traversal
- Includes caching to avoid repeated I/O
- Comprehensive test coverage for all fallback scenarios
- Updated documentation for hybrid approach
@netlify
Copy link

netlify bot commented Aug 23, 2025

Deploy Preview for prettier ready!

Built without sensitive environment variables

Name Link
🔨 Latest commit 75e2fb1
🔍 Latest deploy log https://app.netlify.com/projects/prettier/deploys/68a9d0fb2c9c7300081f8f3f
😎 Deploy Preview https://deploy-preview-17808--prettier.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

- Add dependency injection to plugin-signature module for testability
- Replace Jest mocking with manual mocks using dependency injection
- Export packageJsonCache for test cleanup
- Clear cache between tests to prevent test interference
- All 15 unit tests now pass successfully
@pkg-pr-new
Copy link

pkg-pr-new bot commented Aug 23, 2025

Open in StackBlitz

yarn add https://pkg.pr.new/prettier/prettier/@prettier/plugin-hermes@17808.tgz
yarn add https://pkg.pr.new/prettier/prettier/@prettier/plugin-oxc@17808.tgz
yarn add https://pkg.pr.new/prettier/prettier@17808.tgz

commit: 4aa3052

@nvrakesh06 nvrakesh06 force-pushed the cache-plugin-invalidation branch from 4aa3052 to 1ad22ef Compare August 23, 2025 14:23
- Change plugin metadata from prettierPluginMeta to meta object (ESLint-style)
- Remove package.json fallback system - plugins must implement .meta
- Preserve original plugin order instead of sorting for deterministic behavior
- Extract plugins from options internally instead of passing separately
- Update all tests and documentation to reflect simplified approach
- Remove complex fallback logic and caching mechanisms

Addresses PR comments:
1. Use ESLint-style explanation for plugin metadata
2. Plugins should implement .meta instead of relying on package.json fallback
3. Order matters - don't sort plugins
4. Shouldn't plugins already exist in options? (fisker)
@nvrakesh06 nvrakesh06 force-pushed the cache-plugin-invalidation branch from 1ad22ef to 75e2fb1 Compare August 23, 2025 14:32
@nvrakesh06 nvrakesh06 changed the title cache: include optional plugin identity metadata in cache key (opt-in, backward compatible) Implement plugin cache invalidation with ESLint-style metadata Aug 23, 2025
@nvrakesh06 nvrakesh06 requested a review from fisker August 24, 2025 11:44
@nvrakesh06
Copy link
Author

@fisker Updated based on your comments. Please review when you get a chance.

@nvrakesh06
Copy link
Author

@fisker Just following up on this PR for review. Thanks


return {
...supportInfoToContextOptions(supportInfo),
loadedPlugins,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not be done here, we can add a function to do the hash calculation and wrap with withPlugins(), then the function will receive loaded plugins.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, you already have createPluginSignature, export the wrapped version in index.js should work.

// Create compact signature: "name@version,name@version"
return pluginMetadata
.map(({ name, version }) => `${name}@${version}`)
.join(",");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we simply replace the whole function with

function createPluginSignature(plugins) {
  return plugins.map(plugin => `${plugin.meta?.name}@${plugin.meta?.version}`);
}

?

export const meta = {
name: "prettier-plugin-example",
version: "1.2.3",
namespace: "example"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The namespace should be removed, since we are not going to use. Eslint add this so user can use namespace/foo-rule to reference the rule.

"--plugin",
"./test-plugin.js",
"test.js",
]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can print differnt result to simplify the tests.

Something like this

export default {
	meta: {
	  name: "test-plugin",
	  version: "1.0.0"
	},
	languages: [
	  {
	    name: "test-lang",
	    parsers: ["test-parser"],
	    extensions: [".js"]
	  }
	],
	parsers: {
	  "test-parser": {
	    parse: (text) => ({ /* whatever */ }),
	    astFormat: "test-ast"
	  }
	},
	printers: {
	  "test-ast": {
	    print: (path) => '1.0.0'
	  }
	}
}

@fisker fisker mentioned this pull request Oct 15, 2025
4 tasks
@fisker
Copy link
Member

fisker commented Oct 15, 2025

Hello, @nvrakesh06

I want make a change here, instead of plugin.meta.{name,version}, I'd like to move them to the root.
Doing this because, we already have a name field in the plugin when loading plugins (#18082 is improving it), we can take advantage of that, also simplify the plugin loading process.

@fisker fisker added this to the 3.7 milestone Oct 16, 2025
@fisker fisker modified the milestones: 3.7, 3.8 Nov 26, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants