Skip to content

Allow plugins to override default options#3991

Merged
azz merged 17 commits intoprettier:masterfrom
czosel:plugin-default-options
Feb 27, 2018
Merged

Allow plugins to override default options#3991
azz merged 17 commits intoprettier:masterfrom
czosel:plugin-default-options

Conversation

@czosel
Copy link
Copy Markdown
Contributor

@czosel czosel commented Feb 17, 2018

This is a first attempt to handle #3972. See prettier/plugin-php#52 for a usage example. I attached defaultOptions to 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?

@azz
Copy link
Copy Markdown
Member

azz commented Feb 18, 2018

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: 4

Do you have any thoughts on this @ikatyang?

@czosel
Copy link
Copy Markdown
Contributor Author

czosel commented Feb 19, 2018

That makes sense! I just pushed some changes that add the plugin's options to a new property pluginOptions in supportOptions, which should make it easy to display them in the CLI's help. I read from the same object in options.js to extract the current defaults.

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 name property on the plugin?

If you think this is generally the right way to move forward, i'll continue by adding some tests!

@czosel czosel force-pushed the plugin-default-options branch 2 times, most recently from 6c9077e to f0562a0 Compare February 19, 2018 12:36
Comment thread src/common/support.js Outdated
);
const pluginDefaults = filteredPlugins.reduce((reduced, plugin) => {
const printerName = Object.keys(plugin.printers)[0];
reduced[printerName] = plugin.defaultOptions[option.name];
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

function loadPlugins(plugins) {

Comment thread src/main/options.js Outdated
const pluginDefaults = supportOptions
.filter(
optionInfo =>
optionInfo.pluginDefaults && optionInfo.pluginDefaults[options.parser]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same here, since the parser name might not equal to the printer name.

@czosel
Copy link
Copy Markdown
Contributor Author

czosel commented Feb 22, 2018

@ikatyang Right - i updated the implementation.

Comment thread src/common/load-plugins.js Outdated
const pluginPath = resolve.sync(plugin, { basedir: process.cwd() });
return eval("require")(pluginPath);
const resolved = eval("require")(pluginPath);
resolved.name = plugin;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should make a clone for it so as to avoid unexpected side effects.

return Object.assign({ name: plugin }, resolved);

Comment thread src/cli/util.js
return `${header}${description}${choices}${defaults}`;
const pluginDefaults =
option.pluginDefaults && Object.keys(option.pluginDefaults).length
? `\nPlugin defaults:${Object.keys(option.pluginDefaults).map(
Copy link
Copy Markdown
Member

@ikatyang ikatyang Feb 23, 2018

Choose a reason for hiding this comment

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

Could you add tests for it? (added in 763eaa4)

Comment thread src/main/options.js Outdated
{}
);

Object.assign(defaults, pluginDefaults);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's not mutate this object, assigning to a new object would be better.

Comment thread src/common/support.js
reduced[plugin.name] = plugin.defaultOptions[option.name];
return reduced;
}, {});
return Object.assign(option, { pluginDefaults });
Copy link
Copy Markdown
Member

@ikatyang ikatyang Feb 23, 2018

Choose a reason for hiding this comment

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

Could you add tests for it? (added in eddedde)

Comment thread src/cli/util.js Outdated
const pluginDefaults =
option.pluginDefaults && Object.keys(option.pluginDefaults).length
? `\nPlugin defaults:${Object.keys(option.pluginDefaults).map(
key => `\n* ${key}: ${option.pluginDefaults[key]}`
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should call createDefaultValueDisplay(value) here, though there is no non-primitive value in core options.

Comment thread src/main/options.js Outdated
{}
);

Object.assign({}, defaults, pluginDefaults);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems you forgot to get the assigned defaults and use it below 😂, and also we need some tests for it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You can just assign it to a new variable, e.g. mixedDefaults or something else:

const mixedDefaults = Object.assign({}, defaults, pluginDefaults);

// Object.keys(mixedDefaults) ...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

right - done.

"--parser=bar"
]).test({
status: 0,
stdout: `--tab-width <int>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Of course - didn't think that doing that would be so easy 👍


Default: 2
Plugin defaults:
* ../plugins/automatic/node_modules/prettier-plugin-bar: 4
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could we use the package.json name here? (Happy to do this in a follow up PR though)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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
    

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah, cool. I'm happy with that. Does it work for plugins that are automatically resolved?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done!

print: path => concat(["bar+", path.getValue().text])
}
},
defaultOptions: {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@ikatyang mentioned this above, but could you add an integration test to ensure that this tabWidth is applied?

@azz
Copy link
Copy Markdown
Member

azz commented Feb 24, 2018

LGTM other than missing an integration test.

Comment thread src/main/options.js Outdated
}
});

Object.assign(rawOptions, pluginDefaults);
Copy link
Copy Markdown
Contributor Author

@czosel czosel Feb 24, 2018

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor Author

@czosel czosel Feb 24, 2018

Choose a reason for hiding this comment

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

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:

https://github.com/prettier/prettier/blob/master/src/cli/util.js#L224

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)

Comment thread tests_integration/plugins/defaultOptions/plugin.js Outdated
@czosel czosel force-pushed the plugin-default-options branch 3 times, most recently from 7305253 to 7a20fcf Compare February 25, 2018 12:26
Comment thread src/cli/util.js Outdated
return reduced;
}
return Object.assign(reduced, { [optionInfo.name]: optionInfo.default });
}, Object.assign({}, optionsModule.hiddenDefaults));
Copy link
Copy Markdown
Contributor Author

@czosel czosel Feb 25, 2018

Choose a reason for hiding this comment

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

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:

  1. CLI defaults are applied in cli/util.js
    minimist(
  2. Defaults are applied if an option is null in main/options.js
    Object.keys(defaults).forEach(k => {

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 😄

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

--> #4045

@czosel czosel force-pushed the plugin-default-options branch from 1a2ef07 to 7adffba Compare February 26, 2018 06:32
@czosel
Copy link
Copy Markdown
Contributor Author

czosel commented Feb 26, 2018

Workaround is reverted, tests are passing 🎉

Copy link
Copy Markdown
Member

@ikatyang ikatyang left a comment

Choose a reason for hiding this comment

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

LGTM, but I'd like someone else to take another look.

@czosel
Copy link
Copy Markdown
Contributor Author

czosel commented Feb 27, 2018

Awesome, can't wait! 😉
I also just added some basic docs for options and defaultOptions.

Copy link
Copy Markdown
Member

@azz azz left a comment

Choose a reason for hiding this comment

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

LGTM!

@azz azz merged commit d05a29d into prettier:master Feb 27, 2018
@czosel czosel deleted the plugin-default-options branch February 27, 2018 13:47
Comment thread docs/plugins.md
description: "Move open brace for code blocks onto new line."
}
}
```
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you document each key in the option?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Of course! Can you help my out with the category option? I'll do this in a new PR.

Comment thread docs/plugins.md

If your plugin requires different default values for some of prettier's core options, you can specify them in `defaultOptions`:

```
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

```js?

@lock lock Bot added the locked-due-to-inactivity Please open a new issue and fill out the template instead of commenting. label Jan 18, 2019
@lock lock Bot locked as resolved and limited conversation to collaborators Jan 18, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

locked-due-to-inactivity Please open a new issue and fill out the template instead of commenting.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants