Skip to content

Commit 2cf4cb4

Browse files
iisaduanjakebailey
andauthored
execute command line initial implementation (microsoft#241)
Co-authored-by: Jake Bailey <5341706+jakebailey@users.noreply.github.com>
1 parent 31b5d62 commit 2cf4cb4

27 files changed

+2151
-72
lines changed

cmd/tsgo/main.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/microsoft/typescript-go/internal/compiler/diagnostics"
1919
"github.com/microsoft/typescript-go/internal/core"
2020
"github.com/microsoft/typescript-go/internal/diagnosticwriter"
21+
"github.com/microsoft/typescript-go/internal/execute"
2122
"github.com/microsoft/typescript-go/internal/scanner"
2223
"github.com/microsoft/typescript-go/internal/tspath"
2324
"github.com/microsoft/typescript-go/internal/vfs"
@@ -113,6 +114,10 @@ func parseArgs() *cliOptions {
113114
}
114115

115116
func main() {
117+
if args := os.Args[1:]; len(args) > 0 && args[0] == "tsc" {
118+
exitCode := execute.CommandLine(newSystem(), nil, args[1:])
119+
os.Exit(int(exitCode))
120+
}
116121
opts := parseArgs()
117122

118123
if opts.devel.pprofDir != "" {
@@ -177,9 +182,8 @@ func main() {
177182
var bindTime, checkTime time.Duration
178183

179184
diagnostics := program.GetOptionsDiagnostics()
180-
formatOpts := getFormatOpts(host)
181185
if len(diagnostics) != 0 {
182-
printDiagnostics(diagnostics, formatOpts, compilerOptions)
186+
printDiagnostics(diagnostics, host, compilerOptions)
183187
os.Exit(1)
184188
}
185189

@@ -217,7 +221,7 @@ func main() {
217221
runtime.ReadMemStats(&memStats)
218222

219223
if !opts.devel.quiet && len(diagnostics) != 0 {
220-
printDiagnostics(diagnostics, formatOpts, compilerOptions)
224+
printDiagnostics(diagnostics, host, compilerOptions)
221225
}
222226

223227
if compilerOptions.ListFiles.IsTrue() {
@@ -293,7 +297,8 @@ func getFormatOpts(host ts.CompilerHost) *diagnosticwriter.FormattingOptions {
293297
}
294298
}
295299

296-
func printDiagnostics(diagnostics []*ast.Diagnostic, formatOpts *diagnosticwriter.FormattingOptions, compilerOptions *core.CompilerOptions) {
300+
func printDiagnostics(diagnostics []*ast.Diagnostic, host ts.CompilerHost, compilerOptions *core.CompilerOptions) {
301+
formatOpts := getFormatOpts(host)
297302
if compilerOptions.Pretty.IsTrueOrUnknown() {
298303
diagnosticwriter.FormatDiagnosticsWithColorAndContext(os.Stdout, diagnostics, formatOpts)
299304
fmt.Fprintln(os.Stdout)

cmd/tsgo/sys.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"os"
7+
"runtime"
8+
9+
"github.com/microsoft/typescript-go/internal/core"
10+
"github.com/microsoft/typescript-go/internal/execute"
11+
"github.com/microsoft/typescript-go/internal/tspath"
12+
"github.com/microsoft/typescript-go/internal/vfs"
13+
)
14+
15+
type osSys struct {
16+
writer io.Writer
17+
fs vfs.FS
18+
newLine string
19+
cwd string
20+
}
21+
22+
func (s *osSys) FS() vfs.FS {
23+
return s.fs
24+
}
25+
26+
func (s *osSys) GetCurrentDirectory() string {
27+
return s.cwd
28+
}
29+
30+
func (s *osSys) NewLine() string {
31+
return s.newLine
32+
}
33+
34+
func (s *osSys) Writer() io.Writer {
35+
return s.writer
36+
}
37+
38+
func (s *osSys) EndWrite() {
39+
// do nothing, this is needed in the interface for testing
40+
// todo: revisit if improving tsc/build/watch unittest baselines
41+
}
42+
43+
func newSystem() *osSys {
44+
cwd, err := os.Getwd()
45+
if err != nil {
46+
fmt.Fprintf(os.Stderr, "Error getting current directory: %v\n", err)
47+
os.Exit(int(execute.ExitStatusInvalidProject_OutputsSkipped))
48+
}
49+
50+
return &osSys{
51+
cwd: tspath.NormalizePath(cwd),
52+
fs: vfs.FromOS(),
53+
writer: os.Stdout,
54+
newLine: core.IfElse(runtime.GOOS == "windows", "\r\n", "\n"),
55+
}
56+
}

internal/checker/checker.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,7 @@ type WideningContext struct {
502502

503503
type Program interface {
504504
Options() *core.CompilerOptions
505-
Files() []*ast.SourceFile
505+
SourceFiles() []*ast.SourceFile
506506

507507
BindSourceFiles()
508508
GetEmitModuleFormatOfFile(sourceFile *ast.SourceFile) core.ModuleKind
@@ -779,7 +779,7 @@ func NewChecker(program Program) *Checker {
779779
c.program = program
780780
// c.host = program.host
781781
c.compilerOptions = program.Options()
782-
c.files = program.Files()
782+
c.files = program.SourceFiles()
783783
c.fileIndexMap = createFileIndexMap(c.files)
784784
c.compareSymbols = c.compareSymbolsWorker // Closure optimization
785785
c.languageVersion = c.compilerOptions.GetEmitScriptTarget()

internal/compiler/host.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ func (h *compilerHost) FS() vfs.FS {
4242
return h.fs
4343
}
4444

45+
func (h *compilerHost) SetOptions(options *core.CompilerOptions) {
46+
h.options = options
47+
}
48+
4549
func (h *compilerHost) GetCurrentDirectory() string {
4650
return h.currentDirectory
4751
}

internal/compiler/program.go

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/microsoft/typescript-go/internal/ast"
99
"github.com/microsoft/typescript-go/internal/binder"
10+
"github.com/microsoft/typescript-go/internal/bundled"
1011
"github.com/microsoft/typescript-go/internal/checker"
1112
"github.com/microsoft/typescript-go/internal/compiler/diagnostics"
1213
"github.com/microsoft/typescript-go/internal/compiler/module"
@@ -27,26 +28,26 @@ type ProgramOptions struct {
2728
SingleThreaded bool
2829
ProjectReference []core.ProjectReference
2930
DefaultLibraryPath string
31+
OptionsDiagnostics []*ast.Diagnostic
3032
}
3133

3234
type Program struct {
33-
host CompilerHost
34-
programOptions ProgramOptions
35-
compilerOptions *core.CompilerOptions
36-
configFilePath string
37-
nodeModules map[string]*ast.SourceFile
38-
checkers []*checker.Checker
39-
checkersByFile map[*ast.SourceFile]*checker.Checker
40-
currentDirectory string
35+
host CompilerHost
36+
programOptions ProgramOptions
37+
compilerOptions *core.CompilerOptions
38+
configFilePath string
39+
nodeModules map[string]*ast.SourceFile
40+
checkers []*checker.Checker
41+
checkersByFile map[*ast.SourceFile]*checker.Checker
42+
currentDirectory string
43+
optionsDiagnostics []*ast.Diagnostic
4144

4245
resolver *module.Resolver
4346
resolvedModules map[tspath.Path]module.ModeAwareCache[*module.ResolvedModule]
4447

4548
comparePathsOptions tspath.ComparePathsOptions
4649
defaultLibraryPath string
4750

48-
optionsDiagnostics []*ast.Diagnostic
49-
5051
files []*ast.SourceFile
5152
filesByPath map[tspath.Path]*ast.SourceFile
5253

@@ -72,6 +73,7 @@ func NewProgram(options ProgramOptions) *Program {
7273
p := &Program{}
7374
p.programOptions = options
7475
p.compilerOptions = options.Options
76+
p.optionsDiagnostics = options.OptionsDiagnostics
7577
if p.compilerOptions == nil {
7678
p.compilerOptions = &core.CompilerOptions{}
7779
}
@@ -163,17 +165,22 @@ func NewProgram(options ProgramOptions) *Program {
163165
return p
164166
}
165167

166-
func (p *Program) Files() []*ast.SourceFile {
167-
return p.files
168+
func NewProgramFromParsedCommandLine(config *tsoptions.ParsedCommandLine, host CompilerHost) *Program {
169+
programOptions := ProgramOptions{
170+
RootFiles: config.FileNames(),
171+
Options: config.CompilerOptions(),
172+
Host: host,
173+
// todo: ProjectReferences
174+
OptionsDiagnostics: config.GetConfigFileParsingDiagnostics(),
175+
DefaultLibraryPath: bundled.LibPath(),
176+
}
177+
return NewProgram(programOptions)
168178
}
169179

170-
func (p *Program) SourceFiles() []*ast.SourceFile { return p.files }
171-
func (p *Program) Options() *core.CompilerOptions { return p.compilerOptions }
172-
func (p *Program) Host() CompilerHost { return p.host }
173-
174-
func (p *Program) GetOptionsDiagnostics() []*ast.Diagnostic {
175-
return p.optionsDiagnostics
176-
}
180+
func (p *Program) SourceFiles() []*ast.SourceFile { return p.files }
181+
func (p *Program) Options() *core.CompilerOptions { return p.compilerOptions }
182+
func (p *Program) Host() CompilerHost { return p.host }
183+
func (p *Program) OptionsDiagnostics() []*ast.Diagnostic { return p.optionsDiagnostics }
177184

178185
func (p *Program) BindSourceFiles() {
179186
wg := core.NewWorkGroup(p.programOptions.SingleThreaded)
@@ -274,7 +281,19 @@ func (p *Program) GetGlobalDiagnostics() []*ast.Diagnostic {
274281
for _, checker := range p.checkers {
275282
globalDiagnostics = append(globalDiagnostics, checker.GetGlobalDiagnostics()...)
276283
}
277-
return sortAndDeduplicateDiagnostics(globalDiagnostics)
284+
return SortAndDeduplicateDiagnostics(globalDiagnostics)
285+
}
286+
287+
func (p *Program) GetOptionsDiagnostics() []*ast.Diagnostic {
288+
return SortAndDeduplicateDiagnostics(append(p.GetGlobalDiagnostics(), p.getOptionsDiagnosticsOfConfigFile()...))
289+
}
290+
291+
func (p *Program) getOptionsDiagnosticsOfConfigFile() []*ast.Diagnostic {
292+
// todo update p.configParsingDiagnostics when updateAndGetProgramDiagnostics is implemented
293+
if p.Options() == nil || p.Options().ConfigFilePath == "" {
294+
return nil
295+
}
296+
return p.optionsDiagnostics
278297
}
279298

280299
func (p *Program) getSyntaticDiagnosticsForFile(sourceFile *ast.SourceFile) []*ast.Diagnostic {
@@ -337,7 +356,7 @@ func isCommentOrBlankLine(text string, pos int) bool {
337356
pos+1 < len(text) && text[pos] == '/' && text[pos+1] == '/'
338357
}
339358

340-
func sortAndDeduplicateDiagnostics(diagnostics []*ast.Diagnostic) []*ast.Diagnostic {
359+
func SortAndDeduplicateDiagnostics(diagnostics []*ast.Diagnostic) []*ast.Diagnostic {
341360
result := slices.Clone(diagnostics)
342361
slices.SortFunc(result, ast.CompareDiagnostics)
343362
return slices.CompactFunc(result, ast.EqualDiagnostics)
@@ -348,7 +367,7 @@ func (p *Program) getDiagnosticsHelper(sourceFile *ast.SourceFile, ensureBound b
348367
if ensureBound {
349368
binder.BindSourceFile(sourceFile, p.compilerOptions)
350369
}
351-
return sortAndDeduplicateDiagnostics(getDiagnostics(sourceFile))
370+
return SortAndDeduplicateDiagnostics(getDiagnostics(sourceFile))
352371
}
353372
if ensureBound {
354373
p.BindSourceFiles()
@@ -360,7 +379,7 @@ func (p *Program) getDiagnosticsHelper(sourceFile *ast.SourceFile, ensureBound b
360379
for _, file := range p.files {
361380
result = append(result, getDiagnostics(file)...)
362381
}
363-
return sortAndDeduplicateDiagnostics(result)
382+
return SortAndDeduplicateDiagnostics(result)
364383
}
365384

366385
func (p *Program) TypeCount() int {

internal/core/core.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,22 @@ func Filter[T any](slice []T, f func(T) bool) []T {
2727
return slice
2828
}
2929

30+
func FilterIndex[T any](slice []T, f func(T, int, []T) bool) []T {
31+
for i, value := range slice {
32+
if !f(value, i, slice) {
33+
result := slices.Clone(slice[:i])
34+
for i++; i < len(slice); i++ {
35+
value = slice[i]
36+
if f(value, i, slice) {
37+
result = append(result, value)
38+
}
39+
}
40+
return result
41+
}
42+
}
43+
return slice
44+
}
45+
3046
func Map[T, U any](slice []T, f func(T) U) []U {
3147
if len(slice) == 0 {
3248
return nil

internal/diagnosticwriter/diagnosticwriter.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,12 @@ func writeCodeSnippet(writer io.Writer, sourceFile *ast.SourceFile, start int, l
156156
}
157157
}
158158

159+
func FlattenDiagnosticMessage(d *ast.Diagnostic, newLine string) string {
160+
var output strings.Builder
161+
WriteFlattenedDiagnosticMessage(&output, d, newLine)
162+
return output.String()
163+
}
164+
159165
func WriteFlattenedDiagnosticMessage(writer io.Writer, diagnostic *ast.Diagnostic, newline string) {
160166
fmt.Fprint(writer, diagnostic.Message())
161167

@@ -232,14 +238,17 @@ func WriteErrorSummaryText(output io.Writer, allDiagnostics []*ast.Diagnostic, f
232238
return
233239
}
234240

235-
firstFile := errorSummary.SortedFileList[0]
241+
firstFile := &ast.SourceFile{}
242+
if len(errorSummary.SortedFileList) > 0 {
243+
firstFile = errorSummary.SortedFileList[0]
244+
}
236245
firstFileName := prettyPathForFileError(firstFile, errorSummary.ErrorsByFiles[firstFile], formatOpts)
237246
numErroringFiles := len(errorSummary.ErrorsByFiles)
238247

239248
var message string
240249
if totalErrorCount == 1 {
241250
// Special-case a single error.
242-
if len(errorSummary.GlobalErrors) > 0 {
251+
if len(errorSummary.GlobalErrors) > 0 || firstFileName == "" {
243252
message = diagnostics.Found_1_error.Format()
244253
} else {
245254
message = diagnostics.Found_1_error_in_0.Format(firstFileName)
@@ -333,6 +342,9 @@ func writeTabularErrorsDisplay(output io.Writer, errorSummary *ErrorSummary, for
333342
}
334343

335344
func prettyPathForFileError(file *ast.SourceFile, fileErrors []*ast.Diagnostic, formatOpts *FormattingOptions) string {
345+
if file == nil || len(fileErrors) == 0 {
346+
return ""
347+
}
336348
line, _ := scanner.GetLineAndCharacterOfPosition(file, fileErrors[0].Loc().Pos())
337349
fileName := file.FileName()
338350
if tspath.PathIsAbsolute(fileName) && tspath.PathIsAbsolute(formatOpts.CurrentDirectory) {

internal/execute/export_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package execute
2+
3+
import "github.com/microsoft/typescript-go/internal/tsoptions"
4+
5+
func CommandLineTest(sys System, cb cbType, commandLineArgs []string) (*tsoptions.ParsedCommandLine, ExitStatus) {
6+
parsedCommandLine := tsoptions.ParseCommandLine(commandLineArgs, sys)
7+
return parsedCommandLine, executeCommandLineWorker(sys, cb, parsedCommandLine)
8+
}

0 commit comments

Comments
 (0)