Skip to content

Commit c0da7ed

Browse files
committed
feat: support config parameter ranges with gaps
1 parent bfc80d7 commit c0da7ed

File tree

5 files changed

+154
-12
lines changed

5 files changed

+154
-12
lines changed

src/diagnostics/diagnostics.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ import * as vscode from "vscode";
33
export enum DiagnosticType {
44
UnnecessaryImportOverride,
55
ImportOverride,
6+
ValuesMinMaxConflict,
67
}
78

89
export type Diagnostic =
910
| UnnecessaryImportOverrideDiagnostic
10-
| ImportOverrideDiagnostic;
11+
| ImportOverrideDiagnostic
12+
| ValuesMinMaxConflictDiagnostic;
1113

1214
export interface UnnecessaryImportOverrideDiagnostic {
1315
type: DiagnosticType.UnnecessaryImportOverride;
@@ -20,3 +22,10 @@ export interface ImportOverrideDiagnostic {
2022
value: any;
2123
originalValue: any;
2224
}
25+
26+
export interface ValuesMinMaxConflictDiagnostic {
27+
type: DiagnosticType.ValuesMinMaxConflict;
28+
range: vscode.Range;
29+
localHasValues: boolean;
30+
templateHasValues: boolean;
31+
}

src/diagnostics/importOverrideDiagnostics.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,5 +98,81 @@ export function generateImportOverrideDiagnostics(
9898
}
9999
}
100100

101+
// Check for values/minValue-maxValue conflicts across import boundaries
102+
const valuesAndMinMaxConflicts = importsAndSymbolsAfter
103+
.map(([imp, s]) => {
104+
const importSpecifier = getPropertyValueFromNode(imp);
105+
if (typeof importSpecifier !== "string") return undefined;
106+
const resolvedImport = config.templates[importSpecifier];
107+
if (!resolvedImport) return undefined;
108+
109+
const properties = s
110+
.map((s) => config.getNodeFromSymbol(s))
111+
.filter(nodeIsPropertyNameOrValue)
112+
.map((n) => {
113+
return [
114+
getPropertyNameFromNode(n),
115+
n.parent.valueNode,
116+
n,
117+
] as const;
118+
})
119+
.filter(([name]) => {
120+
if (
121+
name === "values" &&
122+
("minValue" in resolvedImport ||
123+
"maxValue" in resolvedImport)
124+
) {
125+
return true;
126+
}
127+
128+
if (
129+
(name === "minValue" || name === "maxValue") &&
130+
"values" in resolvedImport
131+
) {
132+
return true;
133+
}
134+
135+
return false;
136+
});
137+
return [resolvedImport, properties] as const;
138+
})
139+
.filter(
140+
(conflict) =>
141+
conflict && conflict[1] != undefined && conflict[1].length > 0,
142+
);
143+
144+
for (const block of valuesAndMinMaxConflicts) {
145+
if (!block) continue;
146+
const [imp, properties] = block;
147+
148+
for (const [name, , propNode] of properties) {
149+
const templateHasMinMax = "minValue" in imp || "maxValue" in imp;
150+
const templateHasValues = "values" in imp;
151+
152+
// Template has minValue/maxValue, local has values -> error on values
153+
if (templateHasMinMax && name === "values") {
154+
ret.push({
155+
type: DiagnosticType.ValuesMinMaxConflict,
156+
range: rangeFromNode(config.original, propNode),
157+
localHasValues: true,
158+
templateHasValues: false,
159+
});
160+
}
161+
162+
// Template has values, local has minValue/maxValue -> error on minValue/maxValue
163+
if (
164+
templateHasValues &&
165+
(name === "minValue" || name === "maxValue")
166+
) {
167+
ret.push({
168+
type: DiagnosticType.ValuesMinMaxConflict,
169+
range: rangeFromNode(config.original, propNode),
170+
localHasValues: false,
171+
templateHasValues: true,
172+
});
173+
}
174+
}
175+
}
176+
101177
return ret;
102178
}

src/importOverrides/fixUnnecessary.ts

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ObjectPropertyASTNode, tryExpandPropertyRange } from "../astUtils";
33
import {
44
DiagnosticType,
55
UnnecessaryImportOverrideDiagnostic,
6+
ValuesMinMaxConflictDiagnostic,
67
} from "../diagnostics/diagnostics";
78
import { My } from "../my";
89

@@ -26,15 +27,36 @@ export function register(my: My): vscode.Disposable[] {
2627
d.type === DiagnosticType.UnnecessaryImportOverride,
2728
);
2829

29-
const vscodeDiags = unnecessaryOverrideDiagnostics.map((diag) => {
30-
const ret = new vscode.Diagnostic(
30+
const valuesMinMaxConflictDiagnostics = diag.filter(
31+
(d): d is ValuesMinMaxConflictDiagnostic =>
32+
d.type === DiagnosticType.ValuesMinMaxConflict,
33+
);
34+
35+
const vscodeDiags: vscode.Diagnostic[] = [];
36+
37+
for (const diag of unnecessaryOverrideDiagnostics) {
38+
const d = new vscode.Diagnostic(
3139
diag.range,
3240
"Unnecessary override of imported template property",
3341
vscode.DiagnosticSeverity.Warning,
3442
);
35-
ret.code = DiagnosticType.UnnecessaryImportOverride;
36-
return ret;
37-
});
43+
d.code = DiagnosticType.UnnecessaryImportOverride;
44+
vscodeDiags.push(d);
45+
}
46+
47+
for (const diag of valuesMinMaxConflictDiagnostics) {
48+
const message = diag.localHasValues
49+
? `The "values" field cannot be used when the imported template defines "minValue" or "maxValue".`
50+
: `"minValue"/"maxValue" cannot be used when the imported template defines "values".`;
51+
const d = new vscode.Diagnostic(
52+
diag.range,
53+
message,
54+
vscode.DiagnosticSeverity.Error,
55+
);
56+
d.code = DiagnosticType.ValuesMinMaxConflict;
57+
vscodeDiags.push(d);
58+
}
59+
3860
importDiagnostics.set(activeEditor.document.uri, vscodeDiags);
3961
}),
4062
);

src/panels/assets/style.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
display: grid;
8585
row-gap: 0.5rem;
8686
column-gap: 1rem;
87+
align-items: baseline;
8788
/* Label column as wide as necessary, value column fills the rest without overflow */
8889
grid-template-columns: max-content minmax(0, 1fr);
8990
}

src/panels/components/paramPreview.tsx

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,26 @@ export const ParamPreview: React.FC<ParamPreviewProps> = ({
3939

4040
const headerClass = "param-preview__header" + (hidden ? " hidden" : "");
4141

42+
// Process values field for display
43+
let valuesDisplay: string | null = null;
44+
if (param.values) {
45+
const parts: string[] = [];
46+
for (const def of param.values) {
47+
if ("value" in def) {
48+
parts.push(String(def.value));
49+
} else if ("range" in def && Array.isArray(def.range)) {
50+
const [from, to] = def.range;
51+
const { step } = def;
52+
let rangeText = `${from} to ${to}`;
53+
if (step && step !== 1) {
54+
rangeText += ` (step ${step})`;
55+
}
56+
parts.push(rangeText);
57+
}
58+
}
59+
valuesDisplay = parts.join(", ");
60+
}
61+
4262
return (
4363
<div className="param-preview">
4464
<div className="param-preview__number">
@@ -104,19 +124,33 @@ export const ParamPreview: React.FC<ParamPreviewProps> = ({
104124
{param.unsigned ? " (unsigned)" : " (signed)"}
105125
</span>
106126

107-
{/* Min/Max/Default */}
108-
{param.allowManualEntry !== false && (
127+
{/* Min/Max/Default or Values */}
128+
{valuesDisplay ? (
109129
<>
110-
<span>Range</span>
130+
<span>Allowed values</span>
111131
<span>
112-
{param.minValue} to {param.maxValue}
113-
{param.unit && ` ${param.unit}`}, default{" "}
132+
{valuesDisplay}
133+
{param.unit && ` ${param.unit}`} · default{" "}
114134
{param.defaultValue}
115135
{param.recommendedValue != undefined && (
116-
<>, recommended {param.recommendedValue}</>
136+
<> · recommended {param.recommendedValue}</>
117137
)}
118138
</span>
119139
</>
140+
) : (
141+
param.allowManualEntry !== false && (
142+
<>
143+
<span>Range</span>
144+
<span>
145+
{param.minValue} to {param.maxValue}
146+
{param.unit && ` ${param.unit}`} · default{" "}
147+
{param.defaultValue}
148+
{param.recommendedValue != undefined && (
149+
<> · recommended {param.recommendedValue}</>
150+
)}
151+
</span>
152+
</>
153+
)
120154
)}
121155

122156
{/* Options */}

0 commit comments

Comments
 (0)