pi-lens
Version:
Real-time code feedback for pi — LSP, linters, formatters, type-checking, structural analysis & booboo
402 lines (401 loc) • 8.43 kB
JavaScript
/**
* File Kind Detection for pi-lens
*
* Centralized file type detection to avoid duplication across clients.
* Maps file extensions and paths to semantic file kinds.
*/
import { basename, extname } from "node:path";
// --- Extension Maps ---
export const KIND_EXTENSIONS = {
clojure: [
".clj",
".cljc",
".cljs",
".edn",
],
cmake: [
".cmake",
],
csharp: [
".cs",
],
css: [
".css",
".less",
".sass",
".scss",
],
// From llvm-project/clang/lib/Driver/Types.cpp clang::driver::types::lookupTypeForExtension:
cxx: [
// C
".c",
".h",
// C++
".c++",
".cc",
".cp",
".cpp",
".cxx",
".hh",
".hpp",
".hxx",
// C++ include files
".inl",
".ipp",
".tpp",
".txx",
// C++20 module interface files
".c++m",
".cppm",
".cxxm",
".ixx",
// CUDA
".cu",
// HIP
".hip",
// Objective-C
".m",
".mm",
// OpenCL
".cl",
".clcpp",
],
dart: [
".dart",
],
docker: [
".dockerfile",
],
elixir: [
".ex",
".exs",
],
fish: [
".fish",
],
fsharp: [
".fs",
".fsi",
".fsx",
],
gleam: [
".gleam",
],
go: [
".go",
],
haskell: [
".hs",
".lhs",
],
html: [
".htm",
".html",
],
java: [
".java",
],
json: [
".json",
".json5",
".jsonc",
],
jsts: [
".cjs",
".cts",
".js",
".jsx",
".mjs",
".mts",
".svelte",
".ts",
".tsx",
".vue",
],
kotlin: [
".kt",
".kts",
],
lua: [
".lua",
],
markdown: [
".md",
".mdx",
],
nix: [
".nix",
],
ocaml: [
".ml",
".mli",
],
php: [
".php",
],
powershell: [
".ps1",
".psm1",
".psd1",
],
prisma: [
".prisma",
],
python: [
".py",
".pyi",
],
ruby: [
".gemspec",
".rake",
".rb",
".ru",
],
rust: [
".rs",
],
shell: [
".bash",
".sh",
".zsh",
],
sql: [
".sql",
],
swift: [
".swift",
],
terraform: [
".tf",
".tfvars",
],
toml: [
".toml",
],
yaml: [
".yaml",
".yml",
],
zig: [
".zig",
".zon",
],
};
// Reverse map: extension → file kind (for fast lookup)
const EXT_TO_KIND = new Map();
for (const [kind, exts] of Object.entries(KIND_EXTENSIONS)) {
for (const ext of exts) {
EXT_TO_KIND.set(ext.toLowerCase(), kind);
}
// Also register without leading dot
for (const ext of exts) {
if (ext.startsWith(".")) {
EXT_TO_KIND.set(ext.slice(1).toLowerCase(), kind);
}
}
}
// Special filenames that indicate a file kind
const SPECIAL_FILENAMES = [
{ pattern: /^CMakeLists\.txt$/i, kind: "cmake" },
{ pattern: /^Makefile$/i, kind: "shell" },
{ pattern: /^Dockerfile(\.\w+)?$/i, kind: "docker" },
];
// --- Detection Functions ---
/**
* Detect the file kind from a file path.
* Returns the semantic file kind or undefined if unknown.
*/
export function detectFileKind(filePath) {
if (!filePath || typeof filePath !== "string") {
return undefined;
}
// Check special filenames first
const base = basename(filePath);
for (const { pattern, kind } of SPECIAL_FILENAMES) {
if (pattern.test(base)) {
return kind;
}
}
// Check by extension
const ext = extname(filePath).toLowerCase();
return EXT_TO_KIND.get(ext);
}
/**
* Check if a file kind is supported by a specific tool or capability.
*
* @example
* // Check if TypeScript file
* if (isFileKind(filePath, "jsts")) { ... }
*
* // Check for multiple kinds
* if (isFileKind(filePath, ["jsts", "python"])) { ... }
*/
export function isFileKind(filePath, kind) {
const detected = detectFileKind(filePath);
if (!detected)
return false;
if (Array.isArray(kind)) {
return kind.includes(detected);
}
return detected === kind;
}
/**
* Get all file kinds that match a given file extension.
* Useful for listing which tools might handle a file.
*/
export function getFileKindsForExtension(ext) {
const normalizedExt = ext.startsWith(".") ? ext : `.${ext}`;
const kind = EXT_TO_KIND.get(normalizedExt.toLowerCase());
return kind ? [kind] : [];
}
/**
* Check if a file kind represents a code file (not config/markdown).
*/
export function isCodeKind(kind) {
return [
"jsts",
"python",
"go",
"rust",
"cxx",
"fish",
"shell",
"ruby",
"html",
"php",
"powershell",
"prisma",
"csharp",
"fsharp",
"java",
"kotlin",
"swift",
"dart",
"lua",
"zig",
"haskell",
"elixir",
"gleam",
"ocaml",
"clojure",
"terraform",
"nix",
].includes(kind);
}
/**
* Check if a file kind represents a text/config file.
*/
export function isConfigKind(kind) {
return ["json", "yaml", "markdown", "css", "sql", "docker", "cmake", "toml"].includes(kind);
}
/**
* Get human-readable description of a file kind.
*/
export function getFileKindLabel(kind) {
const labels = {
jsts: "JavaScript/TypeScript",
python: "Python",
go: "Go",
rust: "Rust",
cxx: "C/C++",
cmake: "CMake",
shell: "Shell",
json: "JSON",
markdown: "Markdown",
css: "CSS",
yaml: "YAML",
sql: "SQL",
ruby: "Ruby",
html: "HTML",
docker: "Dockerfile",
php: "PHP",
powershell: "PowerShell",
prisma: "Prisma",
csharp: "C#",
fish: "Fish shell",
fsharp: "F#",
java: "Java",
kotlin: "Kotlin",
swift: "Swift",
dart: "Dart",
lua: "Lua",
zig: "Zig",
haskell: "Haskell",
elixir: "Elixir",
gleam: "Gleam",
ocaml: "OCaml",
clojure: "Clojure",
terraform: "Terraform",
nix: "Nix",
toml: "TOML",
};
return labels[kind] ?? kind;
}
/**
* Get file extensions for a file kind.
*/
export function getExtensionsForKind(kind) {
return [...(KIND_EXTENSIONS[kind] ?? [])];
}
/**
* Check if a file should be scanned for linting/formatting.
* Excludes test files, generated files, etc.
*/
export function isScannableFile(filePath) {
const kind = detectFileKind(filePath);
if (!kind)
return false;
// Exclude test files for most kinds
const base = basename(filePath);
if (base.includes(".test.") ||
base.includes(".spec.") ||
base.startsWith("test-") ||
base.startsWith("spec-")) {
return false;
}
// Only scan code and config files
return isCodeKind(kind) || isConfigKind(kind);
}
/**
* Get the language identifier for LSP/tools that use language IDs.
*/
export function getLanguageId(kind) {
const languageIds = {
jsts: "typescript",
python: "python",
go: "go",
rust: "rust",
cxx: "cpp",
cmake: "cmake",
shell: "shell",
json: "json",
markdown: "markdown",
css: "css",
yaml: "yaml",
sql: "sql",
ruby: "ruby",
html: "html",
docker: "dockerfile",
php: "php",
powershell: "powershell",
prisma: "prisma",
csharp: "csharp",
fish: "fish",
fsharp: "fsharp",
java: "java",
kotlin: "kotlin",
swift: "swift",
dart: "dart",
lua: "lua",
zig: "zig",
haskell: "haskell",
elixir: "elixir",
gleam: "gleam",
ocaml: "ocaml",
clojure: "clojure",
terraform: "terraform",
nix: "nix",
toml: "toml",
};
return languageIds[kind] ?? "plaintext";
}