webfuscator 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"ast.js","sourceRoot":"","sources":["../../src/utils/ast.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ast.js","sourceRoot":"","sources":["../../src/utils/ast.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webfuscator",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "JavaScript obfuscator built on Babel",
5
5
  "keywords": [
6
6
  "babel",
package/src/index.ts CHANGED
@@ -1,2 +1,7 @@
1
1
  export { obfuscate } from 'src/obfuscator'
2
- export type { ManglePropertiesOptions, ObfuscatorOptions, PropertyNameGenerator } from 'src/options'
2
+ export type {
3
+ ManglePropertiesOptions,
4
+ ObfuscatorOptions,
5
+ PackOptions,
6
+ PropertyNameGenerator,
7
+ } from 'src/options'
package/src/obfuscator.ts CHANGED
@@ -7,7 +7,6 @@ import type { ObfuscatorOptions, TransformContext, TransformName } from 'src/opt
7
7
  import { dotToBracket } from 'src/preparation/dot-to-bracket'
8
8
  import { expandDestructuring } from 'src/preparation/expand-destructuring'
9
9
  import { expandTemplateLiterals } from 'src/preparation/expand-template-literals'
10
- import { rewriteClassFields } from 'src/preparation/rewrite-class-fields'
11
10
  import { splitSequenceExpressions } from 'src/preparation/split-sequence-expressions'
12
11
  import { splitVariableDeclarations } from 'src/preparation/split-variable-declarations'
13
12
  import { stringifyObjectKeys } from 'src/preparation/stringify-object-keys'
@@ -34,6 +33,7 @@ import { nullishCoalescingToTernary } from 'src/transforms/nullish-coalescing-to
34
33
  import { numbersToStrings } from 'src/transforms/numbers-to-strings'
35
34
  import { objectMethodToProperty } from 'src/transforms/object-method-to-property'
36
35
  import { optionalChainingToTernary } from 'src/transforms/optional-chaining-to-ternary'
36
+ import { pack } from 'src/transforms/pack'
37
37
  import { packDeclarationsIntoParameters } from 'src/transforms/pack-declarations-into-parameters'
38
38
  import { removeAnonymousFunctionNames } from 'src/transforms/remove-anonymous-function-names'
39
39
  import { removeUnreachableCode } from 'src/transforms/remove-unreachable-code'
@@ -61,13 +61,12 @@ type TransformFn = (ast: File, ctx: TransformContext) => boolean
61
61
  type Phase = readonly [name: string, fn: TransformFn]
62
62
  type ConfigurablePhase = readonly [name: TransformName, fn: TransformFn]
63
63
 
64
- // Mangling must see quoted access and class fields before preparation erases
65
- // those shapes. It is opt-in because outside code may use the same properties.
64
+ // Mangling must see quoted access before preparation erases that distinction.
65
+ // It is opt-in because outside code may use the same properties.
66
66
  const BEFORE_PREPARATION: readonly ConfigurablePhase[] = [['mangleProperties', mangleProperties]]
67
67
 
68
68
  // Shape-normalizing passes. Always run; never exposed in `options.transforms`.
69
69
  const PREPARATION: readonly Phase[] = [
70
- ['rewriteClassFields', asBoolean(rewriteClassFields)],
71
70
  ['expandDestructuring', asBoolean(expandDestructuring)],
72
71
  ['wrapSingleStatements', asBoolean(wrapSingleStatements)],
73
72
  ['splitSequenceExpressions', asBoolean(splitSequenceExpressions)],
@@ -137,6 +136,11 @@ const POST_PROCESS: readonly ConfigurablePhase[] = [
137
136
  // Renaming runs last because later transforms depend on recognizable bindings.
138
137
  const RENAME: readonly ConfigurablePhase[] = [['renameIdentifiers', renameIdentifiers]]
139
138
 
139
+ // Packing serializes the finished program to a string and rebuilds it through
140
+ // the Function constructor. It must see final names and shapes, so it runs after
141
+ // every other rewrite, including renaming.
142
+ const PACK: readonly ConfigurablePhase[] = [['pack', pack]]
143
+
140
144
  /** Parses, transforms, and prints JavaScript without mutating external state. */
141
145
  export function obfuscate(code: string, options: ObfuscatorOptions = {}): string {
142
146
  const log = options.verbose ? makeLogger() : null
@@ -187,6 +191,13 @@ export function obfuscate(code: string, options: ObfuscatorOptions = {}): string
187
191
  run(log, name, () => fn(ast, ctxFor(name, options, baseCtx)))
188
192
  }
189
193
 
194
+ for (const [name, fn] of PACK) {
195
+ if (!isEnabled(name, options)) {
196
+ continue
197
+ }
198
+ run(log, name, () => fn(ast, ctxFor(name, options, baseCtx)))
199
+ }
200
+
190
201
  return run(
191
202
  log,
192
203
  'generate',
@@ -213,6 +224,9 @@ function ctxFor(
213
224
  if (name === 'mangleProperties' && entry && typeof entry === 'object') {
214
225
  return { ...context, mangleProperties: entry }
215
226
  }
227
+ if (name === 'pack' && entry && typeof entry === 'object') {
228
+ return { ...context, pack: entry }
229
+ }
216
230
  return context
217
231
  }
218
232
 
package/src/options.ts CHANGED
@@ -37,6 +37,7 @@ export type TransformName =
37
37
  | 'numbersToStrings'
38
38
  | 'mangleProperties'
39
39
  | 'renameIdentifiers'
40
+ | 'pack'
40
41
 
41
42
  // An override enables the transform and replaces its string-generator mode.
42
43
  type TransformEntry = boolean | { stringGeneratorMode?: StringGeneratorModeOption }
@@ -76,13 +77,25 @@ export interface ManglePropertiesOptions {
76
77
  undeclared?: boolean
77
78
  }
78
79
 
80
+ /** Options for the `Function`-constructor packing transform. */
81
+ export interface PackOptions {
82
+ /**
83
+ * Skip the strict-mode directive so the packed body runs sloppy even when the
84
+ * source is strict. This deliberately changes behavior. Default `false`.
85
+ */
86
+ escapeStrict?: boolean
87
+ /** Override the default string-generator mode for the names pack creates. */
88
+ stringGeneratorMode?: StringGeneratorModeOption
89
+ }
90
+
79
91
  type SimpleTransform = Exclude<
80
92
  TransformName,
81
- 'mangleProperties' | 'renameIdentifiers' | 'specialsToStrings'
93
+ 'mangleProperties' | 'pack' | 'renameIdentifiers' | 'specialsToStrings'
82
94
  >
83
95
 
84
96
  type TransformsMap = Partial<Record<SimpleTransform, boolean>> & {
85
97
  mangleProperties?: boolean | ManglePropertiesOptions
98
+ pack?: boolean | PackOptions
86
99
  renameIdentifiers?: TransformEntry
87
100
  specialsToStrings?: TransformEntry
88
101
  }
@@ -110,6 +123,7 @@ export interface ObfuscatorOptions {
110
123
  // Resolved per-call defaults handed to every transform.
111
124
  export interface TransformContext {
112
125
  mangleProperties?: ManglePropertiesOptions
126
+ pack?: PackOptions
113
127
  seed: number
114
128
  stringGeneratorMode: StringGeneratorModeOption
115
129
  }
@@ -17,7 +17,7 @@ import { StringGenerator } from 'src/utils/string-generator'
17
17
  * that this pipeline must leave unchanged.
18
18
  *
19
19
  * This pass runs before shape preparation, while Babel still distinguishes
20
- * `obj.name` from `obj["name"]` and class fields still carry their source keys.
20
+ * `obj.name` from `obj["name"]`.
21
21
  *
22
22
  * @example
23
23
  * // ◀️ before
@@ -175,7 +175,10 @@ function transformChain(path: OptionalPath): boolean {
175
175
  let check: t.Expression
176
176
 
177
177
  if (path.scope.isStatic(chain)) {
178
- check = chain
178
+ // The receiver stays in the member expression, so the check needs its own
179
+ // node. One node under two parents is visited twice, and a later in-place
180
+ // rewrite such as renameIdentifiers then applies itself twice to it.
181
+ check = t.cloneNode(chain)
179
182
  } else {
180
183
  const tmp = path.scope.generateUidIdentifierBasedOnNode(chain)
181
184
  path.scope.push({ id: t.cloneNode(tmp) })
@@ -212,7 +215,7 @@ function transformChain(path: OptionalPath): boolean {
212
215
  alternate = t.unaryExpression('delete', alternate)
213
216
  }
214
217
 
215
- const newTernary = t.conditionalExpression(condition, shortCircuit, alternate)
218
+ const newTernary = t.conditionalExpression(condition, t.cloneNode(shortCircuit), alternate)
216
219
  const replaced: NodePath<t.ConditionalExpression> = replacementPath.replaceWith(newTernary)[0]
217
220
  replacementPath = replaced.get('alternate')
218
221
  }
@@ -0,0 +1,336 @@
1
+ import generate from '@babel/generator'
2
+ import traverse from '@babel/traverse'
3
+ import type { NodePath, Visitor } from '@babel/traverse'
4
+ import * as t from '@babel/types'
5
+ import type { File } from '@babel/types'
6
+
7
+ import type { TransformContext } from 'src/options'
8
+ import { isInsideWith } from 'src/utils/ast'
9
+ import { isCalleeOrTagOf, isInStrictContext, referencesOrWritesVariable } from 'src/utils/paths'
10
+ import { mulberry32 } from 'src/utils/random'
11
+ import { StringGenerator } from 'src/utils/string-generator'
12
+
13
+ /**
14
+ * Serializes the whole program to a string and rebuilds it at runtime through
15
+ * the `Function` constructor. A detached function body only reaches true globals
16
+ * and its own parameters. A free name might instead be a CommonJS `require`, a
17
+ * bundler-injected binding, or a live import, none reachable from the body and
18
+ * none distinguishable from a global at build time, so free names are routed
19
+ * through a parameter object whose accessors run in the original top-level scope
20
+ * and resolve each name there. Import declarations return above the call. `eval`
21
+ * is left un-routed so a direct eval still reads the body scope. Names inside a
22
+ * `with` stay un-routed too, since they may resolve against the with object at
23
+ * runtime. Export statements cannot live inside a Function body, so pack throws
24
+ * on them. It packs everything else as written. Code that needs `import.meta`,
25
+ * top-level await, dynamic `import()`, or a top-level `arguments` will not run
26
+ * once moved into a Function body.
27
+ *
28
+ * @example
29
+ * // ◀️ before
30
+ * console.log(value);
31
+ *
32
+ * // ▶️ after
33
+ * Function("M", "return M[\"b\"].log(M[\"_\"]);")({
34
+ * get "b"() {
35
+ * return console;
36
+ * },
37
+ * get "_"() {
38
+ * return value;
39
+ * }
40
+ * });
41
+ */
42
+ export function pack(ast: File, ctx: TransformContext): boolean {
43
+ let programPath: NodePath<t.Program> | null = null
44
+ traverse(ast, {
45
+ Program(path) {
46
+ programPath = path
47
+ path.stop()
48
+ },
49
+ })
50
+ if (!programPath) {
51
+ return false
52
+ }
53
+ return packProgram(programPath, ctx)
54
+ }
55
+
56
+ function packProgram(programPath: NodePath<t.Program>, ctx: TransformContext): boolean {
57
+ assertNoExports(programPath)
58
+ const importPaths = collectImportDeclarations(programPath)
59
+ const importDeclarations = importPaths.map((path) => path.node)
60
+ // An import named `Function` shadows the constructor at the output top level,
61
+ // so the wrap reaches the real one through a function instance instead.
62
+ const shadowedConstructor = importDeclarations.some((declaration) =>
63
+ bindsFunctionConstructor(declaration),
64
+ )
65
+
66
+ const programNode = programPath.node
67
+ const wasModule = programNode.sourceType === 'module'
68
+ // Escaping strict is a deliberate opt-in: drop the directive so the packed
69
+ // body runs sloppy even when the source was strict.
70
+ const escapeStrict = ctx.pack?.escapeStrict ?? false
71
+ const bodyStrict = !escapeStrict && isInStrictContext(programPath)
72
+
73
+ // Detaching imports drops their bindings, so their names read as free and are
74
+ // routed like any other global. The declarations return at the output top
75
+ // level, where the accessors close over them.
76
+ for (const path of importPaths) {
77
+ path.remove()
78
+ }
79
+ programPath.scope.crawl()
80
+
81
+ const usedNames = collectUsedNames(programNode, importDeclarations)
82
+ const generator = new StringGenerator(ctx.stringGeneratorMode, mulberry32(ctx.seed))
83
+ const freshIdentifier = (): string => {
84
+ let name: string
85
+ do {
86
+ name = generator.next()
87
+ } while (usedNames.has(name))
88
+ usedNames.add(name)
89
+ return name
90
+ }
91
+ const objectName = freshIdentifier()
92
+ const setterParam = freshIdentifier()
93
+
94
+ const readProperties = new Map<string, string>()
95
+ const typeofProperties = new Map<string, string>()
96
+ const settable = new Set<string>()
97
+ const propertyFor = (registry: Map<string, string>, name: string): string => {
98
+ let property = registry.get(name)
99
+ if (property === undefined) {
100
+ do {
101
+ // `__proto__` as an object-literal key would set the prototype rather
102
+ // than name an accessor.
103
+ property = generator.next()
104
+ } while (property === '__proto__')
105
+ registry.set(name, property)
106
+ }
107
+ return property
108
+ }
109
+ const memberFor = (property: string): t.MemberExpression =>
110
+ t.memberExpression(t.identifier(objectName), t.stringLiteral(property), true)
111
+
112
+ const isRoutable = (path: NodePath<t.Identifier>): boolean => {
113
+ const { name } = path.node
114
+ // The wrapper parameter stays as written. `arguments` and `eval` are never
115
+ // routed: `arguments` binds to its own function's object, and leaving `eval`
116
+ // bare keeps a direct eval reading the body scope rather than the global one.
117
+ if (name === objectName || name === 'arguments' || name === 'eval') {
118
+ return false
119
+ }
120
+ // Inside `with`, a free name may resolve to a property of the with object at
121
+ // runtime, so routing it to the top-level accessor would change behavior.
122
+ if (isInsideWith(path)) {
123
+ return false
124
+ }
125
+ if (!referencesOrWritesVariable(path)) {
126
+ return false
127
+ }
128
+ return !path.scope.getBinding(name)
129
+ }
130
+
131
+ const routingVisitor: Visitor = {
132
+ UnaryExpression(path) {
133
+ if (path.node.operator !== 'typeof') {
134
+ return
135
+ }
136
+ const argument = path.get('argument')
137
+ // A bare `typeof name` must not throw for an undeclared global, so it maps
138
+ // to an accessor that applies `typeof` in the original scope.
139
+ if (!argument.isIdentifier() || !isRoutable(argument)) {
140
+ return
141
+ }
142
+ path.replaceWith(memberFor(propertyFor(typeofProperties, argument.node.name)))
143
+ },
144
+ Identifier(path) {
145
+ // `delete name` must keep hitting the global binding; `delete obj[prop]`
146
+ // would drop the accessor instead. A real global stays reachable bare.
147
+ if (isBareDeleteArgument(path) || !isRoutable(path)) {
148
+ return
149
+ }
150
+ const { name } = path.node
151
+ if (isWriteReference(path)) {
152
+ settable.add(name)
153
+ }
154
+ const member = memberFor(propertyFor(readProperties, name))
155
+ // A member callee would bind `this` to the object; the comma discards the
156
+ // base and keeps the original receiver.
157
+ path.replaceWith(
158
+ isCalleeOrTagOf(path) ? t.sequenceExpression([t.numericLiteral(0), member]) : member,
159
+ )
160
+ },
161
+ }
162
+ programPath.traverse(routingVisitor)
163
+
164
+ // The script completion value is its trailing expression; returning it keeps
165
+ // `eval` of the packed output producing the same value.
166
+ const { body } = programNode
167
+ const lastStatement = body.at(-1)
168
+ if (lastStatement && t.isExpressionStatement(lastStatement)) {
169
+ body[body.length - 1] = t.returnStatement(lastStatement.expression)
170
+ }
171
+
172
+ const innerDirectives = bodyStrict ? [t.directive(t.directiveLiteral('use strict'))] : []
173
+ const innerCode = generate(t.program(body, innerDirectives, 'script'), {
174
+ minified: true,
175
+ comments: false,
176
+ }).code
177
+
178
+ const objectExpression = buildAccessorObject({
179
+ readProperties,
180
+ typeofProperties,
181
+ settable,
182
+ setterParam,
183
+ strict: bodyStrict,
184
+ })
185
+
186
+ const constructorReference = shadowedConstructor
187
+ ? t.memberExpression(
188
+ t.functionExpression(null, [], t.blockStatement([])),
189
+ t.identifier('constructor'),
190
+ )
191
+ : t.identifier('Function')
192
+ const wrapper = t.callExpression(constructorReference, [
193
+ t.stringLiteral(objectName),
194
+ t.stringLiteral(innerCode),
195
+ ])
196
+ // A strict script keeps top-level `this` bound to the global object. A plainly
197
+ // called strict function would instead see `undefined`, so pass the global
198
+ // `this` of the sloppy output script.
199
+ const invocation =
200
+ bodyStrict && !wasModule
201
+ ? t.callExpression(t.memberExpression(wrapper, t.identifier('call')), [
202
+ t.thisExpression(),
203
+ objectExpression,
204
+ ])
205
+ : t.callExpression(wrapper, [objectExpression])
206
+
207
+ programNode.body = [...importDeclarations, t.expressionStatement(invocation)]
208
+ programNode.directives = []
209
+ programNode.sourceType = importDeclarations.length > 0 ? 'module' : 'script'
210
+ return true
211
+ }
212
+
213
+ // Export statements are module-only syntax and cannot exist inside a Function
214
+ // body. Pack throws rather than emit output that fails to parse at runtime.
215
+ function assertNoExports(programPath: NodePath<t.Program>): void {
216
+ const hasExport = programPath.node.body.some(
217
+ (statement) =>
218
+ t.isExportNamedDeclaration(statement) ||
219
+ t.isExportDefaultDeclaration(statement) ||
220
+ t.isExportAllDeclaration(statement),
221
+ )
222
+ if (hasExport) {
223
+ throw new Error('pack cannot wrap a program that uses export statements')
224
+ }
225
+ }
226
+
227
+ function collectImportDeclarations(
228
+ programPath: NodePath<t.Program>,
229
+ ): NodePath<t.ImportDeclaration>[] {
230
+ return programPath
231
+ .get('body')
232
+ .filter((statement): statement is NodePath<t.ImportDeclaration> =>
233
+ statement.isImportDeclaration(),
234
+ )
235
+ }
236
+
237
+ function bindsFunctionConstructor(declaration: t.ImportDeclaration): boolean {
238
+ return declaration.specifiers.some((specifier) => specifier.local.name === 'Function')
239
+ }
240
+
241
+ function isBareDeleteArgument(path: NodePath<t.Identifier>): boolean {
242
+ const parent = path.parentPath
243
+ return (
244
+ parent != null &&
245
+ parent.isUnaryExpression({ operator: 'delete' }) &&
246
+ parent.node.argument === path.node
247
+ )
248
+ }
249
+
250
+ function isWriteReference(path: NodePath<t.Identifier>): boolean {
251
+ const parent = path.parentPath
252
+ if (!parent) {
253
+ return false
254
+ }
255
+ const { node } = path
256
+ if (parent.isAssignmentExpression() && parent.node.left === node) {
257
+ return true
258
+ }
259
+ if (parent.isUpdateExpression() && parent.node.argument === node) {
260
+ return true
261
+ }
262
+ if ((parent.isForInStatement() || parent.isForOfStatement()) && parent.node.left === node) {
263
+ return true
264
+ }
265
+ // Destructuring assignment targets bind through patterns Babel flags here.
266
+ return path.isBindingIdentifier()
267
+ }
268
+
269
+ function collectUsedNames(
270
+ program: t.Program,
271
+ importDeclarations: t.ImportDeclaration[],
272
+ ): Set<string> {
273
+ // The wrapper parameter and setter parameter must avoid every name the body
274
+ // uses, so no nested binding can shadow their accesses.
275
+ const names = new Set<string>(['Function'])
276
+ for (const declaration of importDeclarations) {
277
+ for (const specifier of declaration.specifiers) {
278
+ names.add(specifier.local.name)
279
+ }
280
+ }
281
+ t.traverseFast(program, (node) => {
282
+ if (t.isIdentifier(node)) {
283
+ names.add(node.name)
284
+ }
285
+ })
286
+ return names
287
+ }
288
+
289
+ interface AccessorObjectSpec {
290
+ readProperties: Map<string, string>
291
+ setterParam: string
292
+ settable: Set<string>
293
+ strict: boolean
294
+ typeofProperties: Map<string, string>
295
+ }
296
+
297
+ function buildAccessorObject(spec: AccessorObjectSpec): t.ObjectExpression {
298
+ const { readProperties, setterParam, settable, strict, typeofProperties } = spec
299
+ const properties: t.ObjectMethod[] = []
300
+ for (const [name, property] of readProperties) {
301
+ properties.push(
302
+ t.objectMethod(
303
+ 'get',
304
+ t.stringLiteral(property),
305
+ [],
306
+ t.blockStatement([t.returnStatement(t.identifier(name))]),
307
+ ),
308
+ )
309
+ if (settable.has(name)) {
310
+ const setterBody = t.blockStatement([
311
+ t.expressionStatement(
312
+ t.assignmentExpression('=', t.identifier(name), t.identifier(setterParam)),
313
+ ),
314
+ ])
315
+ // A strict script becomes a sloppy output script, so the setter stays
316
+ // strict to keep an undeclared-global write throwing.
317
+ if (strict) {
318
+ setterBody.directives.push(t.directive(t.directiveLiteral('use strict')))
319
+ }
320
+ properties.push(
321
+ t.objectMethod('set', t.stringLiteral(property), [t.identifier(setterParam)], setterBody),
322
+ )
323
+ }
324
+ }
325
+ for (const [name, property] of typeofProperties) {
326
+ properties.push(
327
+ t.objectMethod(
328
+ 'get',
329
+ t.stringLiteral(property),
330
+ [],
331
+ t.blockStatement([t.returnStatement(t.unaryExpression('typeof', t.identifier(name)))]),
332
+ ),
333
+ )
334
+ }
335
+ return t.objectExpression(properties)
336
+ }
package/src/utils/ast.ts CHANGED
@@ -125,5 +125,11 @@ function ownScopeHasDirectEval(owner: t.Node): boolean {
125
125
  }
126
126
 
127
127
  export function isDirectEvalCall(node: t.Node): boolean {
128
- return t.isCallExpression(node) && t.isIdentifier(node.callee) && node.callee.name === 'eval'
128
+ // `eval?.(...)` is an OptionalCallExpression yet still a direct eval, so both
129
+ // call node shapes count.
130
+ return (
131
+ (t.isCallExpression(node) || t.isOptionalCallExpression(node)) &&
132
+ t.isIdentifier(node.callee) &&
133
+ node.callee.name === 'eval'
134
+ )
129
135
  }
@@ -1,43 +0,0 @@
1
- import type { File } from '@babel/types';
2
- /**
3
- * Moves safe instance fields into the constructor and safe static fields into
4
- * static blocks. The whole class stays intact unless every field can be lowered.
5
- *
6
- * `Object.defineProperty` preserves CreateDataProperty semantics. A plain
7
- * assignment could invoke an inherited setter or fail on an inherited read-only
8
- * property. Static blocks also preserve the class binding and `this`.
9
- *
10
- * Accessor collisions, special keys, inferred names, constructor parameter
11
- * scope, and relocated `this`, `super`, or `new.target` can all expose a rewrite.
12
- * Private or computed fields, derived classes, `with`, and a shadowed `Object`
13
- * are therefore refused.
14
- *
15
- * @example
16
- * // ◀️ before
17
- * class A {
18
- * x = 1;
19
- * static z = 2;
20
- * }
21
- *
22
- * // ▶️ after
23
- * class A {
24
- * constructor() {
25
- * Object.defineProperty(this, "x", {
26
- * value: 1,
27
- * writable: true,
28
- * enumerable: true,
29
- * configurable: true
30
- * });
31
- * }
32
- * static {
33
- * Object.defineProperty(this, "z", {
34
- * value: 2,
35
- * writable: true,
36
- * enumerable: true,
37
- * configurable: true
38
- * });
39
- * }
40
- * }
41
- */
42
- export declare function rewriteClassFields(ast: File): void;
43
- //# sourceMappingURL=rewrite-class-fields.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"rewrite-class-fields.d.ts","sourceRoot":"","sources":["../../src/preparation/rewrite-class-fields.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAA;AAKxC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,IAAI,GAAG,IAAI,CAElD"}