Skip to content

Reject invalid plugin names#5842

Closed
sarupbanskota wants to merge 3 commits intobabel:plugin-orderingfrom
sarupbanskota:enforce_plugin_names
Closed

Reject invalid plugin names#5842
sarupbanskota wants to merge 3 commits intobabel:plugin-orderingfrom
sarupbanskota:enforce_plugin_names

Conversation

@sarupbanskota
Copy link
Copy Markdown
Contributor

@sarupbanskota sarupbanskota commented Jun 8, 2017

Q A
Patch: Bug Fix?
Major: Breaking Change?
Minor: New Feature?
Deprecations?
Spec Compliancy?
Tests Added/Pass?
Fixed Tickets Progress on #5735 and #5827
License MIT
Doc PR
Dependency Changes

⚠️ This change enforces plugins to have a valid name, that is of type string, and is a valid JS identifier

@sarupbanskota sarupbanskota changed the base branch from 7.0 to plugin-ordering June 8, 2017 18:41
constructor(plugin: {}, key?: string) {
if (plugin.name != null && typeof plugin.name !== "string") {
throw new Error("Plugin .name must be a string, null, or undefined");
if (!plugin.name || typeof plugin.name !== "string") {
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.

Hmm, sorry I've been a bit out of the loop. Is there documentation somewhere for why we want to require the name? I'm concerned it'll make it harder for people to upgrade to 7.x

Copy link
Copy Markdown
Contributor Author

@sarupbanskota sarupbanskota Jun 8, 2017

Choose a reason for hiding this comment

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

yep @loganfsmyth, this was discussed in the meeting on 7 June (altho I wasn't around).

It has to do with simplifying the "intelligent plugin sorting" process.

Benefits

  1. Allows for global topological ordering with the DAG map.
  2. Serves as a known identifying key for the plugin.
  3. Provides opportunity to deduplicate identically configured plugins.

Steps

  1. Deprecate unnamed plugins in Babel 6.
  2. Add deprecation message.
  3. Publish a new patch version with that deprecation.
  4. Write a deprecation guide on how to make this change for your plugins.
  5. Enforce named plugins in Babel 7.

Source: https://docs.google.com/document/d/1zO1QiaAyDxmA1OcmdOKYLFmuSXGSr4S50pE0uQFTVEE/edit#heading=h.bcrawxu2t468.


Also relevant: #5814 (comment)

Copy link
Copy Markdown
Contributor Author

@sarupbanskota sarupbanskota Jun 8, 2017

Choose a reason for hiding this comment

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

Note that I rebased this branch on top of plugin-ordering :-)

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.

Babel 6 stuff is happening on another PR - #5827

if (plugin.name != null && typeof plugin.name !== "string") {
throw new Error("Plugin .name must be a string, null, or undefined");
if (!plugin.name || typeof plugin.name !== "string") {
throw new Error("Plugin .name must be a valid JS identifier of type string");
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.

"Plugin .name must be a string"?

Copy link
Copy Markdown
Contributor Author

@sarupbanskota sarupbanskota Jun 8, 2017

Choose a reason for hiding this comment

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

@jridgewell since that constructor is taking in a plugin object with a bunch of properties, I didn't want to make the assumption that the passed in value for name would always be of type string. e.g if it received { name: true } that would still pass the first conditional

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.

Happy to just look for name's existence if you think the type check isn't necessary

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.

No, I'm commenting on your error message. the "valid JS identifier of type string" is needlessly wordy, just say "needs to be a string"

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.

ah, okay - sounds good, thanks for that. I'll update it!

Makes progress on #5735 and #5827
@sarupbanskota
Copy link
Copy Markdown
Contributor Author

I'm guessing the next step will be to name the unnamed core packages?

I wrote a little script:

const fileSystem = require('fs');

const returnFilesWithin = (path) => {
  return fileSystem.readdirSync(path);
}

returnFilesWithin('packages').forEach(package => {
  if (fileSystem.lstatSync(`packages/${package}`).isDirectory()) {
    fileSystem.readFile(`packages/${package}/package.json`, 'utf8', (err, content) => {
      const packageOptions = JSON.parse(content);
      console.log(packageOptions.name);
    });
  }
})

and that printed https://gist.github.com/sarupbanskota/4bf6627eb1501be1b8c14be406164aa4. No empty lines 🤔 What am I missing?

@sarupbanskota
Copy link
Copy Markdown
Contributor Author

sarupbanskota commented Jun 8, 2017

learned from @hzoo that it needs to be within the visitor
e.g in here: https://github.com/babel/babel/blob/7.0/packages/babel-plugin-transform-es2015-computed-properties/src/index.js#L87

visitor: {
  name: "transform-es2015-computed-properties"
  ...
}

i'll think of ways to do it for all core plugin packages


update:

name: "transform-es2015-computed-properties",
visitor: {
  ...
}

@jridgewell
Copy link
Copy Markdown
Member

Why does the visitor have a name and not the plugin?

@hzoo
Copy link
Copy Markdown
Member

hzoo commented Jun 8, 2017

yeah that's incorrect - check http://astexplorer.net/ for an example

@sarupbanskota
Copy link
Copy Markdown
Contributor Author

sarupbanskota commented Jun 10, 2017

Just documenting thought process here. Based on two | examples, we wanna do the following:

  1. Iterate over all core babel plugin packages. These are packages within packages/ that match /^babel-plugin/
  2. Store the name as whatever follows babel-plugin-*
  3. codemod the plugin with codemod --require babel-register --plugin plugin.js packages/${package}/src/index.js
  4. plugin.js effectively does the following:
body:
  type: "ExportDefaultDeclaration"
    declaration: 
      type: "FunctionDeclaration"
        body: 
          type: "BlockStatement"
            body:
              type: "ReturnStatement"
                argument: 
                  type: "ObjectExpression"
                    properties: [Array 1]  ← we wanna inject `name` as another prop here

Instead of manually updating filenames on the core packages, I'm doing this programmatically to teach myself to play with the various babel packages available, but as @nathanhammond and @hzoo discussed on Slack, we could use this logic to build a tiny plugin upgrade service or similar

@hzoo
Copy link
Copy Markdown
Member

hzoo commented Jun 10, 2017

Not every plugin has to be formatted in that way but hopefully most are? I would just check for visitor and add the property

https://github.com/babel/kneden/blob/master/src/index.js#L19
https://github.com/tleunen/babel-plugin-module-resolver/blob/master/src/index.js#L25
https://github.com/lodash/babel-plugin-lodash/blob/master/src/index.js#L141

Still don't think the names should be used as capabilities, but if this somehow will give us better error messages ok.

@sarupbanskota
Copy link
Copy Markdown
Contributor Author

sarupbanskota commented Jun 10, 2017

Ah shoot, I made quite a bit of progress and only noticed your comment now @hzoo.

Anyway, here's what I have so far:
Based on the original exploration, I have a plugin that injects a name property with a dummy name atm: https://github.com/sarupbanskota/babel-plugin-add-name-to-plugin/blob/master/src/index.js

export default function({ types: t }) {

  return {
    visitor: {
      ExportDefaultDeclaration(path) {
        let pluginReturn;
        try {
          pluginReturn = path          // "ExportDefaultDeclaration"
            .get('declaration')        // "FunctionDeclaration"
            .get('body')               // "BlockStatement"
            .get('body.0')             // "ReturnStatement"
            .get('argument')           // "ObjectExpression"
            .get('properties.0');      // "ObjectMethod"
        } catch(e) {
          console.log(e);
        } finally {
          const nameKey = t.identifier("name");
          const nameValue = t.stringLiteral("pluginName");
          pluginReturn.insertBefore(t.objectProperty(nameKey, nameValue));
        }
      }
    }
  }
}

Other than the fact that it may not work for every case, please let me know if you see anything if I'm totally in the wrong direction with the way I've written it!

What's pending for me:

  • Make plugin name injection work for different kinds of files (maybe consider @hzoo's visitor comment)
  • Find a way to pass plugin package name into the plugin file for injection
  • Get the plugin out as an npm package
  • Don't inject name if it's already present

Copy link
Copy Markdown
Contributor Author

@sarupbanskota sarupbanskota left a comment

Choose a reason for hiding this comment

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

What I ran here was the following:

const exec = require('child_process').exec;
const fileSystem = require('fs');

const returnFilesWithin = (path) => {
  return fileSystem.readdirSync(path);
}

returnFilesWithin('packages').forEach(package => {
  const PLUGIN_PACKAGE = /^babel-plugin/;
  if (PLUGIN_PACKAGE.test(package)) {
    if (fileSystem.lstatSync(`packages/${package}/src`).isDirectory()) {
      const cmd = `codemod --require babel-register --plugin plugin.js packages/${package}/src/index.js ${package}`;
      exec(cmd, (error, stdout, stderr) => {
        console.log(package);
      });
    }
  }
});

plugin.js is a plugin I've written locally

@sarupbanskota
Copy link
Copy Markdown
Contributor Author

Just copying over my message from Slack:

I wrote this plugin https://github.com/babel/babel/pull/5842/files#diff-9db534602dc20174af286fa13dba8b05

and ran this code to run it through babel's core packages https://github.com/babel/babel/pull/5842/files#diff-46ab14e9c7ab9cf574a7e8f116b8d6b0

  1. Does the plugin seem written well? what would you improve?
  2. In some cases, e,g https://github.com/babel/babel/pull/5842/files#diff-2ba358dbf7b25653c724e1acd475a922 seems like both ObjectExpression and ExportDefaultDeclaration got triggered, and there were two modifications to the same file, despite adding a check for it. One way to remove it would be writing 2 plugins, is there a better way?

@sarupbanskota
Copy link
Copy Markdown
Contributor Author

I figured out the problem, and will send an update tonight

These changes were done programmatically; the process is
explained at http://bit.ly/2s0r6Oi.

Notes:
babel-plugin-transform-class-properties already has a name.
babel-plugin-transform-regenerator's source seems to be a node_module
Inject plugin name into original module via
facebook/regenerator#296
@sarupbanskota sarupbanskota mentioned this pull request Jul 6, 2017
7 tasks
@loganfsmyth
Copy link
Copy Markdown
Member

I'm triaging old PR on Babel. Since this has been sitting for ages, I'm going to close it. If you want to rebase this to just add plugin names to our packages, feel free to open a new PR.

@loganfsmyth loganfsmyth closed this Sep 7, 2018
@lock lock bot added the outdated A closed issue/PR that is archived due to age. Recommended to make a new issue label Oct 4, 2019
@lock lock bot locked as resolved and limited conversation to collaborators Oct 4, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Has PR outdated A closed issue/PR that is archived due to age. Recommended to make a new issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants