fcs
2025-04-10 0163870d662dc5b7d69ceff977490ede594f7d82
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
152
153
154
import {parse} from '@babel/parser';
import {recursive as walk} from 'babel-walk';
import * as t from '@babel/types';
import detect from './globals';
 
const parseOptions = {
  allowReturnOutsideFunction: true,
  allowImportExportEverywhere: true,
};
 
/**
 * Mimic `with` as far as possible but at compile time
 *
 * @param obj The object part of a with expression
 * @param src The body of the with expression
 * @param exclude A list of variable names to explicitly exclude
 */
export default function addWith(
  obj: string,
  src: string,
  exclude: string[] = [],
) {
  // tslint:disable-next-line: no-parameter-reassignment
  obj = obj + '';
  // tslint:disable-next-line: no-parameter-reassignment
  src = src + '';
 
  let ast;
  try {
    ast = parse(src, parseOptions);
  } catch (e) {
    throw Object.assign(
      new Error('Error parsing body of the with expression'),
      {
        component: 'src',
        babylonError: e,
      },
    );
  }
  let objAst;
  try {
    objAst = parse(obj, parseOptions);
  } catch (e) {
    throw Object.assign(
      new Error('Error parsing object part of the with expression'),
      {
        component: 'obj',
        babylonError: e,
      },
    );
  }
  const excludeSet = new Set([
    'undefined',
    'this',
    ...exclude,
    ...detect(objAst).map((g) => g.name),
  ]);
 
  const vars = new Set(
    detect(ast)
      .map((global) => global.name)
      .filter((v) => !excludeSet.has(v)),
  );
 
  if (vars.size === 0) return src;
 
  let declareLocal = '';
  let local = 'locals_for_with';
  let result = 'result_of_with';
  if (t.isValidIdentifier(obj)) {
    local = obj;
  } else {
    while (vars.has(local) || excludeSet.has(local)) {
      local += '_';
    }
    declareLocal = `var ${local} = (${obj});`;
  }
  while (vars.has(result) || excludeSet.has(result)) {
    result += '_';
  }
 
  const args = [
    'this',
    ...Array.from(vars).map(
      (v) =>
        `${JSON.stringify(v)} in ${local} ?
        ${local}.${v} :
        typeof ${v} !== 'undefined' ? ${v} : undefined`,
    ),
  ];
 
  const unwrapped = unwrapReturns(ast, src, result);
 
  return `;
    ${declareLocal}
    ${unwrapped.before}
    (function (${Array.from(vars).join(', ')}) {
      ${unwrapped.body}
    }.call(${args.join(', ')}));
    ${unwrapped.after};`;
}
 
interface UnwrapReturnsState {
  hasReturn: boolean;
  source(node: t.Node): string;
  replace(node: t.Node, str: string): void;
}
const unwrapReturnsVisitors = walk<UnwrapReturnsState>({
  Function(_node, _state, _c) {
    // returns in these functions are not applicable
  },
 
  ReturnStatement(node, state) {
    state.hasReturn = true;
    let value = '';
    if (node.argument) {
      value = `value: (${state.source(node.argument)})`;
    }
    state.replace(node, `return {${value}};`);
  },
});
 
/**
 * Take a self calling function, and unwrap it such that return inside the function
 * results in return outside the function
 *
 * @param src    Some JavaScript code representing a self-calling function
 * @param result A temporary variable to store the result in
 */
function unwrapReturns(ast: t.Node, src: string, result: string) {
  const charArray = src.split('');
 
  const state: UnwrapReturnsState = {
    hasReturn: false,
    source(node) {
      return src.slice(node.start!, node.end!);
    },
    replace(node, str) {
      charArray.fill('', node.start!, node.end!);
      charArray[node.start!] = str;
    },
  };
 
  unwrapReturnsVisitors(ast, state);
 
  return {
    before: state.hasReturn ? `var ${result} = ` : '',
    body: charArray.join(''),
    after: state.hasReturn ? `;if (${result}) return ${result}.value` : '',
  };
}
 
module.exports = addWith;
module.exports.default = addWith;