@philpl/buble
Version:
The blazing fast, batteries-included ES2015 compiler
297 lines (247 loc) • 8.4 kB
JavaScript
import CompileError from '../../utils/CompileError.js';
import Node from '../Node.js';
import { findIndex } from '../../utils/array.js';
import reserved from '../../utils/reserved.js';
// TODO this code is pretty wild, tidy it up
export default class ClassBody extends Node {
transpile(code, transforms, inFunctionExpression, superName) {
if (transforms.classes) {
const name = this.parent.name;
const indentStr = code.getIndentString();
const i0 =
this.getIndentation() + (inFunctionExpression ? indentStr : '');
const i1 = i0 + indentStr;
const constructorIndex = findIndex(
this.body,
node => node.kind === 'constructor'
);
const constructor = this.body[constructorIndex];
let introBlock = '';
let outroBlock = '';
if (this.body.length) {
code.remove(this.start, this.body[0].start);
code.remove(this.body[this.body.length - 1].end, this.end);
} else {
code.remove(this.start, this.end);
}
if (constructor) {
constructor.value.body.isConstructorBody = true;
const previousMethod = this.body[constructorIndex - 1];
const nextMethod = this.body[constructorIndex + 1];
// ensure constructor is first
if (constructorIndex > 0) {
code.remove(previousMethod.end, constructor.start);
code.move(
constructor.start,
nextMethod ? nextMethod.start : this.end - 1,
this.body[0].start
);
}
if (!inFunctionExpression) code.appendLeft(constructor.end, ';');
}
const classFields = [];
this.body.forEach(element => {
if (element.type === 'FieldDefinition') {
if (element.computed) {
classFields.push(`this${code.slice(element.start, element.end)};`);
} else {
classFields.push(`this.${code.slice(element.start, element.end)};`);
}
code.remove(element.start, element.end);
if (code.byStart[element.end].content !== '') {
let toEnd = 0;
for (; toEnd < code.byStart[element.end].content.length; toEnd++) {
const chars = code.byStart[element.end].content.slice(0, toEnd);
if (chars.indexOf(';') !== -1) {
break;
}
}
if (toEnd > 0) {
code.remove(element.end, element.end + toEnd);
}
}
}
});
const namedFunctions =
this.program.options.namedFunctionExpressions !== false;
const namedConstructor =
namedFunctions ||
this.parent.superClass ||
this.parent.type !== 'ClassDeclaration';
if (this.parent.superClass) {
let inheritanceBlock = `if ( ${superName} ) ${name}.__proto__ = ${
superName
};\n${i0}${name}.prototype = Object.create( ${superName} && ${
superName
}.prototype );\n${i0}${name}.prototype.constructor = ${name};`;
if (constructor) {
introBlock += `\n\n${i0}` + inheritanceBlock;
} else {
const fn =
`function ${name} () {` +
(classFields.length ? `\n${i1}` + classFields.join(`\n${i1}`) + `\n${i1}` : '') +
(superName
? `\n${i1}${superName}.apply(this, arguments);\n${i0}}`
: `}`) +
(inFunctionExpression ? '' : ';') +
(this.body.length ? `\n\n${i0}` : '');
inheritanceBlock = fn + inheritanceBlock;
introBlock += inheritanceBlock + `\n\n${i0}`;
}
} else if (!constructor) {
let fn =
'function ' +
(namedConstructor ? name + ' ' : '') +
'() {' +
(classFields.length ? `\n${i1}` + classFields.join(`\n${i1}`) + `\n${i0}` : '') +
'}';
if (this.parent.type === 'ClassDeclaration') fn += ';';
if (this.body.length) fn += `\n\n${i0}`;
introBlock += fn;
}
if (constructor) {
if (classFields.length) {
code.appendLeft(constructor.value.body.start + 1, `\n${i1}` + classFields.join(`\n${i1}`));
}
}
const scope = this.findScope(false);
const prototypeGettersAndSetters = [];
const staticGettersAndSetters = [];
let prototypeAccessors;
let staticAccessors;
this.body.forEach((method, i) => {
if ((method.kind === 'get' || method.kind === 'set') && transforms.getterSetter) {
CompileError.missingTransform("getters and setters", "getterSetter", method);
}
if (method.type === 'FieldDefinition') {
return;
}
if (method.kind === 'constructor') {
const constructorName = namedConstructor ? ' ' + name : '';
code.overwrite(
method.key.start,
method.key.end,
`function${constructorName}`
);
return;
}
if (method.static) {
const len = code.original[method.start + 6] == ' ' ? 7 : 6;
code.remove(method.start, method.start + len);
}
const isAccessor = method.kind !== 'method';
let lhs;
let methodName = method.key.name;
if (
reserved[methodName] ||
method.value.body.scope.references[methodName]
) {
methodName = scope.createIdentifier(methodName);
}
// when method name is a string or a number let's pretend it's a computed method
let fake_computed = false;
if (!method.computed && method.key.type === 'Literal') {
fake_computed = true;
method.computed = true;
}
if (isAccessor) {
if (method.computed) {
throw new Error(
'Computed accessor properties are not currently supported'
);
}
code.remove(method.start, method.key.start);
if (method.static) {
if (!~staticGettersAndSetters.indexOf(method.key.name))
staticGettersAndSetters.push(method.key.name);
if (!staticAccessors)
staticAccessors = scope.createIdentifier('staticAccessors');
lhs = `${staticAccessors}`;
} else {
if (!~prototypeGettersAndSetters.indexOf(method.key.name))
prototypeGettersAndSetters.push(method.key.name);
if (!prototypeAccessors)
prototypeAccessors = scope.createIdentifier('prototypeAccessors');
lhs = `${prototypeAccessors}`;
}
} else {
lhs = method.static ? `${name}` : `${name}.prototype`;
}
if (!method.computed) lhs += '.';
const insertNewlines =
(constructorIndex > 0 && i === constructorIndex + 1) ||
(i === 0 && constructorIndex === this.body.length - 1);
if (insertNewlines) lhs = `\n\n${i0}${lhs}`;
let c = method.key.end;
if (method.computed) {
if (fake_computed) {
code.prependRight(method.key.start, '[');
code.appendLeft(method.key.end, ']');
} else {
while (code.original[c] !== ']') c += 1;
c += 1;
}
}
const funcName =
method.computed || isAccessor || !namedFunctions
? ''
: `${methodName} `;
const rhs =
(isAccessor ? `.${method.kind}` : '') +
` = ${method.value.async ? 'async ' : ''}function` +
(method.value.generator ? '* ' : ' ') +
funcName;
code.remove(c, method.value.start);
code.prependRight(method.value.start, rhs);
code.appendLeft(method.end, ';');
if (method.value.generator) code.remove(method.start, method.key.start);
let start = method.key.start;
if (method.computed && !fake_computed) {
while (code.original[start] != '[') {
--start;
}
}
if (method.start < start) {
code.overwrite(method.start, start, lhs);
} else {
code.prependRight(method.start, lhs);
}
});
if (prototypeGettersAndSetters.length || staticGettersAndSetters.length) {
const intro = [];
const outro = [];
if (prototypeGettersAndSetters.length) {
intro.push(
`var ${prototypeAccessors} = { ${prototypeGettersAndSetters
.map(name => `${name}: { configurable: true }`)
.join(',')} };`
);
outro.push(
`Object.defineProperties( ${name}.prototype, ${
prototypeAccessors
} );`
);
}
if (staticGettersAndSetters.length) {
intro.push(
`var ${staticAccessors} = { ${staticGettersAndSetters
.map(name => `${name}: { configurable: true }`)
.join(',')} };`
);
outro.push(`Object.defineProperties( ${name}, ${staticAccessors} );`);
}
if (constructor) introBlock += `\n\n${i0}`;
introBlock += intro.join(`\n${i0}`);
if (!constructor) introBlock += `\n\n${i0}`;
outroBlock += `\n\n${i0}` + outro.join(`\n${i0}`);
}
if (constructor) {
code.appendLeft(constructor.end, introBlock);
} else {
code.prependRight(this.start, introBlock);
}
code.appendLeft(this.end, outroBlock);
}
super.transpile(code, transforms);
}
}