import path from 'path' import type { SourceFile } from './SourceFile.js' import type { Type } from './types/Type.js' import { getTypeAs } from './types/getTypeAs.js' import { OptionalType } from './types/OptionalType.js' import { ArrayType } from './types/ArrayType.js' type Comment = '///' | '#' export function createFileMetadataString( filename: string, comment: Comment = '///' ): string { return ` ${comment} ${comment} ${filename} ${comment} This file was generated by nitrogen. DO NOT MODIFY THIS FILE. ${comment} https://github.com/mrousavy/nitro ${comment} Copyright © Marc Rousavy @ Margelo ${comment} `.trim() } export function isFunction(type: Type): boolean { switch (type.kind) { case 'function': return true case 'optional': { const optional = getTypeAs(type, OptionalType) return isFunction(optional.wrappingType) } default: return false } } export function toReferenceType(type: string): `const ${typeof type}&` { return `const ${type}&` } export function escapeCppName(string: string): string { // Replace non-alphanumeric characters with underscores let escapedStr = string.replace(/[^a-zA-Z0-9_]/g, '_') // Ensure the first character is a letter or underscore if (!/^[a-zA-Z_]/.test(escapedStr)) { escapedStr = '_' + escapedStr } return escapedStr } /** * Get a type's "looselyness". * The concept is as follows: * - If a `type` is easy to runtime-check in JSI (e.g. a `number` is just `.isNumber()`), * it should have a very low looselyness value returned here. * - If a `type` is hard to runtime-check in JSI (e.g. `AnyMap` can have any key), * it should have a very high looselyness value returned here. * - Type's looselyness values are compared against each other to determine in which * order they should be runtime type-checked - e.g. `AnyMap` should be last (as it can * always be `true` if it's just an `object`), and `Promise` should be sooner (as it can * be `instanceof` checked) * - As a performance optimization, rank faster checks earlier (e.g. `isNumber()` before * `instanceof`, as `instanceof` requires global constructor lookup) */ function getTypeLooselyness(type: Type): number { switch (type.kind) { case 'array-buffer': // We have `.isArrayBuffer()` return 0 case 'int64': case 'uint64': // We have `.isBigInt()` return 0 case 'boolean': // We have `.isBool()` return 0 case 'null': // We have `isNull()` return 0 case 'number': // We have `isNumber()` (but it can also be an enum) return 1 case 'optional': // We have `isUndefined()` return 0 case 'function': // We have `.isFunction()` return 0 case 'string': // We have `.isString()` (but it can also be a union) return 1 case 'enum': // We have `.isNumber()` or `.isString()` and is within range, only // if it's not within range, it can fallback to `string` or `number` return 0 case 'array': // We have `.isArray()` - but it should extend the item type's looselyness const arrayType = getTypeAs(type, ArrayType) return 0.5 * getTypeLooselyness(arrayType.itemType) case 'hybrid-object': // We have `getNativeState()` return 0 case 'hybrid-object-base': // We have `getNativeState()` return 1 case 'tuple': // We have `.isArray()` and `canConvert()` return 1 case 'date': // We have `instanceof Date` return 2 case 'error': // We have `instanceof Error` return 2 case 'promise': // We have `instanceof Promise` return 2 case 'custom-type': // Up to the user return 1 case 'record': // Super loose, only type counts return 3 case 'map': // This is just super loose return 4 case 'struct': // We check each property individually return 1 case 'variant': // Pretty loose return 2 case 'result-wrapper': // Not loose at all return 0 case 'void': // Not a type return 0 } } /** * Compares the "looselyness" of the types. * Returns a positive number if {@linkcode a} is more loose than {@linkcode b}, * and a negative number if otherwise. */ export function compareLooselyness(a: Type, b: Type): number { return getTypeLooselyness(a) - getTypeLooselyness(b) } export function isNotDuplicate(item: T, index: number, array: T[]): boolean { return array.indexOf(item) === index } export function isCppFile(file: SourceFile): boolean { return file.name.endsWith('cpp') || file.name.endsWith('c') } export function getRelativeDirectory(file: SourceFile): string { return path.join( '..', 'nitrogen', 'generated', file.platform, file.language, ...file.subdirectory, file.name ) } export function getRelativeDirectoryGenerated(...subpath: string[]): string { return path.join('..', 'nitrogen', 'generated', ...subpath) }