Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug]: how do prettier plugins work? #176

Closed
shinypb opened this issue Mar 22, 2024 · 13 comments · Fixed by #417
Closed

[Bug]: how do prettier plugins work? #176

shinypb opened this issue Mar 22, 2024 · 13 comments · Fixed by #417
Assignees
Labels
bug Something isn't working documentation Improvements or additions to documentation question Further information is requested

Comments

@shinypb
Copy link

shinypb commented Mar 22, 2024

(It is entirely possible that I'm just Holding It Wrong; guidance welcome and appreciated. ❤️)

What happened?

I've got prettier set up as described in the docs, with a handful of plugins specified in BUILD as well as the .prettierrc file:

prettier.prettier_binary(
    name = "prettier",
    data = [
        "//:prettierrc",
        "//:node_modules/@trivago/prettier-plugin-sort-imports",
        "//:node_modules/prettier-plugin-tailwindcss",
    ],
    # Allow the binary to be run outside bazel
    env = {"BAZEL_BINDIR": "."},
)

multi_formatter_binary(
    name = "format",
    javascript = ":prettier",
)

Unfortunately, no force on earth can compel these plugins to work:

Formatting JavaScript with Prettier...
[error] Cannot find package '@trivago/prettier-plugin-sort-imports' imported from /cloud/noop.js
FAILED: A formatter tool exited with code 123

I've been trying permutations for hours. Things I've tried that have not worked:

At this point, I am fresh out of ideas and would love a nudge in the right direction. 🙏

Version

Development (host) and target OS/architectures: Ubuntu running in Docker on a M1 Mac

Output of bazel --version: bazel 6.4.0

Version of the Aspect rules, or other relevant rules from your
WORKSPACE or MODULE.bazel file: 0.9.1

Language(s) and/or frameworks involved: JS

How to reproduce

No response

Any other information?

No response

@shinypb shinypb added the bug Something isn't working label Mar 22, 2024
@shinypb shinypb changed the title [Bug]: prettier plugins don't work [Bug]: how do prettier plugins work? Mar 25, 2024
@alexeagle
Copy link
Member

The most sane thing is to ensure the configuration file is JavaScript, and has a require() statement for the plugin, then that is the source of a js_library that declares the corresponding dep.

for example, example/.eslintrc.cjs requires a plugin (though I was forced to use a string rather than a require()) and it has

js_library(
    name = "eslintrc",
    srcs = [".eslintrc.cjs"],
    deps = [
        ":node_modules/@typescript-eslint/eslint-plugin",
        ":node_modules/@typescript-eslint/parser",
    ],
)

you might want to look back in history too, our shell language was a prettier plugin at one point:

"//FIXME(alexeagle)": "This section shouldn't be needed since these are plugins",

@alexeagle alexeagle added the question Further information is requested label Mar 27, 2024
@shinypb
Copy link
Author

shinypb commented Apr 10, 2024

Thanks for the response. I went to try your suggestion (I was on PTO for a bit) and discovered that...just adding the plugins to my bog-standard .prettierrc suddenly started working. I have no idea how I was doing this wrong earlier (and I was the second person to take a swing at it), but I'm not going to look a gift-horse in the mouth.

Thank you for introducing this particular js_library pattern to my brain; I appreciate you.

@shinypb shinypb closed this as completed Apr 10, 2024
@alexeagle alexeagle reopened this Apr 11, 2024
@alexeagle
Copy link
Member

Re-opened to track the missing documentation for this

@shinypb
Copy link
Author

shinypb commented Apr 11, 2024

I was about to re-open to ask for further assistance. 😅 I still can't get this to work properly. Here's a gist file showing the relevant files:
https://gist.github.com/shinypb/8bb8b1490d4025b62bb52128b1869a69

The output I get is:

$  bin/bazel run :format                        
INFO: Invocation ID: 1f6406a2-d750-4e8d-9602-a63be036c3cb
INFO: Analyzed target //:format (1 packages loaded, 93 targets configured).
INFO: Found 1 target...
Target //:format up-to-date:
  bazel-bin/format.sh
INFO: Elapsed time: 1.715s, Critical Path: 0.01s
INFO: 7 processes: 7 internal.
INFO: Build completed successfully, 7 total actions
INFO: Running command line: bazel-bin/format.sh
Formatting JavaScript with Prettier...
[error] Cannot find package '@trivago/prettier-plugin-sort-imports' imported from /cloud/noop.js
FAILED: A formatter tool exited with code 123
Try running 'bazel run @@//:format' to fix this.

Now, if I manually run pnpm install (which creates a top-level node_modules directory inside of my repo), it works, but obviously that's not the right way to do things (and doesn't help me at all on my CI boxes—and frankly, I don't understand why it helps locally, either). I'm sure I am just missing some simple piece of the puzzle, but I haven't been able to figure it out. 🤔 See any obvious errors in my setup?

@alexeagle
Copy link
Member

See any obvious errors in my setup?

No, but that doesn't mean much, as these plugin issues are never obvious.

I tried creating a working repro from your files, but there's too much broken and/or missing. It would be easier to diagnose as a red PR on the example folder in rules_lint.

@alexeagle
Copy link
Member

Alternatively if you can make a fully-functional repro I can clone to see the error...

@alexeagle
Copy link
Member

One thing to check is that the pnpm lockfile is up-to-date with any changes you made to packageExtensions in package.json - that's a manual step (Bazel reads from the lockfile directly)

@shinypb
Copy link
Author

shinypb commented Apr 12, 2024

I put together a repro repo (say that three times fast…)
https://github.com/shinypb/repro_prettier

Things that might surprise you and may ultimately end up being part of the problem, who knows:

  • we are still on Bazel 6.x

Things that surprise me:

  • it works IF if you do pnpm install before running bazel run :format. This continues to really break my mental model of Bazel and I'd love to know why, but the TL;DR is to make sure you rm -rf node_modules before running bazel run :format to actually reproduce the problem.

Thank you for your help!

@alexeagle
Copy link
Member

Bazel's default sandbox is not as strong as you think. It can only be as hermetic as the tools it runs, and a lot of Nodejs tools follow symlinks and can walk outside the sandbox to find node_modules in the source folder, despite our attempts to prevent it with the node-patches in rules_js (note for example they don't work for ESM imports)

@alexeagle alexeagle self-assigned this Apr 12, 2024
@alexeagle alexeagle added the documentation Improvements or additions to documentation label Apr 12, 2024
@linzhiq
Copy link

linzhiq commented Apr 16, 2024

Running into the same problem. Cloned the repo above and updated prettier.config.cjs to

const prettierPluginTailwind = require("pretter-plugin-tailwindcss");

module.exports = {
	plugins: [
		prettierPluginTailwind
	],
};

But still seeing the same error.

Is conclusion that prettier plugin imports cannot be linked to dependencies inside the sandbox?

@sallustfire
Copy link
Contributor

@alexeagle I've bumped into this as well with random failures in different environments. After looking at this more closely, I don't believe the documented pattern of using a peer dep solves the problem. The issue here appears to a consequence of running format explicitly in the source tree rather than the output tree. So, all sorts of things can accidentally be resolved at runtime.

I believe that the example configuration for prettier

load("@npm//:prettier/package_json.bzl", prettier = "bin")

prettier.prettier_binary(
    name = "prettier",
    # Allow the binary to be run outside bazel
    env = {"BAZEL_BINDIR": "."},
)

is going to source whatever prettierrc file it finds in the source tree. Then any require/imports in that config file will be resolved relative to the config file and not the runfiles of the binary. So, if /node_modules/ exists everything will be fine.

All the apparent hermeticity problems go away by declaring the dependency on the config and transitively the plugin.

prettier.prettier_binary(
    name = "prettier",
    data = ["//:prettier_config"],
    # Allow the binary to be run outside bazel
    env = {"BAZEL_BINDIR": "."},
    # default log level is "log" which spams on success
    # https://prettier.io/docs/en/cli.html#--log-level
    fixed_args = [
        "--config=\"$$JS_BINARY__RUNFILES\"/$(rlocationpath //:prettier_config)",
    ],
)

and

js_library(
    name = "prettier_config",
    srcs = ["prettier.config.mjs"],
    deps = ["//:node_modules/prettier-plugin"],
)

Maybe there is a slightly cleaner way to do this? I'm happy to try and push a breaking configuration in example/.

@alexeagle
Copy link
Member

Thanks @sallustfire! I finally found time to reproduce the issue, and also try your approach, which I've put on a branch here: https://github.com/aspect-build/rules_lint/compare/prettier-plugins

I added some logging in the prettier.config.cjs like console.error(require.resolve("prettier-plugin-sql")) and yes, in the prior state you see it resolves in the users node_modules in the source folder, making it non-hermetic.

@sallustfire
Copy link
Contributor

@alexeagle Thanks for confirming. The updated documentation looks helpful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working documentation Improvements or additions to documentation question Further information is requested
Projects
None yet
4 participants