UNPKG

typia

Version:

Superfast runtime validators with only one line

1 lines 11.2 kB
{"version":3,"file":"FileSystemIdentity.mjs","names":[],"sources":["../../src/executable/FileSystemIdentity.ts"],"sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\n\nexport namespace FileSystemIdentity {\n export interface IIdentity {\n readonly caseSensitive: boolean;\n contains(file: string, directory: string): boolean;\n filesystemKey(file: string): string;\n isDeclarationFile(file: string): boolean;\n isSamePath(x: string, y: string): boolean;\n isSupportedExtension(file: string): boolean;\n projectFileKey(file: string): string;\n }\n\n export function create(\n caseSensitive: boolean,\n pathApi: typeof path.posix = path,\n ): IIdentity {\n const fold = (value: string): string =>\n caseSensitive ? value : value.toLowerCase();\n const filesystemKey = (file: string): string =>\n fold(pathApi.normalize(file));\n return {\n caseSensitive,\n contains: (file, directory) => {\n const fileKey: string = filesystemKey(pathApi.resolve(file));\n const directoryKey: string = filesystemKey(pathApi.resolve(directory));\n const prefix: string = directoryKey.endsWith(pathApi.sep)\n ? directoryKey\n : `${directoryKey}${pathApi.sep}`;\n return fileKey === directoryKey || fileKey.startsWith(prefix);\n },\n filesystemKey,\n isDeclarationFile: (file) =>\n DTS_PATTERN.test(fold(pathApi.basename(file))),\n isSamePath: (x, y) => filesystemKey(x) === filesystemKey(y),\n isSupportedExtension: (file) => {\n const filename: string = fold(pathApi.basename(file));\n return (\n TS_PATTERN.test(filename) && DTS_PATTERN.test(filename) === false\n );\n },\n projectFileKey: fold,\n };\n }\n\n export class Policy {\n private caseSensitive_: boolean | undefined;\n private location_: string | undefined;\n\n public observe(caseSensitive: boolean | undefined, location: string): void {\n if (caseSensitive === undefined) return;\n if (this.caseSensitive_ === undefined) {\n this.caseSensitive_ = caseSensitive;\n this.location_ = location;\n } else if (this.caseSensitive_ !== caseSensitive) {\n throw new URIError(\n `Error on TypiaGenerateWizard.generate(): inconsistent filesystem case behavior between ${this.location_} and ${location}.`,\n );\n }\n }\n\n public get(): IIdentity {\n if (this.caseSensitive_ === undefined) {\n throw new URIError(\n \"Error on TypiaGenerateWizard.generate(): unable to determine filesystem case behavior for this run.\",\n );\n }\n return create(this.caseSensitive_);\n }\n }\n\n export async function inspectDirectory(\n directory: string,\n ): Promise<boolean | undefined> {\n const names: string[] = await fs.promises.readdir(directory);\n const entries: Set<string> = new Set(names);\n for (const name of names) {\n const alternate: string | undefined = alternateCase(name);\n if (alternate === undefined) continue;\n if (entries.has(alternate)) return true;\n try {\n await fs.promises.lstat(path.join(directory, alternate));\n return false;\n } catch (error) {\n if (isMissingFileError(error)) return true;\n throw error;\n }\n }\n return undefined;\n }\n\n export async function probeDirectory(directory: string): Promise<boolean> {\n const inspected: boolean | undefined = await inspectDirectory(directory);\n if (inspected !== undefined) return inspected;\n\n for (let attempt = 0; attempt < 10; ++attempt) {\n const name: string =\n `.typia-case-probe-${process.pid}-${Date.now()}-${Math.random()\n .toString(36)\n .slice(2)}`.toLowerCase();\n const original: string = path.join(directory, name);\n const alternate: string = path.join(directory, name.toUpperCase());\n let handle: fs.promises.FileHandle;\n try {\n handle = await fs.promises.open(original, \"wx\");\n } catch (error) {\n if (isAlreadyExistsError(error)) continue;\n throw new URIError(\n `Error on TypiaGenerateWizard.generate(): unable to probe filesystem case behavior at ${directory}: ${formatUnknownError(error)}`,\n );\n }\n let closed: boolean = false;\n try {\n await handle.close();\n closed = true;\n try {\n await fs.promises.lstat(alternate);\n return false;\n } catch (error) {\n if (isMissingFileError(error)) return true;\n throw error;\n }\n } finally {\n let cleanupError: unknown;\n if (closed === false) {\n try {\n await handle.close();\n } catch (error) {\n cleanupError = error;\n }\n }\n try {\n await fs.promises.unlink(original);\n } catch (error) {\n if (isMissingFileError(error) === false) cleanupError ??= error;\n }\n if (cleanupError !== undefined) {\n throw new URIError(\n `Error on TypiaGenerateWizard.generate(): unable to clean filesystem case probe ${original}: ${formatUnknownError(cleanupError)}`,\n );\n }\n }\n }\n throw new URIError(\n `Error on TypiaGenerateWizard.generate(): unable to reserve a filesystem case probe at ${directory}.`,\n );\n }\n\n /**\n * Builds the key that decides whether two paths are the same filesystem\n * object.\n *\n * The identity is read from `fs.BigIntStats` rather than `fs.Stats`, because\n * `fs.Stats.ino` is a JavaScript number. An NTFS file ID is `(sequenceNumber\n * << 48) | mftRecordIndex` and routinely exceeds `Number.MAX_SAFE_INTEGER` —\n * in a 4000-directory probe, 1879 of them did — where the spacing between\n * representable doubles is 8 or more. Two entries sharing a sequence number\n * with MFT record indices within that spacing round to one double. The\n * traversal callers would then treat a distinct directory or file as already\n * visited and skip it with no diagnostic, and the overwrite guard would\n * refuse a legitimate output (samchon/typia#2269).\n *\n * A filesystem that reports no inode at all keeps the canonical path\n * fallback, which is the only identity available there.\n *\n * @param stat Stats read with `{ bigint: true }`.\n * @param realpath Canonical path of the same object.\n * @returns Key that is equal for two paths only when they are one object.\n */\n export function identityKey(\n stat: Pick<fs.BigIntStats, \"dev\" | \"ino\">,\n realpath: string,\n ): string {\n // `BigInt(0)` rather than `0n`: this package compiles at ES2016, where a\n // BigInt literal is a syntax error, while the constructor and the `bigint`\n // type are fine.\n return stat.ino === BigInt(0)\n ? `path:${path.normalize(realpath)}`\n : `inode:${stat.dev}:${stat.ino}`;\n }\n\n function alternateCase(name: string): string | undefined {\n let changed: boolean = false;\n let output: string = \"\";\n for (const character of name) {\n if (character >= \"a\" && character <= \"z\") {\n output += character.toUpperCase();\n changed = true;\n } else if (character >= \"A\" && character <= \"Z\") {\n output += character.toLowerCase();\n changed = true;\n } else output += character;\n }\n return changed ? output : undefined;\n }\n\n function isAlreadyExistsError(error: unknown): boolean {\n return (\n typeof error === \"object\" &&\n error !== null &&\n \"code\" in error &&\n error.code === \"EEXIST\"\n );\n }\n\n function isMissingFileError(error: unknown): boolean {\n return (\n typeof error === \"object\" &&\n error !== null &&\n \"code\" in error &&\n error.code === \"ENOENT\"\n );\n }\n\n function formatUnknownError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n }\n}\n\nconst TS_PATTERN = /\\.[cm]?tsx?$/;\nconst DTS_PATTERN = /\\.d\\.[cm]?tsx?$/;\n"],"mappings":"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"}