Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion crates/ts_goport/src/execute/build/orchestrator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,8 +214,16 @@ pub struct Orchestrator {

impl Orchestrator {
// Go: build/orchestrator.go:94 (*Orchestrator).relativeFileName
// PORT: Go relativePath (:98) is inlined; the name is a file name.
pub fn relative_file_name(&self, file_name: &str) -> String {
convert_to_relative_path(file_name, &self.compare_paths_options)
if let Some(relative) = relative_path_from_directory(
&self.compare_paths_options.current_directory,
file_name,
self.compare_paths_options.use_case_sensitive_file_names,
) {
return relative;
}
file_name.to_string()
}

// Go: build/orchestrator.go:97 (*Orchestrator).toPath (at 673a5f17d713; removed by
Expand Down
28 changes: 19 additions & 9 deletions crates/ts_goport/src/execute/tsc/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,23 @@ use super::compile::{System, Writer, write_str};
use super::compile::CommandLineTesting;
use crate::diagnostics::Category;
use crate::diagnostics_loc::message_localize;
use crate::frontend::tspath::{ComparePathsOptions, convert_to_relative_path, path_is_absolute};
use crate::frontend::tspath::{ComparePathsOptions, relative_path_from_directory};
use crate::locale::Locale;

// Go: diagnosticwriter/diagnosticwriter.go:27 formatFileNameRelativeTo
// PORT: the directory and the case sensitivity are the fields of the
// `compare_paths_options` of `FormattingOptions`.
pub fn format_file_name_relative_to(file_name: &str, options: &ComparePathsOptions) -> String {
if let Some(relative_path) = relative_path_from_directory(
&options.current_directory,
file_name,
options.use_case_sensitive_file_names,
) {
return relative_path;
}
file_name.to_string()
}

// Go: diagnosticwriter/diagnosticwriter.go:203 FormattingOptions
#[derive(Clone, Debug, Default)]
pub struct FormattingOptions {
Expand Down Expand Up @@ -1051,7 +1065,7 @@ pub fn write_location(
let (first_line, first_char) = get_ecma_line_and_utf16_character_of_file_position(&file, pos);
let relative_file_name = match format_opts {
Some(format_opts) => {
convert_to_relative_path(file.file_name(), &format_opts.compare_paths_options)
format_file_name_relative_to(file.file_name(), &format_opts.compare_paths_options)
}
None => file.file_name().to_string(),
};
Expand Down Expand Up @@ -1251,12 +1265,8 @@ fn pretty_path_for_file_error(
return String::new();
}
let line = get_ecma_line_of_file_position(file, AstDiagnostic(file_errors[0]).pos());
let mut file_name = file.file_name().to_string();
if path_is_absolute(&file_name)
&& path_is_absolute(&format_opts.compare_paths_options.current_directory)
{
file_name = convert_to_relative_path(file.file_name(), &format_opts.compare_paths_options);
}
let file_name =
format_file_name_relative_to(file.file_name(), &format_opts.compare_paths_options);
format!(
"{}{}:{}{}",
file_name,
Expand All @@ -1278,7 +1288,7 @@ pub fn write_format_diagnostic(
get_ecma_line_and_utf16_character_of_file_position(&file, wrapped.pos());
let file_name = file.file_name();
let relative_file_name =
convert_to_relative_path(file_name, &format_opts.compare_paths_options);
format_file_name_relative_to(file_name, &format_opts.compare_paths_options);
write_str(
output,
&format!("{}({},{}): ", relative_file_name, line + 1, character + 1),
Expand Down
29 changes: 10 additions & 19 deletions crates/ts_goport/src/frontend/compiler/file_include.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,23 @@ use crate::frontend::prelude::*;

/// Go `caseSensitivity.RelativePathFromDirectory(directory, fileName)` and
/// its fallback to the absolute name (ts#64159: fileInclude.go:155,
/// program.go:2145). Used for the include-reason text and `explain_files`.
// PORT: the names are normalized, so not reducing "." and ".." (rule R3)
// changes nothing here. Another root, or an empty `directory` (Go tests pass
// ""), gives the absolute name (rule R4).
/// program.go:2159). Used for the include-reason text and `explain_files`.
// PORT: another root, an empty `directory` (Go tests pass "") or a relative
// name that looks rooted (`c:/x` under a directory named `c:`) gives the
// absolute name (rule R4).
pub(crate) fn relative_file_name_from_directory(
directory: &str,
file_name: &str,
program: &NewProgram,
) -> String {
let directory_root = &directory[..get_root_length(directory)];
let file_root = &file_name[..get_root_length(file_name)];
if directory_root.is_empty()
|| !directory_root
.trim_end_matches('/')
.eq_ignore_ascii_case(file_root.trim_end_matches('/'))
{
return file_name.to_string();
}
get_relative_path_from_directory(
if let Some(relative_path) = relative_path_from_directory(
directory,
file_name,
&ComparePathsOptions {
use_case_sensitive_file_names: program.use_case_sensitive_file_names(),
current_directory: directory.to_string(),
},
)
program.use_case_sensitive_file_names(),
) {
return relative_path;
}
file_name.to_string()
}

// Go: fileInclude.go:14 fileIncludeKind
Expand Down
5 changes: 4 additions & 1 deletion crates/ts_goport/src/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6518,7 +6518,10 @@ pub fn format_diagnostic(diagnostic: &Diagnostic) -> String {
};
output.push_str(&format!(
"{}({},{}): ",
tspath::convert_to_relative_path(file_name, &compare_options),
crate::execute::tsc::diagnostics::format_file_name_relative_to(
file_name,
&compare_options
),
line + 1,
character + 1
));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
//! Port-only test of the names of files under a directory named like a
//! drive (`c:`).
//!
//! PORT: no Go counterpart. Go (ts#64159) writes the name of a file relative
//! to the current directory with RelativePathFromDirectory
//! (tspath/relative_path.go:82), which gives the absolute name when the
//! relative name looks rooted: `<cwd>/c:/a.ts` is `c:/a.ts` relative, which
//! has a DOS root. The port's diagnostic writer (diagnosticwriter.go:27
//! formatFileNameRelativeTo), `--explainFiles` (program.go:2159,
//! fileInclude.go:155) and `tsc -b` (build/orchestrator.go:94) printed
//! `c:/a.ts`. A file reached through module resolution gets the real path
//! (`<cwd>/c:/...`, osvfs os.go:142), which is how the fuzzer found it. Only
//! a one-letter name followed by `:` is a DOS root (`x:y`, `ab:c` are not).
//!
//! The test runs `tsgo` in a scratch dir, as the names come from the OS
//! realpath. Every expected output is the output of `tsgo-oracle-3e9236361fa9`
//! on the same files, with its scratch dir replaced.

use std::path::PathBuf;
use std::process::{Command, Stdio};

/// A scratch dir with the files of one case. It is deleted on drop.
struct Project(PathBuf);

impl Project {
fn new(name: &str, files: &[(&str, &str)]) -> Project {
let dir = std::env::temp_dir().join(format!(
"tsgo_drive_like_segment_{}_{name}",
std::process::id()
));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
// The names have the real path of the dir, as Go's realpath.
let dir = std::fs::canonicalize(&dir).unwrap();
for (file, text) in files {
let path = dir.join(file);
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::write(path, text).unwrap();
}
Project(dir)
}

fn dir(&self) -> String {
self.0.to_str().unwrap().to_string()
}

/// Runs `tsgo` with `args` and gives the exit code and the output.
fn tsc(&self, args: &[&str]) -> (Option<i32>, String) {
let output = Command::new(env!("CARGO_BIN_EXE_tsgo"))
.args(args)
.current_dir(&self.0)
.stdin(Stdio::null())
.output()
.expect("run tsgo");
let mut text = String::from_utf8_lossy(&output.stdout).into_owned();
text.push_str(&String::from_utf8_lossy(&output.stderr));
(output.status.code(), text)
}
}

impl Drop for Project {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}

/// `text` without its ANSI escapes.
fn strip_escapes(text: &str) -> String {
let mut out = String::new();
let mut chars = text.chars();
while let Some(c) = chars.next() {
if c == '\x1b' {
for c in chars.by_ref() {
if c == 'm' {
break;
}
}
} else {
out.push(c);
}
}
out
}

/// The fuzzer's repro: a module resolved to `<cwd>/c:/node_modules/...`.
#[test]
fn resolved_module_under_a_drive_like_dir_has_its_absolute_name() {
let project = Project::new(
"resolved",
&[
("c:/root/a.ts", "import \"file4\";\n"),
(
"c:/node_modules/file4/index.d.ts",
"let x: number = \"s\";\n",
),
(
"tsconfig.json",
r#"{"compilerOptions":{"noEmit":true},"files":["c:/root/a.ts"]}"#,
),
],
);
let dir = project.dir();
let file4 = format!("{dir}/c:/node_modules/file4/index.d.ts");
let diagnostics = format!(
"{file4}(1,1): error TS1046: Top-level declarations in .d.ts files must start with either a 'declare' or 'export' modifier.
{file4}(1,5): error TS2322: Type 'string' is not assignable to type 'number'.
{file4}(1,17): error TS1039: Initializers are not allowed in ambient contexts.
"
);

let (code, output) = project.tsc(&["-p", "tsconfig.json", "--pretty", "false"]);
assert_eq!(output, diagnostics);
assert_eq!(code, Some(2));

let (code, output) =
project.tsc(&["-p", "tsconfig.json", "--pretty", "false", "--explainFiles"]);
assert!(output.starts_with(&diagnostics), "{output}");
let explained = format!(
"{file4}
Imported via \"file4\" from file 'c:/root/a.ts'
c:/root/a.ts
Part of 'files' list in tsconfig.json
"
);
assert!(output.ends_with(&explained), "{output}");
assert_eq!(code, Some(2));
}

/// A root file named by its absolute path, and names with a `:` that are
/// not a DOS root, in the pretty output and its summary.
#[test]
fn pretty_names_under_a_drive_like_dir_are_absolute() {
let project = Project::new(
"pretty",
&[
("c:/root/a.ts", "import \"./b\";\nlet y: string = 1;\n"),
("c:/root/b.ts", "export {}; let z: string = 2;\n"),
("x:y/c.ts", "let w: string = 3;\n"),
("ab:c/e.ts", "let v: string = 4;\n"),
],
);
let dir = project.dir();
std::fs::write(
project.0.join("tsconfig.json"),
format!(
r#"{{"compilerOptions":{{"noEmit":true}},"files":["{dir}/c:/root/a.ts","x:y/c.ts","ab:c/e.ts"]}}"#
),
)
.unwrap();

let (code, output) = project.tsc(&["-p", "tsconfig.json", "--pretty", "true"]);
assert_eq!(
strip_escapes(&output),
format!(
"ab:c/e.ts:1:5 - error TS2322: Type 'number' is not assignable to type 'string'.

1 let v: string = 4;
~

{dir}/c:/root/a.ts:2:5 - error TS2322: Type 'number' is not assignable to type 'string'.

2 let y: string = 1;
~

{dir}/c:/root/b.ts:1:16 - error TS2322: Type 'number' is not assignable to type 'string'.

1 export {{}}; let z: string = 2;
~

x:y/c.ts:1:5 - error TS2322: Type 'number' is not assignable to type 'string'.

1 let w: string = 3;
~


Found 4 errors in 4 files.

Errors Files
1 ab:c/e.ts:1
1 {dir}/c:/root/a.ts:2
1 {dir}/c:/root/b.ts:1
1 x:y/c.ts:1

"
)
);
assert_eq!(code, Some(2));
}

/// `tsc -b --verbose` of a project under a drive-like dir.
#[test]
fn build_names_under_a_drive_like_dir_are_absolute() {
let project = Project::new(
"build",
&[
("c:/p/a.ts", "export const a = 1;\nlet x: number = \"s\";\n"),
(
"c:/p/tsconfig.json",
r#"{"compilerOptions":{"outDir":"out"},"files":["a.ts"]}"#,
),
],
);
let dir = project.dir();
let config = format!("{dir}/c:/p");
let (code, output) = project.tsc(&["-b", &config, "--verbose", "--pretty", "false"]);
// Without the time of each status line.
let output = output
.lines()
.map(|line| match line.split_once(" - ") {
Some((time, rest)) if time.ends_with("AM") || time.ends_with("PM") => rest,
_ => line,
})
.collect::<Vec<_>>()
.join("\n");
assert_eq!(
output,
format!(
"Projects in this build: \n * {config}/tsconfig.json\n\n\
Project '{config}/tsconfig.json' is out of date because output file '{config}/out/tsconfig.tsbuildinfo' does not exist\n\n\
Building project '{config}/tsconfig.json'...\n\n\
{config}/a.ts(2,5): error TS2322: Type 'string' is not assignable to type 'number'."
)
);
assert_eq!(code, Some(2));
}
1 change: 1 addition & 0 deletions crates/ts_goport/tests/go_baselines/tsctests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod build_info_corrupt;
mod build_prefetch_rereads;
mod checker_common_source_directory;
mod contentmapper_watch;
mod drive_like_segment_path;
mod effect_build_info;
mod explain_files_cache;
mod extended_counts;
Expand Down