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
26 changes: 26 additions & 0 deletions scripts/mobile-native-static-check.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { assert, it } from "@effect/vitest";
import * as Effect from "effect/Effect";

import { requireKotlinTools } from "./mobile-native-static-check.ts";

it.effect("allows Kotlin checks when ktlint and detekt are on PATH", () =>
requireKotlinTools({ ktlint: true, detekt: true }),
);

it.effect("fails when ktlint or detekt is missing instead of skipping", () =>
Effect.gen(function* () {
const ktlintError = yield* requireKotlinTools({ ktlint: false, detekt: true }).pipe(
Effect.flip,
);
if (ktlintError._tag !== "NativeStaticCheckMissingToolError") {
return assert.fail(`Unexpected error: ${ktlintError._tag}`);
}
assert.deepEqual(ktlintError.commands, ["ktlint"]);

const bothError = yield* requireKotlinTools({ ktlint: false, detekt: false }).pipe(Effect.flip);
if (bothError._tag !== "NativeStaticCheckMissingToolError") {
return assert.fail(`Unexpected error: ${bothError._tag}`);
}
assert.deepEqual(bothError.commands, ["ktlint", "detekt"]);
}),
);
68 changes: 45 additions & 23 deletions scripts/mobile-native-static-check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,21 @@ export class NativeStaticCheckCommandError extends Schema.TaggedError<NativeStat
}
}

const kotlinToolCommands = ["ktlint", "detekt"] as const;

export class NativeStaticCheckMissingToolError extends Schema.TaggedError<NativeStaticCheckMissingToolError>()(
"NativeStaticCheckMissingToolError",
{
commands: Schema.Array(Schema.Literals(kotlinToolCommands)),
},
) {
override get message(): string {
const names = this.commands.join(" and ");
const verb = this.commands.length === 1 ? "is" : "are";
return `${names} ${verb} not installed; cannot run Kotlin static analysis. Install via \`brew bundle install --file apps/mobile/Brewfile\` or put the JVM tools on PATH.`;
}
}

const tools = [
{
command: "swiftlint",
Expand Down Expand Up @@ -117,6 +132,16 @@ const warnMissingTool = (tool: NativeStaticTool, checkName: string) =>
`${tool.command} is not installed; skipping ${checkName}. Install it with '${tool.installHint}' or run 'brew bundle install --file apps/mobile/Brewfile'.`,
);

export const requireKotlinTools = Effect.fn("requireKotlinTools")(function* (available: {
readonly ktlint: boolean;
readonly detekt: boolean;
}) {
const missing = kotlinToolCommands.filter((command) => !available[command]);
if (missing.length > 0) {
return yield* new NativeStaticCheckMissingToolError({ commands: missing });
}
});

export const runCommand = Effect.fn("runCommand")(function* (
command: string,
args: ReadonlyArray<string>,
Expand Down Expand Up @@ -253,29 +278,24 @@ const runNativeStaticChecks = Effect.fn("runNativeStaticChecks")(function* () {
}

if (kotlinSources.length > 0) {
const relativeKotlinSources = kotlinSources.map((source) => path.relative(root, source));

if (availableTools.get("ktlint")) {
yield* runCommand("ktlint", relativeKotlinSources, root);
} else {
yield* warnMissingTool(tools[1], "ktlint");
}
yield* requireKotlinTools({
ktlint: availableTools.get("ktlint") === true,
detekt: availableTools.get("detekt") === true,
});

if (availableTools.get("detekt")) {
yield* runCommand(
"detekt",
[
"--config",
"detekt.yml",
"--input",
relativeKotlinSources.join(","),
"--build-upon-default-config",
],
root,
);
} else {
yield* warnMissingTool(tools[2], "detekt");
}
const relativeKotlinSources = kotlinSources.map((source) => path.relative(root, source));
yield* runCommand("ktlint", relativeKotlinSources, root);
yield* runCommand(
"detekt",
[
"--config",
"detekt.yml",
"--input",
relativeKotlinSources.join(","),
"--build-upon-default-config",
],
root,
);
}

yield* Console.log("Skipping generated native project folders: android/, ios/.");
Expand All @@ -284,7 +304,9 @@ const runNativeStaticChecks = Effect.fn("runNativeStaticChecks")(function* () {
export const mobileNativeStaticCheckCommand = Command.make("mobile-native-static-check", {}, () =>
runNativeStaticChecks(),
).pipe(
Command.withDescription("Run mobile native static analysis when native tools are available."),
Command.withDescription(
"Run mobile native static analysis. SwiftLint may be skipped when unavailable; ktlint and detekt are required when Kotlin sources exist.",
),
);

if (import.meta.main) {
Expand Down
Loading