234 lines
6.0 KiB
Go
234 lines
6.0 KiB
Go
package parser_test
|
|
|
|
import (
|
|
"io/fs"
|
|
"iter"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/microsoft/typescript-go/internal/ast"
|
|
"github.com/microsoft/typescript-go/internal/collections"
|
|
"github.com/microsoft/typescript-go/internal/core"
|
|
"github.com/microsoft/typescript-go/internal/parser"
|
|
"github.com/microsoft/typescript-go/internal/repo"
|
|
"github.com/microsoft/typescript-go/internal/testrunner"
|
|
"github.com/microsoft/typescript-go/internal/testutil/fixtures"
|
|
"github.com/microsoft/typescript-go/internal/tspath"
|
|
"github.com/microsoft/typescript-go/internal/vfs/osvfs"
|
|
"gotest.tools/v3/assert"
|
|
)
|
|
|
|
func BenchmarkParse(b *testing.B) {
|
|
for _, f := range fixtures.BenchFixtures {
|
|
b.Run(f.Name(), func(b *testing.B) {
|
|
f.SkipIfNotExist(b)
|
|
|
|
fileName := tspath.GetNormalizedAbsolutePath(f.Path(), "/")
|
|
path := tspath.ToPath(fileName, "/", osvfs.FS().UseCaseSensitiveFileNames())
|
|
sourceText := f.ReadFile(b)
|
|
scriptKind := core.GetScriptKindFromFileName(fileName)
|
|
|
|
opts := ast.SourceFileParseOptions{
|
|
FileName: fileName,
|
|
Path: path,
|
|
}
|
|
|
|
for b.Loop() {
|
|
parser.ParseSourceFile(opts, sourceText, scriptKind)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
type parsableFile struct {
|
|
path string
|
|
name string
|
|
}
|
|
|
|
func allParsableFiles(tb testing.TB, root string) iter.Seq[parsableFile] {
|
|
tb.Helper()
|
|
return func(yield func(parsableFile) bool) {
|
|
tb.Helper()
|
|
err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if d.IsDir() || tspath.TryGetExtensionFromPath(path) == "" {
|
|
return nil
|
|
}
|
|
|
|
testName, err := filepath.Rel(root, path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
testName = filepath.ToSlash(testName)
|
|
|
|
if !yield(parsableFile{path, testName}) {
|
|
return filepath.SkipAll
|
|
}
|
|
return nil
|
|
})
|
|
assert.NilError(tb, err)
|
|
}
|
|
}
|
|
|
|
func FuzzParser(f *testing.F) {
|
|
repo.SkipIfNoTypeScriptSubmodule(f)
|
|
|
|
tests := []string{
|
|
"src",
|
|
"scripts",
|
|
"Herebyfile.mjs",
|
|
}
|
|
|
|
var extensions collections.Set[string]
|
|
for _, es := range tspath.AllSupportedExtensionsWithJson {
|
|
for _, e := range es {
|
|
extensions.Add(e)
|
|
}
|
|
}
|
|
|
|
for _, test := range tests {
|
|
root := filepath.Join(repo.TypeScriptSubmodulePath(), test)
|
|
|
|
for file := range allParsableFiles(f, root) {
|
|
sourceText, err := os.ReadFile(file.path)
|
|
assert.NilError(f, err)
|
|
extension := tspath.TryGetExtensionFromPath(file.path)
|
|
f.Add(extension, string(sourceText), false, false)
|
|
}
|
|
}
|
|
|
|
testDirs := []string{
|
|
filepath.Join(repo.TypeScriptSubmodulePath(), "tests/cases/compiler"),
|
|
filepath.Join(repo.TypeScriptSubmodulePath(), "tests/cases/conformance"),
|
|
filepath.Join(repo.TestDataPath(), "tests/cases/compiler"),
|
|
}
|
|
|
|
for _, testDir := range testDirs {
|
|
if _, err := os.Stat(testDir); os.IsNotExist(err) {
|
|
continue
|
|
}
|
|
|
|
for file := range allParsableFiles(f, testDir) {
|
|
sourceText, err := os.ReadFile(file.path)
|
|
assert.NilError(f, err)
|
|
|
|
type testFile struct {
|
|
content string
|
|
name string
|
|
}
|
|
|
|
testUnits, _, _, _, err := testrunner.ParseTestFilesAndSymlinks(
|
|
string(sourceText),
|
|
file.path,
|
|
func(filename string, content string, fileOptions map[string]string) (testFile, error) {
|
|
return testFile{content: content, name: filename}, nil
|
|
},
|
|
)
|
|
assert.NilError(f, err)
|
|
|
|
for _, unit := range testUnits {
|
|
extension := tspath.TryGetExtensionFromPath(unit.name)
|
|
if extension == "" {
|
|
continue
|
|
}
|
|
f.Add(extension, unit.content, false, false)
|
|
}
|
|
}
|
|
}
|
|
|
|
f.Fuzz(func(t *testing.T, extension string, sourceText string, externalModuleIndicatorOptionsJSX bool, externalModuleIndicatorOptionsForce bool) {
|
|
if !extensions.Has(extension) {
|
|
t.Skip()
|
|
}
|
|
|
|
fileName := "/index" + extension
|
|
path := tspath.Path(fileName)
|
|
|
|
opts := ast.SourceFileParseOptions{
|
|
FileName: fileName,
|
|
Path: path,
|
|
ExternalModuleIndicatorOptions: ast.ExternalModuleIndicatorOptions{
|
|
JSX: externalModuleIndicatorOptionsJSX,
|
|
Force: externalModuleIndicatorOptionsForce,
|
|
},
|
|
}
|
|
|
|
parser.ParseSourceFile(opts, sourceText, core.GetScriptKindFromFileName(fileName))
|
|
})
|
|
}
|
|
|
|
func TestJSDocImportTypeParentChain(t *testing.T) {
|
|
t.Parallel()
|
|
sourceText := `test("", async function () {
|
|
;(/** @type {typeof import("a")} */ ({}))
|
|
})
|
|
|
|
test("", async function () {
|
|
;(/** @type {typeof import("a")} */ a)
|
|
})
|
|
|
|
test("", async function () {
|
|
(/** @type {typeof import("a")} */ ({}))
|
|
;(/** @type {typeof import("a")} */ ({}))
|
|
})
|
|
|
|
test("", async function () {
|
|
(/** @type {typeof import("a")} */ a)
|
|
;(/** @type {typeof import("a")} */ a)
|
|
})
|
|
|
|
test("", async function () {
|
|
(/** @type {typeof import("a")} */ ({}))
|
|
;(/** @type {typeof import("a")} */ ({}))
|
|
})
|
|
`
|
|
opts := ast.SourceFileParseOptions{
|
|
FileName: "/index.js",
|
|
Path: "/index.js",
|
|
}
|
|
|
|
file := parser.ParseSourceFile(opts, sourceText, core.ScriptKindJS)
|
|
|
|
for i := 1; i < len(file.ReparsedClones); i++ {
|
|
a, b := file.ReparsedClones[i-1], file.ReparsedClones[i]
|
|
if a.Pos() == b.Pos() && a.End() == b.End() && a.Kind == b.Kind {
|
|
t.Errorf("duplicate ReparsedClones at [%d] and [%d]: %s pos=%d end=%d", i-1, i, a.Kind.String(), a.Pos(), a.End())
|
|
}
|
|
}
|
|
|
|
for _, imp := range file.Imports() {
|
|
reparsed := ast.GetReparsedNodeForNode(imp)
|
|
if ast.GetSourceFileOfNode(reparsed) == nil {
|
|
t.Errorf("reparsed import at pos=%d has broken parent chain", imp.Pos())
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSourceFileContainsNonASCIIInStringLiteralFastPath(t *testing.T) {
|
|
t.Parallel()
|
|
sourceText := `const x = "─";
|
|
|
|
namespace N {
|
|
export const y = x;
|
|
}
|
|
`
|
|
opts := ast.SourceFileParseOptions{
|
|
FileName: "/index.ts",
|
|
Path: "/index.ts",
|
|
}
|
|
|
|
file := parser.ParseSourceFile(opts, sourceText, core.ScriptKindTS)
|
|
|
|
assert.Assert(t, file.ContainsNonASCII)
|
|
positionMap := file.GetPositionMap()
|
|
assert.Assert(t, !positionMap.IsAsciiOnly())
|
|
afterBoxDrawingCharacter := strings.Index(sourceText, "─") + len("─")
|
|
assert.Equal(t, positionMap.UTF8ToUTF16(afterBoxDrawingCharacter), afterBoxDrawingCharacter-2)
|
|
assert.Equal(t, positionMap.UTF8ToUTF16(len(sourceText)), len(sourceText)-2)
|
|
}
|