diff --git a/src/convert.ts b/src/convert.ts index fdb09cd..1e420b9 100644 --- a/src/convert.ts +++ b/src/convert.ts @@ -35,6 +35,7 @@ interface ConvertConfig { node: ts.Node; parent?: ts.Node | null; inTypeMode?: boolean; + allowPattern?: boolean; ast: ts.SourceFile; additionalOptions: ConvertAdditionalOptions; } @@ -83,7 +84,11 @@ export default function convert(config: ConvertConfig): ESTreeNode | null { loc: nodeUtils.getLoc(node, ast) }; - function converter(child?: ts.Node, inTypeMode?: boolean): ESTreeNode | null { + function converter( + child?: ts.Node, + inTypeMode?: boolean, + allowPattern?: boolean + ): ESTreeNode | null { if (!child) { return null; } @@ -91,18 +96,28 @@ export default function convert(config: ConvertConfig): ESTreeNode | null { node: child, parent: node, inTypeMode, + allowPattern, ast, additionalOptions }); } + /** + * Converts a TypeScript node into an ESTree node. + * @param {ts.Node} child the child ts.Node + * @returns {ESTreeNode|null} the converted ESTree node + */ + function convertPattern(child?: ts.Node): ESTreeNode | null { + return converter(child, config.inTypeMode, true); + } + /** * Converts a TypeScript node into an ESTree node. * @param {ts.Node} child the child ts.Node * @returns {ESTreeNode|null} the converted ESTree node */ function convertChild(child?: ts.Node): ESTreeNode | null { - return converter(child, config.inTypeMode); + return converter(child, config.inTypeMode, false); } /** @@ -111,7 +126,7 @@ export default function convert(config: ConvertConfig): ESTreeNode | null { * @returns {ESTreeNode|null} the converted ESTree node */ function convertChildType(child?: ts.Node): ESTreeNode | null { - return converter(child, true); + return converter(child, true, false); } /** @@ -632,7 +647,7 @@ export default function convert(config: ConvertConfig): ESTreeNode | null { case SyntaxKind.ForOfStatement: { Object.assign(result, { type: SyntaxKind[node.kind], - left: convertChild(node.initializer), + left: convertPattern(node.initializer), right: convertChild(node.expression), body: convertChild(node.statement) }); @@ -691,7 +706,7 @@ export default function convert(config: ConvertConfig): ESTreeNode | null { case SyntaxKind.VariableDeclaration: { Object.assign(result, { type: AST_NODE_TYPES.VariableDeclarator, - id: convertChild(node.name), + id: convertPattern(node.name), init: convertChild(node.initializer) }); @@ -746,42 +761,11 @@ export default function convert(config: ConvertConfig): ESTreeNode | null { break; case SyntaxKind.ArrayLiteralExpression: { - const arrayAssignNode = nodeUtils.findAncestorOfKind( - node, - SyntaxKind.BinaryExpression - ); - const arrayIsInForOf = - node.parent && node.parent.kind === SyntaxKind.ForOfStatement; - const arrayIsInForIn = - node.parent && node.parent.kind === SyntaxKind.ForInStatement; - let arrayIsInAssignment; - - if (arrayAssignNode) { - if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { - arrayIsInAssignment = false; - } else if (node.parent.kind === SyntaxKind.CallExpression) { - arrayIsInAssignment = false; - } else if ( - nodeUtils.getBinaryExpressionType( - (arrayAssignNode as any).operatorToken - ) === AST_NODE_TYPES.AssignmentExpression - ) { - arrayIsInAssignment = - nodeUtils.findChildOfKind( - (arrayAssignNode as any).left, - SyntaxKind.ArrayLiteralExpression, - ast - ) === node || (arrayAssignNode as any).left === node; - } else { - arrayIsInAssignment = false; - } - } - // TypeScript uses ArrayLiteralExpression in destructuring assignment, too - if (arrayIsInAssignment || arrayIsInForOf || arrayIsInForIn) { + if (config.allowPattern) { Object.assign(result, { type: AST_NODE_TYPES.ArrayPattern, - elements: node.elements.map(convertChild) + elements: node.elements.map(convertPattern) }); } else { Object.assign(result, { @@ -793,43 +777,11 @@ export default function convert(config: ConvertConfig): ESTreeNode | null { } case SyntaxKind.ObjectLiteralExpression: { - const ancestorNode = nodeUtils.findFirstMatchingAncestor( - node, - parentNode => - parentNode.kind === SyntaxKind.BinaryExpression || - parentNode.kind === SyntaxKind.ArrowFunction - ); - const objectAssignNode = - ancestorNode && - ancestorNode.kind === SyntaxKind.BinaryExpression && - (ancestorNode as any).operatorToken.kind === SyntaxKind.FirstAssignment - ? ancestorNode - : null; - - let objectIsInAssignment = false; - - if (objectAssignNode) { - if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { - objectIsInAssignment = false; - } else if ((objectAssignNode as any).left === node) { - objectIsInAssignment = true; - } else if (node.parent.kind === SyntaxKind.CallExpression) { - objectIsInAssignment = false; - } else { - objectIsInAssignment = - nodeUtils.findChildOfKind( - (objectAssignNode as any).left, - SyntaxKind.ObjectLiteralExpression, - ast - ) === node; - } - } - // TypeScript uses ObjectLiteralExpression in destructuring assignment, too - if (objectIsInAssignment) { + if (config.allowPattern) { Object.assign(result, { type: AST_NODE_TYPES.ObjectPattern, - properties: node.properties.map(convertChild) + properties: node.properties.map(convertPattern) }); } else { Object.assign(result, { @@ -837,7 +789,6 @@ export default function convert(config: ConvertConfig): ESTreeNode | null { properties: node.properties.map(convertChild) }); } - break; } @@ -845,7 +796,11 @@ export default function convert(config: ConvertConfig): ESTreeNode | null { Object.assign(result, { type: AST_NODE_TYPES.Property, key: convertChild(node.name), - value: convertChild(node.initializer), + value: converter( + node.initializer, + config.inTypeMode, + config.allowPattern + ), computed: nodeUtils.isComputedProperty(node.name), method: false, shorthand: false, @@ -860,7 +815,7 @@ export default function convert(config: ConvertConfig): ESTreeNode | null { key: convertChild(node.name), value: { type: AST_NODE_TYPES.AssignmentPattern, - left: convertChild(node.name), + left: convertPattern(node.name), right: convertChild(node.objectAssignmentInitializer), loc: result.loc, range: result.range @@ -1208,7 +1163,7 @@ export default function convert(config: ConvertConfig): ESTreeNode | null { case SyntaxKind.ArrayBindingPattern: Object.assign(result, { type: AST_NODE_TYPES.ArrayPattern, - elements: node.elements.map(convertChild) + elements: node.elements.map(convertPattern) }); break; @@ -1219,7 +1174,7 @@ export default function convert(config: ConvertConfig): ESTreeNode | null { case SyntaxKind.ObjectBindingPattern: Object.assign(result, { type: AST_NODE_TYPES.ObjectPattern, - properties: node.elements.map(convertChild) + properties: node.elements.map(convertPattern) }); break; @@ -1387,50 +1342,19 @@ export default function convert(config: ConvertConfig): ESTreeNode | null { // Patterns + case SyntaxKind.SpreadAssignment: case SyntaxKind.SpreadElement: { - let type = AST_NODE_TYPES.SpreadElement; - - if ( - node.parent && - node.parent.parent && - node.parent.parent.kind === SyntaxKind.BinaryExpression - ) { - if ((node.parent.parent as ts.BinaryExpression).left === node.parent) { - type = AST_NODE_TYPES.RestElement; - } else if ( - (node.parent.parent as ts.BinaryExpression).right === node.parent - ) { - type = AST_NODE_TYPES.SpreadElement; - } - } - - Object.assign(result, { - type, - argument: convertChild(node.expression) - }); - break; - } - case SyntaxKind.SpreadAssignment: { - let type = AST_NODE_TYPES.SpreadElement; - - if ( - node.parent && - node.parent.parent && - node.parent.parent.kind === SyntaxKind.BinaryExpression - ) { - if ((node.parent.parent as ts.BinaryExpression).right === node.parent) { - type = AST_NODE_TYPES.SpreadElement; - } else if ( - (node.parent.parent as ts.BinaryExpression).left === node.parent - ) { - type = AST_NODE_TYPES.RestElement; - } + if (config.allowPattern) { + Object.assign(result, { + type: AST_NODE_TYPES.RestElement, + argument: convertPattern(node.expression) + }); + } else { + Object.assign(result, { + type: AST_NODE_TYPES.SpreadElement, + argument: convertChild(node.expression) + }); } - - Object.assign(result, { - type, - argument: convertChild(node.expression) - }); break; } @@ -1788,44 +1712,21 @@ export default function convert(config: ConvertConfig): ESTreeNode | null { (result as any).expressions.push(right); } } else { + const type = nodeUtils.getBinaryExpressionType(node.operatorToken); Object.assign(result, { - type: nodeUtils.getBinaryExpressionType(node.operatorToken), + type, operator: nodeUtils.getTextForTokenKind(node.operatorToken.kind), - left: convertChild(node.left), + left: converter( + node.left, + config.inTypeMode, + type === AST_NODE_TYPES.AssignmentExpression + ), right: convertChild(node.right) }); // if the binary expression is in a destructured array, switch it if (result.type === AST_NODE_TYPES.AssignmentExpression) { - const upperArrayNode = nodeUtils.findFirstMatchingAncestor( - node, - parent => - parent.kind === SyntaxKind.ArrayLiteralExpression || - parent.kind === SyntaxKind.ObjectLiteralExpression - ); - const upperArrayAssignNode = - upperArrayNode && - nodeUtils.findAncestorOfKind( - upperArrayNode, - SyntaxKind.BinaryExpression - ); - - let upperArrayIsInAssignment; - - if (upperArrayAssignNode) { - if ((upperArrayAssignNode as any).left === upperArrayNode) { - upperArrayIsInAssignment = true; - } else { - upperArrayIsInAssignment = - nodeUtils.findChildOfKind( - (upperArrayAssignNode as any).left, - SyntaxKind.ArrayLiteralExpression, - ast - ) === upperArrayNode; - } - } - - if (upperArrayIsInAssignment) { + if (config.allowPattern) { delete (result as any).operator; result.type = AST_NODE_TYPES.AssignmentPattern; } diff --git a/src/node-utils.ts b/src/node-utils.ts index 75b8548..b25ad78 100644 --- a/src/node-utils.ts +++ b/src/node-utils.ts @@ -100,33 +100,6 @@ const TOKEN_TO_TEXT: { readonly [P in ts.SyntaxKind]?: string } = { [SyntaxKind.ImportKeyword]: 'import' }; -/** - * Find the first matching child based on the given sourceFile and predicate function. - * @param {ts.Node} node The current ts.Node - * @param {ts.SourceFile} sourceFile The full AST source file - * @param {Function} predicate The predicate function to apply to each checked child - * @returns {ts.Node|undefined} a matching child ts.Node - */ -function findFirstMatchingChild( - node: ts.Node, - sourceFile: ts.SourceFile, - predicate: (node: ts.Node) => boolean -): ts.Node | undefined { - const children = node.getChildren(sourceFile); - for (let i = 0; i < children.length; i++) { - const child = children[i]; - if (child && predicate(child)) { - return child; - } - - const grandChild = findFirstMatchingChild(child, sourceFile, predicate); - if (grandChild) { - return grandChild; - } - } - return undefined; -} - export default { /** * Expose the enum of possible TSNode `kind`s. @@ -148,9 +121,7 @@ export default { getTSNodeAccessibility, findNextToken, findFirstMatchingToken, - findChildOfKind, findFirstMatchingAncestor, - findAncestorOfKind, hasJSXAncestor, unescapeStringLiteralText, isComputedProperty, @@ -460,21 +431,6 @@ function findFirstMatchingToken( return undefined; } -/** - * Finds the first child ts.Node which matches the given kind - * @param {ts.Node} node The parent ts.Node - * @param {number} kind The ts.Node kind to match against - * @param {ts.SourceFile} sourceFile The full AST source file - * @returns {ts.Node|undefined} a matching ts.Node - */ -function findChildOfKind( - node: ts.Node, - kind: number, - sourceFile: ts.SourceFile -): ts.Node | undefined { - return findFirstMatchingChild(node, sourceFile, child => child.kind === kind); -} - /** * Find the first matching ancestor based on the given predicate function. * @param {ts.Node} node The current ts.Node @@ -494,19 +450,6 @@ function findFirstMatchingAncestor( return undefined; } -/** - * Finds the first parent ts.Node which matches the given kind - * @param {ts.Node} node The current ts.Node - * @param {ts.SyntaxKind} kind The ts.Node kind to match against - * @returns {ts.Node|undefined} a matching parent ts.Node - */ -function findAncestorOfKind( - node: ts.Node, - kind: ts.SyntaxKind -): ts.Node | undefined { - return findFirstMatchingAncestor(node, parent => parent.kind === kind); -} - /** * Returns true if a given ts.Node has a JSX token within its hierarchy * @param {ts.Node} node ts.Node to be checked diff --git a/tests/ast-alignment/fixtures-to-test.ts b/tests/ast-alignment/fixtures-to-test.ts index bfdda73..d6182fb 100644 --- a/tests/ast-alignment/fixtures-to-test.ts +++ b/tests/ast-alignment/fixtures-to-test.ts @@ -196,19 +196,6 @@ tester.addFixturePatternConfig('javascript/experimentalOptionalCatchBinding'); tester.addFixturePatternConfig('javascript/for'); tester.addFixturePatternConfig('javascript/forIn', { ignore: [ - /** - * Error: AST difference - * ts-estree: ArrayPattern - * babel: ArrayExpression - */ - 'for-in-array', - 'for-in-with-rest', - /** - * Error: AST difference - * ts-estree: AssignmentExpression - * babel: AssignmentPattern - */ - 'for-in-with-bare-assigment', /** * Babel correctly errors on this file, and we can report on it via: * TS 1189 (ts 3.2) "The variable declaration of a 'for...in' statement cannot have an initializer." @@ -222,17 +209,7 @@ tester.addFixturePatternConfig('javascript/forIn', { ] }); -tester.addFixturePatternConfig('javascript/forOf', { - ignore: [ - /** - * Error: AST difference - * ts-estree: ArrayPattern - * babel: ArrayExpression - */ - 'for-of-array', - 'for-of-with-rest' - ] -}); +tester.addFixturePatternConfig('javascript/forOf'); tester.addFixturePatternConfig('javascript/generators'); tester.addFixturePatternConfig('javascript/globalReturn'); tester.addFixturePatternConfig('javascript/importMeta'); @@ -279,14 +256,7 @@ tester.addFixturePatternConfig('javascript/regex'); tester.addFixturePatternConfig('javascript/regexUFlag'); tester.addFixturePatternConfig('javascript/regexYFlag'); tester.addFixturePatternConfig('javascript/restParams'); -tester.addFixturePatternConfig('javascript/spread', { - ignore: [ - /** - * Error: AST difference - */ - 'complex-spread' - ] -}); +tester.addFixturePatternConfig('javascript/spread'); tester.addFixturePatternConfig('javascript/unicodeCodePointEscapes'); /* ================================================== */ diff --git a/tests/lib/__snapshots__/javascript.ts.snap b/tests/lib/__snapshots__/javascript.ts.snap index 22aa5f7..f6c0c50 100644 --- a/tests/lib/__snapshots__/javascript.ts.snap +++ b/tests/lib/__snapshots__/javascript.ts.snap @@ -51971,7 +51971,7 @@ Object { 1, 23, ], - "type": "ObjectPattern", + "type": "ObjectExpression", }, "loc": Object { "end": Object { @@ -88925,7 +88925,7 @@ Object { 10, 12, ], - "type": "ArrayPattern", + "type": "ArrayExpression", }, "type": "ForInStatement", }, @@ -92385,7 +92385,6 @@ Object { "line": 1, }, }, - "operator": "=", "range": Array [ 5, 10, @@ -92409,7 +92408,7 @@ Object { "type": "Literal", "value": 0, }, - "type": "AssignmentExpression", + "type": "AssignmentPattern", }, "loc": Object { "end": Object { @@ -93559,14 +93558,14 @@ Object { 14, 25, ], - "type": "SpreadElement", + "type": "RestElement", }, ], "range": Array [ 5, 27, ], - "type": "ObjectExpression", + "type": "ObjectPattern", }, "loc": Object { "end": Object { @@ -94442,7 +94441,7 @@ Object { 14, 16, ], - "type": "ArrayPattern", + "type": "ArrayExpression", }, "type": "ForOfStatement", }, @@ -96800,14 +96799,14 @@ Object { 14, 25, ], - "type": "SpreadElement", + "type": "RestElement", }, ], "range": Array [ 5, 27, ], - "type": "ObjectExpression", + "type": "ObjectPattern", }, "loc": Object { "end": Object { @@ -132642,7 +132641,7 @@ Object { 11, 20, ], - "type": "SpreadElement", + "type": "RestElement", }, ], "range": Array [ @@ -132844,14 +132843,14 @@ Object { 45, 55, ], - "type": "SpreadElement", + "type": "RestElement", }, ], "range": Array [ 43, 57, ], - "type": "ObjectExpression", + "type": "ObjectPattern", }, Object { "argument": Object { @@ -132886,7 +132885,7 @@ Object { 59, 63, ], - "type": "SpreadElement", + "type": "RestElement", }, ], "loc": Object { @@ -133046,14 +133045,14 @@ Object { 74, 78, ], - "type": "SpreadElement", + "type": "RestElement", }, ], "range": Array [ 69, 80, ], - "type": "ObjectExpression", + "type": "ObjectPattern", }, }, Object { @@ -133089,14 +133088,14 @@ Object { 82, 90, ], - "type": "SpreadElement", + "type": "RestElement", }, ], "range": Array [ 37, 92, ], - "type": "ObjectExpression", + "type": "ObjectPattern", }, }, Object {