@@ -197,8 +197,14 @@ class SemanticAnalyzer {
197197 // Variable assignment - allowed for var variables
198198 // (let reassignment already caught above)
199199 } else if ( ! this . isValidWritableProperty ( stmt . target ) ) {
200- // Use original target (with inav. prefix) for writability check
201- this . addError ( `Cannot assign to '${ stmt . target } '. Not a valid INAV writable property.` , line ) ;
200+ // Check if it's an intermediate object and provide helpful error
201+ const betterError = this . getImprovedWritabilityError ( stmt . target , line ) ;
202+ if ( betterError ) {
203+ this . addError ( betterError , line ) ;
204+ } else {
205+ // Use original target (with inav. prefix) for writability check
206+ this . addError ( `Cannot assign to '${ stmt . target } '. Not a valid INAV writable property.` , line ) ;
207+ }
202208 }
203209
204210 // Check if value references are valid
@@ -368,7 +374,111 @@ class SemanticAnalyzer {
368374 extractGvarIndex ( gvarStr ) {
369375 return this . propertyAccessChecker . extractGvarIndex ( gvarStr ) ;
370376 }
371-
377+
378+ /**
379+ * Generate improved error message for invalid writable property assignments
380+ * Detects intermediate objects and suggests correct nested properties
381+ * @param {string } target - Property path (e.g., "inav.override.flightAxis.yaw")
382+ * @param {number } line - Line number for error reporting
383+ * @returns {string|null } Improved error message or null if no improvement available
384+ */
385+ getImprovedWritabilityError ( target , line ) {
386+ // Strip 'inav.' prefix if present
387+ const normalizedTarget = target . startsWith ( 'inav.' ) ? target . substring ( 5 ) : target ;
388+ const parts = normalizedTarget . split ( '.' ) ;
389+
390+ // Only applies to override namespace for now
391+ if ( parts [ 0 ] !== 'override' ) {
392+ return null ;
393+ }
394+
395+ // Check if trying to assign to a 3-level intermediate object
396+ // E.g., override.flightAxis.yaw (should be override.flightAxis.yaw.angle or .rate)
397+ if ( parts . length === 3 ) {
398+ const overrideDef = this . getOverrideDefinition ( parts [ 1 ] , parts [ 2 ] ) ;
399+
400+ if ( overrideDef && overrideDef . type === 'object' && overrideDef . properties ) {
401+ const availableProps = Object . keys ( overrideDef . properties ) ;
402+ const suggestions = availableProps . map ( p => `inav.override.${ parts [ 1 ] } .${ parts [ 2 ] } .${ p } ` ) . join ( ', ' ) ;
403+ return `Cannot assign to '${ target } ' - it's an object, not a property. Available properties: ${ suggestions } ` ;
404+ }
405+ }
406+
407+ // Check if trying to assign to a 2-level intermediate object
408+ // E.g., override.vtx (should be override.vtx.power, etc.)
409+ // or override.flightAxis (should be override.flightAxis.roll.angle, etc.)
410+ if ( parts . length === 2 ) {
411+ const categoryDef = this . getOverrideCategoryDefinition ( parts [ 1 ] ) ;
412+
413+ if ( categoryDef && categoryDef . type === 'object' && categoryDef . properties ) {
414+ const propKeys = Object . keys ( categoryDef . properties ) ;
415+
416+ // Check if properties are simple (like vtx.power) or nested (like flightAxis.roll.angle)
417+ const firstProp = categoryDef . properties [ propKeys [ 0 ] ] ;
418+
419+ if ( firstProp && firstProp . type === 'object' && firstProp . properties ) {
420+ // Deeply nested (like flightAxis.roll.angle)
421+ const nestedPropKeys = Object . keys ( firstProp . properties ) ;
422+ const suggestions = propKeys . slice ( 0 , 2 ) . flatMap ( p => {
423+ const nested = categoryDef . properties [ p ] ;
424+ if ( nested && nested . properties ) {
425+ const nestedKeys = Object . keys ( nested . properties ) ;
426+ if ( nestedKeys . length > 0 ) {
427+ const firstNestedProp = nestedKeys [ 0 ] ;
428+ return [ `inav.override.${ parts [ 1 ] } .${ p } .${ firstNestedProp } ` ] ;
429+ }
430+ }
431+ return [ ] ;
432+ } ) . join ( ', ' ) ;
433+ return `Cannot assign to '${ target } ' - it's an object, not a property. Examples: ${ suggestions } , ...` ;
434+ } else {
435+ // Simple properties (like vtx.power, vtx.band, vtx.channel)
436+ const suggestions = propKeys . map ( p => `inav.override.${ parts [ 1 ] } .${ p } ` ) . join ( ', ' ) ;
437+ return `Cannot assign to '${ target } ' - it's an object, not a property. Available properties: ${ suggestions } ` ;
438+ }
439+ }
440+ }
441+
442+ return null ;
443+ }
444+
445+ /**
446+ * Get override definition for a specific property
447+ * @private
448+ */
449+ getOverrideDefinition ( category , property ) {
450+ try {
451+ // Access raw API definitions, not processed structure
452+ const overrideDefs = apiDefinitions . override ;
453+ if ( ! overrideDefs ) return null ;
454+
455+ // For nested objects like flightAxis, check if the property itself has properties
456+ if ( overrideDefs [ category ] && overrideDefs [ category ] . properties ) {
457+ return overrideDefs [ category ] . properties [ property ] ;
458+ }
459+
460+ return null ;
461+ } catch ( error ) {
462+ return null ;
463+ }
464+ }
465+
466+ /**
467+ * Get override category definition
468+ * @private
469+ */
470+ getOverrideCategoryDefinition ( category ) {
471+ try {
472+ // Access raw API definitions, not processed structure
473+ const overrideDefs = apiDefinitions . override ;
474+ if ( ! overrideDefs ) return null ;
475+
476+ return overrideDefs [ category ] ;
477+ } catch ( error ) {
478+ return null ;
479+ }
480+ }
481+
372482 /**
373483 * Check for common unsupported JavaScript features
374484 */
@@ -525,12 +635,9 @@ class SemanticAnalyzer {
525635 let stmtIndex = 0 ;
526636 for ( const stmt of ast . statements ) {
527637 if ( stmt && stmt . type === 'EventHandler' ) {
528- // Each if statement gets a unique key - we want to detect multiple
529- // assignments within the SAME if block, not across different if
530- // statements that happen to have the same condition
531- const handlerKey = stmt . handler === 'ifthen' ?
532- `ifthen:${ stmtIndex } ` :
533- stmt . handler ;
638+ // Each handler gets a unique key - we want to detect multiple
639+ // assignments within the SAME handler, not across different handlers
640+ const handlerKey = `${ stmt . handler } :${ stmtIndex } ` ;
534641
535642 if ( ! handlerAssignments . has ( handlerKey ) ) {
536643 handlerAssignments . set ( handlerKey , new Map ( ) ) ;
0 commit comments