Files
languageservices/actions-workflow-parser/src/model/converter/id-builder.ts
T
2023-01-09 19:02:19 -05:00

123 lines
3.2 KiB
TypeScript

const SEPARATOR = "_";
const MAX_ATTEMPTS = 1000;
const MAX_LENGTH = 100;
export class IdBuilder {
private name: string[] = [];
private readonly distinctNames: Set<string> = new Set();
public appendSegment(value: string) {
if (value.length === 0) {
return;
}
if (this.name.length == 0) {
const first = value[0];
if (this.isAlpha(first) || first == "_") {
// Legal first char
} else if (this.isNumeric(first) || first == "-") {
// Illegal first char, but legal char.
// Prepend "_".
this.name.push("_");
} else {
// Illegal char
}
} else {
// Separator
this.name.push(SEPARATOR);
}
for (const c of value) {
{
if (this.isAlphaNumeric(c) || c == "_" || c == "-") {
// Legal
this.name.push(c);
} else {
// Illegal
this.name.push(SEPARATOR);
}
}
}
}
public build(): string {
const original = this.name.length > 0 ? this.name.join("") : "job";
let suffix = "";
for (let attempt = 1; attempt < MAX_ATTEMPTS; attempt++) {
if (attempt === 1) {
suffix = "";
} else {
suffix = "_" + attempt;
}
const candidate = original.substring(0, Math.min(original.length, MAX_LENGTH - suffix.length)) + suffix;
if (!this.distinctNames.has(candidate)) {
this.distinctNames.add(candidate);
this.name = [];
return candidate;
}
}
throw new Error("Unable to create a unique name");
}
/**
* Adds a known identifier to the set of distinct ids.
* @param value The value to add
* @returns An error if the value is invalid, otherwise undefined
*/
public tryAddKnownId(value: string): string | undefined {
if (!value || !this.isValid(value) || value.length >= MAX_LENGTH) {
return `The identifier '${value}' is invalid. IDs may only contain alphanumeric characters, '_', and '-'. IDs must start with a letter or '_' and and must be less than ${MAX_LENGTH} characters.`;
}
if (value.startsWith(SEPARATOR + SEPARATOR)) {
return `The identifier '${value}' is invalid. IDs starting with '__' are reserved.`;
}
if (this.distinctNames.has(value)) {
return `The identifier '${value}' may not be used more than once within the same scope.`;
}
this.distinctNames.add(value);
return;
}
/**
* A name is valid if it starts with a letter or underscore, and contains only
* letters, numbers, underscores, and hyphens.
* @param name The string name to validate
* @returns Whether the name is valid
*/
private isValid(name: string): boolean {
let first = true;
for (const c of name) {
if (first) {
first = false;
if (!this.isAlpha(c) && c != "_") {
return false;
}
continue;
}
if (!this.isAlphaNumeric(c) && c != "_" && c != "-") {
return false;
}
}
return true;
}
private isAlphaNumeric(c: string): boolean {
return this.isAlpha(c) || this.isNumeric(c);
}
private isNumeric(c: string): boolean {
return c >= "0" && c <= "9";
}
private isAlpha(c: string): boolean {
return (c >= "a" && c <= "z") || (c >= "A" && c <= "Z");
}
}