Skip to content

🎉 @ast-grep/all-langs #74

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

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/green-turtles-jump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ast-grep/all-langs": patch
---

New package: @ast-grep/all-langs
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ jobs:
uses: actions/setup-node@v4
with:
cache: pnpm
node-version: ">= 21"
Copy link

@coderabbitai coderabbitai bot Apr 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Validate Node.js version range in CI
Please confirm that the actions/setup-node action supports the semver range ">= 21". Also ensure that the release workflow (.github/workflows/release.yml) has been updated in the same way for consistency.


🏁 Script executed:

#!/bin/bash
# Verify Node.js version configuration in CI and release workflows
rg -n 'actions/setup-node' -C2 .github/workflows/ci.yml .github/workflows/release.yml

Length of output: 924


🌐 Web query:

Does GitHub actions/setup-node support semver ranges for the "node-version" input?

💡 Result:

Support for Semver Ranges in actions/setup-node

Yes, the GitHub Actions actions/setup-node action supports Semantic Versioning (semver) ranges for the node-version input.

Details

  • The node-version input accepts semver range notations, allowing you to specify versions like '14.x', '16', '>=16.0.0', and similar patterns. For example:

    • '16.x' will use the latest available patch version of Node.js 16.
    • '>=18.0.0' will resolve to the latest version matching the range[2][4][6].
  • This is explicitly supported in the official documentation and examples:

    The action will first check the local cache for a semver match. If unable to find a specific version in the cache, the action will attempt to download a version of Node.js[2][1][4].

  • You can also use other accepted notations such as:

    • 'lts/*' for the latest LTS version
    • 'latest' for the most recent Node.js release
    • Specific versions like '16.13.0'
    • Nightly and canary build notations[2][5]
  • The setup-node action internally uses semver resolution logic to pick the appropriate Node.js version based on the provided range, and this is compatible with the NPM ecosystem’s node-semver parser[3][4].

Example Usage

- uses: actions/setup-node@v4
  with:
    node-version: '18.x'      # Latest 18.x patch

or

- uses: actions/setup-node@v4
  with:
    node-version: '>=16.0.0'  # Latest version >= 16.0.0

Version Files and Ranges

  • The action also supports specifying the Node.js version via a file (.nvmrc, .node-version, package.json). If a semver range is provided in these files (e.g., via engines.node in package.json), that will be used as the version specification[1][6].

Summary Table

Syntax Example Result
'16.x' Latest patch of Node.js 16
'>=18.0.0' Latest available Node.js >= 18.0.0
'lts/*' Latest LTS version
'latest' Latest published Node.js version
'14', '14.x' Latest patch of Node.js 14

In conclusion, use semver ranges or aliases as the node-version input for flexibility in your GitHub Actions workflows[2][4][5][6].

Citations:


🌐 Web query:

What input parameters does actions/setup-node@v4 accept (e.g., node-version vs version)?

💡 Result:

Input Parameters for actions/setup-node@v4

actions/setup-node@v4 provides a range of input parameters to configure Node.js environments in GitHub Actions workflows. Below are the primary and advanced inputs you can use, with notes on precedence and intended use:

Core Version Selection Inputs

  • node-version
    Specifies the Node.js version to use (accepts SemVer, aliases like lts/*, latest, nightly, or canary builds).
    Example:

    with:
      node-version: '18'

    If both node-version and node-version-file are provided, node-version takes precedence[1][2][6][9].

  • node-version-file
    Path to a file containing the Node.js version (e.g., .nvmrc, .node-version, .tool-versions, or package.json).
    Example:

    with:
      node-version-file: '.nvmrc'

    Notable behavior: In a package.json, it checks volta.node first, then engines.node if volta.node is unspecified[1][6].

Update and Architecture Controls

  • check-latest
    Boolean (true or false). If true, downloads the latest available version matching the specification, bypassing local cache. Defaults to false, which favors cached versions for performance[1][6][9].
    Example:

    with:
      check-latest: true
  • architecture
    Target CPU architecture for Node.js (e.g., x86, x64, arm64). Defaults to the system architecture if unspecified[2][6][9].
    Example:

    with:
      architecture: 'x64'

Caching and Authentication (Advanced)

  • cache
    Specifies dependency manager to cache (npm, yarn, pnpm, or '' to disable)[2][6].

  • cache-dependency-path
    Sets the path to lock file(s) for cache key calculation (used if lock file is not in root).

  • registry-url and scope
    Used to set up npm registry authentication, particularly with private registries.

  • token
    Custom token for publishing/authenticating with npm/GitHub package registries.

Summary Table

Parameter Purpose/Example Value Notes
node-version '18', 'lts/*', '16.13.0' Main version selector; takes precedence over node-version-file.
node-version-file '.nvmrc', 'package.json' Uses version specified in file.
check-latest true, false Download the latest version if true. Default: false.
architecture 'x64', 'arm64' Target CPU architecture.
cache 'npm', 'yarn', 'pnpm' Enable caching of dependencies.
cache-dependency-path 'frontend/yarn.lock' Path(s) for dependency lock file(s).
registry-url 'https://npm.pkg.github.com' Custom registry URL.
scope '@my-org' Npm scope for authentication.
token '${{ secrets.NPM_TOKEN }}' Token for private registry auth.

No version Input

  • The parameter is named node-version (not just version). There is no version input; always use node-version when specifying the desired Node.js release[2][9].

Example Usage

- uses: actions/setup-node@v4
  with:
    node-version: '20'
    check-latest: true
    architecture: 'x64'
    cache: 'npm'

References

  • For a full list and the most current details, consult the official documentation[1][2][6][9].
  • The action supports multiple advanced workflows, including dependency caching, multi-architecture builds, and custom registry authentication.

In summary:
Use node-version to specify the Node.js version (not version). node-version-file can be used to read the version from a file, but is overridden by a direct node-version input. Additional parameters provide update, architecture, caching, and authentication controls for your workflow[1][2][6][9].

Citations:


Update release workflow to use node-version input
The actions/setup-node@v4 action does support SemVer ranges (e.g. ">= 21") via the node-version input. Your CI workflow already uses:

with:
  node-version: ">= 21"
  cache: pnpm

However, in your release workflow you’re still using the wrong parameter (version: 9) in two places. Please update both steps in .github/workflows/release.yml to:

- with:
-   version: 9
-   cache: pnpm
+ with:
+   node-version: ">= 21"
+   cache: pnpm

– once at line 39 (and its corresponding cache block) and again at line 73.
Ensure you’re using node-version (not version) for consistency with the CI workflow and the action’s documentation.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need this?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the package.json files for Node.js requirements
echo "Checking package.json files for Node.js engine requirements:"
find . -name "package.json" -type f -exec grep -l "\"engines\"" {} \; | xargs cat 2>/dev/null | grep -A5 "\"engines\"" || echo "No engines field found"

echo -e "\nChecking all-langs package.json:"
cat packages/all/package.json | grep -A20 "dependencies" || echo "Could not find dependencies section"

echo -e "\nChecking release workflow file:"
cat .github/workflows/release.yml | grep -A10 "setup-node" || echo "No setup-node configuration found"

Length of output: 2101


🏁 Script executed:

#!/bin/bash
# Let's check for potential Node.js version requirements in the project or PR

# Check if there are any ESM features or other Node.js 21+ specific features in the new package
echo "Checking all-langs source files for potential Node.js 21+ features:"
find packages/all/src -type f -name "*.ts" -exec cat {} \; | grep -E "(import|export) type|import \{.*\} from"

# Check existing Node.js version in package.json at root level
echo -e "\nChecking root package.json for any Node.js related configuration:"
cat package.json | grep -E "\"(node|engines|volta)\"" -A3 -B3 || echo "No Node.js config found"

# Check if the Nodejs version constraint already exists in release workflow
echo -e "\nChecking if release workflow already has Node.js version constraint:"
grep -n "node-version" .github/workflows/release.yml

Length of output: 1218


🏁 Script executed:

#!/bin/bash
# Let's check more context about this PR

# Check the commit message when Node.js 21 was added to CI workflow
echo "Checking commit history for Node.js version changes:"
git log -p -- .github/workflows/ci.yml | grep -A10 -B10 "node-version" | head -30

# Check if there was discussion about Node.js version requirements in issues or PRs
echo -e "\nChecking for any Node.js version discussions in PRs:"
gh pr list --limit 20 --json title,url | jq '.[] | select(.title | test("node|Node|nodejs|version|v[0-9]+") | .url)'

# Check if any dependencies require Node.js 21+
echo -e "\nChecking package.json files for dependencies that might require Node.js 21+:"
find . -name "package.json" -type f -exec cat {} \; | grep -E "\"@types/node|node-api-headers|napi-rs"

# Let's see the current branch name and if it's related to Node.js version
echo -e "\nCurrent branch info:"
git branch --show-current

Length of output: 910


@HerringtonDarkholme Based on what I found, this change does appear to be useful for consistency:

  1. Your release workflow already has node-version: ">= 21" specified
  2. The project uses @types/node version 22.14.1 (which corresponds to Node.js 22 APIs)
  3. The code uses Node.js built-in test module (node:test) and ES module features

While there's no explicit engines field in package.json defining Node.js requirements, maintaining the same Node.js version constraint across both CI and release workflows ensures consistency in your build environments.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha, @coderabbitai getting confused at pnpm/action-setup@v4 vs actions/setup-node@v4. The version: 9 parameter is for setting up pnpm (not Node), but it's saying to use node-version there, which is wrong.

The code it highlighted as wrong, this one;

with:
  version: 9
  cache: pnpm

simply does not exist. This is the real code:

- name: Install pnpm
  uses: pnpm/action-setup@v4
  with:
    version: 9
- name: Set up NodeJS
  uses: actions/setup-node@v4
  with:
    cache: pnpm
    node-version: ">= 21"

which is correct

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NatoBoram You're absolutely right, and I apologize for the confusion! I completely mixed up the two different actions:

  • pnpm/action-setup@v4 - correctly uses version: 9 for pnpm version
  • actions/setup-node@v4 - correctly uses node-version: ">= 21" for Node.js version

Your code is indeed correct as shown. Thanks for the clarification and for pointing out this mistake. I'll make sure to be more careful distinguishing between different actions and their parameters in the future.

(_/)
(•ㅅ•)
/   づ ♡

- name: Install dependencies
run: pnpm install

Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ jobs:
uses: actions/setup-node@v4
with:
cache: pnpm
node-version: ">= 21"
registry-url: https://registry.npmjs.org/
- name: Install dependencies
run: pnpm install
Expand Down Expand Up @@ -74,6 +75,7 @@ jobs:
uses: actions/setup-node@v4
with:
cache: pnpm
node-version: ">= 21"
registry-url: https://registry.npmjs.org/
- name: Install dependencies
run: pnpm install
Expand Down
3 changes: 3 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"recommendations": ["biomejs.biome"]
}
6 changes: 6 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"[javascript][json][jsonc][typescript]": {
"editor.defaultFormatter": "biomejs.biome",
"editor.minimap.maxColumn": 80
}
}
1 change: 1 addition & 0 deletions packages/all/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
!src
71 changes: 71 additions & 0 deletions packages/all/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
{
"name": "@ast-grep/all-langs",
"version": "0.0.0",
"description": "Exports all officially supported languages.",
"keywords": ["ast-grep"],
"homepage": "https://github.com/ast-grep/langs/tree/main/packages/all",
"bugs": {
"url": "https://github.com/ast-grep/langs/issues"
},
"license": "ISC",
"author": {
"name": "CodeRabbit",
"url": "https://www.coderabbit.ai"
},
"files": ["dist", "!dist/**/*.test.*"],
"main": "dist/index.js",
"repository": "github:ast-grep/langs",
"scripts": {
"build": "tsc",
"clean": "rm -rf dist docs node_modules tsconfig.tsbuildinfo",
"format": "biome format --write",
"format:ci": "biome format",
"lint": "biome lint --fix",
"lint:ci": "biome lint",
"pretest": "tsc",
"test": "node --test './dist/**/*.test.js'"
},
"dependencies": {
"@ast-grep/lang-angular": "workspace:*",
"@ast-grep/lang-bash": "workspace:*",
"@ast-grep/lang-c": "workspace:*",
"@ast-grep/lang-cpp": "workspace:*",
"@ast-grep/lang-csharp": "workspace:*",
"@ast-grep/lang-css": "workspace:*",
"@ast-grep/lang-dart": "workspace:*",
"@ast-grep/lang-elixir": "workspace:*",
"@ast-grep/lang-go": "workspace:*",
"@ast-grep/lang-haskell": "workspace:*",
"@ast-grep/lang-html": "workspace:*",
"@ast-grep/lang-java": "workspace:*",
"@ast-grep/lang-javascript": "workspace:*",
"@ast-grep/lang-json": "workspace:*",
"@ast-grep/lang-kotlin": "workspace:*",
"@ast-grep/lang-lua": "workspace:*",
"@ast-grep/lang-php": "workspace:*",
"@ast-grep/lang-python": "workspace:*",
"@ast-grep/lang-ruby": "workspace:*",
"@ast-grep/lang-rust": "workspace:*",
"@ast-grep/lang-scala": "workspace:*",
"@ast-grep/lang-sql": "workspace:*",
"@ast-grep/lang-swift": "workspace:*",
"@ast-grep/lang-toml": "workspace:*",
"@ast-grep/lang-tsx": "workspace:*",
"@ast-grep/lang-typescript": "workspace:*",
"@ast-grep/lang-yaml": "workspace:*",
"@ast-grep/napi": "^0.37.0"
},
"devDependencies": {
"@types/node": "22.14.1",
"typescript": "^5.8.3"
},
"type": "module",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to think if we want to ship ESM by default. I prefer to do that but other modules are currently in CJS

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh they are? I didn't notice!

"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"types": "dist/index.d.ts",
"module": "dist/index.js"
}
2 changes: 2 additions & 0 deletions packages/all/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./lang.ts"
export * from "./langs.ts"
41 changes: 41 additions & 0 deletions packages/all/src/lang.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Lang as LangNapi } from "@ast-grep/napi"
import test, { describe } from "node:test"
import { Lang } from "./lang.ts"

/** Languages supported in `@ast-grep/[email protected]`. */
const previous = Object.freeze({
Html: "Html",
JavaScript: "JavaScript",
Tsx: "Tsx",
Css: "Css",
TypeScript: "TypeScript",
Bash: "Bash",
C: "C",
Cpp: "Cpp",
CSharp: "CSharp",
Go: "Go",
Elixir: "Elixir",
Haskell: "Haskell",
Java: "Java",
Json: "Json",
Kotlin: "Kotlin",
Lua: "Lua",
Php: "Php",
Python: "Python",
Ruby: "Ruby",
Rust: "Rust",
Scala: "Scala",
Sql: "Sql",
Swift: "Swift",
Yaml: 'Yaml',
})

describe("Lang", () => {
test("The new enum is compatible with the old one", ({ assert }) => {
for (const lang of Object.values(previous)) assert.equal(Lang[lang], lang)
})

test("The new enum is compatible with the built-in ones", ({ assert }) => {
for (const lang of Object.values(LangNapi)) assert.equal(Lang[lang], lang)
})
})
37 changes: 37 additions & 0 deletions packages/all/src/lang.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* An enum of all languages supported by `@ast-grep/langs`.
*/
export const Lang = {
Angular: "Angular",
Bash: "Bash",
C: "C",
Cpp: "Cpp",
CSharp: "CSharp",
Css: "Css",
Dart: "Dart",
Elixir: "Elixir",
Go: "Go",
Haskell: "Haskell",
Html: "Html",
Java: "Java",
JavaScript: "JavaScript",
Json: "Json",
Kotlin: "Kotlin",
Lua: "Lua",
Php: "Php",
Python: "Python",
Ruby: "Ruby",
Rust: "Rust",
Scala: "Scala",
Sql: "Sql",
Swift: "Swift",
Toml: "Toml",
Tsx: "Tsx",
TypeScript: "TypeScript",
Yaml: "Yaml",
} as const

/**
* An enum of all languages supported by `@ast-grep/langs`.
*/
export type Lang = (typeof Lang)[keyof typeof Lang]
31 changes: 31 additions & 0 deletions packages/all/src/langs.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { parse, registerDynamicLanguage } from "@ast-grep/napi"
import { before, describe, test } from "node:test"
import { Lang } from "./lang.ts"
import { langs } from "./langs.ts"

describe("langs", () => {
// @ts-expect-error Type `StaticLangRegistration` is missing the following
// properties from type `LangRegistration`: `libraryPath`, `extensions`.
before(() => registerDynamicLanguage(langs))
Comment on lines +6 to +9
Copy link

@coderabbitai coderabbitai bot Apr 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Consider fixing type definitions instead of suppressing errors.

Using @ts-expect-error is a temporary solution but fixing the underlying type issue would be better for long-term maintenance. The comment indicates that StaticLangRegistration is missing properties from LangRegistration. This could be addressed by extending the type definition.


🏁 Script executed:

#!/bin/bash
# Looking for the definition of StaticLangRegistration and LangRegistration
rg -A 5 "StaticLangRegistration|LangRegistration" --type ts

Length of output: 2974


Enhance static‐language type definitions

File: packages/all/src/langs.test.ts
Lines: 6–9

The TS error here is because your langs object (typed via StaticLangRegistration in packages/all/src/langs.ts) doesn’t include the libraryPath and extensions fields required by LangRegistration (from @ast-grep/napi/types/registerDynamicLang.js). Rather than silencing this with @ts-expect-error, please update the static‐lang types so that langs is a valid LangRegistration map:

  • In packages/all/src/langs.ts, extend your StaticLangRegistration union (or the StaticLangRegistrations interface) to include the missing libraryPath and extensions properties, so that
    registerDynamicLanguage(langs)
    passes type‐checking without suppression.
  • Alternatively, introduce a wrapper or cast that explicitly merges in those fields at compile time, instead of relying on @ts-expect-error.

This will remove the need to disable type‐checking and improve long-term maintainability.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a valid concern. if we need error suppression here, does it also mean users need it as well?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

I need to better understand the type incompatibility between StaticLangRegistration and what registerDynamicLanguage expects.

@HerringtonDarkholme This is an excellent point. If we need to suppress type errors in our own tests, users will likely encounter the same issues when trying to use the package as documented. Let me check how the types are defined to provide a proper solution.


🏁 Script executed:

#!/bin/bash
# Look at the StaticLangRegistration and langs definitions
cat packages/all/src/langs.ts

# Check the imported types from @ast-grep/napi
rg "LangRegistration" --type=typescript -A 10 -B 10

# Check the PR description or documentation about how users are expected to use this package
rg -i "register.*langs" --type=markdown

Length of output: 3245


@HerringtonDarkholme You've raised an excellent concern. If our tests need to suppress type errors with @ts-expect-error, users will indeed face the same issues when trying to use this package, creating a poor developer experience.

Let me examine the exact types to understand what's happening and suggest a proper solution.


🏁 Script executed:

#!/bin/bash
# Let's examine what the LangRegistration type in the napi package defines
fd -t file "registerDynamicLang.js" --exec cat {}

# Let's check sample files from the language packages to see how they're structured
find packages -name "*.js" -path "*/lang-*" | grep -v "node_modules" | head -n 1 | xargs cat 

# Let's also check the PR description to see how users are expected to use this package
grep -r -i "registerDynamicLanguage" --include="*.md" .

Length of output: 4619


@HerringtonDarkholme I'm trying to find the exact definition of LangRegistration to better understand what's missing from our types. Let me dig deeper.


🏁 Script executed:

#!/bin/bash
# Let's try to find the actual definition of LangRegistration to understand the type mismatch
find node_modules -type f -name "*.d.ts" | xargs grep -l "LangRegistration" | xargs cat

# Let's also check if we have access to node_modules/@ast-grep/napi to find these types
ls -la node_modules/@ast-grep/napi/types/ 2>/dev/null || echo "Directory not found"

# Check a sample language package to understand how the lang registrations are structured 
find packages -path "*/lang-*" -name "index.js" | head -n 1 | xargs cat

Length of output: 300


@HerringtonDarkholme You're absolutely right - this issue would affect users too. If we need to use @ts-expect-error in our own tests, users will face the same typing issues.

Looking at the code, we can see:

  1. Each language package export doesn't fully satisfy what registerDynamicLanguage expects
  2. The error tells us that libraryPath and extensions properties are required

I propose three potential solutions:

  1. Create a wrapper function that adapts the types appropriately:

    // In langs.ts
    export function registerAllLangs() {
      // @ts-expect-error - we handle this internally so users don't have to
      return registerDynamicLanguage(langs);
    }
  2. Modify the StaticLangRegistrations interface to ensure type compatibility:

    interface StaticLangRegistrations extends DynamicLangRegistrations {
      // existing properties...
    }
  3. Create an adapter that maps each language to a compatible format:

    export const registerReadyLangs = Object.entries(langs).reduce((acc, [key, value]) => {
      acc[key] = { 
        ...value,
        // Add missing properties here if needed 
      };
      return acc;
    }, {} as DynamicLangRegistrations);

Option #1 is simplest and would hide the typing issue from users entirely. This would be my recommendation since it provides the best developer experience.


🏁 Script executed:

#!/bin/bash
# Let's look at one of the language packages more closely
find packages -path "*/lang-*/src" -type d | head -n 1 | xargs find -name "*.ts" | xargs cat

# Let's check what's in the main PR file to understand what users are supposed to do
cat packages/all/package.json

Length of output: 24088


@HerringtonDarkholme You've identified an important issue. Yes, the error suppression would be necessary for users too, which creates a poor developer experience. I've examined the code in detail, and here's what's happening:

  1. Each language package exports a registration object with required properties:

    type LanguageRegistration = {
      libraryPath: string  
      extensions: string[]
      languageSymbol?: string
      metaVarChar?: string
      expandoChar?: string
    }
  2. The StaticLangRegistration type is defined as a union of all language modules, which means TypeScript only preserves common properties across all union members.

  3. When passing langs to registerDynamicLanguage(), TypeScript can't guarantee all required properties exist on every union member.

Proposed solutions:

  1. Add a helper function (recommended):

    // In langs.ts
    export function registerAllLangs() {
      // @ts-expect-error - we handle this internally so users don't have to
      return registerDynamicLanguage(langs);
    }
  2. Modify the StaticLangRegistration interface to enforce the required properties:

    interface StaticLangRegistration {
      libraryPath: string
      extensions: string[]
      // other common properties...
    }
    
    type LangModule = 
      | typeof angular
      | typeof bash
      | /* other modules */
    
    interface StaticLangRegistrations {
      // Define as before but ensure each value matches StaticLangRegistration
    }
  3. Create an adapter at runtime:

    export const registerReadyLangs = Object.fromEntries(
      Object.entries(langs).map(([lang, reg]) => {
        // Validate that required properties exist
        if (!('libraryPath' in reg) || !('extensions' in reg)) {
          throw new Error(`Language ${lang} is missing required properties`);
        }
        return [lang, reg];
      })
    );

I recommend option #1 as it's the simplest and provides the best developer experience.


🧬 Code Graph Analysis Results

Relevant snippets for understanding packages/all/src/langs.test.ts and the user comment:


File: packages/all/src/lang.ts
Lines 4-37

export const Lang = {
	Angular: "Angular",
	Bash: "Bash",
	C: "C",
	Cpp: "Cpp",
	CSharp: "CSharp",
	Css: "Css",
	Dart: "Dart",
	Elixir: "Elixir",
	Go: "Go",
	Haskell: "Haskell",
	Html: "Html",
	Java: "Java",
	JavaScript: "JavaScript",
	Json: "Json",
	Kotlin: "Kotlin",
	Lua: "Lua",
	Php: "Php",
	Python: "Python",
	Ruby: "Ruby",
	Rust: "Rust",
	Scala: "Scala",
	Sql: "Sql",
	Swift: "Swift",
	Toml: "Toml",
	Tsx: "Tsx",
	TypeScript: "TypeScript",
	Yaml: "Yaml",
} as const

export type Lang = (typeof Lang)[keyof typeof Lang]
  • Defines a constant Lang object enumerating supported language names as string literals.
  • Defines a TypeScript type Lang as the union of all language string literal values.
  • This is the source of the Lang enum used in the test file.

File: packages/all/src/langs.ts
Lines 100-128

export const langs: StaticLangRegistrations = {
	[Lang.Angular]: angular,
	[Lang.Bash]: bash,
	[Lang.C]: c,
	[Lang.Cpp]: cpp,
	[Lang.CSharp]: csharp,
	[Lang.Css]: css,
	[Lang.Dart]: dart,
	[Lang.Elixir]: elixir,
	[Lang.Go]: go,
	[Lang.Haskell]: haskell,
	[Lang.Html]: html,
	[Lang.Java]: java,
	[Lang.JavaScript]: javascript,
	[Lang.Json]: json,
	[Lang.Kotlin]: kotlin,
	[Lang.Lua]: lua,
	[Lang.Php]: php,
	[Lang.Python]: python,
	[Lang.Ruby]: ruby,
	[Lang.Rust]: rust,
	[Lang.Scala]: scala,
	[Lang.Sql]: sql,
	[Lang.Swift]: swift,
	[Lang.Toml]: toml,
	[Lang.Tsx]: tsx,
	[Lang.TypeScript]: typescript,
	[Lang.Yaml]: yaml,
}
  • Exports langs, an object mapping language names (from Lang) to their respective static language registration objects.
  • This object is passed to registerDynamicLanguage in the test setup.
  • Each language registration likely contains metadata such as libraryPath and extensions (referenced in the test file's @ts-expect-error comment).

These snippets provide context on:

  • The Lang enum used in the tests.
  • The langs object registered dynamically before tests run.
  • The possible reason for the TypeScript error suppression in the test file related to missing properties in language registrations.

No other code snippets appear directly relevant to the test file or the user comment about error suppression.

Copy link
Collaborator Author

@NatoBoram NatoBoram Apr 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, users would need it

The proper fix would be to declare the type once somewhere exported and import it in those packages instead of having every package declare it independently. This way, any update would be felt across the monorepo instantly

The helper function is something I wanted to do, but then it somehow didn't work when I installed the package somewhere else to test it

But then it also means that, currently, if someone wants to use registerDynamicLanguage, they'll face this issue

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!


// A newly supported language
test(Lang.Dart, ({ assert }) => {
const sg = parse(Lang.Dart, 'var x = "Hello, world!";"')
const kind = sg.root().kind()
assert.equal(kind, "program")
})

// A previously supported language
test(Lang.Go, ({ assert }) => {
const sg = parse(Lang.Go, 'x := "Hello, world!"')
const kind = sg.root().kind()
assert.equal(kind, "source_file")
})

// A built-in language
test(Lang.TypeScript, ({ assert }) => {
const sg = parse(Lang.TypeScript, 'const x = "Hello, world!"')
const kind = sg.root().kind()
assert.equal(kind, "program")
})
})
128 changes: 128 additions & 0 deletions packages/all/src/langs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import angular from "@ast-grep/lang-angular"
import bash from "@ast-grep/lang-bash"
import c from "@ast-grep/lang-c"
import cpp from "@ast-grep/lang-cpp"
import csharp from "@ast-grep/lang-csharp"
import css from "@ast-grep/lang-css"
import dart from "@ast-grep/lang-dart"
import elixir from "@ast-grep/lang-elixir"
import go from "@ast-grep/lang-go"
import haskell from "@ast-grep/lang-haskell"
import html from "@ast-grep/lang-html"
import java from "@ast-grep/lang-java"
import javascript from "@ast-grep/lang-javascript"
import json from "@ast-grep/lang-json"
import kotlin from "@ast-grep/lang-kotlin"
import lua from "@ast-grep/lang-lua"
import php from "@ast-grep/lang-php"
import python from "@ast-grep/lang-python"
import ruby from "@ast-grep/lang-ruby"
import rust from "@ast-grep/lang-rust"
import scala from "@ast-grep/lang-scala"
import sql from "@ast-grep/lang-sql"
import swift from "@ast-grep/lang-swift"
import toml from "@ast-grep/lang-toml"
import tsx from "@ast-grep/lang-tsx"
import typescript from "@ast-grep/lang-typescript"
import yaml from "@ast-grep/lang-yaml"
import type {
DynamicLangRegistrations,
LangRegistration,
} from "@ast-grep/napi/types/registerDynamicLang.js"
import { Lang } from "./lang.ts"

type StaticLangRegistration =
| typeof angular
| typeof bash
| typeof c
| typeof cpp
| typeof csharp
| typeof css
| typeof dart
| typeof elixir
| typeof go
| typeof haskell
| typeof html
| typeof java
| typeof javascript
| typeof json
| typeof kotlin
| typeof lua
| typeof php
| typeof python
| typeof ruby
| typeof rust
| typeof scala
| typeof sql
| typeof swift
| typeof toml
| typeof tsx
| typeof typescript
| typeof yaml
| LangRegistration

interface StaticLangRegistrations {
Angular: typeof angular
Bash: typeof bash
C: typeof c
Cpp: typeof cpp
CSharp: typeof csharp
Css: typeof css
Dart: typeof dart
Elixir: typeof elixir
Go: typeof go
Haskell: typeof haskell
Html: typeof html
Java: typeof java
JavaScript: typeof javascript
Json: typeof json
Kotlin: typeof kotlin
Lua: typeof lua
Php: typeof php
Python: typeof python
Ruby: typeof ruby
Rust: typeof rust
Scala: typeof scala
Sql: typeof sql
Swift: typeof swift
Toml: typeof toml
Tsx: typeof tsx
TypeScript: typeof typescript
Yaml: typeof yaml

/** To satisfy {@link DynamicLangRegistrations}. */
[langName: string]: StaticLangRegistration
}

/**
* Built-in dynamic languages supported by `@ast-grep/langs`.
*/
export const langs: StaticLangRegistrations = {
[Lang.Angular]: angular,
[Lang.Bash]: bash,
[Lang.C]: c,
[Lang.Cpp]: cpp,
[Lang.CSharp]: csharp,
[Lang.Css]: css,
[Lang.Dart]: dart,
[Lang.Elixir]: elixir,
[Lang.Go]: go,
[Lang.Haskell]: haskell,
[Lang.Html]: html,
[Lang.Java]: java,
[Lang.JavaScript]: javascript,
[Lang.Json]: json,
[Lang.Kotlin]: kotlin,
[Lang.Lua]: lua,
[Lang.Php]: php,
[Lang.Python]: python,
[Lang.Ruby]: ruby,
[Lang.Rust]: rust,
[Lang.Scala]: scala,
[Lang.Sql]: sql,
[Lang.Swift]: swift,
[Lang.Toml]: toml,
[Lang.Tsx]: tsx,
[Lang.TypeScript]: typescript,
[Lang.Yaml]: yaml,
}
Loading