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

Add a mode that converts to @embroider/macros #96

Merged
merged 1 commit into from
Apr 17, 2024
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
5 changes: 5 additions & 0 deletions fixtures/embroider-macros/assert/expectation7.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { assert } from '@ember/debug';
import { isDevelopingApp } from '@embroider/macros';

isDevelopingApp() && !(() => true )() && assert('This is an assertion', (() => true )());
isDevelopingApp() && !false && assert('This is an assertion 2', false);
4 changes: 4 additions & 0 deletions fixtures/embroider-macros/assert/sample.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { assert } from '@ember/debug';

assert('This is an assertion', (() => true )());
assert('This is an assertion 2', false);
10 changes: 10 additions & 0 deletions fixtures/embroider-macros/deprecate/expectation7.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { deprecate } from '@ember/debug';
import { isDevelopingApp } from '@embroider/macros';

isDevelopingApp() &&
!false &&
deprecate('This is deprecated', false, {
until: '3.0.0',
id: 'a-thing',
url: 'http://example.com',
});
7 changes: 7 additions & 0 deletions fixtures/embroider-macros/deprecate/sample.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { deprecate } from '@ember/debug';

deprecate('This is deprecated', false, {
until: '3.0.0',
id: 'a-thing',
url: 'http://example.com'
})
5 changes: 5 additions & 0 deletions fixtures/embroider-macros/glimmer-env/expectation7.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { isDevelopingApp } from '@embroider/macros';

if (isDevelopingApp()) {
console.log('stuff');
}
5 changes: 5 additions & 0 deletions fixtures/embroider-macros/glimmer-env/sample.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { DEBUG } from '@glimmer/env';

if (DEBUG) {
console.log('stuff');
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
]
},
"dependencies": {
"babel-import-util": "^2.0.2",
"semver": "^7.6.0"
},
"devDependencies": {
Expand Down
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 10 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import Macros from './utils/macros';
import { UserOptions, NormalizedOptions, normalizeOptions } from './utils/normalize-options';
import * as Babel from '@babel/core';
import type { types as t } from '@babel/core';
import { ImportUtil } from 'babel-import-util';

interface State {
opts: NormalizedOptions;
macroBuilder: Macros;
util: ImportUtil;
}

export default function macros(babel: typeof Babel): Babel.PluginObj<State> {
Expand Down Expand Up @@ -45,7 +47,12 @@ export default function macros(babel: typeof Babel): Babel.PluginObj<State> {
let binding = path.scope.getBinding(localBindingName)!;

binding.referencePaths.forEach((p) => {
p.replaceWith(buildIdentifier(flagValue, flagName));
if (flagValue === '@embroider/macros') {
p.replaceWith(t.callExpression(state.util.import(p, "@embroider/macros", "isDevelopingApp"), []))
p.scope.crawl();
} else {
p.replaceWith(buildIdentifier(flagValue, flagName));
}
});

path.remove();
Expand All @@ -70,7 +77,8 @@ export default function macros(babel: typeof Babel): Babel.PluginObj<State> {
// most of our plugin declares state.opts as already being normalized.
// This is the spot where we force it become so.
state.opts = normalizeOptions(state.opts as unknown as UserOptions);
this.macroBuilder = new Macros(babel, state.opts);
state.util = new ImportUtil(t, path)
this.macroBuilder = new Macros(babel, state.opts, state.util);

let body = path.get('body');

Expand Down
25 changes: 20 additions & 5 deletions src/utils/builder.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type * as Babel from '@babel/core';
import type { types as t } from '@babel/core';
import type { NodePath } from '@babel/core';
import type { NodePath, types as t } from '@babel/core';
import { CallIdentifierExpression, CallStatementPath } from './babel-type-helpers';
import { ImportUtil } from 'babel-import-util';

export interface Options {
module: boolean | undefined;
global: string | undefined;
assertPredicateIndex: number | undefined;
isDebug: boolean;
isDebug: boolean | "@embroider/macros";
}

interface MacroExpressionOpts {
Expand All @@ -27,18 +27,21 @@ export default class Builder {
private module: boolean | undefined;
private global: string | undefined;
private assertPredicateIndex: number | undefined;
private isDebug: boolean;
private isDebug: boolean | '@embroider/macros';
private util: ImportUtil;

private expressions: [CallStatementPath, (debugIdentifier: t.Expression) => t.Expression][] = [];

constructor(
readonly t: typeof Babel.types,
util: ImportUtil,
options: Options
) {
this.module = options.module;
this.global = options.global;
this.assertPredicateIndex = options.assertPredicateIndex;
this.isDebug = options.isDebug;
this.util = util;
}

/**
Expand Down Expand Up @@ -212,12 +215,24 @@ export default class Builder {
*/
expandMacros() {
let t = this.t;
let flag = t.booleanLiteral(this.isDebug);
for (let i = 0; i < this.expressions.length; i++) {
let expression = this.expressions[i];
let exp = expression[0];
let flag = this._debugExpression(exp);
let logicalExp = expression[1];
exp.replaceWith(t.parenthesizedExpression(logicalExp(flag)));
exp.scope.crawl();
}
}

_debugExpression(target: NodePath) {
if (typeof this.isDebug === 'boolean') {
return this.t.booleanLiteral(this.isDebug);
} else {
return this.t.callExpression(
this.util.import(target, '@embroider/macros', 'isDevelopingApp'),
[]
);
}
}

Expand Down
13 changes: 9 additions & 4 deletions src/utils/macros.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import type { types as t } from '@babel/core';
import type { NormalizedOptions } from './normalize-options';
import type { NodePath } from '@babel/core';
import { isCallStatementPath, name } from './babel-type-helpers';
import type { ImportUtil } from 'babel-import-util';


const SUPPORTED_MACROS = ['assert', 'deprecate', 'warn', 'log'];
type SupportedMacro = 'assert' | 'deprecate' | 'warn' | 'log';
Expand All @@ -14,9 +16,9 @@ export default class Macros {
private localDebugBindings: NodePath<t.Identifier>[] = [];
private builder: Builder;

constructor(babel: typeof Babel, options: NormalizedOptions) {
constructor(babel: typeof Babel, options: NormalizedOptions, util: ImportUtil) {
this.debugHelpers = options.externalizeHelpers;
this.builder = new Builder(babel.types, {
this.builder = new Builder(babel.types, util, {
module: this.debugHelpers?.module,
global: this.debugHelpers?.global,
assertPredicateIndex: options.debugTools && options.debugTools.assertPredicateIndex,
Expand Down Expand Up @@ -61,7 +63,8 @@ export default class Macros {
}
if (this.localDebugBindings.some((b) => b.node.name === path.node.expression.callee.name)) {
let imported = name(
(path.scope.getBinding(path.node.expression.callee.name)!.path.node as t.ImportSpecifier).imported
(path.scope.getBinding(path.node.expression.callee.name)!.path.node as t.ImportSpecifier)
.imported
) as SupportedMacro;
this.builder[`${imported}`](path);
}
Expand All @@ -70,7 +73,9 @@ export default class Macros {
_cleanImports() {
if (!this.debugHelpers?.module) {
if (this.localDebugBindings.length > 0) {
let importPath = this.localDebugBindings[0].findParent((p) => p.isImportDeclaration()) as NodePath<t.ImportDeclaration> | null;
let importPath = this.localDebugBindings[0].findParent((p) =>
p.isImportDeclaration()
) as NodePath<t.ImportDeclaration> | null;
if (importPath === null) {
// import declaration in question seems to have already been removed
return;
Expand Down
26 changes: 17 additions & 9 deletions src/utils/normalize-options.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { gt } from 'semver';

function parseDebugTools(options: UserOptions): {
isDebug: boolean;
isDebug: boolean | '@embroider/macros';
debugToolsImport: string;
assertPredicateIndex: number | undefined;
} {
Expand All @@ -26,8 +26,8 @@ function evaluateFlagValue(
options: UserOptions,
name: string | undefined,
flagName: string,
flagValue: string | boolean | null
): boolean | null {
flagValue: string | boolean | null | "@embroider/macros"
): boolean | null | "@embroider/macros" {
let svelte = options.svelte;

if (typeof flagValue === 'string' && name) {
Expand All @@ -38,15 +38,19 @@ function evaluateFlagValue(
}
} else if (typeof flagValue === 'boolean' || flagValue === null) {
return flagValue;
} else if (flagValue === '@embroider/macros') {
return flagValue;
} else {
throw new Error(`Invalid value specified (${flagValue}) for ${flagName} by ${name}`);
}
}

function parseFlags(options: UserOptions): Record<string, Record<string, boolean | null>> {
function parseFlags(
options: UserOptions
): Record<string, Record<string, boolean | null | '@embroider/macros'>> {
let flagsProvided = options.flags || [];

let combinedFlags: Record<string, Record<string, boolean | null>> = {};
let combinedFlags: Record<string, Record<string, boolean | null | '@embroider/macros'>> = {};
flagsProvided.forEach((flagsDefinition) => {
let source = flagsDefinition.source;
let flagsForSource = (combinedFlags[source] = combinedFlags[source] || {});
Expand All @@ -71,9 +75,9 @@ export interface NormalizedOptions {
module?: boolean;
global?: string;
};
flags: Record<string, Record<string, boolean | null>>;
flags: Record<string, Record<string, boolean | null | '@embroider/macros'>>;
debugTools: {
isDebug: boolean;
isDebug: boolean | '@embroider/macros';
debugToolsImport: string;
assertPredicateIndex: number | undefined;
};
Expand All @@ -85,9 +89,13 @@ export interface UserOptions {
global?: string;
};
svelte?: Record<string, string>;
flags?: { source: string; name?: string; flags: Record<string, boolean | string | null> }[];
flags?: {
source: string;
name?: string;
flags: Record<string, boolean | string | null | '@embroider/macros'>;
}[];
debugTools?: {
isDebug: boolean;
isDebug: boolean | '@embroider/macros';
source: string;
assertPredicateIndex?: number;
};
Expand Down
43 changes: 41 additions & 2 deletions tests/create-tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'code-equality-assertions/jest';
import { type TransformOptions, transform } from '@babel/core';
import * as Babel from '@babel/core';
import { types as t } from '@babel/core';
import { UserOptions } from '../src/utils/normalize-options.js';

const CONSOLE = Object.assign({}, console);

Expand Down Expand Up @@ -216,7 +217,7 @@ export default function createTests(options: Options) {
DebugToolsPlugin,
{
externalizeHelpers: {
module: '@ember/debug',
module: true,
},
debugTools: {
isDebug: true,
Expand Down Expand Up @@ -450,7 +451,45 @@ export default function createTests(options: Options) {
h.generateTest('retains-runtime-definitions');
});

function transformTestHelper(options: TransformOptions) {
describe('@embroider/macros target', function () {
let h = transformTestHelper({
plugins: [
[
DebugToolsPlugin,
{
debugTools: {
source: '@ember/debug',
assertPredicateIndex: 1,
isDebug: '@embroider/macros',
},
externalizeHelpers: {
module: true,
},
flags: [
{
source: '@glimmer/env',
flags: {
DEBUG: '@embroider/macros',
},
},
],
},
],
],
});

h.generateTest('embroider-macros/assert');
h.generateTest('embroider-macros/deprecate');
h.generateTest('embroider-macros/glimmer-env');
});

// This says: the first plugin must be our own plugin. It allows us to
// typecheck the options.
type OurTransformOptions = Omit<TransformOptions, 'plugins'> & {
plugins: [[unknown, UserOptions?], ...NonNullable<TransformOptions['plugins']>];
};

function transformTestHelper(options: OurTransformOptions) {
return {
generateTest(fixtureName: string) {
it(fixtureName, function () {
Expand Down