1// Copyright 2020 the V8 project authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5/** 6 * @fileoverview Mutator 7 */ 8'use strict'; 9 10const babelTraverse = require('@babel/traverse').default; 11const babelTypes = require('@babel/types'); 12 13class Mutator { 14 get visitor() { 15 return null; 16 } 17 18 _traverse(ast, visitor) { 19 let oldEnter = null; 20 if (Object.prototype.hasOwnProperty.call(visitor, 'enter')) { 21 oldEnter = visitor['enter']; 22 } 23 24 // Transparently skip nodes that are marked. 25 visitor['enter'] = (path) => { 26 if (this.shouldSkip(path.node)) { 27 path.skip(); 28 return; 29 } 30 31 if (oldEnter) { 32 oldEnter(path); 33 } 34 } 35 36 babelTraverse(ast, visitor); 37 } 38 39 mutate(source) { 40 if (Array.isArray(this.visitor)) { 41 for (const visitor of this.visitor) { 42 this._traverse(source.ast, visitor); 43 } 44 } else { 45 this._traverse(source.ast, this.visitor); 46 } 47 } 48 49 get _skipPropertyName() { 50 return '__skip' + this.constructor.name; 51 } 52 53 shouldSkip(node) { 54 return Boolean(node[this._skipPropertyName]); 55 } 56 57 skipMutations(node) { 58 // Mark a node to skip further mutations of the same kind. 59 if (Array.isArray(node)) { 60 for (const item of node) { 61 item[this._skipPropertyName] = true; 62 } 63 } else { 64 node[this._skipPropertyName] = true; 65 } 66 67 return node; 68 } 69 70 insertBeforeSkip(path, node) { 71 this.skipMutations(node); 72 path.insertBefore(node); 73 } 74 75 insertAfterSkip(path, node) { 76 this.skipMutations(node); 77 path.insertAfter(node); 78 } 79 80 replaceWithSkip(path, node) { 81 this.skipMutations(node); 82 path.replaceWith(node); 83 } 84 85 replaceWithMultipleSkip(path, node) { 86 this.skipMutations(node); 87 path.replaceWithMultiple(node); 88 } 89 90 annotate(node, message) { 91 babelTypes.addComment( 92 node, 'leading', ` ${this.constructor.name}: ${message} `); 93 } 94} 95 96module.exports = { 97 Mutator: Mutator, 98} 99