Skip to content

What is ts.sys.readDirectory even supposed to do? #46788

Open
@andrewbranch

Description

@andrewbranch

Following up on #46673 (comment), I started a more thorough investigation of ts.matchFiles (which is just ts.sys.readDirectory without the system host) and discovered that even its long-standing behavior prior to 4.4 is confusing and probably undesigned. Consider this file system:

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

Things tend to work as expected if you do three things: (1) use relative paths, (2) read the same directory as your CWD, and (3) don’t use any includes that access a sibling folder with "../". If you start doing those things, especially in combination, the behavior is a bit unpredictable.

Let’s start with a simple example where I think things are working as intended:

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

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

Note that the entries are relative to our CWD / the directory we’re reading (which one? We don’t know yet because they’re the same), but without the leading ./. This seems probably intentional because it matches the format of fs.readdir. Also, replacing the first argument with the empty string yields the same results.

Ok, let’s see what happens if we include something outside of the directory we’re reading:

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

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

No change. This seems to make sense with the name readDirectory. Now let’s find out whether our results are relative to our CWD or to the directory we’re reading. We’ll cd up a level first:

> process.cwd()
'/projects'

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

Interesting—they’re relative to our CWD, not the directory we’re reading, which is a divergence from fs.readdir. Does this mean we can now get includes outside of the directory we’re reading?

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

Yes it does! readDirectory can return results outside of the target directory, but not outside the CWD. This seems very odd to me. Let’s cd back into a and try absolute paths:

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

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

Absolute in, absolute out. Quite possibly an accident? Now for the final test, an absolute input path with an includes outside the CWD:

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

👀

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs InvestigationThis issue needs a team member to investigate its status.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions