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

QUESTION : resolve imports #48

Closed
vogloblinsky opened this issue Jul 20, 2017 · 8 comments
Closed

QUESTION : resolve imports #48

vogloblinsky opened this issue Jul 20, 2017 · 8 comments
Labels

Comments

@vogloblinsky
Copy link
Contributor

Hi,

First, let' say 👍 about this library, very wonderful work !!

I am the maintainer of compodoc, a documentation tool for Angular applications. https://github.com/compodoc/compodoc

I need for some use-case to support dynamic imports of different things.

// module-a.ts
export const VERSION = 5;
// module-b.ts
import { VERSION } from './module-a';
let toto = VERSION;
console.log(toto);

Would be nice if during AST parsing with TypeScript, if i could edit the AST node of toto variable to inject VERSION instead of just having a reference.

Did you know how can i achieve this with your lib or just with TypeScript compiler APIs ?
This blog http://blog.scottlogic.com/2017/05/02/typescript-compiler-api-revisited.html, chapter "TRANSFORMING THE AST" helps a little

Thanks

@dsherret
Copy link
Owner

dsherret commented Jul 21, 2017

I'm glad you like it! There's still a long way to go.... lots of work to do.

Ah, that's pretty cool. I've often thought that generated documentation is a very useful application for using the typescript compiler.

Note on remaining text... I did not re-read what I wrote here... just fired it off. Hopefully it helps as-is...

Using Transformation API

Are you trying to change this when emitting the javascript code? If so, I believe probably the best thing to do to accomplish what you want to do would be to use the transformation api.

Basically:

  1. Create a ts.TransformerFactory to do the transformation (Example)
  2. When emitting, specify to use the transformer factory that you created (Example)

I'm not entirely sure how you would create the TransformerFactory for this scenario. I guess in the TransformerFactory you would need to figure out if VERSION is imported and then keep track of every variable it's assigned to and then every variable those variables are assigned to and keep repeating until there are no new variables. From there, replace all those variables with the reference to VERSION in the transformation factory (return VERSION instead of the past node similarly to the return ts.createLiteral(nameofString); statement in the example I posted above).

Using this Library (Not supported yet)

This is actually a hard problem and I haven't included all the details. Probably the easiest thing to do would be to:

  1. Get the node where VERSION is defined

    const versionDeclaration = ast.getSourceFile("module-a.ts")!.getVariableDeclaration("VERSION")!;
  2. Find all the references of it....

    const versionReferencedSymbols = versionDeclaration.getNameIdentifier().findReferences();
  3. From there, you would want go get all the references that are found in import declarations:

    const importReferences: Identifier[] = [];
    for (const referencedSymbol of versionReferencedSymbols) {
        for (const reference of referencedSymbol.getReferences()) {
            const referenceNode = reference.getNode();
            const importDeclaration = referenceNode.getFirstAncestorByKind(ts.SyntaxKind.ImportDeclaration);
            if (importDeclaration != null)
                importReferences.push(referenceNode);
        }
    }
  4. Now... I was going to keep going here, but there's a lot of scenarios you need to handle. For example, what if someone does import { VERSION as myVersion } from './module-a'; or import * as A from './module-a'; let toto = A.VERSION;. Or what if the imported, then aliased variable is then exported from the current file and used somewhere else? What do you do then? Definitely possible to handle in this library, but just something to think about. Skipping all that, and going to the easy scenario, from these import references you can get the variables:

    // with each import reference....
    const variableDeclarations: VariableDeclaration[] = [];
    for (const referencedSymbol of importReference.findReferences()) {
        for (const reference of referencedSymbol.getReferences()) {
            const parent = reference.getNode().getParentIfKind(ts.SyntaxKind.VariableDeclaration);
            // you would also probably also want a check to make sure VERSION
            // is the only thing assigned to the variable
            if (parent != null)
                variableDeclarations.push(parent);
        }
    }
  5. Then with each variable declaration, you could just rename it to be VERSION:

    variableDeclaration.getNameIdentifier().rename("VERSION");
  6. Then with each variable declaration, get the variable declaration's statement and remove it:

    variableDeclaration.getFirstAncestorByKindOrThrow(ts.SyntaxKind.VariableStatement).remove();
  7. Finally emit it:

    ast.emit();

Anyway, sorry that's not all supported. I'm working on it though...

Sidenote: I have written down the general idea of "reference paths" so it would basically give you a view of how something (like VERSION) flows through your application. I think it would make solving this problem a lot easier. It's something to think about in the future though... I need to get the base of this library down first.

@vogloblinsky
Copy link
Contributor Author

@dsherret
Thanks for your quick reply.
I just need to modify the AST, i didn't emit the code. The documentation is generated just with AST parsing.

@dsherret
Copy link
Owner

Closing for now. Please feel free to reopen this if your question wasn't answered properly or if you need clarification on something.

@vogloblinsky
Copy link
Contributor Author

Hi David,
I am starting working on the usage of ts-imple-ast in my project, with the scenario you give me here : #48 (comment)

Is there any news about the approach or syntax with the latest development of ts-simple-ast ?

@dsherret
Copy link
Owner

@vogloblinsky I think I misunderstood your original question. What's the expected output? Is it this?

// module-b.ts
let toto = 5;
console.log(toto);

If so, I believe that you just need to use .findReferences() on the VERSION variable declaration's name identifier (there's a useful .getNode() method you can use on the reference to get the node. That's missing from that documentation... I'll add it). From there, you will have to loop over all the references... if it's being used in an import declaration (you should be able to tell by checking the parent of the node) then you'll need to remove it from the import declaration... if it's being used in some other expression, then you can call .replaceWithText("5") on the node.

That should be easy to do.

@dsherret dsherret reopened this Oct 31, 2017
@dsherret
Copy link
Owner

Closing for now. Please let me know if this doesn't solve your question.

@vogloblinsky
Copy link
Contributor Author

No, you can leave closed, i manage to get things done. Thanks !

Repository owner locked and limited conversation to collaborators Apr 3, 2021
@dsherret dsherret reopened this Apr 3, 2021
Repository owner unlocked this conversation Apr 3, 2021
@dsherret dsherret closed this as completed Apr 3, 2021
@fuhrmanator
Copy link

4. Now... I was going to keep going here, but there's a lot of scenarios you need to handle. For example, what if someone does import { VERSION as myVersion } from './module-a'; or import * as A from './module-a'; let toto = A.VERSION;

Indeed there are lots of scenarios. I was happy to find this discussion and thought I'd share what I came up with for the alias/renames, because it can happen both on import and export, in case someone finds it useful (maybe it can help to advance the API):

const aliasedExportedSymbols = project.getSourceFiles()
    .map(sourceFile => sourceFile.getExportDeclarations()
    .map(exportDeclaration => exportDeclaration.getNamedExports())).flat().flat();

const importedSymbols = project.getSourceFiles()
    .map(sourceFile => sourceFile.getImportDeclarations()
    .map(importDeclaration => importDeclaration.getNamedImports())).flat().flat();
const aliasedImportedSymbols = importedSymbols
    .filter(importedSymbol => importedSymbol.getAliasNode() !== undefined);

These aren't tested fully, but with some toy examples it gives good results.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants