5e1bff6b2b
The .txt extension match was winning over the basename check in getLanguageFromPath, returning 'text' instead of 'cmake' for CMakeLists.txt. Reordered the function to check basename special-cases first (mirroring detectLanguageId's structure), so CMakeLists.txt, Dockerfile, .env., .emacs, and justfile all fire before the extension lookup. Rewrote tests to defend observable contracts (special filenames, case handling, lookup ordering) instead of re-stating the EXTENSION_LANG table. Added contract test pinning that CMakeLists.txt resolves to 'cmake' not 'text'. Updated CHANGELOG: moved from ### Added (test-only) to ### Fixed (bug fix).
114 lines
4.0 KiB
TypeScript
114 lines
4.0 KiB
TypeScript
/**
|
|
* Contract tests for language detection from file paths.
|
|
*
|
|
* `getLanguageFromPath` returns the highlight language id for a given file
|
|
* path, or undefined if unknown. `detectLanguageId` returns the LSP language
|
|
* identifier, falling back to "plaintext".
|
|
*
|
|
* These tests defend observable contracts (special filenames, case handling,
|
|
* unknown fallbacks, lookup ordering) — not individual entries from the
|
|
* EXTENSION_LANG table, which would just re-state the lookup map.
|
|
*/
|
|
import { describe, expect, it } from "bun:test";
|
|
import { detectLanguageId, getLanguageFromPath } from "../../src/utils/lang-from-path";
|
|
|
|
describe("getLanguageFromPath", () => {
|
|
it("detects Dockerfile by basename (case-insensitive)", () => {
|
|
expect(getLanguageFromPath("Dockerfile")).toBe("dockerfile");
|
|
expect(getLanguageFromPath("dockerfile")).toBe("dockerfile");
|
|
expect(getLanguageFromPath("Dockerfile.dev")).toBe("dockerfile");
|
|
expect(getLanguageFromPath("DOCKERFILE")).toBe("dockerfile");
|
|
});
|
|
|
|
it("detects Containerfile", () => {
|
|
expect(getLanguageFromPath("Containerfile")).toBe("dockerfile");
|
|
});
|
|
|
|
it("detects .env files by prefix", () => {
|
|
expect(getLanguageFromPath(".env.local")).toBe("env");
|
|
expect(getLanguageFromPath(".env.production")).toBe("env");
|
|
// .env itself — themeExtensionKey returns "env" which matches the table
|
|
expect(getLanguageFromPath(".env")).toBe("env");
|
|
});
|
|
|
|
it("detects .emacs", () => {
|
|
expect(getLanguageFromPath(".emacs")).toBe("emacs-lisp");
|
|
});
|
|
|
|
it("detects justfile", () => {
|
|
expect(getLanguageFromPath("justfile")).toBe("just");
|
|
});
|
|
|
|
it("detects CMakeLists.txt as cmake (basename wins over .txt extension)", () => {
|
|
// Without the basename-first check, .txt would match the extension table
|
|
// and return "text" instead of "cmake". This test pins that the basename
|
|
// check fires before the extension lookup.
|
|
expect(getLanguageFromPath("CMakeLists.txt")).toBe("cmake");
|
|
expect(getLanguageFromPath("cmakelists.txt")).toBe("cmake");
|
|
});
|
|
|
|
it("is case-insensitive on extensions", () => {
|
|
expect(getLanguageFromPath("Main.TS")).toBe("typescript");
|
|
expect(getLanguageFromPath("App.TSX")).toBe("tsx");
|
|
});
|
|
|
|
it("returns undefined for unknown extensions", () => {
|
|
expect(getLanguageFromPath("file.unknownext")).toBeUndefined();
|
|
expect(getLanguageFromPath("file.xyz123")).toBeUndefined();
|
|
});
|
|
|
|
it("returns undefined for files with no extension", () => {
|
|
expect(getLanguageFromPath("README")).toBeUndefined();
|
|
});
|
|
|
|
it("returns the last extension when multiple dots are present", () => {
|
|
expect(getLanguageFromPath("config.test.ts")).toBe("typescript");
|
|
});
|
|
|
|
it("handles full paths with directories", () => {
|
|
expect(getLanguageFromPath("/home/user/project/src/index.ts")).toBe("typescript");
|
|
expect(getLanguageFromPath("C:\\Users\\dev\\app\\main.rs")).toBe("rust");
|
|
});
|
|
});
|
|
|
|
describe("detectLanguageId", () => {
|
|
it("detects Dockerfile as dockerfile", () => {
|
|
expect(detectLanguageId("Dockerfile")).toBe("dockerfile");
|
|
expect(detectLanguageId("dockerfile.dev")).toBe("dockerfile");
|
|
});
|
|
|
|
it("detects Containerfile as dockerfile", () => {
|
|
expect(detectLanguageId("Containerfile")).toBe("dockerfile");
|
|
});
|
|
|
|
it("detects .emacs as emacs-lisp", () => {
|
|
expect(detectLanguageId(".emacs")).toBe("emacs-lisp");
|
|
});
|
|
|
|
it("detects Makefile as makefile", () => {
|
|
expect(detectLanguageId("Makefile")).toBe("makefile");
|
|
expect(detectLanguageId("makefile")).toBe("makefile");
|
|
expect(detectLanguageId("gnumakefile")).toBe("makefile");
|
|
});
|
|
|
|
it("detects justfile as just", () => {
|
|
expect(detectLanguageId("justfile")).toBe("just");
|
|
});
|
|
|
|
it("detects CMakeLists.txt as cmake", () => {
|
|
expect(detectLanguageId("CMakeLists.txt")).toBe("cmake");
|
|
});
|
|
|
|
it("detects .cmake extension as cmake", () => {
|
|
expect(detectLanguageId("FindPackage.cmake")).toBe("cmake");
|
|
});
|
|
|
|
it("falls back to plaintext for unknown extensions", () => {
|
|
expect(detectLanguageId("file.unknownext")).toBe("plaintext");
|
|
});
|
|
|
|
it("falls back to plaintext for files with no extension", () => {
|
|
expect(detectLanguageId("README")).toBe("plaintext");
|
|
});
|
|
});
|