-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(validation): Validate script and module names
- Loading branch information
1 parent
9950b74
commit 639e5b4
Showing
7 changed files
with
147 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
export enum IdentType { | ||
ModuleScripts = "snake_case", | ||
Errors = "SCREAMING_SNAKE_CASE", | ||
} | ||
|
||
/** | ||
* A record of regular expressions for each identifier type. | ||
* | ||
* `regexes[identType].test(ident)` will return whether `ident` is a valid | ||
* `identType`. | ||
*/ | ||
export const regexes: Record<IdentType, RegExp> = { | ||
[IdentType.ModuleScripts]: /^[a-z]+(_[a-z0-9]+)*$/, | ||
[IdentType.Errors]: /^[A-Z]+(_[A-Z0-9]+)*$/, | ||
}; | ||
|
||
/** | ||
* A regular expression that matches a string of only printable ASCII | ||
* characters. | ||
* | ||
* Printable ASCII characters range from 0x20 to 0x7e, or space to tilde. | ||
*/ | ||
export const printableAsciiRegex = /^[\x20-\x7E]+$/; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { brightRed, bold } from "https://deno.land/std@0.208.0/fmt/colors.ts"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import { printableAsciiRegex } from "./defs.ts"; | ||
import { brightRed, bold } from "./deps.ts"; | ||
|
||
export class IdentError extends Error { | ||
public constructor(private issue: string, private identifier: string) { | ||
super(`"${debugIdentifier(identifier)}": ${issue}`); | ||
|
||
this.name = "IdentifierError"; | ||
} | ||
|
||
public toString(identifierType = "identifier") { | ||
const highlightedIdentifier = brightRed(bold(debugIdentifier(this.identifier))); | ||
return `invalid ${identifierType} ${highlightedIdentifier}: ${this.issue}`; | ||
|
||
} | ||
} | ||
|
||
/** | ||
* Converts an identifier into a string that can be printed to the console. | ||
* | ||
* This function should be used when working with unverified | ||
* module/script/error names, as it is guaranteed it won't mess up the | ||
* terminal or obscure logging with control characters. | ||
* | ||
* Examples: | ||
* ```ts | ||
* IdentError.debugIdentifier("hello") // "hello" | ||
* IdentError.debugIdentifier("hello\x20world") // "hello world" | ||
* IdentError.debugIdentifier("hello\x7fworld") // "hello\7fworld" | ||
* IdentError.debugIdentifier("hello_wòrld") // "hello_w\xf2rld" | ||
* | ||
* @param ident The identifier to be converted into a safe debugged string | ||
* @returns A string containing all string-safe printable ascii characters, with | ||
* non-printable characters escaped as hex or unicode. | ||
*/ | ||
function debugIdentifier(ident: string): string { | ||
const lenLimited = ident.length > 32 ? ident.slice(0, 32) + "..." : ident; | ||
const characters = lenLimited.split(""); | ||
|
||
let output = ""; | ||
for (const char of characters) { | ||
// If the character is printable without any special meaning, just | ||
// add it. | ||
if (printableAsciiRegex.test(char) && char !== "\\" && char !== '"') { | ||
output += char; | ||
continue; | ||
} | ||
|
||
// Escape `\` and `"` characters to reduce ambiguity | ||
if (char === "\\") { | ||
output += "\\\\"; | ||
continue; | ||
} else if (char === '"') { | ||
output += '\\"'; | ||
continue; | ||
} | ||
|
||
// Escape non-printable characters | ||
const charCode = char.charCodeAt(0); | ||
if (charCode > 255) { // 16-bit unicode escape | ||
output += `\\u${charCode.toString(16).padStart(4, "0")}`; | ||
} else { // 8-bit ascii/extended-ascii escape | ||
output += `\\x${charCode.toString(16).padStart(2, "0")}`; | ||
} | ||
} | ||
return `"${output}"`; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { IdentType, printableAsciiRegex, regexes } from "./defs.ts"; | ||
import { IdentError } from "./errors.ts"; | ||
|
||
export function validateIdentifier( | ||
ident: string, | ||
identType: IdentType, | ||
): IdentError | null { | ||
if (ident.length < 1 || ident.length > 32) { | ||
return new IdentError("must be between 1 and 32 characters", ident); | ||
} | ||
|
||
if (!printableAsciiRegex.test(ident)) { | ||
return new IdentError("must contain only printable ASCII characters", ident); | ||
} | ||
|
||
const regex = regexes[identType]; | ||
if (!regex.test(ident)) { | ||
return new IdentError(`must be ${identType} (match the pattern ${regex})`, ident); | ||
} | ||
|
||
return null; | ||
}; |