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

Fixed imports/exports that are illegal identifiers in the es output #4939

Merged
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 11 additions & 5 deletions src/finalisers/es.ts
Expand Up @@ -3,8 +3,12 @@ import type { ChunkDependency, ChunkExports, ImportSpecifier, ReexportSpecifier
import type { NormalizedOutputOptions } from '../rollup/types';
import type { GenerateCodeSnippets } from '../utils/generateCodeSnippets';
import { getHelpersBlock } from '../utils/interopHelpers';
import { isValidIdentifier } from '../utils/isValidIdentifier';
import type { FinaliserOptions } from './index';

const safeExportName = (name: string): string =>
isValidIdentifier(name) ? name : JSON.stringify(name);

export default function es(
magicString: MagicStringBundle,
{ accessedGlobals, indent: t, intro, outro, dependencies, exports, snippets }: FinaliserOptions,
Expand Down Expand Up @@ -68,7 +72,7 @@ function getImportBlock(
.map(specifier =>
specifier.imported === specifier.local
? specifier.imported
: `${specifier.imported} as ${specifier.local}`
: `${safeExportName(specifier.imported)} as ${specifier.local}`
)
.join(`,${_}`)}${_}}${_}from${_}${pathWithAssertion}`
);
Expand Down Expand Up @@ -100,7 +104,9 @@ function getImportBlock(
for (const specifier of namespaceReexports) {
importBlock.push(
`export${_}{${_}${
name === specifier.reexported ? name : `${name} as ${specifier.reexported}`
name === specifier.reexported
? name
: `${name} as ${safeExportName(specifier.reexported)}`
} };`
);
}
Expand All @@ -110,8 +116,8 @@ function getImportBlock(
`export${_}{${_}${namedReexports
.map(specifier =>
specifier.imported === specifier.reexported
? specifier.imported
: `${specifier.imported} as ${specifier.reexported}`
? safeExportName(specifier.imported)
: `${safeExportName(specifier.imported)} as ${safeExportName(specifier.reexported)}`
)
.join(`,${_}`)}${_}}${_}from${_}${pathWithAssertion}`
);
Expand All @@ -131,7 +137,7 @@ function getExportBlock(exports: ChunkExports, { _, cnst }: GenerateCodeSnippets
exportDeclaration.push(
specifier.exported === specifier.local
? specifier.local
: `${specifier.local} as ${specifier.exported}`
: `${specifier.local} as ${safeExportName(specifier.exported)}`
);
}
if (exportDeclaration.length > 0) {
Expand Down
7 changes: 3 additions & 4 deletions src/utils/generateCodeSnippets.ts
@@ -1,5 +1,6 @@
import type { NormalizedOutputOptions } from '../rollup/types';
import RESERVED_NAMES from './RESERVED_NAMES';
import { isValidIdentifier } from './isValidIdentifier';

export interface GenerateCodeSnippets {
_: string;
Expand Down Expand Up @@ -83,8 +84,8 @@ export function getGenerateCodeSnippets({
];

const isValidPropertyName = reservedNamesAsProps
? (name: string): boolean => validPropertyName.test(name)
: (name: string): boolean => !RESERVED_NAMES.has(name) && validPropertyName.test(name);
? isValidIdentifier
: (name: string): boolean => !RESERVED_NAMES.has(name) && isValidIdentifier(name);

return {
_,
Expand Down Expand Up @@ -130,5 +131,3 @@ export function getGenerateCodeSnippets({

const wrapIfNeeded = (code: string, needsParens: boolean | undefined): string =>
needsParens ? `(${code})` : code;

const validPropertyName = /^(?!\d)[\w$]+$/;
5 changes: 5 additions & 0 deletions src/utils/isValidIdentifier.ts
@@ -0,0 +1,5 @@
const validIdentifier = /^(?!\d)[\w$]+$/;

export function isValidIdentifier(name: string): boolean {
return validIdentifier.test(name);
}
6 changes: 4 additions & 2 deletions src/utils/safeName.ts
@@ -1,15 +1,17 @@
import RESERVED_NAMES from './RESERVED_NAMES';
import { toBase64 } from './base64';
import { isValidIdentifier } from './isValidIdentifier';

export function getSafeName(
baseName: string,
usedNames: Set<string>,
forbiddenNames: Set<string> | null
): string {
let safeName = baseName;
const safeBase = isValidIdentifier(baseName) ? baseName : '_safe';
let safeName = safeBase;
let count = 1;
while (usedNames.has(safeName) || RESERVED_NAMES.has(safeName) || forbiddenNames?.has(safeName)) {
safeName = `${baseName}$${toBase64(count++)}`;
safeName = `${safeBase}$${toBase64(count++)}`;
}
usedNames.add(safeName);
return safeName;
Expand Down
@@ -0,0 +1,10 @@
module.exports = {
description: 'correctly handles illegal identifiers in exports/imports',
options: {
input: ['main'],
output: {
name: 'illegalIdentifiers'
},
external: ['external']
}
};
@@ -0,0 +1,41 @@
define(['exports', 'external'], (function (exports, external) { 'use strict';

function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}

var external__namespace = /*#__PURE__*/_interopNamespaceDefault(external);

console.log(external[":"], external["🤷‍♂️"]); // retain those local bindings

const legal = 10;

Object.defineProperty(exports, '-', {
enumerable: true,
get: function () { return external.bar; }
});
Object.defineProperty(exports, '/', {
enumerable: true,
get: function () { return external["/"]; }
});
exports["🍅"] = external__namespace;
Object.defineProperty(exports, '😭', {
enumerable: true,
get: function () { return external["😂"]; }
});
exports["🔥illegal"] = legal;

}));
@@ -0,0 +1,41 @@
'use strict';

var external = require('external');

function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}

var external__namespace = /*#__PURE__*/_interopNamespaceDefault(external);

console.log(external[":"], external["🤷‍♂️"]); // retain those local bindings

const legal = 10;

Object.defineProperty(exports, '-', {
enumerable: true,
get: function () { return external.bar; }
});
Object.defineProperty(exports, '/', {
enumerable: true,
get: function () { return external["/"]; }
});
exports["🍅"] = external__namespace;
Object.defineProperty(exports, '😭', {
enumerable: true,
get: function () { return external["😂"]; }
});
exports["🔥illegal"] = legal;
@@ -0,0 +1,10 @@
import { ":" as _safe, "🤷‍♂️" as _safe$1 } from 'external';
import * as external from 'external';
export { external as "🍅" };
export { bar as "-", "/", "😂" as "😭" } from 'external';

console.log(_safe, _safe$1); // retain those local bindings

const legal = 10;

export { legal as "🔥illegal" };
@@ -0,0 +1,44 @@
var illegalIdentifiers = (function (exports, external) {
'use strict';

function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}

var external__namespace = /*#__PURE__*/_interopNamespaceDefault(external);

console.log(external[":"], external["🤷‍♂️"]); // retain those local bindings

const legal = 10;

Object.defineProperty(exports, '-', {
enumerable: true,
get: function () { return external.bar; }
});
Object.defineProperty(exports, '/', {
enumerable: true,
get: function () { return external["/"]; }
});
exports["🍅"] = external__namespace;
Object.defineProperty(exports, '😭', {
enumerable: true,
get: function () { return external["😂"]; }
});
exports["🔥illegal"] = legal;

return exports;

})({}, external);
@@ -0,0 +1,18 @@
System.register('illegalIdentifiers', ['external'], (function (exports) {
'use strict';
var _safe, _safe$1;
return {
setters: [function (module) {
_safe = module[":"];
_safe$1 = module["🤷‍♂️"];
exports({ '-': module.bar, '/': module["/"], '🍅': module, '😭': module["😂"] });
}],
execute: (function () {

console.log(_safe, _safe$1); // retain those local bindings

const legal = exports('🔥illegal', 10);

})
};
}));
@@ -0,0 +1,45 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('external')) :
typeof define === 'function' && define.amd ? define(['exports', 'external'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.illegalIdentifiers = {}, global.external));
})(this, (function (exports, external) { 'use strict';

function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}

var external__namespace = /*#__PURE__*/_interopNamespaceDefault(external);

console.log(external[":"], external["🤷‍♂️"]); // retain those local bindings

const legal = 10;

Object.defineProperty(exports, '-', {
enumerable: true,
get: function () { return external.bar; }
});
Object.defineProperty(exports, '/', {
enumerable: true,
get: function () { return external["/"]; }
});
exports["🍅"] = external__namespace;
Object.defineProperty(exports, '😭', {
enumerable: true,
get: function () { return external["😂"]; }
});
exports["🔥illegal"] = legal;

}));
11 changes: 11 additions & 0 deletions test/form/samples/illegal-identifiers-in-imports-exports/main.js
@@ -0,0 +1,11 @@
import { ':' as baz, '🤷‍♂️' as bazinga } from 'external';
console.log(baz, bazinga); // retain those local bindings

const legal = 10;

export { legal as '🔥illegal' };

export { bar as '-', '/', '😂' as '😭' } from 'external';

import * as lib from 'external';
export { lib as '🍅' }