Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Exports, Add UTF-8 Tests #277

Merged
merged 10 commits into from
Jan 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
"@types/acorn": "4.0.5",
"@types/estree": "0.0.41",
"@types/node": "12.12.24",
"@types/temp-write": "3.3.0",
"@types/uuid": "3.4.6",
"ava": "2.4.0",
"builtins": "3.0.0",
Expand All @@ -65,6 +64,10 @@
"*.ts": [
"prettier --config .prettierrc --write",
"git add"
],
"*.test.js": [
"prettier --config .prettierrc --write",
"git add"
]
},
"husky": {
Expand Down
4 changes: 3 additions & 1 deletion src/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const {
} = require('google-closure-compiler/lib/utils.js');
import { Transform } from './types';
import { postCompilation } from './transforms';
import { RenderedChunk } from 'rollup';

enum Platform {
NATIVE = 'native',
Expand Down Expand Up @@ -71,6 +72,7 @@ function orderPlatforms(platformPreference: Platform | string): Array<Platform>
*/
export default function(
compileOptions: CompileOptions,
chunk: RenderedChunk,
transforms: Array<Transform>,
): Promise<string> {
return new Promise((resolve: (stdOut: string) => void, reject: (error: any) => void) => {
Expand All @@ -95,7 +97,7 @@ export default function(
} else if (exitCode !== 0) {
reject(new Error(`Google Closure Compiler exit ${exitCode}: ${stdErr}`));
} else {
resolve(await postCompilation(code, transforms));
resolve(await postCompilation(code, chunk, transforms));
}
});
});
Expand Down
32 changes: 19 additions & 13 deletions src/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,30 @@ import { writeTempFile } from './temp-file';

const DEBUG_ENABLED = false;

/* c8 ignore next 12 */
export const logSource = async (preamble: string, source: string, code?: string): Promise<void> => {
/* c8 ignore next 16 */
export async function logTransformChain(
file: string,
stage: string,
messages: Array<[string, string]>,
): Promise<void> {
if (DEBUG_ENABLED) {
const sourceLocation: string = await writeTempFile(source);
const codeLocation: string = code ? await writeTempFile(code) : '';

console.log(preamble);
console.log(sourceLocation);
if (code) {
console.log(codeLocation);
let output: string = `\n${file} - ${stage}`;
for (const [message, source] of messages) {
output += `\n${message.substr(0, 15).padEnd(18, '.')} - file://${await writeTempFile(
source,
'.js',
)}`;
}
console.log(output);
}
};
}

/* c8 ignore next 6 */
export const log = (preamble: string, message: string | object): void | null => {
/* c8 ignore next 8 */
export const log = (preamble: string | undefined, message: string | object): void | null => {
if (DEBUG_ENABLED) {
console.log(preamble);
if (preamble) {
console.log(preamble);
}
console.log(message);
}
};
13 changes: 10 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ const renderChunk = async (
requestedCompileOptions: CompileOptions = {},
sourceCode: string,
outputOptions: OutputOptions,
chunk: RenderedChunk,
): Promise<{ code: string; map: SourceMapInput } | void> => {
const code = await preCompilation(sourceCode, outputOptions, transforms);
const code = await preCompilation(sourceCode, outputOptions, chunk, transforms);
const [compileOptions, mapFile] = await options(
requestedCompileOptions,
outputOptions,
Expand All @@ -53,7 +54,7 @@ const renderChunk = async (

try {
return {
code: await compiler(compileOptions, transforms),
code: await compiler(compileOptions, chunk, transforms),
map: JSON.parse(await fsPromises.readFile(mapFile, 'utf8')),
};
} catch (error) {
Expand Down Expand Up @@ -82,7 +83,13 @@ export default function closureCompiler(requestedCompileOptions: CompileOptions
},
renderChunk: async (code: string, chunk: RenderedChunk, outputOptions: OutputOptions) => {
const transforms = createTransforms(context, inputOptions);
const output = await renderChunk(transforms, requestedCompileOptions, code, outputOptions);
const output = await renderChunk(
transforms,
requestedCompileOptions,
code,
outputOptions,
chunk,
);
return output || null;
},
};
Expand Down
12 changes: 7 additions & 5 deletions src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/

import { Transform } from './types';
import { ModuleFormat, OutputOptions } from 'rollup';
import { OutputOptions } from 'rollup';
import { CompileOptions } from 'google-closure-compiler';
import { writeTempFile } from './temp-file';
import { log } from './debug';
Expand All @@ -27,10 +27,11 @@ export const ERROR_WARNINGS_ENABLED_LANGUAGE_OUT_INVALID =

/**
* Checks if output format is ESM
* @param format
* @param outputOptions
* @return boolean
*/
export const isESMFormat = (format?: ModuleFormat): boolean => format === 'esm' || format === 'es';
export const isESMFormat = ({ format }: OutputOptions): boolean =>
format === 'esm' || format === 'es';

/**
* Throw Errors if compile options will result in unexpected behaviour.
Expand Down Expand Up @@ -69,13 +70,14 @@ export const defaults = async (
for (const transform of transformers || []) {
const extern = transform.extern(options);
if (extern !== null) {
transformerExterns.push(await writeTempFile(extern));
const writtenExtern = await writeTempFile(extern);
transformerExterns.push(writtenExtern);
}
}

return {
language_out: 'NO_TRANSPILE',
assume_function_wrapper: isESMFormat(options.format),
assume_function_wrapper: isESMFormat(options),
warning_level: 'QUIET',
module_resolution: 'NODE',
externs: transformerExterns.concat(providedExterns),
Expand Down
34 changes: 31 additions & 3 deletions src/parsing/export-details.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,14 @@
* limitations under the License.
*/

import { ExportNamedDeclaration, ExportDefaultDeclaration } from 'estree';
import {
ExportNamedDeclaration,
ExportDefaultDeclaration,
Node,
ExpressionStatement,
MemberExpression,
Expression,
} from 'estree';
import { ExportDetails, Range, ExportClosureMapping } from '../types';

export function NamedDeclaration(declaration: ExportNamedDeclaration): Array<ExportDetails> {
Expand All @@ -26,7 +33,6 @@ export function NamedDeclaration(declaration: ExportNamedDeclaration): Array<Exp
exportDetails.push({
local: specifier.local.name,
exported: specifier.exported.name,
closureName: specifier.exported.name,
type: ExportClosureMapping.NAMED_CONSTANT,
range: declaration.range as Range,
source,
Expand All @@ -46,7 +52,6 @@ export function DefaultDeclaration(
{
local: declaration.name,
exported: declaration.name,
closureName: declaration.name,
type: ExportClosureMapping.NAMED_DEFAULT_FUNCTION,
range: defaultDeclaration.range as Range,
source: null,
Expand All @@ -56,3 +61,26 @@ export function DefaultDeclaration(

return [];
}

export function NodeIsPreservedExport(node: Node): node is ExpressionStatement {
return (
node.type === 'ExpressionStatement' &&
node.expression.type === 'AssignmentExpression' &&
node.expression.left.type === 'MemberExpression' &&
node.expression.left.object.type === 'Identifier' &&
node.expression.left.object.name === 'window'
);
}

export function PreservedExportName(node: MemberExpression): string | null {
const { property }: { property: Expression } = node;

if (property.type === 'Identifier') {
return property.name;
}
if (property.type === 'Literal' && typeof property.value === 'string') {
return property.value;
}

return null;
}
2 changes: 1 addition & 1 deletion src/parsing/import-specifiers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import { IMPORT_SPECIFIER, IMPORT_NAMESPACE_SPECIFIER, IMPORT_DEFAULT_SPECIFIER } from '../types';
import { ImportSpecifier, ImportDefaultSpecifier, ImportNamespaceSpecifier } from 'estree';

export interface Specifiers {
interface Specifiers {
default: string | null;
specific: Array<string>;
local: Array<string>;
Expand Down
3 changes: 1 addition & 2 deletions src/parsing/literal-name.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,5 @@
import { Literal, SimpleLiteral } from 'estree';

export function literalName(literal: Literal): string {
const literalValue = (literal as SimpleLiteral).value;
return typeof literalValue === 'string' ? literalValue : '';
return (literal as SimpleLiteral).value as string;
}
39 changes: 39 additions & 0 deletions src/parsing/preserve-default-export.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* Copyright 2020 The AMP HTML Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { ExpressionStatement, AssignmentExpression, MemberExpression } from 'estree';
import { ExportDetails, Range } from '../types';
import MagicString from 'magic-string';

export function PreserveDefault(
code: string,
source: MagicString,
ancestor: ExpressionStatement,
exportDetails: ExportDetails,
exportInline: boolean,
): boolean {
const assignmentExpression = ancestor.expression as AssignmentExpression;
const memberExpression = assignmentExpression.left as MemberExpression;
const [memberExpressionStart, memberExpressionEnd]: Range = memberExpression.range as Range;

source.overwrite(
memberExpressionStart,
memberExpressionEnd + assignmentExpression.operator.length,
'export default ',
);

return false;
}
111 changes: 111 additions & 0 deletions src/parsing/preserve-named-constant-export.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/**
* Copyright 2020 The AMP HTML Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import {
ExpressionStatement,
AssignmentExpression,
FunctionExpression,
MemberExpression,
} from 'estree';
import { ExportDetails, Range } from '../types';
import MagicString from 'magic-string';

function PreserveFunction(
code: string,
source: MagicString,
ancestor: ExpressionStatement,
exportDetails: ExportDetails,
exportInline: boolean,
): boolean {
// Function Expressions can be inlined instead of preserved as variable references.
// window['foo'] = function(){}; => export function foo(){} / function foo(){}
const assignmentExpression = ancestor.expression as AssignmentExpression;
const memberExpression = assignmentExpression.left as MemberExpression;
const functionExpression = assignmentExpression.right as FunctionExpression;
const [memberExpressionObjectStart] = memberExpression.object.range as Range;
const functionName = exportInline ? exportDetails.exported : exportDetails.local;

if (functionExpression.params.length > 0) {
const [paramsStart] = functionExpression.params[0].range as Range;
// FunctionExpression has parameters.
source.overwrite(
memberExpressionObjectStart,
paramsStart,
`${exportInline ? 'export ' : ''}function ${functionName}(`,
);
} else {
const [bodyStart] = functionExpression.body.range as Range;
source.overwrite(
memberExpressionObjectStart,
bodyStart,
`${exportInline ? 'export ' : ''}function ${functionName}()`,
);
}

return !exportInline;
}

function PreserveIdentifier(
code: string,
source: MagicString,
ancestor: ExpressionStatement,
exportDetails: ExportDetails,
exportInline: boolean,
): boolean {
const assignmentExpression = ancestor.expression as AssignmentExpression;
const left = assignmentExpression.left;
const right = assignmentExpression.right;
const [ancestorStart, ancestorEnd]: Range = ancestor.range as Range;
const [rightStart, rightEnd]: Range = right.range as Range;

if (exportInline) {
source.overwrite(
ancestorStart,
ancestorEnd,
`export var ${exportDetails.exported}=${code.substring(rightStart, rightEnd)};`,
);
} else if (exportDetails.source === null && 'name' in right) {
// This is a locally defined identifier with a name we can use.
exportDetails.local = right.name;
source.remove((left.range as Range)[0], rightEnd + 1);
return true;
} else {
// exportDetails.local =
source.overwrite(
ancestorStart,
ancestorEnd,
`var ${exportDetails.local}=${code.substring(rightStart, rightEnd)};`,
);
}

return !exportInline;
}

export function PreserveNamedConstant(
code: string,
source: MagicString,
ancestor: ExpressionStatement,
exportDetails: ExportDetails,
exportInline: boolean,
): boolean {
const assignmentExpression = ancestor.expression as AssignmentExpression;
switch (assignmentExpression.right.type) {
case 'FunctionExpression':
return PreserveFunction(code, source, ancestor, exportDetails, exportInline);
default:
return PreserveIdentifier(code, source, ancestor, exportDetails, exportInline);
}
}
Loading