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

Prettier Plugin API #3536

Merged
merged 42 commits into from
Dec 26, 2017
Merged

Prettier Plugin API #3536

merged 42 commits into from
Dec 26, 2017

Conversation

azz
Copy link
Member

@azz azz commented Dec 19, 2017

Very early progress - I've only moved files so far. Want to keep the build green and get as much feedback as possible on this one.

Closes #3511

  • Refactor into language-based directories
  • Extract common code from JavaScript printer
  • Refactor printers to new API
  • Refactor parsers to new API
  • Implement extensibility via internal configuration
  • Investigate failing test
  • Test playground
  • Verify no breaking changes
  • Document the API
  • Refactor multiparser into per-language files

Follow ups (2.0 items):

  • Split out the support info and options to be language-specific
  • Make it externally configurable via CLI
  • Make it externally configurable via options/configuration file
  • Switch to monorepo to enforce correct imports (?)
  • Use prettier-python as a proof-of-concept

node &&
node.comments &&
node.comments.length > 0 &&
node.comments.some(comment => comment.value.trim() === "prettier-ignore")
Copy link
Member

Choose a reason for hiding this comment

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

FYI [].some(() => true) === false, so node.comments.length > 0 is not needed other than for explicitness.

Copy link
Member Author

Choose a reason for hiding this comment

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

True. I just moved this code though 😉

@azz
Copy link
Member Author

azz commented Dec 20, 2017

Managed to extract all the generic printing logic from the old printer.js file into main/ast-to-doc.js. Only one failing test! I suspect this is due to the doc being wrapped in less concat()s.

Test failure
$ PRETTIER_DEBUG=1 yarn test template_literals
yarn test v1.0.2
$ jest "template_literals"
 FAIL  tests/template_literals/jsfmt.spec.js
  ● Test suite failed to run

    Couldn't insert all the expressions

      at transformCssDoc (src/main/multiparser.js:386:11)
      at fromBabylonFlowOrTypeScript (src/main/multiparser.js:106:16)
      at Object.printSubtree (src/main/multiparser.js:20:14)
      at genericPrint (src/main/ast-to-doc.js:83:31)
      at comments.printComments.p (src/main/ast-to-doc.js:38:14)
      at Object.printComments (src/main/comments.js:1102:19)
      at printGenerically (src/main/ast-to-doc.js:36:22)
      at FastPath.call (src/builder/fast-path.js:70:18)
      at printPathNoParens (src/language-js/printer-estree.js:1698:29)
      at Object.genericPrint [as print] (src/language-js/printer-estree.js:55:30)
      at genericPrint (src/main/ast-to-doc.js:96:18)
      at comments.printComments.p (src/main/ast-to-doc.js:38:14)
      at Object.printComments (src/main/comments.js:1102:19)
      at printGenerically (src/main/ast-to-doc.js:36:22)
      at FastPath.map (src/builder/fast-path.js:124:19)
      at printJSXElement (src/language-js/printer-estree.js:4310:19)
      at comments.printComments (src/language-js/printer-estree.js:1717:15)
      at Object.printComments (src/main/comments.js:1102:19)
      at printPathNoParens (src/language-js/printer-estree.js:1715:29)
      at Object.genericPrint [as print] (src/language-js/printer-estree.js:55:30)
      at genericPrint (src/main/ast-to-doc.js:96:18)
      at printGenerically (src/main/ast-to-doc.js:34:13)
      at FastPath.call (src/builder/fast-path.js:70:18)
      at printPathNoParens (src/language-js/printer-estree.js:225:14)
      at Object.genericPrint [as print] (src/language-js/printer-estree.js:55:30)
      at genericPrint (src/main/ast-to-doc.js:96:18)
      at comments.printComments.p (src/main/ast-to-doc.js:38:14)
      at Object.printComments (src/main/comments.js:1102:19)
      at printGenerically (src/main/ast-to-doc.js:36:22)
      at path.map (src/language-js/printer-estree.js:2846:25)
      at FastPath.map (src/builder/fast-path.js:124:19)
      at printStatementSequence (src/language-js/printer-estree.js:2830:8)
      at parts.push.path.call.bodyPath (src/language-js/printer-estree.js:201:18)
      at FastPath.call (src/builder/fast-path.js:70:18)
      at printPathNoParens (src/language-js/printer-estree.js:200:14)
      at Object.genericPrint [as print] (src/language-js/printer-estree.js:55:30)
      at genericPrint (src/main/ast-to-doc.js:96:18)
      at comments.printComments.p (src/main/ast-to-doc.js:38:14)
      at Object.printComments (src/main/comments.js:1102:19)
      at printGenerically (src/main/ast-to-doc.js:36:22)
      at printAstToDoc (src/main/ast-to-doc.js:51:13)
      at formatWithCursor (index.js:112:15)
      at format (index.js:137:10)
      at Object.format (index.js:376:12)
      at prettyprint (tests_config/run_spec.js:123:19)
      at fs.readdirSync.forEach.filename (tests_config/run_spec.js:47:22)
          at Array.forEach (<anonymous>)
      at run_spec (tests_config/run_spec.js:21:27)
      at Object.<anonymous> (tests/template_literals/jsfmt.spec.js:1:90)
          at Generator.next (<anonymous>)
          at new Promise (<anonymous>)
          at Generator.next (<anonymous>)
          at <anonymous>
      at process._tickCallback (internal/process/next_tick.js:188:7)

@azz
Copy link
Member Author

azz commented Dec 20, 2017

Any idea what happened to this job? https://travis-ci.org/prettier/prettier/jobs/319135394

@azz azz mentioned this pull request Dec 20, 2017
},
get markdown() {
return eval("require")("./parser-markdown");
return eval("require")("../language-markdown/parser-markdown");
Copy link
Member

Choose a reason for hiding this comment

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

In bundled version: Cannot find module '../language-markdown/parser-markdown' from 'index.js'

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah I'll be moving the output too eventually. Will have to upgrade the playground too.

@azz
Copy link
Member Author

azz commented Dec 22, 2017

As of 35c57c6 Prettier uses the plugin API internally! 🎉

Still have to get the build green (1 failing multiparser test remains), but should be good to review now.

@j-f1
Copy link
Member

j-f1 commented Dec 22, 2017

Why do the language files use eval("require")?

@azz
Copy link
Member Author

azz commented Dec 22, 2017

It's a gross hack to prevent Rollup from bundling in the parsers. I'm just moving it from the parsers.js file. Would probably be better to use Rollup's external as a function, but we can do that in a separate PR.

bin/prettier.js Outdated
@@ -2,4 +2,4 @@

"use strict";

require("../src/cli").run(process.argv.slice(2));
require("../src/cli/cli").run(process.argv.slice(2));
Copy link
Member

Choose a reason for hiding this comment

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

How about src/cli/index?

get parse() {
return eval("require")("./parser-markdown");
},
astFormat: "remark"
Copy link
Member

Choose a reason for hiding this comment

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

@@ -1,7 +1,7 @@
"use strict";

Copy link
Member

Choose a reason for hiding this comment

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

main/doc-to-string -> builder/doc-printer?

I think they should be in the same group since they are parts of the core printer, just like a standalone package.

doc-builders
doc-printer
doc-utils
fast-path

Copy link
Member Author

@azz azz Dec 23, 2017

Choose a reason for hiding this comment

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

Good idea. Renamed it to doc to make it a more cohesive package name. Moved out fast-path as it doesn't really belong.

Need to move FastPath#needsParens to a separate file in language-js I think.

@ikatyang
Copy link
Member

We probably need to split out more things to be language-specific, e.g.

  • massageAST
    • external plugin currently has no ability to use it, --debug-check will always throw errors.
  • hasPragma/insertPragma
    • they're currently only available (valid) in JS/CSS
  • more?

@j-f1
Copy link
Member

j-f1 commented Dec 23, 2017

  • hasPragma/insertPragma
    • they're currently only available (valid) in JS/CSS

Markdown:

<!-- @format -->

GraphQL:

# @format

@azz
Copy link
Member Author

azz commented Dec 23, 2017

That's already a bug. If you use --insert-pragma with a GraphQL file it inserts a /** @format */ comment which is a syntax error. Is there an issue raised for it?

Edit: Raised #3557

@azz
Copy link
Member Author

azz commented Dec 23, 2017

Getting this error on the production build... Feel like I've seen it before...

Summary of all failing tests
 FAIL  tests_integration/__tests__/config-resolution.js
  ● API resolveConfig with file arg
    TypeError: Cannot read property 'children' of undefined
      
      at requireFromString (dist/index.js:23817:8)
      at parseJsFile (dist/index.js:23963:15)
  ● API resolveConfig.sync with file arg
    TypeError: Cannot read property 'children' of undefined
      
      at requireFromString (dist/index.js:23817:8)
      at parseJsFile (dist/index.js:23963:15)
      at loadJs (dist/index.js:23970:7)
      at dist/index.js:24276:16
      at index.forEach.results.reduce.text.replace.split.forEach.isBetween.some.Object.assign.comments.forEach.comments.forEach.tiesToBreak.forEach.path.each.path.each.FastPath$1.needsParens.path.each.path.each.path.each.parts.push.path.each.grouped.length.standalones.length.n.specifiers.n.specifiers.some.path.each.fields.forEach.propsAndLoc.sort.path.each.path.each.parts.push.path.call.path.map.path.call.parts.push.path.each.path.each.parts.push.path.call.path.call.path.call.printedNodes.(anonymous function).node.comments.printedNodes.(anonymous function).node.comments.some.flatGroups.slice.some.path.map.n.children.children.forEach.node.comments.node.comments.some.node.comments.node.comments.some.path.call.apply.path.call.template.quasis.some.path.each.path.each.path.map.value.replace.value.replace.sequencePath.map.node.value.replace.replace.path.map.contents.reduce.path.map.util$12.mapDoc.parts.reduce.path.each.results.push.lines.map.textLine.forEach.util$8.mapDoc.docUtils$3.mapDoc.results.push.exports.printProps.exports.printChildren.plugin.serialize.Object.keys.forEach.loadPlugins$3.reduce.filter.getSupportInfo$1.languages.find.Object.keys.forEach.chainFuncsSync (dist/index.js:23825:42)
      at Array.reduce (native)
      at funcRunner (dist/index.js:23838:16)
      at searchDirectory (dist/index.js:24258:20)
      at load (dist/index.js:24248:9)
      at dist/index.js:33229:49
      at Array.map (native)
      at _resolveConfig (dist/index.js:33229:40)
      at Function.index.forEach.results.reduce.text.replace.split.forEach.isBetween.some.Object.assign.comments.forEach.comments.forEach.tiesToBreak.forEach.path.each.path.each.FastPath$1.needsParens.path.each.path.each.path.each.parts.push.path.each.grouped.length.standalones.length.n.specifiers.n.specifiers.some.path.each.fields.forEach.propsAndLoc.sort.path.each.path.each.parts.push.path.call.path.map.path.call.parts.push.path.each.path.each.parts.push.path.call.path.call.path.call.printedNodes.(anonymous function).node.comments.printedNodes.(anonymous function).node.comments.some.flatGroups.slice.some.path.map.n.children.children.forEach.node.comments.node.comments.some.node.comments.node.comments.some.path.call.apply.path.call.template.quasis.some.path.each.path.each.path.map.value.replace.value.replace.sequencePath.map.node.value.replace.replace.path.map.contents.reduce.path.map.util$12.mapDoc.parts.reduce.path.each.results.push.lines.map.textLine.forEach.util$8.mapDoc.docUtils$3.mapDoc.results.push.exports.printProps.exports.printChildren.plugin.serialize.Object.keys.forEach.loadPlugins$3.reduce.filter.getSupportInfo$1.languages.find.Object.keys.forEach.resolveConfig.sync (dist/index.js:33256:42)
      at Object.<anonymous> (tests_integration/__tests__/config-resolution.js:80:33)


return null;

function getParserName(lang) {
Copy link
Member

Choose a reason for hiding this comment

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

It seems we can re-use the support info (langName/extName) to get the correct parser? and it'll work with all external plugins.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good idea! Done.

package.json Outdated
@@ -47,6 +47,7 @@
"postcss-values-parser": "1.3.1",
"remark-frontmatter": "1.1.0",
"remark-parse": "4.0.0",
"resolve": "^1.5.0",
Copy link
Member

Choose a reason for hiding this comment

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

1.5.0 😂

Copy link
Member Author

Choose a reason for hiding this comment

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

Not again... (at least the script catches it :D)

@azz azz changed the title [WIP] Prettier Plugin API Prettier Plugin API Dec 24, 2017
@azz
Copy link
Member Author

azz commented Dec 24, 2017

Looks like there's a Rollup bug. loadPlugins$2 is not a function. First glance looks like Rollup has bundled in the files in the wrong order. Maybe a circular dependency? I tried upgrading Rollup but there was a stack of breaking changes and after I'd resolved it all I was getting errors with uglify-es so I gave up :(

Any ideas?


const externalPlugins = options.plugins.map(plugin => {
const pluginPath = resolve.sync(plugin, { basedir: process.cwd() });
return eval("require")(pluginPath);
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if there's any way this could support some sort of CLI-based API in addition to the JS ones, say some standard CLI commands that return information as JSON.

Doing so could allow writing Prettier plugins in languages other than JS, which would allow better integration for other language ecosystems.

Copy link
Member Author

Choose a reason for hiding this comment

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

Hypothetically, yes. But it would require an RPC interface to call the various functions (print, embed, parse, etc)

Copy link
Contributor

Choose a reason for hiding this comment

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

Not RPC per se since there's no remote ;)

Just like:

$ prettier-python parse <whatever>

Really this is all around prettier/plugin-python#2.

azz referenced this pull request Dec 25, 2017
…3563)

There's a lot of demand for vue sfc (#2097). This introduces partial support for them: all the html is printed as is, except for the script and style tags which are printed using prettier. I believe that this should cover a lot of the use cases while being simple to support and if we want we can extend to more in the future.

I copy pasted the html parser used by vue (it's just a single 400 lines file) so that we don't run the chancesof conflicts. I'm also very conservative: I only print the style and script at the top level and for the lang attributes we support.

I expect this to be landable as is and provide value, review welcome :)
@azz azz force-pushed the plugin-refactor branch 2 times, most recently from 50ad50e to 5a10c03 Compare December 25, 2017 10:22
@azz
Copy link
Member Author

azz commented Dec 25, 2017

🎄 ✅ Build is green!

Copy link
Contributor

@vjeux vjeux left a comment

Choose a reason for hiding this comment

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

So good! Thanks a lot for driving this, it has been a long time coming now that we support every language on earth to have a proper way of splitting them up :)

@azz azz merged commit 4c9d406 into prettier:master Dec 26, 2017
@azz azz deleted the plugin-refactor branch December 26, 2017 01:23
@azz azz mentioned this pull request Dec 26, 2017
@taion
Copy link
Contributor

taion commented Dec 28, 2017

With the current implementation, can plugins add their own CLI options?

At a glance, it looks like "no" – I think we'd need to do option parsing in two passes – first load the plugins, then load the config options for each plugin.

@azz
Copy link
Member Author

azz commented Dec 28, 2017

Maybe just recommend using a config file for options? I'm happy to look at supporting CLI option contributions if it is easy to implement though.

@taion
Copy link
Contributor

taion commented Dec 28, 2017

See https://github.com/prettier/prettier/pull/3584/files#r158743435 – it's not quite sufficient because if the option is unrecognized,

  1. Prettier warns on the option
  2. Prettier ignores the option

So even adding stuff to config files, disregarding anything like validation, is not sufficient. So unless I'm missing something ATM there's no actual way to configure plugins.

@taion taion mentioned this pull request Dec 28, 2017
@duailibe duailibe added this to the 1.10 milestone Dec 30, 2017
@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.

None yet

7 participants