Skip to content

Commit 1e5884e

Browse files
fix: improve cross-validation normalizer (103→105 matches) (#235)
Three normalizer improvements: 1. Labeled expressions: prefix_expression with label child is now transparent (matches PSI's LABELED_EXPRESSION which is skipped). Fixes SimpleExpressions. 2. Comment-only bodies: control_structure_body containing only comments now produces empty BLOCK instead of being dropped. Fixes CommentsBindingInStatementBlock. 3. Destructuring declarations: DESTRUCTURING_DECLARATION_ENTRY added to SKIP_PSI_NODES, and DESTRUCTURING_DECLARATION children wrapped in VALUE_PARAMETER in lambda parameter lists. Reduces destructuringInLambdas diffs from 20 to 19. 4. Property accessor cap: _nestPropertyAccessors now limits to 2 accessors per property, matching PSI error recovery behavior. Match rate: 103/126 (81.7%) → 105/126 (83.3%)
1 parent c1010e0 commit 1e5884e

File tree

2 files changed

+44
-2
lines changed

2 files changed

+44
-2
lines changed

tools/cross-validation/mapping.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,9 @@ const SKIP_PSI_NODES = new Set([
310310

311311
// DYNAMIC_TYPE — Kotlin/JS specific
312312
'DYNAMIC_TYPE',
313+
314+
// DESTRUCTURING_DECLARATION_ENTRY — PSI wraps each entry, TS uses variable_declaration (transparent)
315+
'DESTRUCTURING_DECLARATION_ENTRY',
313316
]);
314317

315318
// ---------------------------------------------------------------------------

tools/cross-validation/normalizer.js

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,17 +81,31 @@ function _normalizeTsMulti(node) {
8181
return [new Node('IS_EXPRESSION', children)];
8282
}
8383

84+
// prefix_expression with label → transparent (matches PSI's LABELED_EXPRESSION)
85+
if (tsName === 'prefix_expression') {
86+
const hasLabel = node.children.some(c => c.name === 'label');
87+
if (hasLabel) {
88+
return children;
89+
}
90+
}
91+
8492
// control_structure_body → BLOCK only for actual blocks, transparent otherwise
8593
if (tsName === 'control_structure_body') {
8694
const hasStatements = node.children.some(c => c.name === 'statements');
8795
const hasNonCommentChildren = node.children.some(c =>
8896
c.name !== 'statements' && c.name !== 'line_comment' && c.name !== 'multiline_comment'
8997
);
98+
const hasOnlyComments = !hasStatements && !hasNonCommentChildren &&
99+
node.children.some(c => c.name === 'line_comment' || c.name === 'multiline_comment');
90100
if (!hasStatements && hasNonCommentChildren) {
91101
// Single-expression body — transparent
92102
if (children.length === 1) return [children[0]];
93103
if (children.length === 0) return [];
94104
}
105+
// Comment-only body → produce empty BLOCK (PSI keeps BLOCK with comments)
106+
if (hasOnlyComments && children.length === 0) {
107+
return [new Node('BLOCK')];
108+
}
95109
if (children.length === 0) return [];
96110
return [new Node('BLOCK', children)];
97111
}
@@ -166,6 +180,7 @@ function _normalizeTsMulti(node) {
166180
if (psiName === 'VALUE_PARAMETER_LIST') {
167181
children = _absorbIntoValueParameters(children);
168182
children = _wrapBareTypesInValueParameter(children);
183+
children = _wrapDestructuringInValueParameter(children);
169184
}
170185

171186
// PARENTHESIZED → unwrap in type contexts (contains only type/annotation children)
@@ -501,6 +516,7 @@ function _wrapBareTypesInValueParameter(children) {
501516

502517
/**
503518
* Move PROPERTY_ACCESSOR nodes after PROPERTY into the preceding PROPERTY as children.
519+
* Caps at 2 accessors per property (getter + setter) to match PSI error recovery.
504520
* @param {Node[]} children
505521
* @returns {Node[]}
506522
*/
@@ -514,8 +530,13 @@ function _nestPropertyAccessors(children) {
514530
if (child.name === 'PROPERTY_ACCESSOR' && result.length > 0) {
515531
const last = result[result.length - 1];
516532
if (last.name === 'PROPERTY') {
517-
// Create new node to avoid mutation
518-
result[result.length - 1] = new Node('PROPERTY', last.children.concat([child]));
533+
const accessorCount = last.children.filter(c => c.name === 'PROPERTY_ACCESSOR').length;
534+
if (accessorCount < 2) {
535+
// Create new node to avoid mutation
536+
result[result.length - 1] = new Node('PROPERTY', last.children.concat([child]));
537+
continue;
538+
}
539+
// More than 2 accessors: drop extra (PSI treats them as errors)
519540
continue;
520541
}
521542
}
@@ -677,4 +698,22 @@ function _stripAnnotationWrappers(children) {
677698
return result;
678699
}
679700

701+
/**
702+
* Wrap DESTRUCTURING_DECLARATION nodes in VALUE_PARAMETER inside VALUE_PARAMETER_LIST.
703+
* PSI wraps each destructured lambda parameter as VALUE_PARAMETER > DESTRUCTURING_DECLARATION.
704+
* @param {Node[]} children
705+
* @returns {Node[]}
706+
*/
707+
function _wrapDestructuringInValueParameter(children) {
708+
if (!children.some(c => c.name === 'DESTRUCTURING_DECLARATION')) {
709+
return children;
710+
}
711+
return children.map(child => {
712+
if (child.name === 'DESTRUCTURING_DECLARATION') {
713+
return new Node('VALUE_PARAMETER', [child]);
714+
}
715+
return child;
716+
});
717+
}
718+
680719
module.exports = { normalizeTs, normalizePsi };

0 commit comments

Comments
 (0)