Allow plugins to override default options#3991
Conversation
|
I think we want the API to be exports.defaultOptions = {...}Rather than: exports.printers.php.defaultOptions = {...}Because we may want to show this information in the CLI. e.g.: $ prettier --help tab-width
--tab-width <int>
Number of spaces per indentation level.
Default: 2
Plugin defaults:
* @prettier/plugin-php: 4Do you have any thoughts on this @ikatyang? |
|
That makes sense! I just pushed some changes that add the plugin's options to a new property In this implementation a plugin's default options are registered under the name of it's first printer - which works because currently all plugins only have one printer. Maybe there's a better solution, for example a If you think this is generally the right way to move forward, i'll continue by adding some tests! |
6c9077e to
f0562a0
Compare
| ); | ||
| const pluginDefaults = filteredPlugins.reduce((reduced, plugin) => { | ||
| const printerName = Object.keys(plugin.printers)[0]; | ||
| reduced[printerName] = plugin.defaultOptions[option.name]; |
There was a problem hiding this comment.
We probably need to attach the plugin name (package name, plugin filepath, or the name property if provided) in load-plugins.js and use it here as an ID.
prettier/src/common/load-plugins.js
Line 6 in 9643d33
| const pluginDefaults = supportOptions | ||
| .filter( | ||
| optionInfo => | ||
| optionInfo.pluginDefaults && optionInfo.pluginDefaults[options.parser] |
There was a problem hiding this comment.
Same here, since the parser name might not equal to the printer name.
|
@ikatyang Right - i updated the implementation. |
| const pluginPath = resolve.sync(plugin, { basedir: process.cwd() }); | ||
| return eval("require")(pluginPath); | ||
| const resolved = eval("require")(pluginPath); | ||
| resolved.name = plugin; |
There was a problem hiding this comment.
We should make a clone for it so as to avoid unexpected side effects.
return Object.assign({ name: plugin }, resolved);| return `${header}${description}${choices}${defaults}`; | ||
| const pluginDefaults = | ||
| option.pluginDefaults && Object.keys(option.pluginDefaults).length | ||
| ? `\nPlugin defaults:${Object.keys(option.pluginDefaults).map( |
| {} | ||
| ); | ||
|
|
||
| Object.assign(defaults, pluginDefaults); |
There was a problem hiding this comment.
Let's not mutate this object, assigning to a new object would be better.
| reduced[plugin.name] = plugin.defaultOptions[option.name]; | ||
| return reduced; | ||
| }, {}); | ||
| return Object.assign(option, { pluginDefaults }); |
There was a problem hiding this comment.
Could you add tests for it? (added in eddedde)
| const pluginDefaults = | ||
| option.pluginDefaults && Object.keys(option.pluginDefaults).length | ||
| ? `\nPlugin defaults:${Object.keys(option.pluginDefaults).map( | ||
| key => `\n* ${key}: ${option.pluginDefaults[key]}` |
There was a problem hiding this comment.
We should call createDefaultValueDisplay(value) here, though there is no non-primitive value in core options.
| {} | ||
| ); | ||
|
|
||
| Object.assign({}, defaults, pluginDefaults); |
There was a problem hiding this comment.
It seems you forgot to get the assigned defaults and use it below 😂, and also we need some tests for it.
There was a problem hiding this comment.
Oops, thats embarassing 😅 Probably still have to figure out how to rewrite it that i don't have to use let (and test is also still missing)
There was a problem hiding this comment.
You can just assign it to a new variable, e.g. mixedDefaults or something else:
const mixedDefaults = Object.assign({}, defaults, pluginDefaults);
// Object.keys(mixedDefaults) ...| "--parser=bar" | ||
| ]).test({ | ||
| status: 0, | ||
| stdout: `--tab-width <int> |
There was a problem hiding this comment.
It'd be better to just snapshot it (by removing the stdout key) since it's not a simple string and may contains style changes in the future.
There was a problem hiding this comment.
Of course - didn't think that doing that would be so easy 👍
|
|
||
| Default: 2 | ||
| Plugin defaults: | ||
| * ../plugins/automatic/node_modules/prettier-plugin-bar: 4 |
There was a problem hiding this comment.
Could we use the package.json name here? (Happy to do this in a follow up PR though)
There was a problem hiding this comment.
That's the result from --plugin=../plugins/automatic/node_modules/prettier-plugin-bar, I guess it should be fine?
-
--plugin=path/to/plugin.js(path)Plugin defaults: * path/to/plugin.js: 4 -
--plugin=prettier-plugin-bar(package name)Plugin defaults: * prettier-plugin-bar: 4
There was a problem hiding this comment.
Ah, cool. I'm happy with that. Does it work for plugins that are automatically resolved?
There was a problem hiding this comment.
It should work. @czosel could you add a test for that? just clone the test case and set its cwd to path/to/plugins/automatic then remove the --plugin=xxx part.
| print: path => concat(["bar+", path.getValue().text]) | ||
| } | ||
| }, | ||
| defaultOptions: { |
There was a problem hiding this comment.
@ikatyang mentioned this above, but could you add an integration test to ensure that this tabWidth is applied?
|
LGTM other than missing an integration test. |
| } | ||
| }); | ||
|
|
||
| Object.assign(rawOptions, pluginDefaults); |
There was a problem hiding this comment.
Just realized that rawOptions already contains the default options, so assigning the "mixedDefaults" only if the option key is not present in rawOptions (line 73) won't work. I added some tests for it.
Edit: Like this, overriding won't work anymore - i'll try to figure out the right place to assing the plugin options.
There was a problem hiding this comment.
Hmm, if I'm not mistaken the order of option application should be
core defaults < plugin defaults < cli / file overrides
That would mean that we already have to find out which plugin is used before CLI options are applied, so the logic has to be moved to somewhere around here:
What doesn't make this easier is the fact that the active plugin depends on the astFormat option. Maybe we should call minimist twice:
1) minimist(cli options, coredefaults)
2) determine active plugin
3) minimist(cli options, mixeddefaults)
7305253 to
7a20fcf
Compare
| return reduced; | ||
| } | ||
| return Object.assign(reduced, { [optionInfo.name]: optionInfo.default }); | ||
| }, Object.assign({}, optionsModule.hiddenDefaults)); |
There was a problem hiding this comment.
I managed to get the tests passing by adding this workaround - but I'm not really happy with it. I could imagine that there's a case where this breaks, and even if there isn't it doesn't look very clean to me.
I'd be glad if someone could explain the bigger picture on the way defaults are handled right now: To me, the two-step process of applying defaults seems confusing:
- CLI defaults are applied in
cli/util.js
Line 226 in 9ee113c
- Defaults are applied if an option is
nullinmain/options.js
Line 58 in 9ee113c
I saw that there are some use cases where the core options are still null in step two, but I'm not sure what those use cases are, and consequently how the proper process of applying defaults should look like in the future.
Thanks 😄
1a2ef07 to
7adffba
Compare
|
Workaround is reverted, tests are passing 🎉 |
ikatyang
left a comment
There was a problem hiding this comment.
LGTM, but I'd like someone else to take another look.
|
Awesome, can't wait! 😉 |
| description: "Move open brace for code blocks onto new line." | ||
| } | ||
| } | ||
| ``` |
There was a problem hiding this comment.
Can you document each key in the option?
There was a problem hiding this comment.
Of course! Can you help my out with the category option? I'll do this in a new PR.
|
|
||
| If your plugin requires different default values for some of prettier's core options, you can specify them in `defaultOptions`: | ||
|
|
||
| ``` |
This is a first attempt to handle #3972. See prettier/plugin-php#52 for a usage example. I attached
defaultOptionsto the printer to reuse existing logic (getPrinter). Like that, only files handled by the respective plugin should be affected by the changed defaults.This doesn't handle multiparser support, though - but maybe that's something to be handled in the future when there is a concrete use case?