Typescript transformer, `node.parent` is undefined - typescript-compiler-api

I'm currently using a typescript transformer api, and I found that the node.parent is undefined.
My code is:
const transformerFactory: ts.TransformerFactory<ts.Node> = (
context: ts.TransformationContext
) => {
return (rootNode) => {
function visit(node: ts.Node): ts.Node {
node = ts.visitEachChild(node, visit, context);
// HERE node.parent IS UNDEFINED !
return filterFn(node, context);
}
return ts.visitNode(rootNode, visit);
};
};
const transformationResult = ts.transform(
sourceFile, [transformerFactory]
);
How can I find the parent of the node?

You can parse specifying to set the parent nodes:
const sourceFile = ts.createSourceFile(
"fileName.ts",
"class Test {}",
ts.ScriptTarget.Latest,
/* setParentNodes */ true, // specify this as true
);
Or do some operation on the node to get it to set its parent nodes (ex. type check the program... IIRC during binding it ensures the parent nodes are set).
Update based on comment
If you are creating these from a program, then you can do the following:
const options: ts.CompilerOptions = { allowJs: true };
const compilerHost = ts.createCompilerHost(options, /* setParentNodes */ true);
const program = ts.createProgram([this.filePath], options, compilerHost);

Related

How to get the next sibling and ignore tokens in ts-morph?

I try to get the next sibling using getNextSibling from ts-morph.
The problem is I get the node DotToken or OpenBracketToken which I don't care about them. I want the node with the content. the problem this is can be any node (Identifier or StringLiteral or BinaryExpression or anything else).
Is there a safe way to get the node and ignore the tokens?
import { Identifier, Project, SyntaxKind } from "ts-morph";
console.clear();
const project = new Project();
const sourceFile = project.createSourceFile(
"foo.ts",
`
foo.bar.some();
foo[bar].some();
foo['bar'].some();
foo['bar' + 'other'].some();
`
);
const foos = sourceFile
.getDescendantsOfKind(SyntaxKind.Identifier)
.filter((i) => i.getText() === "foo");
foos.forEach((foo) => {
const s = foo.getNextSibling();
console.log({ s: s.getText() }); // outputs: . [ [ [
});
codesandbox.io
ts-ast-viewer.com

get only changed objects from observed array with rxjs

I have an array of objects (not primitives) called "streaks" on my state tree and I want to observe only the changes to that array, not simply emit the entire array every time it changes. When I try this using pairwise() I get two identical arrays every time, even though I thought pairwise() would join the previous version and the current version. Why is pairwise() sending two identical arrays? NOTE streaks[1] and streaks[0] are identical, so _.differenceBy() isn't finding any changes because the two arrays are the same.
import {from} from "rxjs";
import {map, pairwise} from "rxjs/operators";
import * as _ from 'lodash';
const state$ = from(store);
const streaks$ = state$.pipe(
map(state => state.streaks),
// distinctUntilChanged(), <-- i've tried this and nothing is output at all
pairwise(),
map(streaks => {
let diff = _.differenceBy(streaks[0], streaks[1], _.isEqual);
console.log('diff', diff); //<-- this is an empty array
return diff;
})
);
streaks$.subscribe((streaksArray) => {
console.log('STREAKS$ 0', streaksArray); //<-- this is never even hit
} );
I solved this issue by creating an distinctUntilChangedArray operator that uses a self written compareArray function
Create compare array function
const compareArray<T> = (first: T[], second: T[], comparator=: (obj: T, obj2: T) => boolean): boolean {
return (first.length === 0 && second.length === 0)
|| (first.length === second.length && first.every((value, index) => {
return comparator
? comparator(value, second[index])
: JSON.stringify(value) === JSON.stringify(second[index]);
}))
}
Maybe you find a better array comparator. This is just my personal implementation of it
Create distinctUntilChangedArray operator
const distinctUntilChangedArray<T> = (comparator?: (prev: T, curr: T) => boolean): MonoTypeOperatorFunction<T[]> {
return distinctUntilChanged((prev: T[], curr: T[]) => {
return compareArray(first, second, comparator);
}
}
Final usage
interface nonPrimitive {
id: number;
name: string;
}
const nonPrimitiveComparator = (prev: nonPrimitive, curr: nonPrimitive): boolean => {
return prev.id === curr.id && prev.name === curr.name;
}
const source$: Observable<nonPrimitive>;
const distinctedSource$ = source$.pipe(
distinctUntilChangedArray(nonPrimitiveComparator)
);

Flowtype - generic array

How i can write generic function, which take Array of Objects (any type of Object, possible even null and undefined), and filter it to return just valid items of array? If i write it lite this, i will lose genericity :/
// #flow
// Types
type Person = {
id: string,
name: string,
};
type Car = {
id: string,
color: string,
};
// Function definition
const isNotUndefinedOrNull = item => !(item === null || item === undefined);
export const trimList = (list: Array<any> | $ReadOnlyArray<any>): Array<any> => {
return list.filter(isNotUndefinedOrNull);
};
// Constants
const persons = [{ id: 'p1', name: 'Johny' }, null, undefined];
const cars = [{ id: 'c1', color: 'red' }, null, undefined];
// Calls
const trimmedPersons = trimList(persons);
const trimmedCars = trimList(cars);
PROBLEM is, there i have trimmed cars and persons, but flow doesnt know, there is Cars in the trimmedCars list and neither know there is Persons in trimmedPersons list. Flow see just Array and i dont know, how to write is right, to not lose this info.
Flow try
As flow has a bug with Refine array types using filter we use explicit type casting ((res): any): T[]).
function filterNullable<T>(items: (?T)[]): T[] {
const res = items.filter(item => !(item === null || item === undefined);
return ((res): any): T[]);
}
// Example
const a: number[] = filterNullable([1, 2, null, undefined]);
i found it :)
export function trimList<V>(list: Array<?V> | $ReadOnlyArray<?V>): Array<V> {
return R.filter(isNotUndefinedOrNull, list);
}

How to change immutablejs Record with methods from derived class?

I have 3 classes derived from Record. Definitions of first two classes are below.
// Base.js
import {Record} from 'immutable';
import * as uuid from 'uuid';
export const Base = defaultValues => {
return class extends Record({
key: null,
...defaultValues,
}) {
constructor(props) {
super(Object.assign({}, props, {key: (props && props.key) || uuid.v4()}));
}
};
};
// LOBase.js
import {Base} from './BaseModel';
export const LOBase = defaultValues => {
return class extends Base({
created_at: new Date(null),
updated_at: new Date(null),
deleted_at: new Date(null),
isActive: new Boolean(),
isDeleted: new Boolean(),
publishState: new String(),
...defaultValues,
}) {};
};
And this is my last class derived from LOBase and where my problem is.
// Question.js
import {List, Record, fromJS} from 'immutable';
import _ from 'lodash';
import {LOBase} from './base/LOBaseModel';
export class Question extends LOBase({
id: '',
name: 'test',
description: '',
questionType: 1,
title: 'title',
version: new String(),
customData: {},
//...
}) {
insertOption() {
let index = this.customData.options.length;
this.updateIn(['customData', 'options'], options => {
return options.splice(index, 0, {
someGenericStuff: [],
// ...
});
});
return this;
}
static MultipleChoice() {
let defaultCustomData = {
options: [],
//...
};
let question = new Question()
.set('questionType', QUESTION_TYPE_MULTIPLE_CHOICE)
.set('customData', new Record(defaultCustomData)())
//...
.insertOption()
.insertOption()
.insertOption();
return question;
}
// ...
}
I use let question = Question.MultipleChoice() to create a new Question instance. And when i use question.insertOption() it works fine. But when I do this in the reducer on the state I get an error saying "A state mutation was detected inside a dispatch".
How can I achieve to change question object in the state? Should I clone original Record before doing that? What is the Immutablejs way to do that?
Thanks in advance.
insertOption uses this.updateIn but does not return or store the result.
When you return this at the end of the function you actually return the same immutable Record without the changes.
So, unless I'm missing something here, you should probably go with:
insertOption() {
let index = this.customData.options.length;
return this.updateIn(['customData', 'options'], options => {
return options.splice(index, 0, {
someGenericStuff: [],
// ...
});
});
}
The updateIn will return a new instance of the Record with the updated values.
You did not add your state structure and reducer (if you can please do), but you should be sure to return a new state object every time and not just changing the question field.
BTW, you are doing a sequence of mutation methods one after the other (set, set, updateIn). This is not suggestable from a performance perspective. I'd suggest replacing it with withMutations in the following manner:
static insertOption(record) {
let index = record.customData.options.length;
return record.updateIn(['customData', 'options'], options => {
return options.splice(index, 0, {
someGenericStuff: [],
// ...
});
});
}
static MultipleChoice() {
// ...
let question = new Question();
question.withMutations(record => {
record.merge({
questionType: QUESTION_TYPE_MULTIPLE_CHOICE,
customData: new Record(defaultCustomData)()
})
Question.insertOption(record);
})
return question;
}

How can I dynamic set a value into a property?

How to set a property to value that should be resolve.. like this one..
const getDataFromServer = (id) => ({id: * 2})
R.set(payloadProp, getDataFromServer)({id: 4}); // WRONG, is setting to a function instend to resolve a function with `{id: 4}`
const fetch = (id) => {
return { num: id * 2 }
};
const idProp = R.lensProp('id');
const getDataFromServer = R.pipe(R.view(idProp), fetch);
const payloadProp = R.lensProp('payload');
const setPayloadFromFetch = R.set(payloadProp, getDataFromServer); // NOT WORK, return payload as function
const obj = { id: 1, payload: { message: 'request' } }
const ret = setPayloadFromFetch(obj);
console.log(ret);
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.23.0/ramda.min.js"></script>
The problem is that R.set takes a value, not a function, for its second parameter. And you can't switch to R.over, which does take function but calls it with the current value at that lens, not the full data object supplied to the outer function.
The simplest way to solve this is simply to pass the object in both places:
const setPayloadFromFetch = obj => R.set(payloadProp, getDataFromServer(obj), obj);
But if you're intent on making this point-free, lift is your friend:
const setPayloadFromFetch = R.lift(R.set(payloadProp))(getDataFromServer, identity);
although you could also use R.ap, which would be very nice except that R.set takes its parameters in the wrong order for it, and so you have to use R.flip
const setPayloadFromFetch = R.ap(R.flip(R.set(payloadProp)), getDataFromServer);
You can see all these in the Ramda REPL.

Resources