Skip to content

Output JSON AST directly from tsc #61260

Closed as not planned
Closed as not planned

Description

@jjrscott

🔍 Search Terms

"ast json"
"ast output"
"abstract syntax tree output"

✅ Viability Checklist

⭐ Suggestion

I am NOT a TypeScript or JavaScript developer but need to generate other languages from structures held in these languages. This is the crux of the what follows though it may not use the correct vocabulary.

Allow a TypeScript or JavaScript file to be output as a JSON abstract syntax tree directly from tsc. This would likely consist of a correctly coded version of the below script.

Note that this issue does not require the output format to be frozen, as the output from the command could itself be defined to be correct. While users of this functionality (such as myself) may have to change our downstream scripts, we would at least benefit from a known correct producer of AST JSON output.

#!/usr/bin/env node

// Original source from https://github.com/seachicken/tsparser

"use strict";

const ts = require("typescript");
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value
const getCircularReplacer = function () {
  const seen = new WeakSet();
  return function (key, value) {
    if (typeof value === "object" && value !== null) {
      if (seen.has(value)) {
        return;
      }
      seen.add(value);
    }
    return value;
  };
};

const sourceFiles = process.argv.slice(2).map((input) => {
  const program = ts.createProgram([input], { allowJs: true });
  const sourceFile = program.getSourceFile(input);
  function parse(node) {
    // https://github.com/microsoft/TypeScript/blob/1294ee8c1f37c8bf214db67e180edb3220a52236/src/compiler/types.ts#L21
    node.kindName = ts.SyntaxKind[node.kind];
    node.start = node.getStart(sourceFile);
    ts.forEachChild(node, parse);
  }
  ts.forEachChild(sourceFile, parse);
  return sourceFile;
});

try {
  process.stdout.write(JSON.stringify(sourceFiles, getCircularReplacer()));
} catch (error) {
  process.stdout.write("\n");
  process.stderr.write("".concat(error, "\n"));
}

📃 Motivating Example

I have a number of structure definitions held in TypeScript which need to be shared in other languages such as Kotlin and Go. As is often the case in these situations I have expertise only in the destination language but am required to accurately parse the source language.

💻 Use Cases

  1. What do you want to use this for?
    • Exporting Typescrypt to other languages, either computational such as Swift, or a definition language such as OpenAPI.
  2. What shortcomings exist with current approaches?
    • The user often does not have the expertise to find and code the correct TypeScript solution.
  3. What workarounds are you using in the meantime?
    • The above script (which may or may not be correct)

Activity

  1. MartinJohns commented on Feb 24, 2025

    @MartinJohns
    Contributor

    IMO this falls under non-goal 4 of the Language Design Goals:

    Provide an end-to-end build pipeline. Instead, make the system extensible so that external tools can use the compiler for more complex build workflows.

  2. jjrscott commented on Feb 24, 2025

    @jjrscott
    Author

    Thanks Martin Johns (@MartinJohns) though I'd argue it falls under the "instead" part of non-goal 4:

    Instead, make the system extensible so that external tools can use the compiler for more complex build workflows.

    In this case the "external tool" in question is written in an entirely different language / build system.

  3. RyanCavanaugh commented on Feb 24, 2025

    @RyanCavanaugh
    Member

    Internals like this aren't something we want to prioritize emitting from tsc. There's nothing particularly meaningful you can do with this, and many other parsers already exist.

  4. jakebailey commented on Feb 24, 2025

    @jakebailey
    Member

    FWIW you can already do this today with alternative TypeScript parsers which spit out estree-compatible objects. For example, @typescript-eslint/parser converts our AST into one readable by ESLint, and that same library is used by prettier to format code. oxc has similar support (though a different tree than ts-eslint).

  5. jjrscott commented on Feb 24, 2025

    @jjrscott
    Author

    Ryan Cavanaugh (@RyanCavanaugh) I'm using the above code to generate cross platform types from existing TypeScript code. Given the web side of our team are using tsc to compile their code it made sense to use this project rather than any other. As a non-TypeScript developer I would need to significally upskill to be able to advocate another external dependancy within my group.

    I appreciate that this might not be important within the TypeScript community but it really helps us "neighbours" use TypeScript code. The above code took me a few days to find and pin down as I didn't even know what I was looking for beyond that I needed the same out on the CLI that I was seeing on https://astexplorer.net.

    Given TypeScript's frankly astonishing type description abilities, it would be a shame for other external parties to miss out on what I eventually found to be possible (I'm still not sure the above script is "correct").

  6. typescript-bot commented on Feb 28, 2025

    @typescript-bot
    Contributor

    This issue has been marked as "Out of Scope" and has seen no recent activity. It has been automatically closed for house-keeping purposes.

  7. jjrscott commented on Feb 28, 2025

    @jjrscott
    Author

    This is disappointing. I believed this request would fall clearly into the goals of:

    • Goal 2: "Provide a structuring mechanism for larger pieces of code."
    • Non-goal 4: "Provide an end-to-end build pipeline. Instead, make the system extensible so that external tools can use the compiler for more complex build workflows."

    As a non-TypeScript programmer, utilising TypeScript in a larger project requires signficant upskilling. Hopefully, others treading the same path will at least be able to use the above code to make progress with their own requirements.

  8. MartinJohns commented on Feb 28, 2025

    @MartinJohns
    Contributor

    Thanks @MartinJohns though I'd argue it falls under the "instead" part of non-goal 4:

    Instead, make the system extensible so that external tools can use the compiler for more complex build workflows.

    In this case the "external tool" in question is written in an entirely different language / build system.

    You misunderstand what this part means. It doesn't mean the TypeScript tooling will support all kinds of use cases that may be needed by third-party tooling. It means the compiler API is open and usable by third-parties, so they can write tools utilizing the compiler API to fulfill their use cases (one example being your script).

  9. jjrscott commented on Mar 2, 2025

    @jjrscott
    Author

    Martin Johns (@MartinJohns) While from an internal perspective the compiler API may hook of choice, externally tsc is the TypeScript project. One is forced to learn of tsc in the first few minutes, discovering and then using the compiler API took days, even for a self professed language/compiler nerd. While external non-TypeScript programmers can write tools utilizing the compiler API to fulfill their use cases, as I did, the cost was very high.

    I can only advise that, in this particlar case and in my experience, the barriers to entry are too high.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Out of ScopeThis idea sits outside of the TypeScript language design constraintsSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions