googlefeud / ts-macros Goto Github PK
View Code? Open in Web Editor NEWA typescript transformer / plugin that allows you to write macros for typescript!
Home Page: https://googlefeud.github.io/ts-macros/
License: MIT License
A typescript transformer / plugin that allows you to write macros for typescript!
Home Page: https://googlefeud.github.io/ts-macros/
License: MIT License
Describe the bug
Unfortunately, I have not been able to reduce to a minimal test case.
I have this class:
export class Operation extends Node<OperationValue> {
async eval(context: Context): Promise<Node> {
let [left, op, right] = this.value
if (!left.operate) {
throw new TypeError(`Cannot operate on ${left.type}`)
}
return await this.evalIfNot(context, async () => {
if (context.shouldOperate(op)) {
left = await left.eval(context)
right = await right.eval(context)
return left.operate!(right, op, context)
}
let node = this.clone()
node.evaluated = true
return node
})
}
}
I am getting the following error when trying to build with TypeScript:
error TS8000: No possible candidates for "operate" call
25 return left.operate!(right, op, context)
What's confusing is that the documentation seems to imply that a macro always requires a preceding $
character, which does not exist here. However, when I tried to create a simple test case in the ts-macros playground, I couldn't reproduce it.
I'm refactoring so I don't need a non-null assertion here, but I thought I should report it anyway.
Describe the bug
When using postfix macros (in playground at least), they do not pass the type checker and get underlined with an error "Property does not exist on type".
Code to reproduce
function $try(resultObj: Save<{ value?: number, is_err: () => boolean}>) {
$$escape!(() => {
if (resultObj.is_err()) {
return resultObj;
}
});
return resultObj.value;
}
const val1 = $try!({ value: 123, is_err: () => false });
const val2 = ({ value: 123, is_err: () => false }).$try!();
// ~~~~
// Property '$try' does not exist on type '{ value: number; is_err: () => boolean; }'.(2339)
//
// typeof val2 === any.
Expected behavior
Expected val2 to have the same type as val1, and no errors to be displayed.
Hello, I've been dabbling around the lib for a while. I just wanna know how to make imports work inside the $$raw
macro.
Here is an example
import { $$raw, RawContext } from "ts-macros";
import * as fs from "fs";
export function $writeMyFile(): string {
return $$raw!((ctx: RawContext) => {
const myfile = fs.readFileSync('file.txt', 'utf8')
return ctx.factory.createStringLiteral(myfile.toString());
});
}
But this macro does not compile and gives me an error
$fs is not defined
9 var css = fs.readFileSync('file.txt', 'utf8');
I'm aware that $$readFile
macro exists, but I wanted to do something beyond that (which includes writing, using third party libs, etc.). I'd also be grateful to have an insight on how does $$raw
macro actually work, since it gives me some issues to (just like this one) which are non-existent outside of its scope.
Hi again.
Describe the bug
When using version 2.2.1
(with either [email protected]
or [email protected]
), I get that cryptic error when I use a certain macro ($$readFile
for instance) in certain scenarios.
Code to reproduce
Apparently, we cannot use $$readFile
in a namespace...
// ❌ error.ts
import { $$readFile } from "ts-macros";
namespace Foo {
$$readFile!("src/index.ts");
}
or in a object literal AND in a class...
// ❌ error.ts
import { $$readFile } from "ts-macros";
const Foo = {
a: $$readFile!("src/index.ts"),
};
class Bar {
foo() {
$$readFile!("src/index.ts");
}
}
but we can use $$ts
inside a namespace...
// ✅ fine.ts
import { $$ts } from "ts-macros";
namespace Foo {
$$ts!(";");
}
or $$readFile
in a object literal...
// ✅ fine.ts
import { $$readFile } from "ts-macros";
const Foo = {
a: $$readFile!("src/index.ts"),
};
or $$readFile
in a class...
// ✅ fine.ts
import { $$readFile } from "ts-macros";
class Bar {
foo() {
$$readFile!("src/index.ts");
}
}
Additional context
Doesn't occur in 2.1.0
. Not sure if this is related to ts-patch
or typescript=^5
.
Is your feature request related to a problem? Please describe.
MacroError
is a fantastic way of telling the user what the error. I think if someone is writing custom macros, they should be able to throw using MacroError
.
Describe the solution you'd like
import {MacroError} from 'ts-macro'
function $justThrow() {
$$raw((ctx) => {
throw MacroError(ctx, "This just fails")
})
}
Describe alternatives you've considered
I have copy pasted all the code from the library to throw an error from my macro.
Additional context
None
Describe the bug
ESM imports that contain the type
keyword are not modified by the compiler when the transform is enabled.
Code to reproduce
import type { foo } from './test.js';
import { bar, type baz } from './test.js';
Expected behavior
Imports should be modified by the compiler to only include "real" imports.
The above example should compile to the following:
import { bar } from './test.js';
Additional context
Note: This issue also occurred while using ttypescript (@1.5.15
) with typescript (@4.9.5
).
Version info:
Describe the bug
The code below produces the following error on npx tsc
:
src/bug240.ts:1:7 - error TS2451: Cannot redeclare block-scoped variable 'temp_1'.
1 const temp_1 = 5;export const a = temp_1;const temp_1 = "five";export const b = temp_1;
~~~~~~
src/bug240.ts:1:48 - error TS2451: Cannot redeclare block-scoped variable 'temp_1'.
1 const temp_1 = 5;export const a = temp_1;const temp_1 = "five";export const b = temp_1;
~~~~~~
Found 2 errors in the same file, starting at: src/bug240.ts:1
Code to reproduce
import {$$escape} from 'ts-macros'
function $dummy<T>(expr: T) {
return $$escape!(() => {
const temp: T = expr;
return temp;
})
}
export const a = $dummy!(5);
export const b = $dummy!('five');
Expected behavior
The code should compile and run without error; presumably the object code would use two different identifiers for the temporary variables in each of the two calls to $dummy!
Additional context
pnpm ls
produces:
devDependencies:
@types/node 20.5.0
source-map 0.7.4
ts-macros 2.4.0
ts-patch 3.0.2
typescript 5.1.6
tsconfig.json contains:
{
"compilerOptions": {
"target": "ES2022",
"rootDir": "./src",
"outDir": "./build",
"moduleResolution": "nodenext",
"plugins": [ {
"transform": "ts-macros/dist/type-resolve",
"transformProgram": true
} ]
}
}
Describe the bug
Splitting files generates strange code.
Code to reproduce
input
// src/Vector2.ts
export namespace Vector2 {
export function $new(): Vector2 {
return [0, 0] as unknown as Vector2;
}
export function $from(x: number, y: number): Vector2 {
return [x, y] as unknown as Vector2;
}
}
export interface Vector2 {
$x(): number;
$y(): number;
}
function $asData(v: Vector2): [number, number, number] {
return v as unknown as [number, number, number];
}
export function $x(v: Vector2): number {
return $asData!(v)[0];
}
export function $y(v: Vector2): number {
return $asData!(v)[1];
}
// src/index.ts
import { Vector2 } from '../Vector2';
const vec1 = Vector2.$new!();
console.log(vec1.$x!());
console.log(vec1.$y!());
const vec2 = Vector2.$from!(1, 2);
export {}
output
// src/Vector2.js
export var Vector2;
(function (Vector2) {
})(Vector2 || (Vector2 = {}));
// src/index.js
import { Vector2 } from '../Vector2';
const vec1 = [v, c1];
console.log(vec1[]);
console.log(vec1[]);
const vec2 = [Vector2, 1];
Expected behavior
// src/index.js
import { Vector2 } from '../Vector2';
const vec1 = [0, 0];
console.log(vec1[0]);
console.log(vec1[1]);
const vec2 = [1, 2];
Additional context
If i don't split the file, the Expected Behavior will come out properly.
I'm sorry for making an issue again even though it hasn't been long since you patched :[
Hello!
But emitted result not expected:
(() => {
return arr_1[i_1] * 2;
})();
const pushed_1 = null;
Want to see something like this:
const pushed = arr_1[i_1] * 2;
const pushed_1 = pushed;
But self-executing function has additional runtime cost and desire to avoid it totally:
const pushed_1 = (() => {
return arr_1[i_1] * 2;
})();
res_1.push(pushed_1);
I'm not certain this is a ts-macros bug or a ts-patch bug, but my perception would be that:
The bug and all its details are here.
To re-summarize:
// chevrotain.d.ts
declare module 'chevrotain' {
interface CstParser {
// things that augment this type
}
}
// cssParser.ts
import { CstParser, EOF } from 'chevrotain'
class CssParser extends CstParser {
// class implementation
}
// outputs
import { EOF } from 'chevrotain'
Someone on the TypeScript team investigated and said that disabling ts-patch (which disables ts-macros) solves this bug.
Describe the bug
A clear and concise description of what the bug is.
When you split a file, the macro behaves strangely
Code to reproduce
Paste the relevant code which reproduces the error, or give detailed instructions on how to reproduce it.
input:
// test.ts
export function $m1() {
return 1;
}
// index.ts
import { $m1 } from "./test";
const m1 = $m1!();
m1;
output:
// index.js
import { $m1 } from "../test";
const m1 = null;
m1;
Expected behavior
// index.js
import { $m1 } from "../test";
const m1 = 1;
m1;
Additional context
Add any other context about the problem here.
It should also be possible to duplicate the names of macros on a file-by-file basis
For example, if file1.ts
has a macro named m1
and file2.ts
has a macro named m1
, you should be able to use the macro you want, depending on which one you import.
// file1.ts
export function $m1() {
return 1;
}
// file2.ts
export function $m1() {
return 2;
}
// index.ts
import { $m1 } from './file1';
import { $m1 as $m2 } from './file2';
const test = $m1!(); // it should be 1
const test = $m2!(); // it should be 2
Describe the bug
When a recursive method is defined inside of $$raw
then I always end up getting a stack overflow exception.
Code to reproduce
import { $$raw } from "ts-macros"
export function $recurse() {
return $$raw!((ctx, arg) => {
function loop(i: number): number {
return i > 10 ? i : loop(i + 1)
}
return ctx.factory.createStringLiteral(loop(0).toString())
})
}
const foo = $recurse!()
Expected behavior
There shouldn't be any stackoverflow
Additional context
Nothing
Is your feature request related to a problem? Please describe.
Suppose I make a macro:
function $reflect<T>(expr: T) {
expr.reflectedType = $$typeToString!<T>(true, false, true);
}
Then indeed the following code
export const foo = {x: 1, y: 2}
export const bar = {a: 'A', b: 'B'}
export const baz = {yes: true, no: false}
$reflect!(foo)
$reflect!(bar)
$reflect!(baz)
generates
export const foo = {x: 1, y: 2}
export const bar = {a: 'A', b: 'B'}
export const baz = {yes: true, no: false}
foo.reflectedType = "{x: number, y: number}";
bar.reflectedType = "{a: string, b.string}";
baz.reflectedType = "{yes: boolean, no: boolean}";
as desired (ignore for the moment that it then generates a TypeScript error since the type of foo has no reflectedType
property).
But what If I would like to create a different macro so that I could abbreviate the last three lines with say:
$reflectTypes!([foo, bar, baz])
and generate the exact same code?
Describe the solution you'd like
I'd like there to be a way to cycle through an array of literals, accessing the type of each in turn.
Describe alternatives you've considered
I tried
export function $reflectTypes<ImplTuple>(tup: ImplTuple) {
+[[tup], <T>(elt: T) =>
elt.reflectedType = $$typeToString!<T>(true, false, true)]
}
but that fails with
TS8000: `typeToString` macro expects one type parameter.
even though it certainly looks like I gave it one type parameter. I also tried the more baroque
export function $reflectTypes<ImplTuple>(tup: ImplTuple) {
+[[tup, [0,1,2]], <N extends number>(elt: ImplTuple[N], n: N) =>
elt.reflectedType = $$typeToString!<ImplTuple[N]>(true, false, true)]
}
and that actually seems to get through macro processing but it produces "{}"
for all of the type strings.
Maybe I am just missing how this should be done? If so, guidance would be welcome. Thanks!
Additional context
Currently running ts-macros 2.5.0, ts-patch 3.0.2
I'd like to use ts-macros with Vite, a successor to create-react-app and a popular modern development framework for web.
May it be possible for you to create an example repository of ts-macros correctly configured with Vite? If so, it would be great if the example repository could also include a small custom macro.
Thanks, I'd really appreciate it!
Describe the bug
Overloading macros will not allow compilation.
src/test/index.ts:1:47 - error TS8000: Macro $Vector3 is already defined.
1 function $Vector3(): [number, number, number];
2
3 function $Vector3(x: number, y: number, z: number): [number, number, number];
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Code to reproduce
function $Vector3(): [number, number, number];
function $Vector3(x: number, y: number, z: number): [number, number, number];
function $Vector3(x = 0, y = 0, z = 0): [number, number, number] {
return [x, y, z];
}
const vec1 = $Vector3!(1, 2, 3);
Expected behavior
Compilation should be done well as below
const vec1 = [1, 2, 3];
Additional context
If the macro is not intended for overloading, it would be better to make other errors
Library is perfect !
Is there a y plans to support other than webpack bundlers: vite, esbuild?
Hi!
Thanks for this amazing library! This should be officially supported by typescript IMHO 🙏
I am trying to create schema for types using the $$raw
macro. However, I am unable to figure out how to get access to the type-param that's being passed to the macro for eg:
Input
type Foo = {
bar: string
}
const FooSchema = $generateSchema!<Foo>()
The expected output should look like something like below.
Output
type Foo = {
bar: string
}
const FooSchema = {
name: Foo,
fields: [
{name: "bar", type: "string"}
]
}
Since Foo
is a type and not a value, how do I get access to it in my macro?
Describe the bug
Macro and the invoking need to be in separate file
// def.ts
export function $macro(a: number, b: number) {
return a + b;
}
// usage.ts
import {$macro} from "./def.ts"
console.log($macro!(5, 4));
Code to reproduce
Load the files with ts-loader
.
webpack --watch
#all good
# edit macro def
# still old result
Expected behavior
Updated result
Additional context
I'm not sure there's anything you can do here, this seems to be related to ts-loader.
Describe the bug
Currently, imports of macros are kept, but they should be eliminated.
Other than being a design expectation/requirement (there shouldn't be macros leftovers in the executable code), it also causes a runtime exception.
Code to reproduce
The following code
// macros.ts
// @internal
export function $test() {
console.log("Test!");
}
// index.ts
import { $test } from "./macros.js";
$test!();
gets transpiled to:
// macros.js
export {};
// index.js
import { $test } from "./macros.js"; // <--- this should have been removed
console.log("Test!");
This isn't a problem in CommonJs, but it is in ECMAScript modules (The requested module './macros.js' does not provide an export named '$test'
).
Expected behavior
Imports of macros should be removed!
Additional context
NA
Describe the bug
When using a decorator either on fields, methods, accessors or classes, the compilation fails with TypeError: Cannot read properties of undefined (reading 'kind')
here.
Of course, this only happens when using ts-macros
(with ttypescript
).
Code to reproduce
The following snippet is enough to reproduce the bug on the playground (you don't have to use ts-macro
at all):
declare function decorator(...args: any[]): any
class Foo {
@decorator // <--- comment out to make it work
get bar() {
return 0;
}
}
Expected behavior
It should compile!
Additional context
I believe this is ts-macro
specific-issue - I already use ttypescript
with another transformer in a repo of mine and it works fine.
Describe the bug
Generates invalid code for macros nested in namespace.
Code to reproduce
input
namespace Vector3 {
export function $from(x: number, y: number, z: number): [number, number, number] {
return [x, y, z];
}
}
const vec1 = Vector3.$from!(1, 2, 3);
output
var Vector3;
(function (Vector3) {
})(Vector3 || (Vector3 = {}));
const vec1 = [Vector3, 1, 2];
Expected behavior
var Vector3;
(function (Vector3) {
})(Vector3 || (Vector3 = {}));
const vec1 = [1, 2, 3];
Describe the bug
The order of the parameters to a generic macro appears to affect the type it believes it is instantiated with.
Code to reproduce
function $tplFirst<T>(expr: T, name: string) {
console.log('Deduced', name, '=', expr, 'is a', $$typeToString!<T>())
}
function $tplSecond<T>(name: string, expr: T) {
console.log('Believe', name, '=', expr, 'is a', $$typeToString!<T>())
}
$tplFirst!([37, 45], 'correctly')
$tplSecond!('bizarrely', [37, 45])
Expected behavior
Output:
Deduced correctly = [37, 45] is a number[]
Believe bizarrely = [37, 45] is a number[]
Actual behavior
Output:
Deduced correctly = [ 37, 45 ] is a number[]
Believe bizarrely = [ 37, 45 ] is a string
** Additional context **
I'm trying to make a macro that represents a named, type-annotated expression, so it's very natural that the name should come first in the macro call. I'd really like to find a way to deduce the type of the expression (and get information about that type) while allowing the users of the macro to put the name first.
Describe the bug
The macro does not generate code when dividing files in rollup.
Code to reproduce
sources:
//macro.ts
export function $test() {
console.log("test");
}
//index.ts
import { $test } from "macro";
function $test2() {
console.log("test2");
}
class Test {
constructor() {
$test!();
$test2!();
}
}
export default Test;
setting fIles:
//package.json
{
"name": "macro-rollup",
"version": "1.0.0",
"scripts": {
"build": "rollup -c"
},
"devDependencies": {
"rollup": "^2.79.1",
"rollup-plugin-ts": "^3.0.2",
"ts-macros": "^1.3.6",
"typescript": "^4.8.4"
}
}
//rollup.config.js
import rollupPluginTs from "rollup-plugin-ts";
import tsMacroTransformer from 'ts-macros';
import tsConfig from "./tsconfig.json";
export default {
input: 'index.ts',
output: {
file: 'dist/index.js',
format: 'esm'
},
plugins: [
rollupPluginTs({
tsconfig: {
...tsConfig.compilerOptions,
},
transformers: (options) => ({
before: [tsMacroTransformer(options.program)]
})
})
]
}
//tsconfig.json
{
"compilerOptions": {
"target": "ES6",
"outDir": "dist",
"strict": true,
"baseUrl": "./",
"esModuleInterop": true,
"downlevelIteration": true,
"moduleResolution": "Node",
"forceConsistentCasingInFileNames": true,
"stripInternal": true,
"resolveJsonModule": true
}
}
rollup build result:
//index.js
class Test {
constructor() {
console.log("test2");
}
}
export { Test as default };
Expected behavior
//index.js
class Test {
constructor() {
console.log("test");
console.log("test2");
}
}
export { Test as default };
Additional context
I think this is a fatal 🤔
Describe the bug
If using array destruction, $$inlineFunc
can't handle array alias
Describe the bug
When adding Save<>
around an argument type, return type changes for some reason.
Code to reproduce
abstract class _Result<T, E> {
public value: T | E;
}
class Ok<T> extends _Result<T, never> {
constructor(public value: T) {
super();
}
}
class Err<E> extends _Result<never, E> {
constructor(public value: E) {
super();
}
}
type Result<T, E> = Ok<T> | Err<E>;
function $try<T, E>(result: Save<Result<T, E>>): T {
$$escape!(() => {
if (result instanceof Err) {
return result;
}
});
return result.value as T;
}
declare const result: Result<null, string>;
const z = $try!(result);
// ^? z: string
Expected behavior
Expected type of z
to be null
.
Additional context
After removing Save
from type of $try.result
, type of z
becomes null
, as expected.
Describe the bug
If you build more than once using gulp, there will be an error.
Code to reproduce
// package.json
{
"name": "ts-macros-bug",
"version": "1.0.0",
"scripts": {
"build": "gulp"
},
"devDependencies": {
"gulp": "^4.0.2",
"gulp-typescript": "^6.0.0-alpha.1",
"ts-macros": "^1.3.3",
"typescript": "^4.8.3"
}
}
// index.ts
function $someMacro() {
console.log('Hello world!');
}
$someMacro!();
// gulpfile.js
const gulp = require('gulp');
const ts = require('gulp-typescript');
const macroTransformer = require('ts-macros').default;
gulp.task('build1', function () {
return gulp.src('index.ts')
.pipe(ts({
outFile: 'output1.js',
getCustomTransformers: (program) => ({
before: [ macroTransformer(program) ]
})
}))
.pipe(gulp.dest('.'));
});
gulp.task('build2', function () {
return gulp.src('index.ts')
.pipe(ts({
outFile: 'output2.js',
getCustomTransformers: (program) => ({
before: [ macroTransformer(program) ]
})
}))
.pipe(gulp.dest('.'));
});
gulp.task('default', gulp.series('build1', 'build2'));
If you build this project through npm run build, you will get an error.
[03:11:54] Using gulpfile ~\Desktop\ts-macros-bug\gulpfile.js
[03:11:54] Starting 'default'...
[03:11:54] Starting 'build1'...
[03:11:56] Finished 'build1' after 1.92 s
[03:11:56] Starting 'build2'...
index.ts:1:1 - error TS8000: Macro $someMacro is already defined.
1 function $someMacro() {
~~~~~~~~~~~~~~~~~~~~~~~
2 console.log('Hello world!');
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3 }
~
Expected behavior
error should not appear.
Additional context
I think this is a problem that happens because the transformer has a global variable that stores macros.
I get a 404
According to cevek/ttypescript@0c36bbf, ttypescript will no longer be receiving updates for typscript 5.0 and above.
Its probably best to update the README here accordingly.
The macro here seems to crash the playground site, and is not working on my project:
https://googlefeud.github.io/ts-macros/playground?code=FAMwrgdgxgLglgewgAgCQjhAJgIQJ4CSWAPACoB8AFAJTIDewwyzaUATgKYCGMHAYpix9IsRBACEZKqlQw8ABw6kEAZRhtMAc0kUaAGhlwsHCDHGUA5BmwXkAahlzFytRojapNe8gv4iF6mpgAF9GcGh4JFZOHn5BYQixTycOADkuAFsOAC5kAGd1LT0AOlKITJzkAEE2Ni48YgK3TXJaBhZ7AG0vAF5yeiYWLjy8aGRw0SjyrORKTgBHXIAlDnmwDgK9ZE485Y35JDyOLYgOAA8YXKatNsGO5nU8AfuX+6hDhAAbDmLPhE1KIZjKZzCl0llqMVwkFXshgsgoDwoAALWYcWoINi3WEdAD0uNOF0o6LYmJhr1CHVCwQAuiFgFBPsM8sgAMLIrgQU6fZ4sAo8OBQcaQbLXdzIHo+XgFCz04DoQR+LCSdmc7lUILAfEAARgeQAtHBNBBMRxQIJVVyOJ8lZYAIwWLYWABMjp8AGYAkA
I am trying to call the static method on a class, but using $$ident! for a class to call that static method seems to not be working. Also, it would be really cool if you could generate a function that doesn't need //@ts-ignore when you call it
I'm trying to create a logging macro for Typescript that I want to use across my project.
Using a macro inside the same file works:
// replication.ts
function $logInfo(domain: string | object, message: string) {
console.log(`(${domain}) ${message}`);
}
class Replication {
static toString() {
return 'Replication';
}
static connect() {
$logInfo!(this, 'connect');
}
}
// index.bundle.js
class Replication {
static toString() {
return 'Replication';
}
static connect() {
console.log(`(${this}) ${"connect"}`);
}
}
I bundle my Typescript with Webpack using the ts-loader
.
But if try to import my macro from another file, my macro is omitted entirely from the bundled output:
// logging.ts
function $logInfo(domain: string | object, message: string) {
console.log(`(${domain}) ${message}`);
}
export {
$logInfo,
};
// replication.ts
import {
$logInfo
} from './logging';
class Replication {
static toString() {
return 'Replication';
}
static connect() {
$logInfo!(this, 'connect');
}
}
// index.bundle.js
class Replication {
static toString() {
return 'Replication';
}
static connect() {
}
}
I would like to know what the best practices are when it comes to sharing macros between files.
Here's the relevant portion of my Webpack configuration:
module: {
rules: [
// compile typescript
{
test: /\.ts$/,
exclude: /node_modules/,
use: [
{
loader: 'ts-loader',
options: {
configFile: 'tsconfig.json',
compilerOptions: {
soureMap: Boolean(devtool),
outDir: path.resolve(targetPath, 'js')
},
getCustomTransformers: (program) => ({
before: [TsMacros.default(program)]
}),
},
},
],
},
],
],
Describe the bug
If there is a method that is defined on an object literal which uses $$raw
there the expansion fails.
Code to reproduce
const Foo = {
$bar() {
$$raw!((ctx) => {
return ctx.factory.createStringLiteral("Yello!")
})
}
}
Foo.$bar!()
Expected behavior
Macro expansion to succeed. Instead one gets the following error
Error: `raw` macro must be called inside another macro.
Additional context
None
Describe the bug
Some imports are removed by accident, even if there is no macro defined or invoked.
Code to reproduce
//index.ts
import * as mod from "./module";
mod.fn2();
//module.ts
export default function fn() {
console.log("Hello from default");
};
export function fn2() {
console.log("Hello from fn2");
};
Expected behavior
After transpilation, "./module"
import should be kept.
Additional context
Reproduces with ttypescript
& ts 4.9
as well as with ts-loader
and ts 5.0
You have a screen capture of a live transformation in the readme, that could be perfectly vanilla javascript with:
console.log(["a", "b"].includes(searchItem))
as someone knowing their languages, I usually advice against purely esoteric usage examples like this.
could someone come up with a better example for the readme?
Describe the bug
Emitted code reuses the incoming value multiple times.
function $try(result: Result<unknown, unknown>) {
if (result.is_err()) {
return result;
}
return result.value;
}
console.log(
$try!(Err(new Error('foo'))),
);
produces the following output:
console.log((() => {
if ((0, result_1.Err)(new Error('foo')).is_err()) {
return (0, result_1.Err)(new Error('foo'));
}
return (0, result_1.Err)(new Error('foo')).value;
})());
Expected behavior
Should not emit redundant code.
Additional context
I want to have a macro that allows for something like try!() or Rusts' result?
.
Describe the bug
A clear and concise description of what the bug is.
macros chaning not works properly
Code to reproduce
Paste the relevant code which reproduces the error, or give detailed instructions on how to reproduce it.
input:
// Vector2.ts
export namespace Vector2 {
export function $new(): Vector2 {
return [0, 0] as unknown as Vector2;
}
}
export interface Vector2 {
$x(): number;
$y(): number;
}
function $asData(v: Vector2): [number, number] {
return v as unknown as [number, number];
}
export function $x(v: Vector2): number {
return $asData!(v)[0];
}
export function $y(v: Vector2): number {
return $asData!(v)[1];
}
//index.ts
import { Vector2 } from "./Vector2";
const vec1 = Vector2.$new!();
console.log(vec1.$x!());
console.log(vec1.$y!());
output:
import { Vector2 } from "./Vector2";
const vec1 = [0, 0];
console.log(null);
console.log(null);
Expected behavior
A clear and concise description of what you expected to happen.
output should be:
import { Vector2 } from "./Vector2";
const vec1 = [0, 0];
console.log(vec1[0]);
console.log(vec1[1]);
Additional context
Add any other context about the problem here.
In version 1.3.3, you saved the name, not the symbol of the macro,
so it would have been possible to decide which macro to map to when chaining.
However, by storing symbols, it became impossible to know which macro to map to when chaining.
So to solve this problem, I think we need a syntax that determines which macro to actually map to when declaring a macro member on an interface.
for example:
//new builtin function
type Keys<T> = {
[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
type MacroMap<T> = {
[K in Keys<T>]: (v: T) => ReturnType<T[K]>;
};
export function $$macroMapper<T, M extends MacroMap<T> = MacroMap<T>>(map: M): void {
map;
}
export namespace Vector2 {
export function $new(): Vector2 {
return [0, 0] as unknown as Vector2;
}
}
export interface Vector2 {
$x(): number;
$y(): number;
}
function $asData(v: Vector2): [number, number] {
return v as unknown as [number, number];
}
export function $x(v: Vector2): number {
return $asData!(v)[0];
}
export function $y(v: Vector2): number {
return $asData!(v)[1];
}
$$macroMapper<Vector2>({
$x: $x,
$y: $y
}); // now we know what macro to map.
Describe the bug
I think this code has enough information(this) to specify the method. Why not?
Code to reproduce
code:
export namespace Vector2 {
export namespace Methods {
export function $asData(v: Vector2): [number, number] {
return v as unknown as [number, number];
}
export function $x(self: Vector2): number {
return $asData(self)[0];
}
export function $y(self: Vector2): number {
return $asData(self)[1];
}
}
export function $from(x: number, y: number): Vector2 {
return [x, y] as unknown as Vector2;
}
}
interface Vector2 {
$x(): number;
$y(): number;
}
export namespace Vector3 {
export namespace Methods {
export function $asData(v: Vector3): [number, number, number] {
return v as unknown as [number, number, number];
}
export function $x(self: Vector3): number {
return $asData(self)[0];
}
export function $y(self: Vector3): number {
return $asData(self)[1];
}
}
export function $from(x: number, y: number, z: number): Vector3 {
return [x, y, z] as unknown as Vector3;
}
}
interface Vector3 {
$x(): number;
$y(): number;
$z(): number;
}
const v2 = Vector2.$from!(1, 2);
console.log(v2.$x!(), v2.$y!());
error:
src/index.ts:53:13 - error TS8000: More than one possible candidate for "$x" call
53 console.log(v2.$x!(), v2.$y!());
Expected behavior
A clear and concise description of what you expected to happen.
const v2 = [1, 2];
console.log(v2[0], v2[1]);
Additional context
Add any other context about the problem here.
Is your feature request related to a problem? Please describe.
I want to extract some boolean on compile time via TS AST and emit one or another code.
Describe the solution you'd like
function $testMacro(param?: number) {
return $$cond!(
(ctx, paramAst) => !!paramAst, // params similar $$raw; check that param passed
() => $$escape(() => {
// code for passed number `param`
}),
() => $$escape(() => {
// code for undefined `param`
}),
)
}
Describe alternatives you've considered
I don't understand how to implement example above ☝️ using existing instruments now (v2.0.1). Please, provide workaround how to implement conditional macros
Describe the bug
When I build the project macros are not processed. There is no form of troubleshooting available and cannot locate any resources on how to fix this.
The instructions provided have not worked for me
Code to reproduce
Code I have works on the playground and not in my project. Code provided in the documentation does not work for me.
Expected behavior
That the macros would actually be processed
Additional context
I have ttypescript
added as a dev dependency along with ts-macros
I have added the plugin to tsconfig.json
although it says ts-docs
and then writes ts-macros
but neither work (also the intentation is weird with plugins
on the same level as compilerOptions
and yet plugins
is a child)
I tried out my code in the playground beforehand and even copied over some of the examples and they don't work either.
I was able to integrate ts-macros to Angular14 application using ngx-build-plus library.
Describe the bug
My $contains macro works when calling it like
const needle = 'three';
const haystack = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine'];
this.needle_found = $contains!(this.needle, ...this.haystack );
the macro itself
export function $contains<T>(value: T, ...possible: Array<T>): boolean {
return possible.includes(value);
}
But the project does not compile when trying to extend String interface with it.
The error message:
error TS8000: No possible candidates for "$contains" call
declare global {
interface String {
$contains<T>(...possible: Array<T>) : boolean;
}
}
this.needle_found = this.needle.$contains!(...this.haystack);
Code to reproduce
Here is a quick bare bones project to reproduce the error
https://github.com/AlariOis/angular-ts-macros
I know that messing around with private Angular build system API is a good way to shoot yourself in the foot and there is a good chance the problem is not with ts-macros.
Just to clarify - both examples work fine on my node backend, it is the Angular project that has the issue.
Describe the bug
Conditional macro not working with:
undefined
){obj: true}
)Describe the bug
I'm using XRegExp
to build complex / nested regular expressions. I'm investigating using ts-macros
for this purpose.
Code to reproduce
import * as XRegExp from 'xregexp'
const $buildFragments = (rawFragments: string[][]) => {
const fragments: Record<string, RegExp> = {}
rawFragments.forEach(fragment => {
fragments[fragment[0]] = XRegExp.build(fragment[1], fragments)
})
return fragments
}
const fragments: Record<string, RegExp> = $buildFragments!(rawFragments)
(Fragments are regex strings like:)
const fragments = () => [
['newline', '\\n|\\r\\n?|\\f'],
['whitespace', '[ ]|\\t|{{newline}}'],
['ws', '{{whitespace}}+'],
['comment', '\\/\\*[^*]*\\*+(?:[^/*][^*]*\\*+)*\\/'],
['hex', '[\\da-fA-F]'],
['unicode', '\\\\{{hex}}{1,6}{{whitespace}}?']
]
Expected behavior
I expected a static object to be output with a map of regular expressions.
Additional context
Add any other context about the problem here.
Describe the bug
When using this library, type-only imports are not removed from code during transpilation.
Code to reproduce
https://github.com/WillsterJohnson/GoogleFeud-ts-macros-61
Expected behavior
Type only imports should not be emitted as they are a TypeScript-specific feature.
Additional information
ts-macros v2.3.0
ts-patch v3.0.2
typescript v5.1.6
Describe the bug
"typeof" is lost after transform
Code to reproduce
function $what(unit: any) {
return typeof unit
}
const b = {}
const typeofSomething = $what!(b)
got transformed into
const b = {};
const typeofSomething = b;
Expected behavior
should transform into
const b = {}
const typeofSomething = typeof b;
Additional context
Add any other context about the problem here.
Is this possible? I think it would be really cool to support tagged templates as macros, helps a lot on the readability.
I wanted to implement something similar to twin.macro
$tw!`flex flex-col justify-center items-center`
any thoughts on this?
Describe the bug
Node.js Warning about circular dependency. View the trace below —
(node:62719) Warning: Accessing non-existent property 'tracing' of module exports inside circular dependency
at emitCircularRequireWarning (node:internal/modules/cjs/loader:748:11)
at Object.get (node:internal/modules/cjs/loader:764:5)
at checkVariableDeclaration (/Users/tushar/Documents/Projects/ts-macro-schema/node_modules/typescript/lib/typescript.js:83763:39)
at checkSourceElementWorker (/Users/tushar/Documents/Projects/ts-macro-schema/node_modules/typescript/lib/typescript.js:86977:28)
at checkSourceElement (/Users/tushar/Documents/Projects/ts-macro-schema/node_modules/typescript/lib/typescript.js:86808:17)
at Object.forEach (/Users/tushar/Documents/Projects/ts-macro-schema/node_modules/typescript/lib/typescript.js:377:30)
at checkVariableStatement (/Users/tushar/Documents/Projects/ts-macro-schema/node_modules/typescript/lib/typescript.js:83776:16)
at checkSourceElementWorker (/Users/tushar/Documents/Projects/ts-macro-schema/node_modules/typescript/lib/typescript.js:86946:28)
at checkSourceElement (/Users/tushar/Documents/Projects/ts-macro-schema/node_modules/typescript/lib/typescript.js:86808:17)
at Object.forEach (/Users/tushar/Documents/Projects/ts-macro-schema/node_modules/typescript/lib/typescript.js:377:30)
Code to reproduce
Simply run npx ttsc --watch
Expected behavior
No warning should occur
Additional context
package.json
{
"devDependencies": {
"jest": "^29.3.1",
"ts-macros": "^2.0.0",
"ttypescript": "^1.5.13",
"typescript": "4.8.4"
},
"scripts": {
"test": "jest",
"build": "ttsc -b"
},
"license": "MIT"
}
Describe the bug
Does not work inside classes
Code to reproduce
function $property() {
property = 0
}
$property!()
export class ArduPilotSub extends Vehicle.Abstract {
$property!()
constructor() {
super(Vehicle.Firmware.ArduPilot, Vehicle.Type.Sub)
}
}
Expected behavior
The output would be something like:
export class ArduPilotSub extends Vehicle.Abstract {
property = 0;
constructor() {
super(Vehicle.Firmware.ArduPilot, Vehicle.Type.Sub);
}
}
Ref: https://doc.qt.io/qt-5/properties.html
Additional context
It would be nice to have more examples, like:
How do I use a type as a parameter.
$macro!(number, test, 0)
Generating something like:
const test: number = 0
Describe the bug
For complex types, $$typeToString replaces parts of the type description with ...
Code to reproduce
These are the same examples as in #73. I am using the following macro to reflect the types of arrow functions that I am exporting:
export function $implement<Impl>(name: string, expr: Impl) {
$$define!(name, expr, false, true); // Final `true` is export
$$ident!(name).reflectedType = $$typeToString!<Impl>();
}
It works fine on
$implement!('square',
<T>(dep: Dependencies<'multiply', T>) => (z:T) => dep.multiply(z, z))
where square.reflectedType
ends up getting the very reasonable value <T>(dep: { multiply: (a: T, b: T) => T; }) => (z: T) => T
. However, on
$implement!('sqrt',
<T>(dep: Dependencies<'equal' | 'conservativeSqrt' | 'unaryMinus', RealType<T>>
& Dependencies<'zero' | 'complex', T>
& Dependencies<'absquare' | 're' | 'divideReal', Complex<T>>
& {
addTR: Signature<'addReal', T>,
addRR: Signature<'add', RealType<T>>,
addCR: Signature<'addReal', Complex<T>>
}):
Signature<'sqrt', Complex<T>> =>
z => {
const myabs = dep.conservativeSqrt(dep.absquare(z))
const r = dep.re(z)
const negr = dep.unaryMinus(r)
if (dep.equal(myabs, negr)) {
// pure imaginary square root; z.im already zero
return dep.complex(
dep.zero(z.re), dep.addTR(z.im, dep.conservativeSqrt(negr)))
}
const num = dep.addCR(z, myabs)
const denomsq = dep.addRR(dep.addRR(myabs, myabs), dep.addRR(r, r))
const denom = dep.conservativeSqrt(denomsq)
return dep.divideReal(num, denom)
})
it produces sqrt.reflectedType
equal to
<T>(dep: { unaryMinus: (a: RealType<T>) => RealType<T>; conservativeSqrt: (a: RealType<T>) => RealType<T>; equal: (a: RealType<T>, b: RealType<...>) => boolean; } & { ...; } & { ...; } & { ...; }) => (a: Complex<...>) => Complex<...>
so we have lost much of the type information to those ellipses.
Expected behavior
I would like there to be a way to force $$typeToString to produce the entire description of the typescript type of its template argument, without any ellipsization.
Additional context
Typescript 5.1.6, ts-macros 2.4.1, ts-patch 3.0.2
Describe the bug
I am using the following macro to reflect the types of arrow functions that I am exporting:
export function $implement<Impl>(name: string, expr: Impl) {
$$define!(name, expr, false, true); // Final `true` is export
$$ident!(name).reflectedType = $$typeToString!<Impl>();
}
It works with simple function bodies, but with a more complicated function body, the variables are renamed inconsistently.
Code to reproduce
The following call to the $implement macro works fine:
$implement!('square',
<T>(dep: Dependencies<'multiply', T>) => (z:T) => dep.multiply(z, z))
(note that Dependencies
is just a generic type that looks up the call signature of the 'multiply' function that square will
use).
But it fails on the following case (sorry this is not a very clean example, let me know if you need me to try to pare this down more to get a simpler one that fails):
$implement!('sqrt',
<T>(dep: Dependencies<'equal' | 'conservativeSqrt' | 'unaryMinus', RealType<T>>
& Dependencies<'zero' | 'complex', T>
& Dependencies<'absquare' | 're' | 'divideReal', Complex<T>>
& {
addTR: Signature<'addReal', T>,
addRR: Signature<'add', RealType<T>>,
addCR: Signature<'addReal', Complex<T>>
}):
Signature<'sqrt', Complex<T>> =>
z => {
const myabs = dep.conservativeSqrt(dep.absquare(z))
const r = dep.re(z)
const negr = dep.unaryMinus(r)
if (dep.equal(myabs, negr)) {
// pure imaginary square root; z.im already zero
return dep.complex(
dep.zero(z.re), dep.addTR(z.im, dep.conservativeSqrt(negr)))
}
const num = dep.addCR(z, myabs)
const denomsq = dep.addRR(dep.addRR(myabs, myabs), dep.addRR(r, r))
const denom = dep.conservativeSqrt(denomsq)
return dep.divideReal(num, denom)
})
(this is an algorithm for finding the square root of a complex number, given various other options on complex numbers and on their real parts).
Expected behavior
I would expect both calls to $implement to produce working functions. The first one is fine, but the second one generates
export const sqrt = (dep) => z => {
const myabs_1 = dep.conservativeSqrt(dep.absquare(z));
const r_1 = dep.re(z);
const negr_1 = dep.unaryMinus(r);
if (dep.equal(myabs_1, negr_1)) {
// pure imaginary square root; z.im already zero
return dep.complex(dep.zero(z.re), dep.addTR(z.im, dep.conservativeSqrt(negr_1)));
}
const num_1 = dep.addCR(z, myabs);
const denomsq_1 = dep.addRR(dep.addRR(myabs, myabs), dep.addRR(r, r));
const denom_1 = dep.conservativeSqrt(denomsq);
return dep.divideReal(num_1, denom_1);
};
which fails to execute because some of the occurrences of myabs
(for example) have been renamed to myabs_1
, but others have not, so the generated code is no longer self-consistent.
Additional context
Typescript 5.1.6, ts-macros 2.4.1, ts-patch 3.0.2
I don't see why ts-macros needs to rename variables at all inside the function body z => { CODE... }
, since that CODE is in its own scope that will not bleed variable names into it or out of it, so far as I can see...
Is your feature request related to a problem? Please describe.
There was a problem implementing hot reloading using gulp-typescript.
https://github.com/ivogabe/gulp-typescript#incremental-compilation
when performing an hot reloading in the way that you see in this document,
the entire project is recompiled when hot reloading, which takes a very long time.
The gulp-typescript recompiles only for source files that have changed if the isolatedModules
option is true.
Transformers do not receive ts.Program
instances at this time. Therefore, ts-macros cannot work.
Describe the solution you'd like
Is it possible to implement an optionally receive program parameters from Transformers and cache the first compilation type info results?
Maybe the direction of my question itself is wrong. I'm sorry in advance.
Describe alternatives you've considered
none
Additional context
I'm not sure if this is just a gulp-typescript's incremental compilation problem.
Is your feature request related to a problem? Please describe.
I am writing a macro which should, among other things, export an identifier specified in the macro call. I can't seem to find a way to do this. In particular, function bodies are not allowed to export identifiers, so it seems I need to use the $$ts
builtin macro, but if I write e.g.
$$ts!(`export const ${name} = ${desired_value};`)
the identifier is mangled presumably for hygienic reasons. I suspect that I could use $$raw to do this, but it is hard to find good documentation on how to use factory.createExportAssignment or factory.createExportDeclaration. If you think it's best to do this with $$raw rather than add a facility for exporting, I respect that but if you have any pointers to information about how to use the Compiler API for something like this they would be welcome.
Describe the solution you'd like
I could imagine several designs, but I think the easiest to use would be another flag on $$define which specifies whether to export the definition, defaulting to false. Another possibility would be an "unhygienic" option to $$ts. Yet a third option would be an explicit $$export built-in macro.
Describe alternatives you've considered
I believe these are covered above.
Additional context
If a PR would help make this happen I am willing to try even though I am almost completely unfamiliar with the TypeScript Compiler API. But for this, I'd need an indication of the desired design for allowing exports. And if you have any suggestions for useful resources to get intformation about the TypeScript API it would be appreciated.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.