382 lines
12 KiB
Go
382 lines
12 KiB
Go
package tsoptions
|
|
|
|
import (
|
|
"fmt"
|
|
"iter"
|
|
"slices"
|
|
"strings"
|
|
"sync"
|
|
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/glob"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/module"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/outputpaths"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/vfs"
|
|
)
|
|
|
|
const (
|
|
fileGlobPattern = "*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,json}"
|
|
recursiveFileGlobPattern = "**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,json}"
|
|
)
|
|
|
|
type ParsedCommandLine struct {
|
|
ParsedConfig *core.ParsedOptions `json:"parsedConfig"`
|
|
|
|
ConfigFile *TsConfigSourceFile `json:"configFile"` // TsConfigSourceFile, used in Program and ExecuteCommandLine
|
|
Errors []*ast.Diagnostic `json:"errors"`
|
|
Raw any `json:"raw"`
|
|
CompileOnSave *bool `json:"compileOnSave"`
|
|
|
|
comparePathsOptions tspath.ComparePathsOptions
|
|
wildcardDirectoriesOnce sync.Once
|
|
wildcardDirectories map[string]bool
|
|
includeGlobsOnce sync.Once
|
|
includeGlobs []*glob.Glob
|
|
extraFileExtensions []FileExtensionInfo
|
|
|
|
sourceAndOutputMapsOnce sync.Once
|
|
sourceToProjectReference map[tspath.Path]*SourceOutputAndProjectReference
|
|
outputDtsToProjectReference map[tspath.Path]*SourceOutputAndProjectReference
|
|
|
|
commonSourceDirectory string
|
|
commonSourceDirectoryOnce sync.Once
|
|
|
|
resolvedProjectReferencePaths []string
|
|
resolvedProjectReferencePathsOnce sync.Once
|
|
|
|
literalFileNamesLen int
|
|
fileNamesByPath map[tspath.Path]string // maps file names to their paths, used for quick lookups
|
|
fileNamesByPathOnce sync.Once
|
|
}
|
|
|
|
func NewParsedCommandLine(
|
|
compilerOptions *core.CompilerOptions,
|
|
rootFileNames []string,
|
|
comparePathsOptions tspath.ComparePathsOptions,
|
|
) *ParsedCommandLine {
|
|
return &ParsedCommandLine{
|
|
ParsedConfig: &core.ParsedOptions{
|
|
CompilerOptions: compilerOptions,
|
|
FileNames: rootFileNames,
|
|
},
|
|
comparePathsOptions: comparePathsOptions,
|
|
}
|
|
}
|
|
|
|
type SourceOutputAndProjectReference struct {
|
|
Source string
|
|
OutputDts string
|
|
Resolved *ParsedCommandLine
|
|
}
|
|
|
|
var (
|
|
_ module.ResolvedProjectReference = (*ParsedCommandLine)(nil)
|
|
_ outputpaths.OutputPathsHost = (*ParsedCommandLine)(nil)
|
|
)
|
|
|
|
func (p *ParsedCommandLine) ConfigName() string {
|
|
if p == nil {
|
|
return ""
|
|
}
|
|
return p.ConfigFile.SourceFile.FileName()
|
|
}
|
|
|
|
func (p *ParsedCommandLine) SourceToProjectReference() map[tspath.Path]*SourceOutputAndProjectReference {
|
|
return p.sourceToProjectReference
|
|
}
|
|
|
|
func (p *ParsedCommandLine) OutputDtsToProjectReference() map[tspath.Path]*SourceOutputAndProjectReference {
|
|
return p.outputDtsToProjectReference
|
|
}
|
|
|
|
func (p *ParsedCommandLine) ParseInputOutputNames() {
|
|
p.sourceAndOutputMapsOnce.Do(func() {
|
|
sourceToOutput := map[tspath.Path]*SourceOutputAndProjectReference{}
|
|
outputDtsToSource := map[tspath.Path]*SourceOutputAndProjectReference{}
|
|
|
|
for outputDts, source := range p.GetOutputDeclarationAndSourceFileNames() {
|
|
path := tspath.ToPath(source, p.GetCurrentDirectory(), p.UseCaseSensitiveFileNames())
|
|
projectReference := &SourceOutputAndProjectReference{
|
|
Source: source,
|
|
OutputDts: outputDts,
|
|
Resolved: p,
|
|
}
|
|
if outputDts != "" {
|
|
outputDtsToSource[tspath.ToPath(outputDts, p.GetCurrentDirectory(), p.UseCaseSensitiveFileNames())] = projectReference
|
|
}
|
|
sourceToOutput[path] = projectReference
|
|
}
|
|
p.outputDtsToProjectReference = outputDtsToSource
|
|
p.sourceToProjectReference = sourceToOutput
|
|
})
|
|
}
|
|
|
|
func (p *ParsedCommandLine) CommonSourceDirectory() string {
|
|
p.commonSourceDirectoryOnce.Do(func() {
|
|
p.commonSourceDirectory = outputpaths.GetCommonSourceDirectory(
|
|
p.ParsedConfig.CompilerOptions,
|
|
func() []string {
|
|
return core.Filter(
|
|
p.ParsedConfig.FileNames,
|
|
func(file string) bool {
|
|
return !(p.ParsedConfig.CompilerOptions.NoEmitForJsFiles.IsTrue() && tspath.HasJSFileExtension(file)) &&
|
|
!tspath.IsDeclarationFileName(file)
|
|
})
|
|
},
|
|
p.GetCurrentDirectory(),
|
|
p.UseCaseSensitiveFileNames(),
|
|
)
|
|
})
|
|
return p.commonSourceDirectory
|
|
}
|
|
|
|
func (p *ParsedCommandLine) GetCurrentDirectory() string {
|
|
return p.comparePathsOptions.CurrentDirectory
|
|
}
|
|
|
|
func (p *ParsedCommandLine) UseCaseSensitiveFileNames() bool {
|
|
return p.comparePathsOptions.UseCaseSensitiveFileNames
|
|
}
|
|
|
|
func (p *ParsedCommandLine) GetOutputDeclarationAndSourceFileNames() iter.Seq2[string, string] {
|
|
return func(yield func(dtsName string, inputName string) bool) {
|
|
for _, fileName := range p.ParsedConfig.FileNames {
|
|
var outputDts string
|
|
if !tspath.IsDeclarationFileName(fileName) && !tspath.FileExtensionIs(fileName, tspath.ExtensionJson) {
|
|
outputDts = outputpaths.GetOutputDeclarationFileNameWorker(fileName, p.CompilerOptions(), p)
|
|
}
|
|
if !yield(outputDts, fileName) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *ParsedCommandLine) GetOutputFileNames() iter.Seq[string] {
|
|
return func(yield func(outputName string) bool) {
|
|
for _, fileName := range p.ParsedConfig.FileNames {
|
|
if tspath.IsDeclarationFileName(fileName) {
|
|
continue
|
|
}
|
|
jsFileName := outputpaths.GetOutputJSFileName(fileName, p.CompilerOptions(), p)
|
|
isJson := tspath.FileExtensionIs(fileName, tspath.ExtensionJson)
|
|
if jsFileName != "" {
|
|
if !yield(jsFileName) {
|
|
return
|
|
}
|
|
if !isJson {
|
|
sourceMap := outputpaths.GetSourceMapFilePath(jsFileName, p.CompilerOptions())
|
|
if sourceMap != "" {
|
|
if !yield(sourceMap) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if isJson {
|
|
continue
|
|
}
|
|
if p.CompilerOptions().GetEmitDeclarations() {
|
|
dtsFileName := outputpaths.GetOutputDeclarationFileNameWorker(fileName, p.CompilerOptions(), p)
|
|
if dtsFileName != "" {
|
|
if !yield(dtsFileName) {
|
|
return
|
|
}
|
|
if p.CompilerOptions().GetAreDeclarationMapsEnabled() {
|
|
declarationMap := dtsFileName + ".map"
|
|
if !yield(declarationMap) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *ParsedCommandLine) GetBuildInfoFileName() string {
|
|
return outputpaths.GetBuildInfoFileName(p.CompilerOptions(), p.comparePathsOptions)
|
|
}
|
|
|
|
// WildcardDirectories returns the cached wildcard directories, initializing them if needed
|
|
func (p *ParsedCommandLine) WildcardDirectories() map[string]bool {
|
|
if p == nil {
|
|
return nil
|
|
}
|
|
|
|
p.wildcardDirectoriesOnce.Do(func() {
|
|
if p.wildcardDirectories == nil {
|
|
p.wildcardDirectories = getWildcardDirectories(
|
|
p.ConfigFile.configFileSpecs.validatedIncludeSpecs,
|
|
p.ConfigFile.configFileSpecs.validatedExcludeSpecs,
|
|
p.comparePathsOptions,
|
|
)
|
|
}
|
|
})
|
|
|
|
return p.wildcardDirectories
|
|
}
|
|
|
|
func (p *ParsedCommandLine) WildcardDirectoryGlobs() []*glob.Glob {
|
|
wildcardDirectories := p.WildcardDirectories()
|
|
if wildcardDirectories == nil {
|
|
return nil
|
|
}
|
|
|
|
p.includeGlobsOnce.Do(func() {
|
|
if p.includeGlobs == nil {
|
|
globs := make([]*glob.Glob, 0, len(wildcardDirectories))
|
|
for dir, recursive := range wildcardDirectories {
|
|
if parsed, err := glob.Parse(fmt.Sprintf("%s/%s", tspath.NormalizePath(dir), core.IfElse(recursive, recursiveFileGlobPattern, fileGlobPattern))); err == nil {
|
|
globs = append(globs, parsed)
|
|
}
|
|
}
|
|
p.includeGlobs = globs
|
|
}
|
|
})
|
|
|
|
return p.includeGlobs
|
|
}
|
|
|
|
// Normalized file names explicitly specified in `files`
|
|
func (p *ParsedCommandLine) LiteralFileNames() []string {
|
|
if p != nil && p.ConfigFile != nil {
|
|
return p.FileNames()[0:p.literalFileNamesLen]
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *ParsedCommandLine) SetParsedOptions(o *core.ParsedOptions) {
|
|
p.ParsedConfig = o
|
|
}
|
|
|
|
func (p *ParsedCommandLine) SetCompilerOptions(o *core.CompilerOptions) {
|
|
p.ParsedConfig.CompilerOptions = o
|
|
}
|
|
|
|
func (p *ParsedCommandLine) CompilerOptions() *core.CompilerOptions {
|
|
if p == nil {
|
|
return nil
|
|
}
|
|
return p.ParsedConfig.CompilerOptions
|
|
}
|
|
|
|
func (p *ParsedCommandLine) SetTypeAcquisition(o *core.TypeAcquisition) {
|
|
p.ParsedConfig.TypeAcquisition = o
|
|
}
|
|
|
|
func (p *ParsedCommandLine) TypeAcquisition() *core.TypeAcquisition {
|
|
return p.ParsedConfig.TypeAcquisition
|
|
}
|
|
|
|
// All file names matched by files, include, and exclude patterns
|
|
func (p *ParsedCommandLine) FileNames() []string {
|
|
return p.ParsedConfig.FileNames
|
|
}
|
|
|
|
func (p *ParsedCommandLine) FileNamesByPath() map[tspath.Path]string {
|
|
p.fileNamesByPathOnce.Do(func() {
|
|
p.fileNamesByPath = make(map[tspath.Path]string, len(p.ParsedConfig.FileNames))
|
|
for _, fileName := range p.ParsedConfig.FileNames {
|
|
path := tspath.ToPath(fileName, p.GetCurrentDirectory(), p.UseCaseSensitiveFileNames())
|
|
p.fileNamesByPath[path] = fileName
|
|
}
|
|
})
|
|
return p.fileNamesByPath
|
|
}
|
|
|
|
func (p *ParsedCommandLine) ProjectReferences() []*core.ProjectReference {
|
|
return p.ParsedConfig.ProjectReferences
|
|
}
|
|
|
|
func (p *ParsedCommandLine) ResolvedProjectReferencePaths() []string {
|
|
p.resolvedProjectReferencePathsOnce.Do(func() {
|
|
p.resolvedProjectReferencePaths = core.Map(p.ParsedConfig.ProjectReferences, core.ResolveProjectReferencePath)
|
|
})
|
|
return p.resolvedProjectReferencePaths
|
|
}
|
|
|
|
func (p *ParsedCommandLine) ExtendedSourceFiles() []string {
|
|
if p == nil || p.ConfigFile == nil {
|
|
return nil
|
|
}
|
|
return p.ConfigFile.ExtendedSourceFiles
|
|
}
|
|
|
|
func (p *ParsedCommandLine) GetConfigFileParsingDiagnostics() []*ast.Diagnostic {
|
|
if p.ConfigFile != nil {
|
|
// todo: !!! should be ConfigFile.ParseDiagnostics, check if they are the same
|
|
return slices.Concat(p.ConfigFile.SourceFile.Diagnostics(), p.Errors)
|
|
}
|
|
return p.Errors
|
|
}
|
|
|
|
// PossiblyMatchesFileName is a fast check to see if a file is currently included by a config
|
|
// or would be included if the file were to be created. It may return false positives.
|
|
func (p *ParsedCommandLine) PossiblyMatchesFileName(fileName string) bool {
|
|
path := tspath.ToPath(fileName, p.GetCurrentDirectory(), p.UseCaseSensitiveFileNames())
|
|
if _, ok := p.FileNamesByPath()[path]; ok {
|
|
return true
|
|
}
|
|
|
|
for _, include := range p.ConfigFile.configFileSpecs.validatedIncludeSpecs {
|
|
if !strings.ContainsAny(include, "*?") && !vfs.IsImplicitGlob(include) {
|
|
includePath := tspath.ToPath(include, p.GetCurrentDirectory(), p.UseCaseSensitiveFileNames())
|
|
if includePath == path {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
if wildcardDirectoryGlobs := p.WildcardDirectoryGlobs(); len(wildcardDirectoryGlobs) > 0 {
|
|
for _, glob := range wildcardDirectoryGlobs {
|
|
if glob.Match(fileName) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (p *ParsedCommandLine) GetMatchedFileSpec(fileName string) string {
|
|
return p.ConfigFile.configFileSpecs.getMatchedFileSpec(fileName, p.comparePathsOptions)
|
|
}
|
|
|
|
func (p *ParsedCommandLine) GetMatchedIncludeSpec(fileName string) (string, bool) {
|
|
if len(p.ConfigFile.configFileSpecs.validatedIncludeSpecs) == 0 {
|
|
return "", false
|
|
}
|
|
|
|
if p.ConfigFile.configFileSpecs.isDefaultIncludeSpec {
|
|
return p.ConfigFile.configFileSpecs.validatedIncludeSpecs[0], true
|
|
}
|
|
|
|
return p.ConfigFile.configFileSpecs.getMatchedIncludeSpec(fileName, p.comparePathsOptions), false
|
|
}
|
|
|
|
func (p *ParsedCommandLine) ReloadFileNamesOfParsedCommandLine(fs vfs.FS) *ParsedCommandLine {
|
|
parsedConfig := *p.ParsedConfig
|
|
fileNames, literalFileNamesLen := getFileNamesFromConfigSpecs(
|
|
*p.ConfigFile.configFileSpecs,
|
|
p.GetCurrentDirectory(),
|
|
p.CompilerOptions(),
|
|
fs,
|
|
p.extraFileExtensions,
|
|
)
|
|
parsedConfig.FileNames = fileNames
|
|
parsedCommandLine := ParsedCommandLine{
|
|
ParsedConfig: &parsedConfig,
|
|
ConfigFile: p.ConfigFile,
|
|
Errors: p.Errors,
|
|
Raw: p.Raw,
|
|
CompileOnSave: p.CompileOnSave,
|
|
comparePathsOptions: p.comparePathsOptions,
|
|
wildcardDirectories: p.wildcardDirectories,
|
|
includeGlobs: p.includeGlobs,
|
|
extraFileExtensions: p.extraFileExtensions,
|
|
literalFileNamesLen: literalFileNamesLen,
|
|
}
|
|
return &parsedCommandLine
|
|
}
|