99
1010'use strict' ;
1111
12+ import apiDefinitions from '../api/definitions/index.js' ;
13+
1214/**
1315 * Helper class for checking property access validity
1416 */
1517class PropertyAccessChecker {
1618 /**
1719 * @param {Object } context - Context object containing dependencies
18- * @param {Object } context.inavAPI - INAV API definitions
20+ * @param {Object } context.inavAPI - INAV API definitions (processed structure)
1921 * @param {Function } context.getVariableHandler - Getter for variable handler instance
2022 * @param {Function } context.addError - Function to add errors
2123 * @param {Function } context.addWarning - Function to add warnings
@@ -184,10 +186,18 @@ class PropertyAccessChecker {
184186
185187 const apiObj = this . inavAPI [ apiCategory ] ;
186188
187- // For category-only access (e.g., "flight"), warn that it needs a property
189+ // For category-only access (e.g., "flight"), error that it needs a property
188190 if ( parts . length === startIndex ) {
189191 if ( apiObj . properties . length > 0 || Object . keys ( apiObj . nested ) . length > 0 ) {
190- this . addWarning ( 'incomplete-access' , `'inav.${ propPath } ' needs a property. Did you mean to access a specific property?` , line ) ;
192+ // Build helpful error message with available properties
193+ const availableProps = [
194+ ...apiObj . properties . slice ( 0 , 3 ) . map ( p => `inav.${ apiCategory } .${ p } ` ) ,
195+ ...Object . keys ( apiObj . nested ) . slice ( 0 , 2 ) . map ( p => `inav.${ apiCategory } .${ p } .*` )
196+ ] . join ( ', ' ) ;
197+ this . addError (
198+ `Cannot use 'inav.${ propPath } ' - it's an object, not a property. Examples: ${ availableProps } ` ,
199+ line
200+ ) ;
191201 }
192202 return ;
193203 }
@@ -203,12 +213,48 @@ class PropertyAccessChecker {
203213
204214 // Check if it's a nested object
205215 if ( apiObj . nested [ propertyPart ] ) {
216+ // Must have a nested property - can't use intermediate object directly
217+ if ( parts . length === startIndex + 1 ) {
218+ // Accessing intermediate object without going deeper
219+ const nestedProps = apiObj . nested [ propertyPart ] ;
220+ const suggestions = nestedProps . slice ( 0 , 3 ) . map ( p => `inav.${ apiCategory } .${ propertyPart } .${ p } ` ) . join ( ', ' ) ;
221+ this . addError (
222+ `Cannot use 'inav.${ propPath } ' - it's an object, not a property. ` +
223+ `Available properties: ${ suggestions } ${ nestedProps . length > 3 ? ', ...' : '' } ` ,
224+ line
225+ ) ;
226+ return ;
227+ }
228+
206229 // Check nested property if present
207230 if ( parts . length > startIndex + 1 ) {
208231 const nestedPart = parts [ startIndex + 1 ] ;
209232 const nestedProps = apiObj . nested [ propertyPart ] ;
233+
234+ // Check if nested part is ALSO an object (3-level nesting like override.flightAxis.yaw)
235+ // Need to check the API definition to see if this is a leaf or another object
210236 if ( ! nestedProps . includes ( nestedPart ) ) {
211237 this . addError ( `Unknown property '${ nestedPart } ' in 'inav.${ propPath } '. Available: ${ nestedProps . join ( ', ' ) } ` , line ) ;
238+ return ;
239+ }
240+
241+ // Check if we stopped at a nested object (e.g., override.flightAxis.yaw instead of override.flightAxis.yaw.angle)
242+ if ( parts . length === startIndex + 2 ) {
243+ // Need to check if nestedPart is itself an object
244+ // This requires checking the actual API definition structure
245+ const isNestedObject = this . isPropertyAnObject ( apiCategory , propertyPart , nestedPart ) ;
246+ if ( isNestedObject ) {
247+ const deeperProps = this . getNestedObjectProperties ( apiCategory , propertyPart , nestedPart ) ;
248+ if ( deeperProps && deeperProps . length > 0 ) {
249+ const suggestions = deeperProps . slice ( 0 , 3 ) . map ( p => `inav.${ apiCategory } .${ propertyPart } .${ nestedPart } .${ p } ` ) . join ( ', ' ) ;
250+ this . addError (
251+ `Cannot use 'inav.${ propPath } ' - it's an object, not a property. ` +
252+ `Available properties: ${ suggestions } ${ deeperProps . length > 3 ? ', ...' : '' } ` ,
253+ line
254+ ) ;
255+ return ;
256+ }
257+ }
212258 }
213259 }
214260 return ;
@@ -254,7 +300,7 @@ class PropertyAccessChecker {
254300 */
255301 checkWritableOverride ( inavPath ) {
256302 const parts = inavPath . split ( '.' ) ;
257- // parts = ['override', 'throttle'] or ['override', 'vtx', 'power']
303+ // parts = ['override', 'throttle'] or ['override', 'vtx', 'power'] or ['override', 'flightAxis', 'yaw', 'angle']
258304
259305 if ( parts . length < 2 ) {
260306 return false ;
@@ -270,9 +316,29 @@ class PropertyAccessChecker {
270316 return true ;
271317 }
272318
273- // Check nested properties (e.g., override.vtx.power)
319+ // Check nested properties (e.g., override.vtx.power or override.flightAxis.yaw.angle )
274320 if ( parts . length >= 3 && apiObj . nested && apiObj . nested [ parts [ 1 ] ] ) {
275- return apiObj . nested [ parts [ 1 ] ] . includes ( parts [ 2 ] ) ;
321+ // Verify the property exists in the nested list
322+ if ( ! apiObj . nested [ parts [ 1 ] ] . includes ( parts [ 2 ] ) ) {
323+ return false ;
324+ }
325+
326+ // For 3-level paths, need to check if parts[2] is itself an object (intermediate)
327+ // If it has deeper nesting (like flightAxis.yaw.angle), the 3-level path is incomplete
328+ if ( parts . length === 3 ) {
329+ // Check if this property is an intermediate object using the helper method
330+ const isObject = this . isPropertyAnObject ( 'override' , parts [ 1 ] , parts [ 2 ] ) ;
331+ if ( isObject ) {
332+ // It's an intermediate object - need to go deeper (e.g., yaw.angle or yaw.rate)
333+ return false ;
334+ }
335+ // It's a leaf property - writable
336+ return true ;
337+ }
338+
339+ // For 4+ level paths (e.g., override.flightAxis.yaw.angle), verify all levels exist
340+ // This is handled by the broader validation in checkApiPropertyAccess
341+ return true ;
276342 }
277343
278344 return false ;
@@ -286,6 +352,60 @@ class PropertyAccessChecker {
286352 const match = gvarStr . match ( / g v a r \[ ( \d + ) \] / ) ;
287353 return match ? parseInt ( match [ 1 ] ) : - 1 ;
288354 }
355+
356+ /**
357+ * Check if a property is itself an object (not a leaf value)
358+ * Used to detect 3-level nested objects like override.flightAxis.yaw
359+ * @param {string } category - API category (e.g., 'override')
360+ * @param {string } parentProp - Parent property (e.g., 'flightAxis')
361+ * @param {string } childProp - Child property (e.g., 'yaw')
362+ * @returns {boolean } True if childProp is an object with more properties
363+ * @private
364+ */
365+ isPropertyAnObject ( category , parentProp , childProp ) {
366+ try {
367+ // Use raw API definitions to preserve nested structure
368+ const categoryDef = apiDefinitions [ category ] ;
369+ if ( ! categoryDef ) return false ;
370+
371+ const parentDef = categoryDef [ parentProp ] ;
372+ if ( ! parentDef || ! parentDef . properties ) return false ;
373+
374+ const childDef = parentDef . properties [ childProp ] ;
375+ if ( ! childDef ) return false ;
376+
377+ // Simple check: typeof object with properties
378+ return childDef . type === 'object' && childDef . properties && Object . keys ( childDef . properties ) . length > 0 ;
379+ } catch ( error ) {
380+ return false ;
381+ }
382+ }
383+
384+ /**
385+ * Get properties of a nested object
386+ * @param {string } category - API category (e.g., 'override')
387+ * @param {string } parentProp - Parent property (e.g., 'flightAxis')
388+ * @param {string } childProp - Child property (e.g., 'yaw')
389+ * @returns {string[]|null } Array of property names or null
390+ * @private
391+ */
392+ getNestedObjectProperties ( category , parentProp , childProp ) {
393+ try {
394+ // Use raw API definitions
395+ const categoryDef = apiDefinitions [ category ] ;
396+ if ( ! categoryDef ) return null ;
397+
398+ const parentDef = categoryDef [ parentProp ] ;
399+ if ( ! parentDef || ! parentDef . properties ) return null ;
400+
401+ const childDef = parentDef . properties [ childProp ] ;
402+ if ( ! childDef || ! childDef . properties ) return null ;
403+
404+ return Object . keys ( childDef . properties ) ;
405+ } catch ( error ) {
406+ return null ;
407+ }
408+ }
289409}
290410
291411export { PropertyAccessChecker } ;
0 commit comments