Add safe Consumer array methods
MODIFY
packages/@romefrontend/cli-flags/Parser.ts
packages/@romefrontend/cli-flags/Parser.ts
@@ -235,6 +235,11 @@ export default class Parser<T> {
return;
}
+ // These flags are ambiguous with how we handle `--no-` booleans
+ if (toKebabCase(key).startsWith("no-")) {
+ throw new Error(`CLI flag ${key} cannot start with "no"`);
+ }
+
const value = flags[key];
// Allow omitting a string flag value
@@ -249,6 +254,13 @@ export default class Parser<T> {
});
defaultFlags[key] = (def.default as FlagValue);
+ // Automatically convert number strings
+ if (def.type === "number" && typeof value !== "number") {
+ if (valueConsumer.exists() || def.required) {
+ valueConsumer.setValue(valueConsumer.asNumberString());
+ }
+ }
+
// We've parsed arguments like `--foo bar` as `{foo: 'bar}`
// However, --foo may be a boolean flag, so `bar` needs to be correctly added to args
if (

MODIFY
packages/@romefrontend/cli-layout/grid/formatANSI.ts
packages/@romefrontend/cli-layout/grid/formatANSI.ts
@@ -173,11 +173,11 @@ function getTokenColors(consumer: undefined | Consumer): Theme {
};
tokenColorsCache.set(consumer, theme);
- for (const prop of consumer.get("tokenColors").asArray()) {
+ for (const prop of consumer.get("tokenColors").asIterable()) {
const settings = prop.get("settings");
const scope = prop.get("scope");
const scopes = Array.isArray(scope.asUnknown())
- ? scope.asArray().map((elem) => elem.asString())
+ ? scope.asMappedArray((elem) => elem.asString())
: scope.asString().split(",").map((scope) => scope.trim());
for (const scope of scopes) {

MODIFY
packages/@romefrontend/cli/cli.ts
packages/@romefrontend/cli/cli.ts
@@ -101,13 +101,13 @@ export default async function cli() {
{
description: "Change the display width. By default it is automatically inferred and updated from the terminal.",
},
- ).asPossibleNumberString().asOneIndexedNumberOrVoid(),
+ ).asOneIndexedNumberOrVoid(),
colorDepth: c.get(
"outputColorDepth",
{
description: "Change the display width. By default it is automatically inferred and updated from the terminal.",
},
- ).asPossibleNumberString().asNumberSetOrVoid([1, 4, 8, 24]),
+ ).asNumberSetOrVoid([1, 4, 8, 24]),
redirectError: c.get(
"outputRedirectError",
{

MODIFY
packages/@romefrontend/cli/testWorker.ts
packages/@romefrontend/cli/testWorker.ts
@@ -17,7 +17,7 @@ export default async function testWorker() {
programName: "rome test-worker",
defineFlags(c): TestWorkerFlags {
return {
- inspectorPort: c.get("inspectorPort").asPossibleNumberString().asNumber(),
+ inspectorPort: c.get("inspectorPort").asNumber(),
};
},
});

MODIFY
packages/@romefrontend/codec-js-manifest/index.ts
packages/@romefrontend/codec-js-manifest/index.ts
@@ -97,7 +97,7 @@ function normalizeStringArray(consumer: Consumer, loose: boolean): Array<string>
}
}
- return consumer.asArray().map((item) => item.asString());
+ return consumer.asMappedArray((item) => item.asString());
} else {
return [];
}
@@ -193,7 +193,7 @@ function normalizeLicense(
// Support some legacy ways of specifying licenses: https://docs.npmjs.com/files/package.json#license
const raw = licenseProp.asUnknown();
if (loose && Array.isArray(raw)) {
- const licenseIds = licenseProp.asArray().map((consumer) =>
+ const licenseIds = licenseProp.asMappedArray((consumer) =>
extractLicenseFromObjectConsumer(consumer)[0]
);
licenseId = `(${licenseIds.join(" OR ")})`;
@@ -347,7 +347,7 @@ function normalizePeople(
const people: Array<ManifestPerson> = [];
- for (const item of consumer.asArray()) {
+ for (const item of consumer.asIterable()) {
people.push(normalizePerson(item, loose));
}
@@ -498,7 +498,7 @@ function normalizeExportsConditions(value: Consumer): ManifestExportConditions {
);
} else if (Array.isArray(unknown)) {
// Find the first item that passes validation
- for (const elem of value.asArray()) {
+ for (const elem of value.asIterable()) {
const {consumer, diagnostics} = elem.capture();
const result = normalizeExportsConditions(consumer);
if (diagnostics.length === 0) {

MODIFY
packages/@romefrontend/codec-json/stringify.ts
packages/@romefrontend/codec-json/stringify.ts
@@ -164,7 +164,7 @@ function stringifyArray(consumer: Consumer, info: StringifyObjectOptions) {
let buff: Array<string> = [];
- const arr = consumer.asArray();
+ const arr = consumer.asIterable();
for (const consumer of arr) {
// Add element comments
const comments = getComments(consumer, info).outer;

MODIFY
packages/@romefrontend/consume/Consumer.ts
packages/@romefrontend/consume/Consumer.ts
@@ -611,6 +611,11 @@ export default class Consumer {
return this.fork(key, value[key], metadata);
}
+ getIndex(index: number): Consumer {
+ const arr = this.asPlainArray();
+ return this.fork(index, arr[index]);
+ }
+
markUsedProperty(name: string) {
this.usedNames.add(name);
}
@@ -649,37 +654,34 @@ export default class Consumer {
}
}
- asPossibleNumberString(def?: number): Consumer {
+ asNumberString(def?: string): number {
this.declareDefinition({
type: "number",
default: def,
required: def === undefined,
});
- if (this.exists()) {
- const str = this.asUndeclaredString();
+ const str = this.asString(def);
const num = parseFloat(str);
if (isNaN(num)) {
this.unexpected(descriptions.CONSUME.EXPECTED_VALID_NUMBER);
+ return 0;
} else {
- return this.cloneConsumer({
- value: num,
- });
+ return num;
}
}
- return this.cloneConsumer({
- value: def,
+ asNumberStringOrVoid(): undefined | number {
+ this.declareDefinition({
+ type: "number",
+ default: undefined,
+ required: false,
});
- }
- asPossibleParsedJSON(): Consumer {
- if (typeof this.asUnknown() === "string") {
- return this.cloneConsumer({
- value: JSON.parse(this.asString()),
- });
+ if (this.exists()) {
+ return this.asNumberString();
} else {
- return this;
+ return undefined;
}
}
@@ -712,7 +714,7 @@ export default class Consumer {
asJSONArray(): JSONArray {
const arr: JSONArray = [];
- for (const value of this.asArray()) {
+ for (const value of this.asIterable()) {
arr.push(value.asJSONValue());
}
return arr;
@@ -812,7 +814,7 @@ export default class Consumer {
return [...value];
}
- asArray(optional?: boolean): Array<Consumer> {
+ asIterable(optional?: boolean): Iterable<Consumer> {
const arr = this.asPlainArray(optional);
return arr.map((val, index) => {
@@ -820,11 +822,15 @@ export default class Consumer {
});
}
- asImplicitArray(): Array<Consumer> {
+ asMappedArray<T>(callback: (c: Consumer) => T): Array<T> {
+ return Array.from(this.asIterable(), callback);
+ }
+
+ asImplicitMappedArray<T>(callback: (c: Consumer) => T): Array<T> {
if (Array.isArray(this.asUnknown())) {
- return this.asArray();
+ return this.asMappedArray(callback);
} else if (this.exists()) {
- return [this];
+ return [callback(this)];
} else {
return [];
}
@@ -837,7 +843,7 @@ export default class Consumer {
required: false,
});
if (this.exists()) {
- return this.asUndeclaredDate();
+ return this.asDate();
} else {
return undefined;
}
@@ -849,10 +855,6 @@ export default class Consumer {
default: def,
required: def === undefined,
});
- return this.asUndeclaredDate(def);
- }
-
- asUndeclaredDate(def?: Date): Date {
const value = this.getValue(def);
if (!(value instanceof Date)) {
this.unexpected(descriptions.CONSUME.EXPECTED_DATE);
@@ -868,7 +870,7 @@ export default class Consumer {
required: false,
});
if (this.exists()) {
- return this.asUndeclaredBoolean();
+ return this.asBoolean();
} else {
return undefined;
}
@@ -880,10 +882,6 @@ export default class Consumer {
default: def,
required: def === undefined,
});
- return this.asUndeclaredBoolean(def);
- }
-
- asUndeclaredBoolean(def?: boolean): boolean {
const value = this.getValue(def);
if (typeof value !== "boolean") {
this.unexpected(descriptions.CONSUME.EXPECTED_BOOLEAN);
@@ -900,7 +898,7 @@ export default class Consumer {
});
if (this.exists()) {
- return this.asUndeclaredString();
+ return this.asString();
} else {
return undefined;
}
@@ -912,10 +910,7 @@ export default class Consumer {
default: def,
required: def === undefined,
});
- return this.asUndeclaredString(def);
- }
- asUndeclaredString(def?: string): string {
const value = this.getValue(def);
if (typeof value !== "string") {
this.unexpected(descriptions.CONSUME.EXPECTED_STRING);
@@ -934,14 +929,8 @@ export default class Consumer {
required: def === undefined,
allowedValues: validValues,
});
- return this.asUndeclaredStringSet(validValues, def);
- }
- asUndeclaredStringSet<ValidValue extends string>(
- validValues: Array<ValidValue>,
- def?: ValidValue,
- ): ValidValue {
- const value = this.asUndeclaredString(def);
+ const value = this.asString(def);
// @ts-ignore
if (validValues.includes(value)) {
@@ -973,7 +962,7 @@ export default class Consumer {
});
if (this.exists()) {
- return this.asUndeclaredStringSet(validValues);
+ return this.asStringSet(validValues);
} else {
return undefined;
}
@@ -986,7 +975,7 @@ export default class Consumer {
required: false,
});
if (this.exists()) {
- return this.asUndeclaredBigInt();
+ return this.asBigInt();
} else {
return undefined;
}
@@ -998,10 +987,7 @@ export default class Consumer {
default: def,
required: def === undefined,
});
- return this.asUndeclaredBigInt(def);
- }
- asUndeclaredBigInt(def?: number | bigint): bigint {
const value = this.getValue(def);
if (typeof value === "number") {
@@ -1056,7 +1042,7 @@ export default class Consumer {
"path",
);
- return createUnknownFilePath(this.asUndeclaredString(def));
+ return createUnknownFilePath(this.asString(def));
}
asUnknownFilePathOrVoid(): undefined | UnknownFilePath {
@@ -1136,7 +1122,7 @@ export default class Consumer {
});
if (this.exists()) {
- return this.asUndeclaredNumber();
+ return this.asNumber();
} else {
return undefined;
}
@@ -1166,7 +1152,13 @@ export default class Consumer {
default: def,
required: def === undefined,
});
- return this.asUndeclaredNumber(def);
+
+ const value = this.getValue(def);
+ if (typeof value !== "number") {
+ this.unexpected(descriptions.CONSUME.EXPECTED_NUMBER);
+ return 0;
+ }
+ return value;
}
asNumberInRange(
@@ -1200,18 +1192,20 @@ export default class Consumer {
default?: UnknownNumber;
},
): UnknownNumber {
- const num = this.asUndeclaredNumber(opts.default);
const min = ob1Get(opts.min);
const max = ob1Get(opts.max);
+ const def = ob1Get(opts.default);
this.declareDefinition({
type: "number",
- default: opts.default,
- required: opts.default !== undefined,
+ default: def,
+ required: def !== undefined,
min,
max,
});
+ const num = this.asNumber(def);
+
// Nice error message when both min and max are specified
if (min !== undefined && max !== undefined && (num < min || num > max)) {
this.unexpected(descriptions.CONSUME.EXPECTED_NUMBER_BETWEEN(min, max));
@@ -1229,15 +1223,6 @@ export default class Consumer {
return num;
}
- asUndeclaredNumber(def?: UnknownNumber): number {
- const value = this.getValue(def);
- if (typeof value !== "number") {
- this.unexpected(descriptions.CONSUME.EXPECTED_NUMBER);
- return 0;
- }
- return value;
- }
-
asNumberSet<ValidValue extends number>(
validValues: Array<ValidValue>,
def?: ValidValue,
@@ -1248,14 +1233,8 @@ export default class Consumer {
required: def === undefined,
allowedValues: validValues,
});
- return this.asUndeclaredNumberSet(validValues, def);
- }
- asUndeclaredNumberSet<ValidValue extends number>(
- validValues: Array<ValidValue>,
- def?: ValidValue,
- ): ValidValue {
- const value = this.asUndeclaredNumber(def);
+ const value = this.asNumber(def);
// @ts-ignore
if (validValues.includes(value)) {
@@ -1287,7 +1266,7 @@ export default class Consumer {
});
if (this.exists()) {
- return this.asUndeclaredNumberSet(validValues);
+ return this.asNumberSet(validValues);
} else {
return undefined;
}

MODIFY
packages/@romefrontend/core/client/commands/run.ts
packages/@romefrontend/core/client/commands/run.ts
@@ -59,7 +59,7 @@ export default createLocalCommand({
case "executeCode": {
const {syntaxError} = await executeMain({
// Remove the first argument which will be the file path
- args: data.get("args").asArray().map((item) => item.asString()),
+ args: data.get("args").asMappedArray((item) => item.asString()),
path: createAbsoluteFilePath(data.get("filename").asString()),
code: data.get("code").asString(),
sourceMap: SourceMapConsumer.fromJSON(data.get("map").asAny()),

MODIFY
packages/@romefrontend/core/server/commands/check.ts
packages/@romefrontend/core/server/commands/check.ts
@@ -36,7 +36,7 @@ export default createServerCommand<Flags>({
examples: [],
defineFlags(consumer: Consumer): Flags {
return {
- decisions: consumer.get("decisions").asImplicitArray().map((item) =>
+ decisions: consumer.get("decisions").asImplicitMappedArray((item) =>
item.asString()
),
apply: consumer.get(

MODIFY
packages/@romefrontend/core/server/commands/config.ts
packages/@romefrontend/core/server/commands/config.ts
@@ -123,7 +123,7 @@ export default createServerCommand<Flags>({
switch (action) {
case "push": {
keyConsumer.setValue([
- ...keyConsumer.asArray(true).map((c) => c.asUnknown()),
+ ...Array.from(keyConsumer.asIterable(true), (c) => c.asUnknown()),
...(Array.isArray(value) ? value : []),
]);
break;

MODIFY
packages/@romefrontend/core/server/lsp/LSPServer.ts
packages/@romefrontend/core/server/lsp/LSPServer.ts
@@ -245,7 +245,7 @@ export default class LSPServer {
const workspaceDirectories = params.get("workspaceDirectories");
if (workspaceDirectories.exists()) {
- for (const elem of workspaceDirectories.asArray()) {
+ for (const elem of workspaceDirectories.asIterable()) {
await this.initProject(getPathFromTextDocument(elem));
}
}
@@ -304,10 +304,10 @@ export default class LSPServer {
async handleNotification(method: string, params: Consumer): Promise<void> {
switch (method) {
case "workspace/didChangeWorkspaceDirectories": {
- for (const elem of params.get("added").asArray()) {
+ for (const elem of params.get("added").asIterable()) {
await this.initProject(getPathFromTextDocument(elem));
}
- for (const elem of params.get("removed").asArray()) {
+ for (const elem of params.get("removed").asIterable()) {
this.unwatchProject(getPathFromTextDocument(elem));
}
break;
@@ -326,11 +326,11 @@ export default class LSPServer {
const path = getPathFromTextDocument(params.get("textDocument"));
const contentChanges = params.get("contentChanges");
- if (contentChanges.asArray()[0].has("range")) {
+ if (contentChanges.getIndex(0).has("range")) {
const patches = getWorkerBufferPatches(contentChanges);
await this.request.requestWorkerPatchBuffer(path, patches);
} else {
- const content = contentChanges.asArray()[0].get("text").asString();
+ const content = contentChanges.getIndex(0).get("text").asString();
await this.request.requestWorkerUpdateBuffer(path, content);
}
break;

MODIFY
packages/@romefrontend/core/server/lsp/utils.ts
packages/@romefrontend/core/server/lsp/utils.ts
@@ -159,7 +159,7 @@ export function diffTextEdits(
export function getWorkerBufferPatches(
contentChanges: Consumer,
): Array<WorkerBufferPatch> {
- return contentChanges.asArray().map((change) => {
+ return contentChanges.asMappedArray((change) => {
const start = change.get("range").get("start");
const end = change.get("range").get("end");

MODIFY
packages/@romefrontend/core/server/testing/TestServerRunner.ts
packages/@romefrontend/core/server/testing/TestServerRunner.ts
@@ -556,7 +556,10 @@ export default class TestServerRunner {
const frames: Array<ErrorFrame> = [];
- const callFrames = params.get("callFrames").asArray().slice(0, 20);
+ const callFrames = Array.from(params.get("callFrames").asIterable()).slice(
+ 0,
+ 20,
+ );
for (const callFrame of callFrames) {
const loc = callFrame.get("location");
@@ -566,7 +569,7 @@ export default class TestServerRunner {
loc.get("columnNumber").asZeroIndexedNumber(),
);
- const name = callFrame.get("scopeChain").asArray()[0].get("name").asString(
+ const name = callFrame.get("scopeChain").getIndex(0).get("name").asString(
"",
).split("$").pop();

MODIFY
packages/@romefrontend/project/load.ts
packages/@romefrontend/project/load.ts
@@ -295,7 +295,7 @@ export async function normalizeProjectConfig(
}
if (files.has("assetExtensions")) {
- config.files.assetExtensions = files.get("assetExtensions").asArray().map((
+ config.files.assetExtensions = files.get("assetExtensions").asMappedArray((
item,
) => item.asString());
}
@@ -317,7 +317,7 @@ export async function normalizeProjectConfig(
if (categoryExists(targets)) {
for (const [name, object] of targets.asMap()) {
const target: ProjectConfigTarget = {
- constraints: object.get("constraints").asImplicitArray().map((item) =>
+ constraints: object.get("constraints").asImplicitMappedArray((item) =>
item.asString()
),
};

MODIFY
packages/@romefrontend/project/utils.ts
packages/@romefrontend/project/utils.ts
@@ -31,7 +31,7 @@ export function assertHardMeta(meta: ProjectConfigMeta): ProjectConfigMetaHard {
export function arrayOfStrings(consumer: Consumer): Array<string> {
if (consumer.exists()) {
- return consumer.asArray().map((item) => item.asString());
+ return consumer.asMappedArray((item) => item.asString());
} else {
return [];
}
@@ -39,7 +39,7 @@ export function arrayOfStrings(consumer: Consumer): Array<string> {
export function arrayOfPatterns(consumer: Consumer): PathPatterns {
// TODO consumer.handleThrownDiagnostics
- return consumer.asArray().map((item) => {
+ return consumer.asMappedArray((item) => {
return parsePathPattern({
path: consumer.filename,
input: item.asString(),

Add safe Consumer array methods (#855)