Skip to content
Merged
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
15 changes: 12 additions & 3 deletions crates/codescythe/analyze.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ pub fn analyze_path(
config: &CodescytheConfig,
options: AnalysisOptions,
) -> Result<Analysis> {
let cwd = normalize_path(cwd);
let cwd = absolute_normalize_path(cwd)?;
if !cwd.exists() {
anyhow::bail!("analysis root does not exist: {}", cwd.display());
}
Expand Down Expand Up @@ -321,7 +321,7 @@ fn discover_project_files(cwd: &Path, config: &CodescytheConfig) -> Result<Vec<P
.filter_entry(|entry| should_enter(entry))
{
let entry = entry?;
if !entry.file_type().is_file() {
if !entry.path().is_file() {
continue;
}
let relative = relative_path(cwd, entry.path());
Expand Down Expand Up @@ -426,7 +426,7 @@ fn build_glob_set(patterns: &[String]) -> Result<GlobSet> {
}

fn should_enter(entry: &DirEntry) -> bool {
if !entry.file_type().is_dir() {
if !entry.path().is_dir() {
return true;
}
!matches!(
Expand Down Expand Up @@ -1277,6 +1277,15 @@ fn relative_path(cwd: &Path, path: &Path) -> String {
.replace('\\', "/")
}

fn absolute_normalize_path(path: &Path) -> Result<PathBuf> {
let path = if path.is_absolute() {
path.to_path_buf()
} else {
env::current_dir()?.join(path)
};
Ok(normalize_path(&path))
}

fn normalize_path(path: &Path) -> PathBuf {
let mut normalized = PathBuf::new();
for component in path.components() {
Expand Down
2 changes: 1 addition & 1 deletion crates/codescythe_cli/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ mod tests {
#[test]
fn explicit_directory_overrides_config_parent() {
let directory = Path::new("/tmp/runfiles/_main");
let config = Path::new("/tmp/runfiles/_main/pplx/frontend/codescythe.json");
let config = Path::new("/tmp/runfiles/_main/workspace/frontend/codescythe.json");

let analysis_root = analysis_root(Some(directory), Some(config)).unwrap();

Expand Down
1 change: 1 addition & 0 deletions tests/bazel/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package(default_visibility = ["//visibility:public"])
175 changes: 175 additions & 0 deletions tests/bazel/codescythe_test.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
CodescytheSourcesInfo = provider(
doc = "Source files collected for a Codescythe runfiles test.",
fields = {
"sources": "depset of source files",
},
)

def _source_group_impl(ctx):
return [
DefaultInfo(
files = depset(
ctx.files.srcs,
transitive = [dep[DefaultInfo].files for dep in ctx.attr.deps],
),
),
]

source_group = rule(
implementation = _source_group_impl,
attrs = {
"deps": attr.label_list(),
"srcs": attr.label_list(allow_files = True),
},
)

def _codescythe_sources_aspect_impl(target, ctx):
transitive = []
if hasattr(ctx.rule.attr, "deps"):
transitive.extend([
dep[CodescytheSourcesInfo].sources
for dep in ctx.rule.attr.deps
if CodescytheSourcesInfo in dep
])

direct = []
if hasattr(ctx.rule.attr, "srcs"):
for src in ctx.rule.attr.srcs:
direct.extend(src.files.to_list())

return [
CodescytheSourcesInfo(
sources = depset(direct, transitive = transitive),
),
]

_codescythe_sources_aspect = aspect(
implementation = _codescythe_sources_aspect_impl,
attr_aspects = ["deps"],
)

def _codescythe_test_impl(ctx):
source_depsets = [
target[CodescytheSourcesInfo].sources
for target in ctx.attr.targets
if CodescytheSourcesInfo in target
]
script = ctx.actions.declare_file(ctx.label.name + ".sh")
ctx.actions.write(
output = script,
is_executable = True,
content = _test_script(
codescythe = ctx.executable._codescythe.short_path,
config = ctx.file.config.short_path,
expected_exit_code = ctx.attr.expected_exit_code,
must_contain = ctx.attr.must_contain,
must_not_contain = ctx.attr.must_not_contain,
),
)

runfiles = ctx.runfiles(
files = [ctx.executable._codescythe, ctx.file.config] + ctx.files.data,
transitive_files = depset(transitive = source_depsets),
).merge(ctx.attr._codescythe[DefaultInfo].default_runfiles)

return [DefaultInfo(executable = script, runfiles = runfiles)]

codescythe_test = rule(
implementation = _codescythe_test_impl,
attrs = {
"config": attr.label(allow_single_file = True, mandatory = True),
"data": attr.label_list(allow_files = True),
"expected_exit_code": attr.int(default = 0),
"must_contain": attr.string_list(),
"must_not_contain": attr.string_list(),
"targets": attr.label_list(
aspects = [_codescythe_sources_aspect],
mandatory = True,
),
"_codescythe": attr.label(
default = Label("//crates/codescythe_cli:codescythe"),
executable = True,
cfg = "exec",
),
},
test = True,
)

def _test_script(codescythe, config, expected_exit_code, must_contain, must_not_contain):
return """#!/usr/bin/env bash
set -euo pipefail

runfile() {{
local path="$1"
if [[ -n "${{RUNFILES_DIR:-}}" && -e "${{RUNFILES_DIR}}/${{path}}" ]]; then
printf '%s\\n' "${{RUNFILES_DIR}}/${{path}}"
return
fi
if [[ -n "${{RUNFILES_DIR:-}}" ]]; then
for workspace in "${{TEST_WORKSPACE:-_main}}" _main codescythe; do
if [[ -e "${{RUNFILES_DIR}}/${{workspace}}/${{path}}" ]]; then
printf '%s\\n' "${{RUNFILES_DIR}}/${{workspace}}/${{path}}"
return
fi
done
fi
if [[ -n "${{TEST_SRCDIR:-}}" ]]; then
for workspace in "${{TEST_WORKSPACE:-_main}}" _main codescythe; do
if [[ -e "${{TEST_SRCDIR}}/${{workspace}}/${{path}}" ]]; then
printf '%s\\n' "${{TEST_SRCDIR}}/${{workspace}}/${{path}}"
return
fi
done
fi
printf '%s\\n' "${{path}}"
}}

codescythe="$(runfile {codescythe})"
config="$(runfile {config})"
stdout="${{TEST_TMPDIR}}/codescythe.stdout.json"
stderr="${{TEST_TMPDIR}}/codescythe.stderr.txt"

set +e
"${{codescythe}}" --config "${{config}}" --json --compact-json >"${{stdout}}" 2>"${{stderr}}"
status="$?"
set -e

if [[ "${{status}}" -ne {expected_exit_code} ]]; then
echo "expected exit code {expected_exit_code}, got ${{status}}" >&2
cat "${{stdout}}" >&2
cat "${{stderr}}" >&2
exit 1
fi

if [[ -s "${{stderr}}" ]]; then
cat "${{stderr}}" >&2
exit 1
fi

must_contain=({must_contain})
for needle in "${{must_contain[@]}}"; do
if ! grep -F -- "${{needle}}" "${{stdout}}" >/dev/null; then
echo "expected Codescythe output to contain: ${{needle}}" >&2
cat "${{stdout}}" >&2
exit 1
fi
done

must_not_contain=({must_not_contain})
for needle in "${{must_not_contain[@]}}"; do
if grep -F -- "${{needle}}" "${{stdout}}" >/dev/null; then
echo "expected Codescythe output not to contain: ${{needle}}" >&2
cat "${{stdout}}" >&2
exit 1
fi
done
""".format(
codescythe = _shell_quote(codescythe),
config = _shell_quote(config),
expected_exit_code = expected_exit_code,
must_contain = " ".join([_shell_quote(value) for value in must_contain]),
must_not_contain = " ".join([_shell_quote(value) for value in must_not_contain]),
)

def _shell_quote(value):
return "'" + value.replace("'", "'\\''") + "'"
48 changes: 48 additions & 0 deletions tests/fixtures/runfiles-false-positive/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
load("//tests/bazel:codescythe_test.bzl", "codescythe_test", "source_group")

source_group(
name = "app_sources",
srcs = [
"src/main.ts",
],
deps = [
":generated_sources",
":library_sources",
],
)

source_group(
name = "generated_sources",
srcs = [
"generated/client.ts",
],
)

source_group(
name = "library_sources",
srcs = [
"src/used.ts",
],
)

source_group(
name = "all_sources",
deps = [
":app_sources",
],
)

codescythe_test(
name = "codescythe_false_positive_test",
config = "codescythe.json",
data = ["package.json"],
expected_exit_code = 0,
must_contain = [
"\"processed\":3",
"\"total\":3",
],
must_not_contain = [
"#virtual_generated/api/foo",
],
targets = [":all_sources"],
)
15 changes: 15 additions & 0 deletions tests/fixtures/runfiles-false-positive/codescythe.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"$schema": "../../../codescythe.schema.json",
"entry": "src/main.ts",
"project": [
"src/**/*.ts",
"generated/**/*.ts"
],
"aliases": {
"#generated/*": "./generated/*.ts"
},
"unresolvedImports": {
"mode": "report",
"ignore": ["#virtual_generated/**"]
}
}
1 change: 1 addition & 0 deletions tests/fixtures/runfiles-false-positive/generated/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const client = 'client';
7 changes: 7 additions & 0 deletions tests/fixtures/runfiles-false-positive/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "codescythe-bazel-runfiles-false-positive-fixture",
"type": "module",
"imports": {
"#app/*": "./src/*.ts"
}
}
5 changes: 5 additions & 0 deletions tests/fixtures/runfiles-false-positive/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { used } from '#app/used';
import { client } from '#generated/client';
import '#virtual_generated/api/foo';

console.log(used, client);
1 change: 1 addition & 0 deletions tests/fixtures/runfiles-false-positive/src/used.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const used = 'used';
80 changes: 80 additions & 0 deletions tests/fixtures/runfiles-fixture/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
load("//tests/bazel:codescythe_test.bzl", "codescythe_test", "source_group")

source_group(
name = "entry_sources",
srcs = [
"workspace/frontend/apps/client/platform/platformRuntime.ts",
],
deps = [
":generated_sources",
":library_sources",
],
)

source_group(
name = "generated_sources",
srcs = [
"protobuf/generated/client.ts",
],
)

source_group(
name = "library_sources",
srcs = [
"workspace/frontend/lib/runtime.ts",
],
)

source_group(
name = "unused_sources",
srcs = [
"protobuf/wrong/client.ts",
"typespec/schema.ts",
"workspace/frontend/dead.ts",
],
)

source_group(
name = "all_sources",
deps = [
":entry_sources",
":unused_sources",
],
)

filegroup(
name = "fixture_files",
srcs = [
":all_sources",
"codescythe.json",
"package.json",
"workspace/frontend/apps/client/index.html",
"workspace/frontend/apps/client/platform/index.html",
"workspace/frontend/apps/client/spa/index.html",
],
visibility = ["//visibility:public"],
)

codescythe_test(
name = "codescythe_runfiles_test",
config = "codescythe.json",
data = [
"package.json",
"workspace/frontend/apps/client/index.html",
"workspace/frontend/apps/client/platform/index.html",
"workspace/frontend/apps/client/spa/index.html",
],
expected_exit_code = 1,
must_contain = [
"./missing",
"\"processed\":6",
"\"total\":6",
"protobuf/wrong/client.ts",
"typespec/schema.ts",
"workspace/frontend/dead.ts",
],
must_not_contain = [
"#virtual_generated/api/foo",
],
targets = [":all_sources"],
)
Loading
Loading