HintRuleKindPreprocessor.java
/*******************************************************************************
* Copyright (c) 2026 Carsten Hammer and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.sandbox.jdt.triggerpattern.internal;
import java.util.LinkedHashMap;
import java.util.Map;
import org.sandbox.jdt.triggerpattern.api.PatternKind;
import org.sandbox.jdt.triggerpattern.internal.HintFileParser.HintParseException;
/** Removes explicit {@code @kind:} metadata before compatibility parsing. */
final class HintRuleKindPreprocessor {
record Result(String parserSource, Map<String, PatternKind> kindsByRuleId) {
Result {
kindsByRuleId= Map.copyOf(kindsByRuleId);
}
}
private HintRuleKindPreprocessor() {
}
static Result preprocess(String source) throws HintParseException {
String normalized= source == null ? "" : source.replace("\r\n", "\n").replace('\r', '\n'); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
String[] lines= normalized.split("\n", -1); //$NON-NLS-1$
StringBuilder output= new StringBuilder(normalized.length());
Map<String, PatternKind> kinds= new LinkedHashMap<>();
String ruleId= null;
PatternKind ruleKind= null;
int kindLine= -1;
boolean inBlockComment= false;
boolean inEmbeddedJava= false;
for (int index= 0; index < lines.length; index++) {
String line= lines[index];
String trimmed= line.stripLeading();
boolean metadataVisible= true;
if (inEmbeddedJava) {
metadataVisible= false;
if (trimmed.contains("?>")) { //$NON-NLS-1$
inEmbeddedJava= false;
}
} else if (inBlockComment) {
metadataVisible= false;
if (trimmed.contains("*/")) { //$NON-NLS-1$
inBlockComment= false;
}
} else if (trimmed.startsWith("<?")) { //$NON-NLS-1$
metadataVisible= false;
inEmbeddedJava= !trimmed.contains("?>"); //$NON-NLS-1$
} else if (trimmed.startsWith("/*")) { //$NON-NLS-1$
metadataVisible= false;
inBlockComment= !trimmed.contains("*/"); //$NON-NLS-1$
} else if (trimmed.startsWith("//")) { //$NON-NLS-1$
metadataVisible= false;
}
boolean stripLine= false;
if (metadataVisible && trimmed.startsWith("@id:")) { //$NON-NLS-1$
String parsedId= trimmed.substring(4).trim();
if (parsedId.isBlank()) {
throw new HintParseException("Per-rule id must not be blank", index + 1); //$NON-NLS-1$
}
if (ruleId != null && !ruleId.equals(parsedId)) {
throw new HintParseException("Rule contains multiple @id values", index + 1); //$NON-NLS-1$
}
ruleId= parsedId;
} else if (metadataVisible && trimmed.startsWith("@kind:")) { //$NON-NLS-1$
if (ruleKind != null) {
throw new HintParseException("Rule contains multiple @kind values", index + 1); //$NON-NLS-1$
}
String value= trimmed.substring(6).trim();
try {
ruleKind= PatternKind.valueOf(value);
} catch (IllegalArgumentException exception) {
throw new HintParseException("Unknown pattern kind " + value, index + 1); //$NON-NLS-1$
}
kindLine= index + 1;
stripLine= true;
}
if (metadataVisible && ";;".equals(trimmed)) { //$NON-NLS-1$
if (ruleKind != null) {
if (ruleId == null) {
throw new HintParseException("@kind requires an explicit @id in the same rule", //$NON-NLS-1$
kindLine);
}
PatternKind previous= kinds.putIfAbsent(ruleId, ruleKind);
if (previous != null && previous != ruleKind) {
throw new HintParseException("Conflicting @kind values for rule " + ruleId, kindLine); //$NON-NLS-1$
}
}
ruleId= null;
ruleKind= null;
kindLine= -1;
}
if (!stripLine) {
output.append(line);
}
if (index + 1 < lines.length) {
output.append('\n');
}
}
if (ruleKind != null) {
throw new HintParseException("Rule with @kind is missing ';;' terminator", kindLine); //$NON-NLS-1$
}
return new Result(output.toString(), kinds);
}
}