222
schangxiang@126.com
2025-06-13 6a8393408d8cefcea02b7a598967de8dc1e565c2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
'use strict';
 
var estraverse = require('estraverse');
var syntax = estraverse.Syntax;
var escope = require('escope');
var escallmatch = require('escallmatch');
var AssertionVisitor = require('./assertion-visitor');
var Transformation = require('./transformation');
var EspowerError = require('./espower-error');
var typeName = require('type-name');
var find = require('array-find');
var isSpreadElement = function (node) {
    return node.type === 'SpreadElement';
};
 
 
function Instrumentor (options) {
    verifyOptionPrerequisites(options);
    this.options = options;
    this.matchers = options.patterns.map(function (pattern) { return escallmatch(pattern, options); });
}
 
Instrumentor.prototype.instrument = function (ast) {
    return estraverse.replace(ast, this.createVisitor(ast));
};
 
Instrumentor.prototype.createVisitor = function (ast) {
    verifyAstPrerequisites(ast, this.options);
    var that = this;
    var assertionVisitor;
    var storage = {};
    var skipping = false;
    var escopeOptions = {
        ecmaVersion: this.options.ecmaVersion,
        sourceType: this.options.sourceType
    };
    if (this.options.visitorKeys) {
        escopeOptions.childVisitorKeys = this.options.visitorKeys;
    }
    var scopeManager = escope.analyze(ast, escopeOptions);
    var globalScope = scopeManager.acquire(ast);
    var scopeStack = [];
    scopeStack.push(globalScope);
    var transformation = new Transformation();
    var visitor = {
        enter: function (currentNode, parentNode) {
            if (/Function/.test(currentNode.type)) {
                scopeStack.push(scopeManager.acquire(currentNode));
            }
            var controller = this;
            var path = controller.path();
            var currentKey = path ? path[path.length - 1] : null;
            if (assertionVisitor) {
                if (assertionVisitor.toBeSkipped(controller)) {
                    skipping = true;
                    return controller.skip();
                }
                if (!assertionVisitor.isCapturingArgument() && !isCalleeOfParentCallExpression(parentNode, currentKey)) {
                    return assertionVisitor.enterArgument(controller);
                }
            } else if (currentNode.type === syntax.CallExpression) {
                var matcher = find(that.matchers, function (matcher) { return matcher.test(currentNode); });
                if (matcher) {
                    // skip modifying argument if SpreadElement appears immediately beneath assert
                    if (currentNode.arguments.some(isSpreadElement)) {
                        skipping = true;
                        return controller.skip();
                    }
                    // entering target assertion
                    assertionVisitor = new AssertionVisitor(matcher, Object.assign({
                        storage: storage,
                        transformation: transformation,
                        globalScope: globalScope,
                        scopeStack: scopeStack
                    }, that.options));
                    assertionVisitor.enter(controller);
                    return undefined;
                }
            }
            return undefined;
        },
        leave: function (currentNode, parentNode) {
            try {
                var controller = this;
                var resultTree = currentNode;
                var path = controller.path();
                var espath = path ? path.join('/') : '';
                if (transformation.isTarget(espath)) {
                    transformation.apply(espath, resultTree);
                    return resultTree;
                }
                if (!assertionVisitor) {
                    return undefined;
                }
                if (skipping) {
                    skipping = false;
                    return undefined;
                }
                if (assertionVisitor.isLeavingAssertion(controller)) {
                    assertionVisitor.leave(controller);
                    assertionVisitor = null;
                    return undefined;
                }
                if (!assertionVisitor.isCapturingArgument()) {
                    return undefined;
                }
                if (assertionVisitor.toBeCaptured(controller)) {
                    resultTree = assertionVisitor.captureNode(controller);
                }
                if (assertionVisitor.isLeavingArgument(controller)) {
                    return assertionVisitor.leaveArgument(resultTree);
                }
                return resultTree;
            } finally {
                if (/Function/.test(currentNode.type)) {
                    scopeStack.pop();
                }
            }
        }
    };
    if (this.options.visitorKeys) {
        visitor.keys = this.options.visitorKeys;
    }
    return visitor;
};
 
function isCalleeOfParentCallExpression (parentNode, currentKey) {
    return parentNode.type === syntax.CallExpression && currentKey === 'callee';
}
 
function verifyAstPrerequisites (ast, options) {
    var errorMessage;
    if (typeof ast.loc === 'undefined') {
        errorMessage = 'ECMAScript AST should contain location information.';
        if (options.path) {
            errorMessage += ' path: ' + options.path;
        }
        throw new EspowerError(errorMessage, verifyAstPrerequisites);
    }
}
 
function verifyOptionPrerequisites (options) {
    if (options.destructive === false) {
        throw new EspowerError('options.destructive is deprecated and always treated as destructive:true', verifyOptionPrerequisites);
    }
    if (typeName(options.patterns) !== 'Array') {
        throw new EspowerError('options.patterns should be an array.', verifyOptionPrerequisites);
    }
}
 
module.exports = Instrumentor;