Implement prettier.getFileInfo() method and --file-info CLI option#4341
Implement prettier.getFileInfo() method and --file-info CLI option#4341ikatyang merged 4 commits intoprettier:masterfrom kachkaev:file-info
Conversation
|
🤔 should we also have |
I don't have an opinion on this one way or the other.
Well, this logic needs to exist somewhere, right? It's better to do it once in the main repo than reproduced in all of the editor implementations. Or maybe I'm misunderstanding you? I've looked over each of the scenarios you've documented, everything looks good to me! |
|
Re
After a bit of thinking, I guess that moving this logic to the editor implementation is a better thing. The main reason is that there are two ways of formatting a file:
In the first approach, it's OK to set What is also a bit confusing is that this flag may look over-promising. Someone may reasonably think that I understand that implementing the logic of New proposal: {
"exists": true,
"ignored": false,
"parser": "markdown", // can be null after 2.0
"parserIsFallback": false // to be removed in 2.0
}Or even (to simplify {
"exists": true,
"ignored": false,
"inferredParser": "markdown", // can be null even in v1.x
"parser": "markdown" // or babylon if inferredParser is null
// parser can be removed in 2.0 as it will be the same as "inferredParser"
}There is an internal function called Or actually we can even remove {
"exists": true,
"ignored": false,
"inferredParser": "markdown", // or null, even in v1.x
}WDYT? Will this make your |
|
@robwise I can provide a prebuilt copy of prettier that you can test in prettier/prettier-atom#404. The reason why a prebuilt version is better is because it does not have Would you like me to change the result of {
"exists": true,
"ignored": false,
"inferredParser": "markdown", // or null, even in v1.x
} |
|
@kachkaev What happened to As far as |
|
Oh just saw the previous comment.
I'm not sure I understand this one, why would we return |
@robwise on first ‘format on save’. That is when the file name has already been given, there is some content to prettify, but the file has never been put on disk yet. I'm still sceptical about adding Apart from {
"exists": true,
"ignored": false,
"inferredParser": "markdown", // or null, even in v1.x
}The lack of if (fileInfo.exists && !fileInfo.ignored && fileInfo.inferredParser) {
// format
}or even like this to allow format on save for a file that has not been saved on disk yet: if (!fileInfo.ignored && fileInfo.inferredParser) {
// format
}IMO that's the way to go – prettier should not decide too much for the editor plugin. |
I'm still confused about this point: if the file does not even exist on disk, why would we have Prettier claim it's formattable at that point? How would it know anything about the file at all—at this point, it doesn't exist except as a string of text inside of my editor's text buffer, and Prettier knows nothing about my editor's text buffer?
Well, agree to disagree, but I can definitely get the job done with what you've shown in your examples (where you demonstrate combining some of the flags), so 👍from me.
Yeah, the main differences being no |
|
@robwise done! I also rebased on top of the latest master and updated the first comment in this PR. If you'd like to try out Waiting to hear your feedback and will proceed to writing tests after that. |
In atom you would not be able to infer a parser while you don't know the file extension, i.e. until you save for the first time – that's true. But what about other editors? Imagine a hypothetical editor that always has a file path input field on top of a text area and you fill in both before you save for the first time. In this scenario, What I really like about having only If you're still worried about the size of an const { isFormattable } = require('prettier-file-info');
if (isFormattable(prettier.getFileInfo(filePath))) {
// format on save
} |
|
@suchipi could you please have a look at this PR? If you're happy with its design and implementation, I'll tick the first checkbox and proceed to writing tests. It'd be also great if we could go through #4192 before 1.13 because that PR is also important for text editors in a context of prettier plugins. |
|
@kachkaev I'm getting an error: // test.js
const prettier = require('prettier')
const path = require('path')
const filePath = path.join(__dirname, 'test.js')
const fileInfo = prettier.getFileInfo(filePath)
console.log('file info:', JSON.stringify(fileInfo, null, 2)) |
|
@robwise – sorry for not mentioning what arguments getFileInfo(
filePath: string,
opts: Options,
ignorePath: string,
doNotIgnoreNodeModules: boolean
): FileInfoThe reason why you were getting an exception was because you did not pass This worked for me: const filePath = path.join(__dirname, "test.js");
const ignorePath = path.join(__dirname, ".prettierignore");
const fileInfo = prettier.getFileInfo(filePath, {}, ignorePath);or just const fileInfo = prettier.getFileInfo("test.js", {}, '.prettierignore');(paths are resolved based on I'll make all arguments but the first one optional in the next few days, but you are free to test the method even before the changes. Apologies for not sharing this caveat with you – I was only testing the new functionality as the |
|
No worries. I was curious about the I was thinking it might be more something like this, that way you aren't forced to provide an type getFileInfo = (
filePath: string,
opts?: { configPath?: string, ignorePath?: string, doNotIgnoreNodeModules?: boolean }
) => FileInfo; |
|
Fair point, I’ll check that. I was not sure that mixing the options with two additional parameters is a good thing, but if the same is done in other methods, I’m happy to go for the trick. Apart from this, do things work for you in general? I’m keen to cover the new feature with tests and remove the PR’s WIP status. |
|
@kachkaev All seems to work so far, now we're able to get rid of that |
|
@kachkaev |
|
@duailibe sure, just give me a few days ;–) |
|
No hurry! |
|
@duailibe the changes have been rebased on top of the latest master. @robwise thanks to your suggestion and the new internal const fileInfo = getFileInfo({
filepath: 'path/to/file.js',
ignorePath: 'path/to/.prettierignore', // optional, '.prettierignore' by default, can be set to false for disabling
withNodeModules: true, // optional, assumed false by default
...otherPrettierOptions, // optional
});When It's unfortunate that If everyone is happy with the new design, I'm happy to proceed to tests to remove WIP status from this PR. |
|
@kachkaev sorry it took me so long to look at this, the API looks good to me. I'm not super familiar with the stuff in |
|
@suchipi great to hear that you're OK with the API (hope you looked at the version in my last commit as I forgot to update the first one and only did this now). Could you please cc someone who could look at |
|
@kachkaev if I don't pass an |
|
@robwise I guess it will be While running Prettier in the context of a text editor, it’s probably worth setting this to |
duailibe
left a comment
There was a problem hiding this comment.
Looks good to me.
Last suggestion: isFormattable that returns true if path is not ignored and a parser can be inferred.
| ignored, | ||
| inferredParser | ||
| }; | ||
| } |
There was a problem hiding this comment.
My opinions:
getFileInfo(filepath, {ignorePath, withNodeModules, plugins})- returns
{ignored, inferredParser}
There was a problem hiding this comment.
getFileInfo(filepath, {ignorePath, withNodeModules, plugins})
Looks a bit simpler to use and does not expose different naming styles for filepath and ignorePath – I like this! Will change the API again as you suggest. We can also avoid setting ignorePath when a method is called directly (not via CLI) – that may work a bit more predictably for Prettier API users. The only problem that remains is with plugins – they will not be found automatically unless cwd matches project path (which will be the case for editor plugins). We need #4192 to solve that.
Last suggestion: isFormattable that returns true if path is not ignored and a parser can be inferred
This will bloat the output without adding any new information and may also cause confusion; see #4341 (comment), #4341 (comment) and #4341 (comment)
There was a problem hiding this comment.
Sorry I wasn't clear. I'm suggesting an alternative API to getFileInfo not an additional return value.
What I mean is: most integrations will use this function and use something like:
if (fileinfo.ignored || !fileinfo.parser) return;
// proceed to formatSo maybe we could do instead:
if (!prettier.isFileSupported(filepath, someOptions)) return;
// proceed to formatWDYT?
There was a problem hiding this comment.
What you’re suggesting will work when using format on save. But when a person calls format manually, I can imagine a scenario when .prettierifnore is ignored. I’d let editors decide what to do in this case.
There was a problem hiding this comment.
Hmm in the second scenario we can just avoid passing .prettierignore as an option and this will do the job. Guess you’re right and I should open a competing PR with your suggestion (isFileSupported()). What would be an appropriate CLI option name in this case? 🤔
There was a problem hiding this comment.
I don't know if we need separate PRs, they'll probably be very similar, we just need to figure out the API but it's your call!
What would be an appropriate CLI option name in this case?
Now you got me 😂
|
@lipis in https://travis-ci.org/prettier/prettier/builds/376725574 – yes, while in https://travis-ci.org/prettier/prettier/builds/376719918 it went well (that's a commit with the same content). Do you see some button that would manually restart https://travis-ci.org/prettier/prettier/jobs/376725575? If not, I'll have to force-push again and again until all four jobs succeed. |
|
There was button indeed, to restart only that job. Let's see :) 🕥 |
|
All green! |
|
Squashed and rebased on top of the latest master that contains #4192 (had to squash because placing intermediate commits on top of master was creating conflicts). Old commits are still available in branch I'll sortly add a test case for |
|
Everything will be squashed anyways.. so no worries about the history! It's mostly for you |
| @@ -22,6 +23,13 @@ function withPlugins(fn) { | |||
| return fn.apply(null, args); | |||
| }; | |||
| } | |||
There was a problem hiding this comment.
nit: Empty line between functions :P
|
All done 🎉 I guess the PR is ready to be merged after someone checks out my last two commits. |
|
the second to last is OK :P |
| @@ -0,0 +1,40 @@ | |||
| "use strict"; | |||
|
|
|||
| const createIgnorer = require("../common/create-ignorer"); | |||
There was a problem hiding this comment.
const createIgnorer = require("./create-ignorer");| }, | ||
| "file-info": { | ||
| type: "path", | ||
| category: coreOptions.CATEGORY_CONFIG, |
There was a problem hiding this comment.
It should be same as support-info, i.e. using default category.
| } | ||
|
|
||
| // the method has been implemented as asynchronous to avoid possible breaking changes in future | ||
| function getFileInfo(filePath, opts) { |
There was a problem hiding this comment.
This async method looks just like
(...args) => Promise.resolve(_getFileInfo(...args))We need to use createIgnorer/createIgnorer.sync if we'd like to make it really async.
There was a problem hiding this comment.
Would not ...args fail in Node 4? I'm not sure it supports args spreading.
I agree that having createIgnorer./createIgnorer.sync would be useful, but does this have to be done now? The async interface for getFileInfo is mainly a placeholder for keeping things future-proof 🤔
I'll address other issues now. Well spotted! 👀
There was a problem hiding this comment.
It's just a quick demo, not meant to be used in node 4. 😂
does this have to be done now?
Yeah, it can be another PR, just a reminder.
There was a problem hiding this comment.
Changing the function to the following does not work:
function getFileInfo(filePath, opts) {
return Promise.resolve(_getFileInfo(filePath, opts));
}It throws rather than rejects, because _getFileInfo() is sync.
| } catch (e) { | ||
| reject(e); | ||
| } | ||
| }); |
There was a problem hiding this comment.
This could just be Promise.resolve().then(() => _getFileInfo(filePath, options)).
There was a problem hiding this comment.
I'm happy to include this to #4451, which has a couple of other minor enhancements. See commit log.
* Fix plugin resolution when path does not start with ./
* Minor consistency fix: use "if (resolvedPluginSearchDir) {...}" in load-plugins
* Use more appropriate quotes in plugin-resolution.js
* Try both local paths and node modules when resolving plugins manually
* Test bespoke plugin loading by node module name
* Simplify getFileInfo() as suggested by @j-f1
#4341 (comment)
|
@kachkaev Why was this made async? |
|
@robwise There's a sync and an async method. |
|
@duailibe ah excellent, that makes things much easier! |
Closes #4253
This PR adds the following prettier method:
and a corresponding CLI option:
The new API method is designed to help text editors decide whether a file needs to be formatted on save. A call to it is vital given the introduction of plugins and thus no fixed mapping between file extensions and parsers. A file with the given path does not have to exist for the method to be called.
Usage examples
prettier --file-info README.md{ "ignored": false, "inferredParser": "markdown" }markdownparser, according to the file extensionREADME.mdin.prettierignore→ignored: falseprettier --file-info README{ "ignored": false, "inferredParser": "markdown" }READMEis a special caseprettier --file-info src/something.js{ "ignored": false, "inferredParser": "babylon" }babylonparser for a js file – usual thingprettier --file-info src/something.elm{ "ignored": false, "inferredParser": null }.elmfiles by default, so can't infer a parseryarn add prettier-plugin-elmprettier --file-info src/something.elm{ "ignored": false, "inferredParser": "elm" }prettier-plugin-elmin this case) helps--file-infopick the right parserprettier --file-info src/something.js --ignore-path .ignoresrc{ "ignored": true, "inferredParser": "babylon" }.ignoresrccontainssrc, the result containsignored: trueprettier --file-info node_modules/module/something.js{ "ignored": true, "inferredParser": "babylon" }node_modulesis ignored by defaultprettier --file-info node_modules/module/something.js --with-node-modules{ "ignored": false, "inferredParser": "babylon" }--with-node-modulesmakes the file insidenode_modulesnotignoredoutdated info (changed after further discussion)
prettier --file-info README.md{ "exists": true, "ignored": false, "inferredParser": "markdown" }markdownparser, according to the file extensionREADME.mdin.prettierignore→ignored: falseprettier --file-info README{ "exists": false, "ignored": false, "inferredParser": "markdown" }READMEis a special caseprettier --file-info src/something.js{ "exists": true, "ignored": false, "inferredParser": "babylon" }babylonparser for a js file – usual thingprettier --file-info src/something.elm{ "exists": true, "ignored": false, "inferredParser": null }.elmfiles by default, so can't infer a parseryarn add prettier-plugin-elmprettier --file-info src/something.elm{ "exists": true, "ignored": false, "inferredParser": "elm" }prettier-plugin-elmin this case) helps--file-infopick the right parserprettier --file-info src/something.js --ignore-path .ignoresrc{ "exists": true, "ignored": true, "inferredParser": "babylon" }.ignoresrccontainssrc, the result containsignored: trueprettier --file-info node_modules/module/something.js{ "exists": false, "ignored": true, "inferredParser": "babylon" }node_modulesis ignored by defaultprettier --file-info node_modules/module/something.js --with-node-modules{ "exists": false, "ignored": false, "inferredParser": "babylon" }--with-node-modulesmakes the file insidenode_modulesnotignoredoutdated info-2 (changed after further discussion)
prettier --file-info README.md{ "exists": true, "ignored": false, "inferredParser": "markdown" }markdownparser, according to the file extensionREADME.mdin.prettierignore→ignored: falseprettier --file-info README{ "exists": false, "ignored": false, "inferredParser": "markdown" }READMEis a special caseprettier --file-info src/something.js{ "exists": true, "ignored": false, "inferredParser": "babylon" }babylonparser for a js file – usual thingprettier --file-info src/something.elm{ "exists": true, "ignored": false, "inferredParser": null }.elmfiles by default, so can't infer a parseryarn add prettier-plugin-elmprettier --file-info src/something.elm{ "exists": true, "ignored": false, "inferredParser": "elm" }prettier-plugin-elmin this case) helps--file-infopick the right parserprettier --file-info src/something.js --ignore-path .ignoresrc{ "exists": true, "ignored": true, "inferredParser": "babylon" }.ignoresrccontainssrc, the result containsignored: trueprettier --file-info node_modules/module/something.js{ "exists": false, "ignored": true, "inferredParser": "babylon" }node_modulesis ignored by defaultprettier --file-info node_modules/module/something.js --with-node-modules{ "exists": false, "ignored": false, "inferredParser": "babylon" }--with-node-modulesmakes the file insidenode_modulesnotignoredIn the simplest scenario, should be enough to just call
if (prettier.getFileInfo(filePath).formattable) {...}as @robwise suggests. Additional fields in the output may be useful for ‘forced format on save’, status display in UI and other cases. JSON output also makes this method expandable in future.Usage examples
prettier --file-info README.md{ "formattable": true, "ignored": false, "parser": "markdown", "parserIsFallback": false }markdownparser, according to the file extensionREADME.mdin.prettierignore→ignored: falseformattableprettier --file-info README{ "formattable": false, "ignored": false, "parser": "markdown", "parserIsFallback": false }READMEis a special caseprettier --file-info src/something.js{ "formattable": true, "ignored": false, "parser": "babylon", "parserIsFallback": false }babylonparser for a js file – usual thingprettier --file-info src/something.elm{ "formattable": false, "ignored": false, "parser": "babylon", "parserIsFallback": true }.elmfiles by default, so picksbabylonparser as a fallback oneformattable(editors should not attempt to format it on save as this will produce an error anyway)yarn add prettier-plugin-elmprettier --file-info src/something.elm{ "formattable": true, "ignored": false, "parser": "elm", "parserIsFallback": false }prettier-plugin-elmin this case) helps--file-infopick the right parser and mark the file asformattableprettier --file-info src/something.js --ignore-path .ignoresrc{ "formattable": false, "ignored": true, "parser": "babylon", "parserIsFallback": false }.ignoresrccontainssrc, the result containsignored: trueignored: trueimpliesformattable: falseprettier --file-info node_modules/module/something.js{ "formattable": false, "ignored": true, "parser": "babylon", "parserIsFallback": false }node_modulesis ignored by default and hence notformattableprettier --file-info node_modules/module/something.js --with-node-modules{ "formattable": true, "ignored": false, "parser": "babylon", "parserIsFallback": false }--with-node-modulesmakes the file insidenode_modulesnotignoredand thusformattable