Skip to content

Conversation

andersk
Copy link
Contributor

@andersk andersk commented Mar 29, 2023

Description

Users don’t expect Prettier to follow symlinks:

  • If a source tree contains a symlink an external location outside the source tree, Prettier should stay within the tree it was asked to format, and shouldn’t check or modify those external files.

  • If a source tree contains a symlink to an internal location within itself, Prettier will already find that location via its normal path; symlinks should not cause Prettier to format those files multiple times.

  • If a source tree contains a symlink to a location that’s ignored via .prettierignore, Prettier should ignore that location without the user needing to separately list the symlinks as ignored too.

Following symlinks without special care can lead to problems:

  • Cycles of symlinks would cause Prettier to recurse infinitely or for a very long time, depending on the filesystem.

  • A symlink to (say) your home directory could cause Prettier to rewrite every file in your home directory.

Fixes #12080.

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.

Try the playground for this PR

@andersk andersk force-pushed the symlinks branch 2 times, most recently from 6cb4b40 to f4bfebf Compare March 29, 2023 23:56
Users don’t expect Prettier to follow symlinks:

• If a source tree contains a symlink an external location outside the
  source tree, Prettier should stay within the tree it was asked to
  format, and shouldn’t check or modify those external files.

• If a source tree contains a symlink to an internal location within
  itself, Prettier will already find that location via its normal
  path; symlinks should not cause Prettier to format those files
  multiple times.

• If a source tree contains a symlink to a location that’s ignored via
  .prettierignore, Prettier should ignore that location without the user
  needing to separately list the symlinks as ignored too.

Following symlinks without special care can lead to problems:

• Cycles of symlinks would cause Prettier to recurse infinitely or for
  a very long time, depending on the filesystem.

• A symlink to (say) your home directory could cause Prettier to
  rewrite every file in your home directory.

Fixes prettier#12080.

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
@fisker
Copy link
Member

fisker commented Mar 30, 2023

@andersk Thanks for working on this, we have more than glob patterns, so please fix the new tests I added.

@fisker
Copy link
Member

fisker commented Mar 30, 2023

Maybe just skip if stat.isSymbolicLink() in this if

@andersk
Copy link
Contributor Author

andersk commented Mar 30, 2023

If Prettier throws an error when the user provides an argument that matches only non-supported files, it seems consistent that Prettier should also throw an error when the user provides an argument that matches only symlinks. I don’t think it should silently skip them as a special case.

@fisker
Copy link
Member

fisker commented Mar 30, 2023

it seems consistent that Prettier should also throw an error when the user provides an argument that matches only symlinks

We already did that?

error: `No matching files. Patterns: ${context.filePatterns.join(" ")}`,


Maybe we should discuss what's expected first.

  1. Patterns only
prettier a/* b/*

Skip symlink, if non-match, throws

  1. argument is symlink
prettier symlink/to/file
prettier symlink/to/directory

throws since no "file" matched.

  1. pattern + symlink
prettier a/* symlink/to/file symlink/to/directory

Skip symlink, if non-match, throws

Sounds good?

So, if stat.isSymbolicLink() === true, we just skip it should already work as expected.

@andersk
Copy link
Contributor Author

andersk commented Mar 30, 2023

Okay, updated with an specific error for that case (Explicitly specified symbolic link was ignored rather than the generic No matching files).

@andersk andersk force-pushed the symlinks branch 2 times, most recently from e7ad984 to ffbb10b Compare March 30, 2023 03:20
@andersk andersk force-pushed the symlinks branch 2 times, most recently from f397aae to 79e2801 Compare March 30, 2023 03:32
Signed-off-by: Anders Kaseorg <andersk@mit.edu>
andersk and others added 2 commits March 29, 2023 21:13
Co-authored-by: fisker <lionkay@gmail.com>
Signed-off-by: Anders Kaseorg <andersk@mit.edu>
Signed-off-by: Anders Kaseorg <andersk@mit.edu>
Copy link
Member

@fisker fisker left a comment

Choose a reason for hiding this comment

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

Good job.

@fisker fisker added this to the 3.0 milestone Apr 20, 2023
@fisker fisker requested a review from sosukesuzuki April 20, 2023 08:22
@fisker
Copy link
Member

fisker commented Apr 22, 2023

@sosukesuzuki @kachkaev Can you help review?

@fisker fisker mentioned this pull request Apr 22, 2023
4 tasks
Copy link
Member

@sosukesuzuki sosukesuzuki left a comment

Choose a reason for hiding this comment

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

LGTM!

@fisker fisker merged commit dcc081d into prettier:main May 4, 2023
@andersk andersk deleted the symlinks branch May 18, 2023 00:24
@sanmai-NL
Copy link
Contributor

sanmai-NL commented Aug 15, 2023

@andersk You open with a fine problem statement, but ultimately the solution design is very crude: report an error and exit with a fault exit status whenever a symbolic link happens to be specified on the command line. In CI, it's not always possible to filter the list of files beforehand based on some special characteristics. Instead, Prettier should handle the scenarios you describe itself.

@sanmai-NL
Copy link
Contributor

sanmai-NL commented Aug 15, 2023

If Prettier throws an error when the user provides an argument that matches only non-supported files, it seems consistent that Prettier should also throw an error when the user provides an argument that matches only symlinks. I don’t think it should silently skip them as a special case.

@andersk

It does not follow that Prettier should skip uncontrollably. Right now, Prettier cannot be controlled in the way it handles symlinks. It now simply fails and fails CI pipelines.

@andersk
Copy link
Contributor Author

andersk commented Aug 15, 2023

@sanmai-NL Neither prettier . nor prettier "*.js" will error in the presence of skipped symlinks (unless there are only symlinks). Is it possible you accidentally wrote prettier *.js, where the unquoted wildcard is expanded by the shell before Prettier even sees it?

@sanmai-NL
Copy link
Contributor

sanmai-NL commented Aug 16, 2023

I didn't specify globs or relative paths (or rather, our tooling), but literal paths.

I note that Prettier does have CLI flags to ignore unmatched paths, etc., but nothing to ignore symlinks.

Whenever something changes to a matched explicit path that is a symlink (e.g., file type changed to symlink), some CI tooling will specify it to Prettier and the fault will trigger.

@sanmai-NL
Copy link
Contributor

@andersk What can you do to fix this situation? We have pipelines failing on this with no recourse ...

Otherwise, I'll have to submit PR that restores the original behavior.

@andersk
Copy link
Contributor Author

andersk commented Oct 9, 2023

@sanmai-NL I can’t do anything—I’m not a Prettier maintainer.

But if I understand the linked issue correctly, it looks like your use case will be addressed by evilmartians/lefthook#538, so you might try helping there.

If you don’t want to wait for that, you can use the lefthook exclude option to avoid passing unsupported files.

@sanmai-NL
Copy link
Contributor

@andersk patching other tools to work-around a breakage you introduced (unwittingly) is at best a stop-gap measure. You could've taken on to fix your work in a new PR. At least one compromise solution has been proposed in this thread.

I'll create a PR to fix your PR. Or perhaps revert it, if that takes undue time.

@andersk
Copy link
Contributor Author

andersk commented Oct 9, 2023

@sanmai-NL The change was not made unwittingly. It is consistent with the way Prettier handles other unsupported files, and was discussed with the maintainer ahove.

I’m sorry that your CI had been unintentionally relying on the previous behavior, and I understand you’re unhappy that it fails after the Prettier 3 upgrade, but such things happen—major releases sometimes intentionally make breaking changes, and the purpose of CI is to fail when things break. Although there is always some value in avoiding breaking changes, here I still believe there was more value gained by eliminating unexpected behavior around symlinks, and throwing an error is better than either silently processing unexpected files or silently skipping them.

@fisker
Copy link
Member

fisker commented Oct 10, 2023

@sanmai-NL I'm sorry that this change breaks your tool, but please open an new issue to continue discussion. This is an merged PR.

@prettier prettier locked as off-topic and limited conversation to collaborators Oct 10, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

endless recursion when dir has symlink which points to its parent dir
4 participants