@lihautan/babel-plugin-transform-destructure-number

Number destructuring.let a.b = 3.142;a === 3 // trueb === 142 // true— ๐ŸŒ… Iron Therapy ๐ŸŒ‡ (@__Jonathanks) July 22, 2020

Usage no npm install needed!

<script type="module">
  import lihautanBabelPluginTransformDestructureNumber from 'https://cdn.skypack.dev/@lihautan/babel-plugin-transform-destructure-number';
</script>

README

transform-destructure-number

Inspiration

๐Ÿ’ก The Idea

Destructuring decimal number via .?

Cool! ๐Ÿ˜Ž

Too bad it's not a valid syntax:

let a.b = 3.142;
a === 3; // true
b === 142; // true

Which means, this require us to fork the babel parser.

We can create a parser plugin to extend the existing parsing method. This allow us to turn on / off the parser plugin from the transform plugin. This is just like how babel typescript plugin or flow plugin works.

// filename: https://github.com/babel/babel/tree/master/packages/babel-parser/src/plugin-utils.js

import estree from "./plugins/estree";
+import jsWeDoNotDeserve from "./plugins/jsWeDoNotDeserve";
import flow from "./plugins/flow";
import jsx from "./plugins/jsx";
import typescript from "./plugins/typescript";

// ...

export const mixinPlugins: { [name: string]: MixinPlugin } = {
   estree,
+  jsWeDoNotDeserve,
   jsx,
   // ...

We can first copy the original implementation of parseVarId, the method to parse variable name in a VariableDeclarator.

If we encounter a . token after the variable name, we create a different AST node, DestructureNumberPattern instead.

const a.b = 1.3;

// ...
{
  type: "VariableDeclarator",
  id: {
    type: "DestructureNumberPattern",
    left: {
      type: "Identifier",
      name: "a",
    },
    right: {
      type: "Identifier",
      name: "b",
    },
  },
  init: {
    type: "NumericLiteral",
    value: 1.3,
  },
}
// @flow
import { types as tt, type TokenType } from '../tokenizer/types';
import type Parser from '../parser';
import * as N from '../types';
import {
  type BindingTypes,
  BIND_NONE,
  BIND_LEXICAL,
  BIND_VAR,
} from '../util/scopeflags';

export default (superClass: Class<Parser>): Class<Parser> =>
  class extends superClass {
    parseVarId(
      decl: N.VariableDeclarator,
      kind: 'var' | 'let' | 'const'
    ): void {
      const startPos = this.state.start;
      const startLoc = this.state.startLoc;

+      decl.id = this.parseBindingAtom();
+      if (this.eat(tt.dot) && decl.id.type === 'Identifier') {
+        const node = this.startNodeAt(startPos, startLoc);
+        node.left = decl.id;
+        node.right = this.parseIdentifier();
+        decl.id = this.finishNode(node, 'DestructureNumberPattern');
+      }
      this.checkLVal(
        decl.id,
        kind === 'var' ? BIND_VAR : BIND_LEXICAL,
        undefined,
        'variable declaration',
        kind !== 'var'
      );
    }

    checkLVal(
      expr: N.Expression,
      bindingType: BindingTypes = BIND_NONE,
      checkClashes: ?{ [key: string]: boolean },
      contextDescription: string,
      disallowLetBinding?: boolean
    ): void {
      switch (expr.type) {
        case 'DestructureNumberPattern':
          break;
        default:
          super.checkLVal(
            expr,
            bindingType,
            checkClashes,
            contextDescription,
            disallowLetBinding
          );
      }
    }
  };

๐Ÿ“˜ The Code

export default function ({ types: t, template }) {
  return {
    name: 'transform-destructure-number',
    visitor: {
      VariableDeclarator(path) {
        if (path.node.id.type === 'DestructureNumberPattern') {
          if (t.isNumericLiteral(path.node.init)) {
            const [a, b] = path.node.init.extra.raw.split('.');

            path.replaceWith(
              template.statement`let [${path.node.id.left}, ${
                path.node.id.right
              }] = [${a}, ${b || '0'}]`().declarations[0]
            );
          } else {
            throw new Error('Destructure number with number!');
          }
        }
      },
    },
  };
}

๐Ÿงช Try it out

<a href="https://twitter.com/share?ref_src=twsrc%5Etfw" class="twitter-share-button" data-show-count="false"

Tweet

๐Ÿ“ฆ Babel Plugin

npm version