/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @flow strict * @format */ 'use strict'; import type {ESNode, Program} from 'hermes-estree'; import type {VisitorKeysType} from '../traverse/getVisitorKeys'; import {SimpleTransform} from '../transform/SimpleTransform'; // https://github.com/prettier/prettier/blob/d962466a828f8ef51435e3e8840178d90b7ec6cd/src/language-js/parse/postprocess/index.js#L161-L182 function transformChainExpression(node: ESNode): ESNode { switch (node.type) { case 'CallExpression': // $FlowExpectedError[cannot-spread-interface] return { ...node, type: 'OptionalCallExpression', callee: transformChainExpression(node.callee), }; case 'MemberExpression': // $FlowExpectedError[cannot-spread-interface] return { ...node, type: 'OptionalMemberExpression', object: transformChainExpression(node.object), }; // No default } return node; } export default function mutate( rootNode: Program, visitorKeys: ?VisitorKeysType, ): void { // Since we don't return the result of `transform` we need to be careful not to replace the Program root node. SimpleTransform.transform(rootNode, { transform(node): ESNode | null { // prettier fully expects the parent pointers are NOT set and // certain cases can crash due to prettier infinite-looping // whilst naively traversing the parent property // https://github.com/prettier/prettier/issues/11793 // Note: Only needed for prettier V2, this is supported in V3 if (node.parent) { // $FlowExpectedError[cannot-write] delete node.parent; } // prettier currently relies on the AST being in the old-school, deprecated AST format for optional chaining // so we have to apply their transform to our AST so it can actually format it. // Note: Only needed for prettier V2, this is supported in V3 if (node.type === 'ChainExpression') { return transformChainExpression(node.expression); } // Prettier currently relies on comparing the `node` vs `node.value` start positions to know if an // `ObjectTypeProperty` is a method or not (instead of using the `node.method` boolean). To correctly print // the node when its not a method we need the start position to be different from the `node.value`s start // position. if (node.type === 'ObjectTypeProperty') { if ( node.method === false && node.kind === 'init' && node.range[0] === 1 && node.value.range[0] === 1 ) { // $FlowExpectedError[cannot-write] // $FlowExpectedError[cannot-spread-interface] node.value = { ...node.value, range: [2, node.value.range[1]], }; } return node; } // Prettier currently relies on comparing the the start positions to know if the import/export specifier should have a // rename (eg `Name` vs `Name as Name`) when the name is exactly the same // So we need to ensure that the range is always the same to avoid the useless code printing if (node.type === 'ImportSpecifier') { if (node.local.name === node.imported.name) { if (node.local.range == null) { // for our TS-ast printing which has no locs // $FlowExpectedError[cannot-write] node.local.range = [0, 0]; } // $FlowExpectedError[cannot-write] node.imported.range = [...node.local.range]; } return node; } if (node.type === 'ExportSpecifier') { if (node.local.name === node.exported.name) { if (node.local.range == null) { // for our TS-ast printing which has no locs // $FlowExpectedError[cannot-write] node.local.range = [0, 0]; } // $FlowExpectedError[cannot-write] node.exported.range = [...node.local.range]; } return node; } return node; }, visitorKeys, }); }