-
Notifications
You must be signed in to change notification settings - Fork 902
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
Type Node missing properties #1136
Comments
It's missing so much more than that. |
The quick fix by RReverser is here. Although as of acorn 8.8.0 and TS 4.7.4, the correct fix is like // acorn.d.ts
import { Options } from 'acorn';
import * as ESTree from 'estree';
declare module 'acorn' {
type ExtendNode<T> = {
[K in keyof T]: T[K] extends object ? ExtendNode<T[K]> : T[K];
} & (T extends ESTree.Node
? {
start: number;
end: number;
}
: unknown);
export function parse(s: string, o: Options): ExtendNode<ESTree.Program>;
} Changelog:
Edit: There are some problems with fix memtioned above: declare module 'acorn' {
// ... (fix code omitted) ...
// Problem with Union over non-Node types
type ID = ExtendNode<ESTree.ClassExpression>['id'];
// -> ESTree.Identifier | null | undefined
// Expect: ExtendNode<ESTree.Identifier> | null | undefined
type Elements = ExtendNode<ESTree.ArrayPattern>['elements'];
// -> (ESTree.Pattern | null)[]
// Expect: Array<ExtendNode<ESTree.Pattern> | null>
// Problem with Comments
type CommentType = Comment['type'];
// -> string
type NonNull<T> = T extends null | undefined ? never : T;
type ESComments = NonNull<ExtendNode<ESTree.Program>['comments']>;
type TestStartEnd = ESComments[number] extends { start: number; end: number } ? true : false;
// -> false
// The two types of Comment are incompatible with eath other
} So the correct fix is like: // acorn.d.ts
import * as ESTree from 'estree';
declare module 'acorn' {
type ExtendObject<T> = {
[K in keyof T]: ExtendNode<T[K]>;
};
type WithStartEnd<T> = T extends ESTree.Node | ESTree.Comment
? { start: number; end: number }
: unknown;
export type ExtendNode<T> = T extends object ? ExtendObject<T> & WithStartEnd<T> : T;
export function parse(s: string, o: Options): ExtendNode<ESTree.Program>;
// fix type of Comment property 'type'
export type AcornComment = Omit<Comment, 'type'> & {
type: 'Line' | 'Block';
};
} Now the declare module 'acorn' {
// ... (fix code omitted) ...
// Problem with Union over non-Node types
type ID = ExtendNode<ESTree.ClassExpression>['id'];
// -> ExtendNode<ESTree.Identifier> | null | undefined
type Elements = ExtendNode<ESTree.ArrayPattern>['elements'];
// -> Array<ExtendNode<ESTree.Pattern> | null>
// Problem with Comments
type NonNull<T> = T extends null | undefined ? never : T;
type ESComments = NonNull<ExtendNode<ESTree.Program>['comments']>;
type TestStartEnd = ESComments[number] extends { start: number; end: number } ? true : false;
// -> true
type TestCompability = AcornComment extends ESComments[number] ? true : false;
// -> true
// AcornComment is now subtype of ExtendNode<ESTree.Comment>
} Example use case: import { parse, type AcornComment, type ExtendNode } from 'acorn';
export const parseCode = (code: string) => {
const comments: AcornComment[] = [];
const program = parse(code, {
ecmaVersion: 'latest',
sourceType: 'module',
onComment: comments,
});
program.comments = comments;
return program;
};
export const dumpImportDeclarations = (code: string) => {
const program = parseCode(code);
const result: Array<ExtendNode<ImportDeclaration>> = [];
for (const node of program.body) {
if (node.type === 'ImportDeclaration') {
result.push(node);
}
}
return result;
}; P.S. If you do not care about messing with estree's interfaces, you can do like: import * as ESTree from 'estree';
declare module 'acorn' {
export function parse(s: string, o: Options): ESTree.Program;
// fix type of Comment property 'type'
export type AcornComment = Omit<Comment, 'type'> & {
type: 'Line' | 'Block';
};
}
declare module 'estree' {
interface BaseNodeWithoutComments {
start: number;
end: number;
}
} Which will also fix the problem, as long as your code does not depend on properties of estree's nodes. |
The type of ' |
I am considering putting some work in the near future in creating a real |
I haven't thought deeply about what such types would look like, but I'd gladly review a more complete set of types. If at all possible, try to write them in a way that reuses the ESTree types, so that we don't have to duplicate all those. |
I spent some time looking into this, and I feel it is easier to copy & paste the estree types instead of try to programmatically reuse them in some way, unless I missed something. For example, I have been doing this for a while: import * as estree from 'estree/index'
interface AcornBaseNode {
start: number
end: number
}
type ProgramBase = Omit<estree.Program, 'leadingComments' | 'trailingComments' | 'body' | 'comments'> & AcornBaseNode
interface Program extends ProgramBase {
body: Array<Directive | Statement | ModuleDeclaration>
} Basically I am overriding the I noticed that |
That makes sense, yeah. It's a shame, since it is quite a lot of types we'd have to maintain, and it means people can't just take an Acorn tree and pass it to some tool that is typed to take ESTree nodes, but if they didn't explicitly set it up in a way that allows extension (and even added non-standard stuff like comment properties) then reusing it may just not be practical. |
Thanks! I have two more questions about handling options:
Basically the questions are around how loose the types should be wrt parse options. Thanks! |
That sounds like way too much work an complexity. Just make the types reflect the tree you get the most recent version, I'd say. I'd make |
I created an early version of the work in a repository: https://github.com/rwalle/acorn-types Want to know if this looks like a good start, and how to proceed from here. I tried using these files in one of my projects that heavily uses acorn and so far it seems working fine, even helped to find some bugs. I added some comments in the parse options and for acorn-walk methods. They should be handy. Some thoughts:
There may be a few more things but I don't remember them now. I can come back with additional items. Thanks |
Those look promising! Instead of abusing this issue to become a messy review of it, I'm going to open issues on your repository with concerns I have. As for weirdness in the current types, yes, those are somewhat shoddy, and it would be great to replace them wholesale. If the types look wrong, they probably are. I've updated the comments in options.js. Writing better docs for acorn-walk would definitely be good, but I don't really have time for it at the moment. |
I think we'll want to bundle these with the package itself, for ease of use (and to not have to go through DefinitelyTyped every time we find an issue). But maybe it does make sense to develop (and test) them in their own, separate repository, so that we don't add too much machinery for it to this one. |
Merged the new, much better types in attached patch. |
@RReverser In case you want to take a look at these before a release, I'm going to wait a few days before I tag a new version. |
Thanks, I will. It's unfortunate it's not based on ESTree after all, but good to see progress unblocked. |
In acorn.d.ts the class
Node
can have (among other fields) the fieldsource
. This isn't listed anywhere in the d.ts. Making this library very hard to use with typescript.The text was updated successfully, but these errors were encountered: