///|
/// An in-memory file used by browser, test, and editor integrations.
pub(all) struct VirtualFile {
path : String
content : String
} derive(Debug, Eq)
///|
/// Result of resolving a target inside a virtual project.
pub(all) struct WorkspaceResult {
target : String
discovered_configs : Array[String]
resolution : Resolution
diagnostics : Array[Diagnostic]
} derive(Debug, Eq)
///|
fn find_virtual_file(files : Array[VirtualFile], path : String) -> VirtualFile? {
let normalized = normalize_path(path)
for file in files {
if normalize_path(file.path) == normalized {
return Some(file)
}
}
None
}
///|
fn join_path(directory : String, name : String) -> String {
if directory == "/" {
"/" + name
} else if directory == "" {
name
} else {
directory + "/" + name
}
}
///|
fn directory_of_target(target : String) -> String {
match parent_path(target) {
Some(directory) => directory
None => ""
}
}
///|
/// Discover `.editorconfig` files in leaf-to-root order without accessing the
/// host filesystem. Search stops after parsing a file with `root = true`.
pub fn discover_virtual_configs(
files : Array[VirtualFile],
target : String,
) -> (Array[ConfigLayer], Array[Diagnostic]) {
let layers : Array[ConfigLayer] = []
let diagnostics : Array[Diagnostic] = []
let mut directory = directory_of_target(normalize_path(target))
let mut finished = false
while !finished {
let candidate = join_path(directory, ".editorconfig")
match find_virtual_file(files, candidate) {
Some(file) => {
let parsed = parse_document(normalize_path(file.path), file.content)
layers.push({
path: normalize_path(file.path),
directory,
config: parsed.config,
})
for diagnostic in parsed.diagnostics {
diagnostics.push(diagnostic)
}
if parsed.config.root {
finished = true
}
}
None => ()
}
if !finished {
match parent_path(directory) {
Some(parent) if parent != directory => directory = parent
_ => finished = true
}
}
}
(layers, diagnostics)
}
///|
/// Resolve one target using a complete in-memory project snapshot.
pub fn resolve_workspace(
files : Array[VirtualFile],
target : String,
) -> WorkspaceResult {
let normalized_target = normalize_path(target)
let (layers, parse_diagnostics) = discover_virtual_configs(
files, normalized_target,
)
let resolution = resolve_layers(layers, normalized_target)
let diagnostics = parse_diagnostics.copy()
for diagnostic in resolution.diagnostics {
diagnostics.push(diagnostic)
}
let discovered : Array[String] = []
for layer in layers {
discovered.push(layer.path)
}
{
target: normalized_target,
discovered_configs: discovered,
resolution,
diagnostics,
}
}
///|
/// Resolve several files efficiently against the same virtual project.
pub fn resolve_workspace_batch(
files : Array[VirtualFile],
targets : Array[String],
) -> Array[WorkspaceResult] {
let results : Array[WorkspaceResult] = []
for target in targets {
results.push(resolve_workspace(files, target))
}
results
}
///|
/// List files whose effective configuration differs for a selected property.
pub fn group_targets_by_property(
results : Array[WorkspaceResult],
property_name : String,
) -> Map[String, Array[String]] {
let groups : Map[String, Array[String]] = Map([])
for result in results {
let value = match result.resolution.get(property_name) {
Some(property) => property.value
None => ""
}
match groups.get(value) {
Some(paths) => paths.push(result.target)
None => groups[value] = [result.target]
}
}
groups
}
///|
/// Find project files that have no matching EditorConfig section.
pub fn uncovered_targets(results : Array[WorkspaceResult]) -> Array[String] {
let paths : Array[String] = []
for result in results {
if !result.resolution.sections.any(trace => trace.matched) {
paths.push(result.target)
}
}
paths
}