From dabc07a6bbc3cec1437d442eed571ab25298ed66 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Thu, 4 Apr 2019 07:38:33 +0200 Subject: [PATCH] Add `extractAssignedNames` --- README.md | 33 +++- src/attachScopes.ts | 50 +----- src/extractAssignedNames.ts | 46 ++++++ src/index.ts | 1 + src/pluginutils.d.ts | 3 + test/extractAssignedNames.test.ts | 263 ++++++++++++++++++++++++++++++ 6 files changed, 344 insertions(+), 52 deletions(-) create mode 100644 src/extractAssignedNames.ts create mode 100644 test/extractAssignedNames.test.ts diff --git a/README.md b/README.md index 49e19f7..3561b97 100644 --- a/README.md +++ b/README.md @@ -37,16 +37,12 @@ See [rollup-plugin-inject](https://github.com/rollup/rollup-plugin-inject) or [r ```js import { attachScopes } from 'rollup-pluginutils'; -import { parse } from 'acorn'; import { walk } from 'estree-walker'; export default function myPlugin ( options = {} ) { return { transform ( code ) { - const ast = parse( code, { - ecmaVersion: 6, - sourceType: 'module' - }); + const ast = this.parse( code ); let scope = attachScopes( ast, 'scope' ); @@ -128,6 +124,33 @@ Outputs the string ES module source: */ ``` +### extractAssignedNames + +Extract the names of all assignment targets from patterns. + +```js +import { extractAssignedNames } from 'rollup-pluginutils'; +import { walk } from 'estree-walker'; + +export default function myPlugin ( options = {} ) { + return { + transform ( code ) { + const ast = this.parse( code ); + + walk( ast, { + enter ( node ) { + if ( node.type === 'VariableDeclarator' ) { + const declaredNames = extractAssignedNames(node.id); + // do something with the declared names + // e.g. for `const {x, y: z} = ... => declaredNames = ['x', 'z'] + } + } + }); + } + }; +} +``` + ## License diff --git a/src/attachScopes.ts b/src/attachScopes.ts index a40b0c4..c633035 100644 --- a/src/attachScopes.ts +++ b/src/attachScopes.ts @@ -1,4 +1,5 @@ import { Node, walk } from 'estree-walker'; +import extractAssignedNames from './extractAssignedNames'; import { AttachedScope, AttachScopes } from './pluginutils'; const blockDeclarations = { @@ -6,51 +7,6 @@ const blockDeclarations = { let: true }; -interface Extractors { - [key: string]: (names: Array, param: Node) => void; -} - -const extractors: Extractors = { - Literal(names: Array, param: Node) { - names.push(param.value as string); - }, - - Identifier(names: Array, param: Node) { - names.push(param.name); - }, - - ObjectPattern(names: Array, param: Node) { - param.properties.forEach((prop: Node) => { - if (prop.type === 'RestElement') { - extractors.RestElement(names, prop); - } else { - extractors[(prop.value || prop.key).type](names, prop.value || prop.key); - } - }); - }, - - ArrayPattern(names: Array, param: Node) { - param.elements.forEach((element: Node) => { - if (element) extractors[element.type](names, element); - }); - }, - - RestElement(names: Array, param: Node) { - extractors[param.argument.type](names, param.argument); - }, - - AssignmentPattern(names: Array, param: Node) { - return extractors[param.left.type](names, param.left); - } -}; - -function extractNames(param: Node): Array { - const names: Array = []; - - extractors[param.type](names, param); - return names; -} - interface ScopeOptions { parent?: AttachedScope; block?: boolean; @@ -70,7 +26,7 @@ class Scope implements AttachedScope { if (options.params) { options.params.forEach(param => { - extractNames(param).forEach(name => { + extractAssignedNames(param).forEach(name => { this.declarations[name] = true; }); }); @@ -83,7 +39,7 @@ class Scope implements AttachedScope { // is a block scope, so we need to go up this.parent!.addDeclaration(node, isBlockDeclaration, isVar); } else if (node.id) { - extractNames(node.id).forEach(name => { + extractAssignedNames(node.id).forEach(name => { this.declarations[name] = true; }); } diff --git a/src/extractAssignedNames.ts b/src/extractAssignedNames.ts new file mode 100644 index 0000000..996f41a --- /dev/null +++ b/src/extractAssignedNames.ts @@ -0,0 +1,46 @@ +import { Node } from 'estree-walker'; + +interface Extractors { + [key: string]: (names: Array, param: Node) => void; +} + +const extractors: Extractors = { + ArrayPattern(names: Array, param: Node) { + for (const element of param.elements) { + if (element) extractors[element.type](names, element); + } + }, + + AssignmentPattern(names: Array, param: Node) { + extractors[param.left.type](names, param.left); + }, + + Identifier(names: Array, param: Node) { + names.push(param.name); + }, + + MemberExpression() {}, + + ObjectPattern(names: Array, param: Node) { + for (const prop of param.properties) { + if (prop.type === 'RestElement') { + extractors.RestElement(names, prop); + } else { + extractors[prop.value.type](names, prop.value); + } + } + }, + + RestElement(names: Array, param: Node) { + extractors[param.argument.type](names, param.argument); + } +}; + +const extractAssignedNames = function extractAssignedNames(param: Node): Array { + const names: Array = []; + + extractors[param.type](names, param); + return names; +}; + +export { extractAssignedNames as default }; diff --git a/src/index.ts b/src/index.ts index 5351a8b..dc4600e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,3 +3,4 @@ export { default as attachScopes } from './attachScopes'; export { default as createFilter } from './createFilter'; export { default as makeLegalIdentifier } from './makeLegalIdentifier'; export { default as dataToEsm } from './dataToEsm'; +export { default as extractAssignedNames } from './extractAssignedNames'; diff --git a/src/pluginutils.d.ts b/src/pluginutils.d.ts index 92735e6..6bb4448 100644 --- a/src/pluginutils.d.ts +++ b/src/pluginutils.d.ts @@ -33,3 +33,6 @@ export const makeLegalIdentifier: MakeLegalIdentifier; export type DataToEsm = (data: any, options?: DataToEsmOptions) => string; export const dataToEsm: DataToEsm; + +export type ExtractAssignedNames = (param: Node) => Array; +export const extractAssignedNames: ExtractAssignedNames; diff --git a/test/extractAssignedNames.test.ts b/test/extractAssignedNames.test.ts new file mode 100644 index 0000000..2b3bdfb --- /dev/null +++ b/test/extractAssignedNames.test.ts @@ -0,0 +1,263 @@ +import { extractAssignedNames } from '../src/index'; + +describe('extractAssignedNames', function() { + it('extracts an Identifier', function() { + const node = { + type: 'Identifier', + start: 6, + end: 7, + name: 'x' + }; + + expect(extractAssignedNames(node)).toEqual(['x']); + }); + + it('extracts from array patterns', function() { + const node = { + type: 'ArrayPattern', + start: 6, + end: 29, + elements: [ + null, + { + type: 'Identifier', + start: 9, + end: 10, + name: 'a' + }, + { + type: 'AssignmentPattern', + start: 12, + end: 17, + left: { + type: 'Identifier', + start: 12, + end: 13, + name: 'b' + }, + right: { + type: 'Identifier', + start: 16, + end: 17, + name: 'c' + } + }, + { + type: 'ArrayPattern', + start: 19, + end: 22, + elements: [ + { + type: 'Identifier', + start: 20, + end: 21, + name: 'd' + } + ] + }, + { + type: 'RestElement', + start: 24, + end: 28, + argument: { + type: 'Identifier', + start: 27, + end: 28, + name: 'e' + } + } + ] + }; + + expect(extractAssignedNames(node)).toEqual(['a', 'b', 'd', 'e']); + }); + + it('extracts from object patterns', function() { + const node = { + type: 'ObjectPattern', + start: 6, + end: 42, + properties: [ + { + type: 'Property', + start: 7, + end: 8, + method: false, + shorthand: true, + computed: false, + key: { + type: 'Identifier', + start: 7, + end: 8, + name: 'a' + }, + kind: 'init', + value: { + type: 'Identifier', + start: 7, + end: 8, + name: 'a' + } + }, + { + type: 'Property', + start: 10, + end: 14, + method: false, + shorthand: false, + computed: false, + key: { + type: 'Identifier', + start: 10, + end: 11, + name: 'b' + }, + value: { + type: 'Identifier', + start: 13, + end: 14, + name: 'c' + }, + kind: 'init' + }, + { + type: 'Property', + start: 16, + end: 29, + method: false, + shorthand: false, + computed: false, + key: { + type: 'Identifier', + start: 16, + end: 17, + name: 'd' + }, + value: { + type: 'ObjectPattern', + start: 19, + end: 29, + properties: [ + { + type: 'Property', + start: 20, + end: 28, + method: false, + shorthand: false, + computed: false, + key: { + type: 'Identifier', + start: 20, + end: 21, + name: 'e' + }, + value: { + type: 'AssignmentPattern', + start: 23, + end: 28, + left: { + type: 'Identifier', + start: 23, + end: 24, + name: 'f' + }, + right: { + type: 'Identifier', + start: 27, + end: 28, + name: 'g' + } + }, + kind: 'init' + } + ] + }, + kind: 'init' + }, + { + type: 'Property', + start: 31, + end: 35, + method: false, + shorthand: false, + computed: false, + key: { + type: 'Literal', + start: 31, + end: 32, + value: 1, + raw: '1' + }, + value: { + type: 'Identifier', + start: 34, + end: 35, + name: 'h' + }, + kind: 'init' + }, + { + type: 'RestElement', + start: 37, + end: 41, + argument: { + type: 'Identifier', + start: 40, + end: 41, + name: 'i' + } + } + ] + }; + + expect(extractAssignedNames(node)).toEqual(['a', 'c', 'f', 'h', 'i']); + }); + + it('ignores updated member expressions', function() { + const node = { + type: 'ArrayPattern', + start: 0, + end: 11, + elements: [ + { + type: 'MemberExpression', + start: 1, + end: 4, + object: { + type: 'Identifier', + start: 1, + end: 2, + name: 'a' + }, + property: { + type: 'Identifier', + start: 3, + end: 4, + name: 'b' + }, + computed: false + }, + { + type: 'MemberExpression', + start: 6, + end: 10, + object: { + type: 'Identifier', + start: 6, + end: 7, + name: 'c' + }, + property: { + type: 'Identifier', + start: 8, + end: 9, + name: 'd' + }, + computed: true + } + ] + }; + + expect(extractAssignedNames(node)).toEqual([]); + }); +});