From 90a773eeed4144ae609d7a2765d7a2a319e141c0 Mon Sep 17 00:00:00 2001 From: Luke Aguilar Date: Sun, 21 Apr 2024 12:57:37 +1000 Subject: [PATCH 1/4] Prototyping, partially functional. absolute vs rel path parsing needs to be fixed --- cmd/root.go | 10 +++++++ internal/dart/language.go | 63 +++++++++++++++++++++++++++++++++++++++ internal/dart/parser.go | 59 ++++++++++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+) create mode 100644 internal/dart/language.go create mode 100644 internal/dart/parser.go diff --git a/cmd/root.go b/cmd/root.go index 8b099c7..bbc520a 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -7,6 +7,7 @@ import ( "path/filepath" "github.com/gabotechs/dep-tree/internal/config" + "github.com/gabotechs/dep-tree/internal/dart" "github.com/gabotechs/dep-tree/internal/dummy" "github.com/gabotechs/dep-tree/internal/js" "github.com/gabotechs/dep-tree/internal/language" @@ -99,6 +100,7 @@ func inferLang(files []string, cfg *config.Config) (language.Language, error) { python int rust int dummy int + dart int }{} top := struct { lang string @@ -130,6 +132,12 @@ func inferLang(files []string, cfg *config.Config) (language.Language, error) { top.v = score.dummy top.lang = "dummy" } + case utils.EndsWith(file, dart.Extensions): + score.dart += 1 + if score.dart > top.v { + top.v = score.dart + top.lang = "dart" + } } } if top.lang == "" { @@ -142,6 +150,8 @@ func inferLang(files []string, cfg *config.Config) (language.Language, error) { return rust.MakeRustLanguage(&cfg.Rust) case "python": return python.MakePythonLanguage(&cfg.Python) + case "dart": + return &dart.Language{}, nil case "dummy": return &dummy.Language{}, nil default: diff --git a/internal/dart/language.go b/internal/dart/language.go new file mode 100644 index 0000000..7627468 --- /dev/null +++ b/internal/dart/language.go @@ -0,0 +1,63 @@ +package dart + +import ( + "bytes" + "fmt" + "os" + "path/filepath" + + "github.com/gabotechs/dep-tree/internal/language" +) + +var Extensions = []string{"dart"} + +type Language struct{} + +func (l *Language) ParseFile(path string) (*language.FileInfo, error) { + content, err := os.ReadFile(path) + if err != nil { + return nil, err + } + file, err := ParseFile(path) + if err != nil { + return nil, err + } + currentDir, _ := os.Getwd() + relPath, _ := filepath.Rel(currentDir, path) + return &language.FileInfo{ + Content: file.Statements, // dump the parsed statements into the FileInfo struct. + Loc: bytes.Count(content, []byte("\n")), // get the amount of lines of code. + Size: len(content), // get the size of the file in bytes. + AbsPath: path, // provide its absolute path. + RelPath: relPath, // provide the path relative to the current dir. + }, nil +} + +func (l *Language) ParseImports(file *language.FileInfo) (*language.ImportsResult, error) { + var result language.ImportsResult + + for _, statement := range file.Content.([]Statement) { + if statement.Import != nil { + fmt.Println(filepath.Join(filepath.Dir(file.AbsPath), statement.Import.From)) + result.Imports = append(result.Imports, language.ImportEntry{ + AbsPath: filepath.Join(filepath.Dir(file.AbsPath), statement.Import.From), + }) + } + } + + return &result, nil +} + +func (l *Language) ParseExports(file *language.FileInfo) (*language.ExportsResult, error) { + var result language.ExportsResult + + for _, statement := range file.Content.([]Statement) { + if statement.Export != nil { + result.Exports = append(result.Exports, language.ExportEntry{ + AbsPath: filepath.Join(filepath.Dir(file.AbsPath), statement.Export.From), + }) + } + } + + return &result, nil +} diff --git a/internal/dart/parser.go b/internal/dart/parser.go new file mode 100644 index 0000000..60f916c --- /dev/null +++ b/internal/dart/parser.go @@ -0,0 +1,59 @@ +package dart + +import ( + "bufio" + "os" + "regexp" + "strings" +) + +var packageRegex = regexp.MustCompile(`package:[^/]+\/`) +var importRegex = regexp.MustCompile(`import\s+(['"])(.*?\.dart)`) +var exportRegex = regexp.MustCompile(`export\s+(['"])(.*?\.dart)`) + +type ImportStatement struct { + From string +} + +type ExportStatement struct { + From string +} + +type Statement struct { + Import *ImportStatement + Export *ExportStatement +} + +type File struct { + Statements []Statement +} + +func ParseFile(path string) (*File, error) { + file, err := os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + + var fileData File + scanner := bufio.NewScanner(file) + for scanner.Scan() { + // Remove package patterns from the line + line := packageRegex.ReplaceAllString(scanner.Text(), "") + line = strings.TrimSpace(line) + + if importMatch := importRegex.FindStringSubmatch(line); importMatch != nil { + fileData.Statements = append(fileData.Statements, Statement{ + Import: &ImportStatement{From: importMatch[2]}, + }) + } else if exportMatch := exportRegex.FindStringSubmatch(line); exportMatch != nil { + fileData.Statements = append(fileData.Statements, Statement{ + Export: &ExportStatement{From: exportMatch[2]}, + }) + } + } + if err := scanner.Err(); err != nil { + return nil, err + } + return &fileData, nil +} From 387f80eb055e4d5a57fe3d5442171254d7d7b37e Mon Sep 17 00:00:00 2001 From: Luke Aguilar Date: Mon, 22 Apr 2024 09:56:35 +1000 Subject: [PATCH 2/4] Fixed relative vs absolute imports --- internal/dart/language.go | 15 ++++++++++++--- internal/dart/parser.go | 24 ++++++++++++++++++++---- internal/dart/resolve.go | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 7 deletions(-) create mode 100644 internal/dart/resolve.go diff --git a/internal/dart/language.go b/internal/dart/language.go index 7627468..5568d4c 100644 --- a/internal/dart/language.go +++ b/internal/dart/language.go @@ -2,7 +2,6 @@ package dart import ( "bytes" - "fmt" "os" "path/filepath" @@ -38,9 +37,19 @@ func (l *Language) ParseImports(file *language.FileInfo) (*language.ImportsResul for _, statement := range file.Content.([]Statement) { if statement.Import != nil { - fmt.Println(filepath.Join(filepath.Dir(file.AbsPath), statement.Import.From)) + var importPath string + + if statement.Import.IsAbsolute { + // Code files must always be in the /lib directory. + importPath = filepath.Join(findClosestDartRootDir(file.AbsPath), "lib", statement.Import.From) + } else { + // Relative imports are relative to the current file. + importPath = filepath.Join(filepath.Dir(file.AbsPath), statement.Import.From) + } + + // fmt.Println(importPath) result.Imports = append(result.Imports, language.ImportEntry{ - AbsPath: filepath.Join(filepath.Dir(file.AbsPath), statement.Import.From), + AbsPath: importPath, }) } } diff --git a/internal/dart/parser.go b/internal/dart/parser.go index 60f916c..5539190 100644 --- a/internal/dart/parser.go +++ b/internal/dart/parser.go @@ -12,7 +12,8 @@ var importRegex = regexp.MustCompile(`import\s+(['"])(.*?\.dart)`) var exportRegex = regexp.MustCompile(`export\s+(['"])(.*?\.dart)`) type ImportStatement struct { - From string + From string + IsAbsolute bool } type ExportStatement struct { @@ -38,13 +39,28 @@ func ParseFile(path string) (*File, error) { var fileData File scanner := bufio.NewScanner(file) for scanner.Scan() { - // Remove package patterns from the line - line := packageRegex.ReplaceAllString(scanner.Text(), "") + line := scanner.Text() + + // Remove comments + if idx := strings.Index(line, "//"); idx != -1 { + line = line[:idx] + } + line = strings.TrimSpace(line) + // Remove package patterns from the line and determine if the import is absolute + originalLine := line // Keep the original line to check for package later + line = packageRegex.ReplaceAllString(line, "") + + // Check if the package pattern was matched to set IsAbsolute + isAbsolute := line != originalLine + if importMatch := importRegex.FindStringSubmatch(line); importMatch != nil { fileData.Statements = append(fileData.Statements, Statement{ - Import: &ImportStatement{From: importMatch[2]}, + Import: &ImportStatement{ + From: importMatch[2], + IsAbsolute: isAbsolute, + }, }) } else if exportMatch := exportRegex.FindStringSubmatch(line); exportMatch != nil { fileData.Statements = append(fileData.Statements, Statement{ diff --git a/internal/dart/resolve.go b/internal/dart/resolve.go new file mode 100644 index 0000000..b62ae60 --- /dev/null +++ b/internal/dart/resolve.go @@ -0,0 +1,39 @@ +package dart + +import ( + "os" + "path/filepath" + "sync" +) + +// rootDir stores the found root directory to avoid repeated filesystem checks. +var rootDir string +var lock sync.Once + +// findClosestDartRootDir finds the closest directory from the given path that contains a Dart project root indicator file. +// It caches the result after the first filesystem scan and reuses it for subsequent calls. +func findClosestDartRootDir(path string) string { + lock.Do(func() { + setRootDir(path) + }) + return rootDir +} + +// setRootDir performs the filesystem traversal to locate the root directory. +func setRootDir(path string) { + var rootIndicatorFiles = []string{"pubspec.yaml", "pubspec.yml"} + currentPath := path + for { + for _, file := range rootIndicatorFiles { + if _, err := os.Stat(filepath.Join(currentPath, file)); err == nil { + rootDir = currentPath + return + } + } + parentDir := filepath.Dir(currentPath) + if parentDir == currentPath { + panic("no Dart project root found. Make sure there is a pubspec.yaml or pubspec.yml in the project root.") + } + currentPath = parentDir + } +} From b6db44a3fc93659bb5307c3f0055b06a39cce340 Mon Sep 17 00:00:00 2001 From: Luke Aguilar Date: Tue, 23 Apr 2024 11:39:52 +1000 Subject: [PATCH 3/4] Treat exports as imports --- internal/dart/language.go | 13 ++++++++++++- internal/dart/parser.go | 12 ++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/internal/dart/language.go b/internal/dart/language.go index 5568d4c..7a07d80 100644 --- a/internal/dart/language.go +++ b/internal/dart/language.go @@ -62,8 +62,19 @@ func (l *Language) ParseExports(file *language.FileInfo) (*language.ExportsResul for _, statement := range file.Content.([]Statement) { if statement.Export != nil { + var exportPath string + + if statement.Export.IsAbsolute { + // Code files must always be in the /lib directory. + exportPath = filepath.Join(findClosestDartRootDir(file.AbsPath), "lib", statement.Export.From) + } else { + // Relative imports are relative to the current file. + exportPath = filepath.Join(filepath.Dir(file.AbsPath), statement.Export.From) + } + + // fmt.Println(exportPath) result.Exports = append(result.Exports, language.ExportEntry{ - AbsPath: filepath.Join(filepath.Dir(file.AbsPath), statement.Export.From), + AbsPath: exportPath, }) } } diff --git a/internal/dart/parser.go b/internal/dart/parser.go index 5539190..56cbf37 100644 --- a/internal/dart/parser.go +++ b/internal/dart/parser.go @@ -17,7 +17,8 @@ type ImportStatement struct { } type ExportStatement struct { - From string + From string + IsAbsolute bool } type Statement struct { @@ -64,7 +65,14 @@ func ParseFile(path string) (*File, error) { }) } else if exportMatch := exportRegex.FindStringSubmatch(line); exportMatch != nil { fileData.Statements = append(fileData.Statements, Statement{ - Export: &ExportStatement{From: exportMatch[2]}, + Import: &ImportStatement{ // Treat exports like imports! + From: exportMatch[2], + IsAbsolute: isAbsolute, + }, + Export: &ExportStatement{ + From: exportMatch[2], + IsAbsolute: isAbsolute, + }, }) } } From a5ba276da1a3e135155577b3e26656fbfb1a5d65 Mon Sep 17 00:00:00 2001 From: Luke Aguilar Date: Fri, 29 Nov 2024 10:53:19 +1100 Subject: [PATCH 4/4] Minor fix --- internal/dart/language.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/internal/dart/language.go b/internal/dart/language.go index 7a07d80..ed6b8b4 100644 --- a/internal/dart/language.go +++ b/internal/dart/language.go @@ -47,10 +47,7 @@ func (l *Language) ParseImports(file *language.FileInfo) (*language.ImportsResul importPath = filepath.Join(filepath.Dir(file.AbsPath), statement.Import.From) } - // fmt.Println(importPath) - result.Imports = append(result.Imports, language.ImportEntry{ - AbsPath: importPath, - }) + result.Imports = append(result.Imports, language.EmptyImport(importPath)) } } @@ -72,7 +69,6 @@ func (l *Language) ParseExports(file *language.FileInfo) (*language.ExportsResul exportPath = filepath.Join(filepath.Dir(file.AbsPath), statement.Export.From) } - // fmt.Println(exportPath) result.Exports = append(result.Exports, language.ExportEntry{ AbsPath: exportPath, })