Skip to content

Commit

Permalink
fix(babel-preset,shaker): do not delete exported functions even if it…
Browse files Browse the repository at this point in the history
…s body was deleted

fixes #1237
  • Loading branch information
Anber committed Apr 24, 2023
1 parent 4b75b2d commit dbe250b
Show file tree
Hide file tree
Showing 14 changed files with 385 additions and 42 deletions.
8 changes: 8 additions & 0 deletions .changeset/little-cats-promise.md
@@ -0,0 +1,8 @@
---
'@linaria/babel-preset': patch
'@linaria/shaker': patch
'@linaria/testkit': patch
'@linaria/utils': patch
---

Fix module function deletion when containing restricted code (fixes #1226)
1 change: 1 addition & 0 deletions packages/babel/src/index.ts
Expand Up @@ -22,6 +22,7 @@ export { default as transform } from './transform';
export * from './types';
export { default as loadLinariaOptions } from './transform-stages/helpers/loadLinariaOptions';
export type { PluginOptions } from './transform-stages/helpers/loadLinariaOptions';
export { prepareCode } from './transform-stages/1-prepare-for-eval';
export { transformUrl } from './transform-stages/4-extract';
export { default as isNode } from './utils/isNode';
export { default as getTagProcessor } from './utils/getTagProcessor';
Expand Down
2 changes: 1 addition & 1 deletion packages/babel/src/transform-stages/1-prepare-for-eval.ts
Expand Up @@ -97,7 +97,7 @@ function getMatchedRule(
return { action: 'ignore' };
}

function prepareCode(
export function prepareCode(
babel: Core,
filename: string,
originalCode: string,
Expand Down
4 changes: 4 additions & 0 deletions packages/shaker/src/plugins/__tests__/shaker-plugin.test.ts
Expand Up @@ -306,6 +306,10 @@ describe('shaker', () => {
Object.defineProperty(exports, \\"__esModule\\", {
value: true
});
Object.defineProperty(exports, \\"createContext\\", {
enumerable: !0,
get: function () {}
});
exports.default = defaultExports;"
`);
});
Expand Down
22 changes: 7 additions & 15 deletions packages/shaker/src/plugins/shaker-plugin.ts
Expand Up @@ -2,24 +2,24 @@ import type core from '@babel/core';
import type { BabelFile, PluginObj, NodePath } from '@babel/core';
import type { Binding } from '@babel/traverse';
import type {
VariableDeclarator,
Program,
Identifier,
MemberExpression,
Program,
VariableDeclarator,
} from '@babel/types';

import { createCustomDebug } from '@linaria/logger';
import type { IExport, IReexport, IState } from '@linaria/utils';
import {
applyAction,
collectExportsAndImports,
dereference,
findActionForNode,
getFileIdx,
isRemoved,
reference,
removeWithRelated,
sideEffectImport,
reference,
findActionForNode,
dereference,
mutate,
} from '@linaria/utils';

import shouldKeepSideEffect from './utils/shouldKeepSideEffect';
Expand Down Expand Up @@ -311,15 +311,7 @@ export default function shakerPlugin(
(!binding || outerReferences.length === 0)
) {
if (action) {
mutate(action[1], (p) => {
if (isRemoved(p)) return;

if (action[0] === 'remove') {
p.remove();
} else if (action[0] === 'replace') {
p.replaceWith(action[2]);
}
});
applyAction(action);
} else {
removeWithRelated([path]);
}
Expand Down
@@ -0,0 +1,3 @@
**/*.js
**/*.ts
**/*.tsx
@@ -0,0 +1,90 @@
// slugify
'use strict';

Object.defineProperty(exports, '__esModule', {
value: true,
});
var _exportNames = {
asyncResolveFallback: true,
collectExportsAndImports: true,
slugify: true,
};
Object.defineProperty(exports, 'asyncResolveFallback', {
enumerable: true,
get: function () {
return _asyncResolveFallback.default;
},
});
Object.defineProperty(exports, 'collectExportsAndImports', {
enumerable: true,
get: function () {
return _collectExportsAndImports.default;
},
});
Object.defineProperty(exports, 'slugify', {
enumerable: true,
get: function () {
return _slugify.default;
},
});
var _asyncResolveFallback = _interopRequireWildcard(
require('./asyncResolveFallback')
);
var _collectExportsAndImports = _interopRequireWildcard(
require('./collectExportsAndImports')
);
Object.keys(_collectExportsAndImports).forEach(function (key) {
if (key === 'default' || key === '__esModule') return;
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
if (key in exports && exports[key] === _collectExportsAndImports[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _collectExportsAndImports[key];
},
});
});
var _slugify = _interopRequireDefault(require('./slugify'));
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
}
function _getRequireWildcardCache(nodeInterop) {
if (typeof WeakMap !== 'function') return null;
var cacheBabelInterop = new WeakMap();
var cacheNodeInterop = new WeakMap();
return (_getRequireWildcardCache = function (nodeInterop) {
return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
})(nodeInterop);
}
function _interopRequireWildcard(obj, nodeInterop) {
if (!nodeInterop && obj && obj.__esModule) {
return obj;
}
if (obj === null || (typeof obj !== 'object' && typeof obj !== 'function')) {
return { default: obj };
}
var cache = _getRequireWildcardCache(nodeInterop);
if (cache && cache.has(obj)) {
return cache.get(obj);
}
var newObj = {};
var hasPropertyDescriptor =
Object.defineProperty && Object.getOwnPropertyDescriptor;
for (var key in obj) {
if (key !== 'default' && Object.prototype.hasOwnProperty.call(obj, key)) {
var desc = hasPropertyDescriptor
? Object.getOwnPropertyDescriptor(obj, key)
: null;
if (desc && (desc.get || desc.set)) {
Object.defineProperty(newObj, key, desc);
} else {
newObj[key] = obj[key];
}
}
}
newObj.default = obj;
if (cache) {
cache.set(obj, newObj);
}
return newObj;
}
39 changes: 39 additions & 0 deletions packages/testkit/src/__snapshots__/prepareCode.test.ts.snap
@@ -0,0 +1,39 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`prepareCode Testing transformation for for-debug: code 1`] = `
"'use strict';
Object.defineProperty(exports, '__esModule', {
value: true
});
Object.defineProperty(exports, 'asyncResolveFallback', {
enumerable: true,
get: function get() {}
});
Object.defineProperty(exports, 'collectExportsAndImports', {
enumerable: true,
get: function get() {}
});
Object.defineProperty(exports, 'slugify', {
enumerable: true,
get: function get() {
return _slugify.default;
}
});
var _slugify = _interopRequireDefault(require('./slugify'));
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}"
`;

exports[`prepareCode Testing transformation for for-debug: imports 1`] = `
Map {
"./slugify" => Array [
"default",
],
}
`;

exports[`prepareCode Testing transformation for for-debug: metadata 1`] = `Object {}`;
2 changes: 2 additions & 0 deletions packages/testkit/src/preeval.test.ts
Expand Up @@ -83,6 +83,7 @@ describe('preeval', () => {

expect(code).toMatchSnapshot();
});

it('should keep object members that look like window globals', () => {
const { code } = run`
class Test {
Expand All @@ -95,6 +96,7 @@ describe('preeval', () => {

expect(code).toMatchSnapshot();
});

it('should keep type parameters that look like window globals', () => {
const { code } = run`
const blah = window.Foo;
Expand Down
68 changes: 68 additions & 0 deletions packages/testkit/src/prepareCode.test.ts
@@ -0,0 +1,68 @@
import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
import { join } from 'path';

import * as babel from '@babel/core';

import { prepareCode } from '@linaria/babel-preset';

const testCasesDir = join(__dirname, '__fixtures__', 'prepare-code-test-cases');

const testCaseFolders = readdirSync(testCasesDir).filter((file) =>
statSync(join(testCasesDir, file)).isDirectory()
);

const rules = [
{
test: () => true,
action: require('@linaria/shaker').default,
},
];

const pluginOptions = {
rules,
babelOptions: {
babelrc: false,
configFile: false,
presets: [
['@babel/preset-env', { loose: true }],
'@babel/preset-react',
'@babel/preset-typescript',
],
},
};

const extensions = ['ts', 'tsx', 'js', 'jsx'];

describe('prepareCode', () => {
testCaseFolders.forEach((testCaseFolder) => {
test(`Testing transformation for ${testCaseFolder}`, () => {
const root = join(testCasesDir, testCaseFolder);
const inputFileWithoutExtension = join(root, 'input');
const inputFilePath = extensions
.map((ext) => `${inputFileWithoutExtension}.${ext}`)
.find((path) => existsSync(path))!;

const input = readFileSync(inputFilePath, 'utf8');
const [firstLine, ...restLines] = input.split('\n');
const only = firstLine
.slice(2)
.split(',')
.map((s) => s.trim());

const [transformedCode, imports, metadata] = prepareCode(
babel,
inputFilePath,
restLines.join('\n'),
only,
{
root,
pluginOptions,
}
);

expect(transformedCode).toMatchSnapshot('code');
expect(imports).toMatchSnapshot('imports');
expect(metadata).toMatchSnapshot('metadata');
});
});
});
Expand Up @@ -17,6 +17,12 @@ exports[`removeWithRelated should not delete params of functions 1`] = `
}"
`;

exports[`removeWithRelated should not remove top-level functions with empty bodies 1`] = `
"function testFn() {}
export const testArrow1 = () => {};
const testArrow2 = () => {};"
`;

exports[`removeWithRelated should remove node if it becomes invalid after removing its children 1`] = `""`;

exports[`removeWithRelated should remove the whole import 1`] = `""`;
Expand Down
56 changes: 48 additions & 8 deletions packages/testkit/src/utils/removeWithRelated.test.ts
Expand Up @@ -94,14 +94,16 @@ describe('removeWithRelated', () => {

it('should shake try/catch', () => {
const code = run`
const a = 1;
/* remove */const b = 2;
function c() {
try {
return a;
} catch(e) {
return b;
{
const a = 1;
/* remove */const b = 2;
function c() {
try {
return a;
} catch(e) {
return b;
}
}
}
`;
Expand Down Expand Up @@ -130,4 +132,42 @@ describe('removeWithRelated', () => {

expect(code).toMatchSnapshot();
});

it('should remove functions with empty bodies', () => {
const code = run`
function container() {
function testFn(arg) {
/* remove */console.log(arg);
}
const testArrow = (arg) => {
/* remove */console.log(arg);
}
}
`;

expect(code).toBe('function container() {}');
});

it('should not remove top-level functions with empty bodies', () => {
const code = run`
function testFn(arg) {
/* remove */console.log(arg);
}
export const testArrow1 = (arg) => {
/* remove */console.log(arg);
}
const testArrow2 = (arg) => (
/* remove */testFn = arg
)
export default function testDefaultFn(arg) {
/* remove */console.log(arg);
}
`;

expect(code).toMatchSnapshot();
});
});

0 comments on commit dbe250b

Please sign in to comment.