Skip to content

Commit

Permalink
fix(gazelle): support imports within declare-module expressions (#3449)
Browse files Browse the repository at this point in the history
Support import/export within `declare module`.

---

### Type of change

- Bug fix (change which fixes an issue)

### Test plan

- Covered by existing test cases
- New test cases added

GitOrigin-RevId: 2635bf37a3d4a5f20e9c312570b522351e97f231
  • Loading branch information
jbedard authored and gregmagolan committed Sep 22, 2023
1 parent eb34aba commit 5d2c14e
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 29 deletions.
5 changes: 4 additions & 1 deletion gazelle/common/treesitter/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")

go_library(
name = "treesitter",
srcs = ["queries.go"],
srcs = [
"queries.go",
"traversal.go",
],
importpath = "aspect.build/cli/gazelle/common/treesitter",
visibility = ["//visibility:public"],
deps = ["@com_github_smacker_go_tree_sitter//:go-tree-sitter"],
Expand Down
32 changes: 32 additions & 0 deletions gazelle/common/treesitter/traversal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package treesitter

import sitter "github.com/smacker/go-tree-sitter"

func GetNodeChildByTypePath(node *sitter.Node, childPath ...string) *sitter.Node {
for _, childType := range childPath {
node = GetNodeChildByType(node, childType)
if node == nil {
return nil
}
}

return node
}

func GetNodeChildByType(node *sitter.Node, childType string) *sitter.Node {
// TODO: assert only one child of type?

for i := 0; i < int(node.NamedChildCount()); i++ {
child := node.NamedChild(i)
if child.Type() == childType {
return child
}
}

return nil
}

func GetNodeStringField(node *sitter.Node, name string) *sitter.Node {
// TODO: assert its a string field, assert only one child
return node.ChildByFieldName(name).NamedChild(0)
}
20 changes: 18 additions & 2 deletions gazelle/js/parser/tests/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ var testCases = []struct {
desc: "import depth",
ts: `
import package from "from/internal/package";
// Use the import.
export default package;
`,
Expand Down Expand Up @@ -281,7 +281,6 @@ var testCases = []struct {
// https://www.typescriptlang.org/docs/handbook/modules.html#shorthand-ambient-modules
declare module 'module-with-no-body';
declare /* 1 */ module /* 2 */ 'comments-2' /* 3 */;
/* 1 */ declare module /* 2 */ 'comments-3'; /* 3 */
declare module "module-quotes-1"
Expand All @@ -290,6 +289,23 @@ var testCases = []struct {
expectedModules: []string{"module-x", "module-with-no-body", "comments-2", "comments-3", "module-quotes-1"},
typeOnly: true,
},
{
desc: "declare module sub-imports",
ts: `
declare module 'lib-imports' {
export * from 'lib-export-star';
export * as foo from 'lib-export-star-as';
import f /*c*/ from 'lib-from-default';
import { x /*c*/ } /*c*/ from /*c*/ 'lib-impt'
export { x } /*c*/
}
`,
filename: "declare-module-sub.ts",
expectedModules: []string{"lib-imports"},
expectedImports: []string{"lib-export-star", "lib-export-star-as", "lib-from-default", "lib-impt"},
typeOnly: true,
},
}

func RunParserTests(t *testing.T, parser parser.Parser, includeTypeOnly bool, parserPost string) {
Expand Down
67 changes: 41 additions & 26 deletions gazelle/js/parser/treesitter/parser_treesitter.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,27 @@ func (p *TreeSitterParser) ParseSource(filePath, sourceCodeStr string) (parser.P
for i := 0; i < int(rootNode.NamedChildCount()); i++ {
node := rootNode.NamedChild(i)

if rootImport := getRootImport(node, sourceCode); rootImport != nil {
imports = append(imports, rootImport.Content(sourceCode))
} else if rootModule := getRootModuleDeclaration(node, sourceCode); rootModule != nil {
modules = append(modules, rootModule.Content(sourceCode))
if isImportStatement(node) {
if rootImport := getImportStatementName(node); rootImport != nil {
imports = append(imports, rootImport.Content(sourceCode))
}
} else if isModuleDeclaration(node) {
if modDeclName := getModuleDeclarationName(node); modDeclName != nil {
modules = append(modules, modDeclName.Content(sourceCode))
}

// Import/export statements within a module declaration.
if moduleRootNode := treeutils.GetNodeChildByTypePath(node, "module", "statement_block"); moduleRootNode != nil {
for j := 0; j < int(moduleRootNode.NamedChildCount()); j++ {
moduleNode := moduleRootNode.NamedChild(j)

if isImportStatement(moduleNode) {
if moduleImport := getImportStatementName(moduleNode); moduleImport != nil {
imports = append(imports, moduleImport.Content(sourceCode))
}
}
}
}
}
}

Expand Down Expand Up @@ -125,41 +142,39 @@ func (p *TreeSitterParser) ParseSource(filePath, sourceCodeStr string) (parser.P
return result, errs
}

// Return a Node representing the `from` value of an import statement within the given root node.
func getRootImport(node *sitter.Node, sourceCode []byte) *sitter.Node {
// Determine if a node is an import/export statement that may contain a `from` value.
func isImportStatement(node *sitter.Node) bool {
nodeType := node.Type()

// Top level `import ... from "..."` statement.
// Top level `export ... from "..."` statement.
if nodeType == "import_statement" || nodeType == "export_statement" {
from := node.ChildByFieldName("source")
if from != nil {
return from.Child(1)
}
return nil
}
return nodeType == "import_statement" || nodeType == "export_statement"
}

// Return a Node representing the `from` value of an import/export statement.
func getImportStatementName(importStatement *sitter.Node) *sitter.Node {
from := importStatement.ChildByFieldName("source")
if from != nil {
return from.Child(1)
}
return nil
}

func getRootModuleDeclaration(node *sitter.Node, sourceCode []byte) *sitter.Node {
// Determine if a node is a module declaration.
func isModuleDeclaration(node *sitter.Node) bool {
nodeType := node.Type()

// Top level `declare module "..." [{ ... }]` statement.
// `declare module "..." [{ ... }]` statement.
// See: https://www.typescriptlang.org/docs/handbook/modules.html#ambient-modules
//
// Example node: (ambient_declaration (module name: (string (string_fragment)) body: (statement_block)))
if nodeType == "ambient_declaration" {
for i := 0; i < int(node.NamedChildCount()); i++ {
child := node.NamedChild(i)
if child.Type() == "module" {
for j := 0; j < int(child.NamedChildCount()); j++ {
if child.NamedChild(j).Type() == "string" {
return child.NamedChild(j).NamedChild(0)
}
}
}
}
return nodeType == "ambient_declaration"
}

// Return a Node representing the module declaration name
func getModuleDeclarationName(node *sitter.Node) *sitter.Node {
if module := treeutils.GetNodeChildByType(node, "module"); module != nil {
return treeutils.GetNodeStringField(module, "name")
}

return nil
Expand Down
Empty file.
10 changes: 10 additions & 0 deletions gazelle/js/tests/declare_module/import/BUILD.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
load("@aspect_rules_ts//ts:defs.bzl", "ts_project")

ts_project(
name = "import",
srcs = [
"lib.d.ts",
"other.ts",
],
deps = ["//lib"],
)
3 changes: 3 additions & 0 deletions gazelle/js/tests/declare_module/import/lib.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
declare module 'lib-imports' {
export * from 'lib-lib';
}
1 change: 1 addition & 0 deletions gazelle/js/tests/declare_module/import/other.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// Just to ensure generation until https://github.com/aspect-build/silo/pull/3438

0 comments on commit 5d2c14e

Please sign in to comment.