typia
Version:
Superfast runtime validators with only one line
213 lines • 9.73 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.FileSystemIdentity = void 0;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
var FileSystemIdentity;
(function (FileSystemIdentity) {
function create(caseSensitive, pathApi = path_1.default) {
const fold = (value) => caseSensitive ? value : value.toLowerCase();
const filesystemKey = (file) => fold(pathApi.normalize(file));
return {
caseSensitive,
contains: (file, directory) => {
const fileKey = filesystemKey(pathApi.resolve(file));
const directoryKey = filesystemKey(pathApi.resolve(directory));
const prefix = directoryKey.endsWith(pathApi.sep)
? directoryKey
: `${directoryKey}${pathApi.sep}`;
return fileKey === directoryKey || fileKey.startsWith(prefix);
},
filesystemKey,
isDeclarationFile: (file) => DTS_PATTERN.test(fold(pathApi.basename(file))),
isSamePath: (x, y) => filesystemKey(x) === filesystemKey(y),
isSupportedExtension: (file) => {
const filename = fold(pathApi.basename(file));
return (TS_PATTERN.test(filename) && DTS_PATTERN.test(filename) === false);
},
projectFileKey: fold,
};
}
FileSystemIdentity.create = create;
class Policy {
observe(caseSensitive, location) {
if (caseSensitive === undefined)
return;
if (this.caseSensitive_ === undefined) {
this.caseSensitive_ = caseSensitive;
this.location_ = location;
}
else if (this.caseSensitive_ !== caseSensitive) {
throw new URIError(`Error on TypiaGenerateWizard.generate(): inconsistent filesystem case behavior between ${this.location_} and ${location}.`);
}
}
get() {
if (this.caseSensitive_ === undefined) {
throw new URIError("Error on TypiaGenerateWizard.generate(): unable to determine filesystem case behavior for this run.");
}
return create(this.caseSensitive_);
}
}
FileSystemIdentity.Policy = Policy;
function inspectDirectory(directory) {
return __awaiter(this, void 0, void 0, function* () {
const names = yield fs_1.default.promises.readdir(directory);
const entries = new Set(names);
for (const name of names) {
const alternate = alternateCase(name);
if (alternate === undefined)
continue;
if (entries.has(alternate))
return true;
try {
yield fs_1.default.promises.lstat(path_1.default.join(directory, alternate));
return false;
}
catch (error) {
if (isMissingFileError(error))
return true;
throw error;
}
}
return undefined;
});
}
FileSystemIdentity.inspectDirectory = inspectDirectory;
function probeDirectory(directory) {
return __awaiter(this, void 0, void 0, function* () {
const inspected = yield inspectDirectory(directory);
if (inspected !== undefined)
return inspected;
for (let attempt = 0; attempt < 10; ++attempt) {
const name = `.typia-case-probe-${process.pid}-${Date.now()}-${Math.random()
.toString(36)
.slice(2)}`.toLowerCase();
const original = path_1.default.join(directory, name);
const alternate = path_1.default.join(directory, name.toUpperCase());
let handle;
try {
handle = yield fs_1.default.promises.open(original, "wx");
}
catch (error) {
if (isAlreadyExistsError(error))
continue;
throw new URIError(`Error on TypiaGenerateWizard.generate(): unable to probe filesystem case behavior at ${directory}: ${formatUnknownError(error)}`);
}
let closed = false;
try {
yield handle.close();
closed = true;
try {
yield fs_1.default.promises.lstat(alternate);
return false;
}
catch (error) {
if (isMissingFileError(error))
return true;
throw error;
}
}
finally {
let cleanupError;
if (closed === false) {
try {
yield handle.close();
}
catch (error) {
cleanupError = error;
}
}
try {
yield fs_1.default.promises.unlink(original);
}
catch (error) {
if (isMissingFileError(error) === false)
cleanupError !== null && cleanupError !== void 0 ? cleanupError : (cleanupError = error);
}
if (cleanupError !== undefined) {
throw new URIError(`Error on TypiaGenerateWizard.generate(): unable to clean filesystem case probe ${original}: ${formatUnknownError(cleanupError)}`);
}
}
}
throw new URIError(`Error on TypiaGenerateWizard.generate(): unable to reserve a filesystem case probe at ${directory}.`);
});
}
FileSystemIdentity.probeDirectory = probeDirectory;
/**
* Builds the key that decides whether two paths are the same filesystem
* object.
*
* The identity is read from `fs.BigIntStats` rather than `fs.Stats`, because
* `fs.Stats.ino` is a JavaScript number. An NTFS file ID is `(sequenceNumber
* << 48) | mftRecordIndex` and routinely exceeds `Number.MAX_SAFE_INTEGER` —
* in a 4000-directory probe, 1879 of them did — where the spacing between
* representable doubles is 8 or more. Two entries sharing a sequence number
* with MFT record indices within that spacing round to one double. The
* traversal callers would then treat a distinct directory or file as already
* visited and skip it with no diagnostic, and the overwrite guard would
* refuse a legitimate output (samchon/typia#2269).
*
* A filesystem that reports no inode at all keeps the canonical path
* fallback, which is the only identity available there.
*
* @param stat Stats read with `{ bigint: true }`.
* @param realpath Canonical path of the same object.
* @returns Key that is equal for two paths only when they are one object.
*/
function identityKey(stat, realpath) {
// `BigInt(0)` rather than `0n`: this package compiles at ES2016, where a
// BigInt literal is a syntax error, while the constructor and the `bigint`
// type are fine.
return stat.ino === BigInt(0)
? `path:${path_1.default.normalize(realpath)}`
: `inode:${stat.dev}:${stat.ino}`;
}
FileSystemIdentity.identityKey = identityKey;
function alternateCase(name) {
let changed = false;
let output = "";
for (const character of name) {
if (character >= "a" && character <= "z") {
output += character.toUpperCase();
changed = true;
}
else if (character >= "A" && character <= "Z") {
output += character.toLowerCase();
changed = true;
}
else
output += character;
}
return changed ? output : undefined;
}
function isAlreadyExistsError(error) {
return (typeof error === "object" &&
error !== null &&
"code" in error &&
error.code === "EEXIST");
}
function isMissingFileError(error) {
return (typeof error === "object" &&
error !== null &&
"code" in error &&
error.code === "ENOENT");
}
function formatUnknownError(error) {
return error instanceof Error ? error.message : String(error);
}
})(FileSystemIdentity || (exports.FileSystemIdentity = FileSystemIdentity = {}));
const TS_PATTERN = /\.[cm]?tsx?$/;
const DTS_PATTERN = /\.d\.[cm]?tsx?$/;
//# sourceMappingURL=FileSystemIdentity.js.map