UNPKG

env-prompt

Version:

A dependency-free utility that prompts you for your project's environment variables.

183 lines (137 loc) 6.29 kB
import { getColumn, getLine, Token } from "lib/env/lexer"; import { NewlineType } from "lib/options"; export interface FileCoordinates { line: number column: number position: number // TODO get rid of? } // Ts-jest doesn't support extending Error, so instead we have to implement it. // Otherwise, we get this error thrown in our tests when trying to call // setters on subclasses of Error: "(intermediate value).setToken is not a function" // // This is only an issue with ts-jest, but works fine in the code compiled by esbuild. export abstract class FileError implements Error { public readonly name: string = '' public readonly message: string = '' protected filePath: string public setFilePath(filePath: string): this { this.filePath = filePath return this } public getFilePath(): string { return this.filePath } } export class FileNotFoundError extends FileError {} export class LexicalError extends FileError { protected char: string protected coordinates: FileCoordinates public setChar(char: string): this { this.char = char return this } public getChar(): string { return this.char } public setCoordinates(coordinates: FileCoordinates): this { this.coordinates = coordinates return this } public getCoordinates(): FileCoordinates { return this.coordinates } } export abstract class ParseError extends FileError { protected token: Token public setToken(token: Token): this { this.token = token return this } public getToken(): Token { return this.token } } export class InvalidTokenAfterCommentError extends ParseError {} export class ExpectedAssignmentAfterIdentifierError extends ParseError {} export class UnexpectedTokenError extends ParseError {} export class InvalidTokenAfterIdentifierError extends ParseError {} export class DuplicateVariableError extends ParseError {} export class InvalidArgumentError implements Error { public readonly name: string = '' public readonly message: string = '' // TODO remove this and use "name" protected argumentName: string public setArgumentName(argumentName: string): this { this.argumentName = argumentName return this } public getArgumentName(): string { return this.argumentName } } export class InvalidNewlineTypeError extends InvalidArgumentError {} export class MissingArgumentValueError implements Error { public name: string = '' public readonly message: string = '' public setName (name: string): this { this.name = name return this } } export const getMessageForError = (error: Error): string => { const fileNotFound = error instanceof FileNotFoundError if (fileNotFound) return getMessageForFileNotFoundError(error as FileNotFoundError) const isLexicalError = error instanceof LexicalError if (isLexicalError) return getMessageForLexicalError(error as LexicalError) const isParseError = error instanceof ParseError if (isParseError) return getMessageForParseError(error as ParseError) const isInvalidArgumentError = error instanceof InvalidArgumentError if (isInvalidArgumentError) return getMessageForInvalidArgumentError(error as InvalidArgumentError) const isMissingArgumentValueError = error instanceof MissingArgumentValueError if (isMissingArgumentValueError) return getMessageForMissingArgumentValueError(error as MissingArgumentValueError) return `ERROR: ${(error).message}` } const getMessageForFileNotFoundError = (error: FileNotFoundError): string => { const filePath = error.getFilePath() return `Could not locate ${filePath}` } const getMessageForLexicalError = (error: LexicalError): string => { const filePath = error.getFilePath() const coordinates = error.getCoordinates() const positionDescription = getPositionDescription(filePath, coordinates) return `Unrecognized token ${positionDescription}` } const getMessageForParseError = (error: ParseError): string => { const token = error.getToken() const filePath = error.getFilePath() const positionDescription = getPositionDescription(filePath, token) const invalidTokenAfterComment = error instanceof InvalidTokenAfterCommentError if (invalidTokenAfterComment) return `Expected newline or end of document after comment ${positionDescription}` const expectedAssignmentAfterIdentifier = error instanceof ExpectedAssignmentAfterIdentifierError if (expectedAssignmentAfterIdentifier) return `Expected = after variable "${token.value}" ${positionDescription}` const unexpectedToken = error instanceof UnexpectedTokenError if (unexpectedToken) return `Unexpected ${token.value} ${positionDescription}` const invalidTokenAfterIdentifier = error instanceof InvalidTokenAfterIdentifierError if (invalidTokenAfterIdentifier) return `Expected line break or comment after variable declaration ${positionDescription}` const duplicateVariable = error instanceof DuplicateVariableError if (duplicateVariable) { // TODO put this var in other IFs? const positionDescription = getPositionDescription(filePath, token) return `Duplicate variable declaration "${token.value}" ${positionDescription}` } return `Unknown parse error ${positionDescription}` } const getMessageForInvalidArgumentError = (error: InvalidArgumentError) => { const isInvalidNewlineTypeError = error instanceof InvalidNewlineTypeError if (isInvalidNewlineTypeError) { const validTypes = Object.values(NewlineType) return `Invalid newline type. Valid types: "${validTypes.join('", "')}"` } const name = error.getArgumentName() return `Invalid argument ${name}` } const sanitizeArgumentName = (name: string): string => name.split('').filter(char => /^[-a-zA-Z]$/.test(char)).join('') const getMessageForMissingArgumentValueError = ({ name }: MissingArgumentValueError): string => `Missing value for argument ${sanitizeArgumentName(name)}` const getPositionDescription = (filePath: string, { line, column }: FileCoordinates): string => `at ${filePath}:${line}:${column}`