TypeScript ts.sys.readDirectory 到底应该做什么?

nhn9ugyo  于 5个月前  发布在  TypeScript
关注(0)|答案(3)|浏览(60)

根据#46673(评论),我开始对ts.matchFiles(只是没有系统主机的ts.sys.readDirectory)进行了更彻底的调查,并发现即使在4.4之前的长期行为也令人困惑,可能是未设计的。考虑以下文件系统:

projects/
├─ a/
│  ├─ subfolder/
│  │  └─ 1.ts
│  └─ 2.ts
└─ b/
   └─ 3.ts

如果你做三件事,事情往往会按预期工作:(1)使用相对路径,(2)读取与CWD相同的目录,(3)不使用任何访问具有"../"的兄弟文件夹的includes。如果你开始这样做,尤其是结合在一起,行为就有点不可预测了。
让我们从一个我认为事情按预期进行的简单示例开始:

> process.cwd()
'/projects/a'

> ts.sys.readDirectory(".", [".ts"], /*excludes*/ undefined, /*includes*/ ["**/*"])
[
  '2.ts',
  'subfolder/1.ts'
]

请注意,条目相对于我们的CWD /我们正在读取的目录(哪个?我们还不知道,因为它们是相同的),但没有前缀./。这可能是为了故意这样,因为它与fs.readdir的格式相匹配。此外,用空字符串替换第一个参数也会得到相同的结果。
好的,让我们看看如果我们在我们正在读取的目录之外执行某些操作会发生什么:

> process.cwd()
'/projects/a'

> ts.sys.readDirectory(".", [".ts"], /*excludes*/ undefined, /*includes*/ ["**/*", "../b/**/*"])
[
  '2.ts',
  'subfolder/1.ts'
]

没有改变。这似乎有道理,因为名字是readDirectory。现在让我们找出我们的结果是相对于我们的CWD还是相对于我们正在读取的目录。我们首先将级别提升一级:

> process.cwd()
'/projects'

> ts.sys.readDirectory("a", [".ts"], /*excludes*/ undefined, /*includes*/ ["**/*"])
[
  'a/2.ts',
  'a/subfolder/1.ts'
]

有趣——它们相对于我们的CWD,而不是我们正在读取的目录,这是与fs.readdir的分歧。这是否意味着我们现在可以在我们正在读取的目录之外获取includes?

> ts.sys.readDirectory("a", [".ts"], /*excludes*/ undefined, /*includes*/ ["**/*", "../b/**/*"])
[
  'a/2.ts',
  'a/subfolder/1.ts',
  'b/3.ts'
]

是的! readDirectory可以返回目标目录之外的结果,但不能返回CWD之外的结果。这对我来说似乎很奇怪。让我们回到a并尝试绝对路径:

> process.cwd()
'/projects/a'

> ts.sys.readDirectory("/projects/a", [".ts"], /*excludes*/ undefined, /*includes*/ ["**/*"])
[
  '/projects/a/2.ts',
  '/projects/a/subfolder/1.ts'
]

绝对输入,绝对输出。很可能是一个意外?现在进行最后的测试,一个绝对输入路径,CWD之外有一个includes:

> ts.sys.readDirectory("/projects/a", [".ts"], /*excludes*/ undefined, /*includes*/ ["**/*", "../b/**/*"])
[
  '/projects/a/2.ts',
  '/projects/a/subfolder/1.ts',
  '/projects/b/3.ts'
]
fnx2tebb

fnx2tebb1#

#46673中,@rbuckton说你可能对这个有一些一般性的了解。

dddzy1tm

dddzy1tm2#

不幸的是,matchFiles的不一致性与编译器中的其他路径操作以及Program如何读取文件的方式保持一致。
首先,它假设currentDirectory参数是一个绝对路径。然而,它并没有对此进行验证,因为在我们的测试中有很多地方我们只是返回""作为getCurrentDirectory的结果。
它匹配的每个结果都会使用我们现有的combinePaths函数与path预先组合:

  • combinePaths(".", "2.ts") -> "2.ts"
  • combinePaths("a", "2.ts") -> "a/2.ts"
  • combinePaths("/projects/a", "2.ts") -> "/projects/a/2.ts"

最后,readDirectory最初只是readDirectory(path: string),没有支持globs的功能。后来通过一种旨在与现有API消费者和我们自己的内部用例兼容的方式添加了包含/排除功能。我们没有返回从当前目录派生的绝对路径,因为这个函数的结果被上游函数消耗,而这些函数期望结果以特定的格式出现(参见ParsedCommandLine.fileNames)。
简而言之,matchFiles(和readDirectory)是专门为适应特定用例而设计的。这就是为什么尽管社区提出了要求(#34545, #13793),API仍然没有公开的原因之一。

4uqofj5v

4uqofj5v3#

值得注意的是,我非常惊讶地发现,在我的readDirectory实现中,仅仅为了接近tsc的行为,我不得不做所有这些事情:

readDirectory(directoryName, extensions, excludePaths, includePaths, depth)
{
	return from(new DirectoryStream(directoryName, { recursive: true }))
		.where(it => !it.isDirectory)
		.where(it => extensions.includes(FS.extensionOf(it.fullPath)))
		.where(it => {
			// implicitly exclude node_modules, etc.
			return !from(TS.commonPackageFolders)
				.any(dirName => it.fullPath.includes(`${dirName}/`) && !it.fullPath.includes("@types/"));
		})
		.where(it => !FS.match(it.fullPath, excludePaths))
		.where(it => FS.match(it.fullPath, includePaths))
		.select(it => it.fullPath)
		.toArray();
}

我原以为只需要返回一个简单的目录列表,但似乎readDirectory的责任比它的名字所暗示的要多得多。特别是,不得不手动排除node_modules(但包含@types/!)是非常令人惊讶的,我只是通过尝试和错误以及研究tsc的源代码才弄明白这一点。
编译器API确实需要更好地文档化。我会试着解释一下,但即使是我自己,也不太有信心把所有的细节都弄对,我不想误导别人。

相关问题