You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
ResultFormatter.position (lib/utils/result_formatter.ts:51-65), ResultFormatter.altitude (67-78), ResultFormatter.flightNumber (80-91), and ResultFormatter.temperature/totalAirTemp (331-359) all validate their input before pushing to formatted.items. Eight sibling formatters do not — they store whatever number they receive (including NaN) and then interpolate it into the formatted string, producing strings like "NaN knots", "NaN mach", and "NaN" in downstream UI:
Formatter
Line
Output on NaN input
heading
361-369
raw.heading = NaN, value: "NaN"
groundspeed
301-309
raw.groundspeed = NaN, value: "NaN knots"
airspeed
311-319
raw.airspeed = NaN, value: "NaN knots"
mach
321-329
raw.mach = NaN, value: "NaN mach"
day
440-448
raw.day = NaN, value: "NaN"
month
450-458
raw.month = NaN, value: "NaN"
departureDay
460-468
raw.departure_day = NaN, value: "NaN"
arrivalDay
470-478
raw.arrival_day = NaN, value: "NaN"
Because JavaScript's Number(...) returns NaN for common ACARS "unavailable" markers ('***', '---', '****') and any non-numeric text, every caller that passes Number(field) or parseInt(field) straight to one of these formatters propagates NaN all the way to the consumer.
Affected callers (non-exhaustive)
Real call sites already in the codebase that can silently emit NaN:
lib/plugins/Label_10_Slash.ts:41 — ResultFormatter.heading(decodeResult, Number(parts[5])) — any 10 message with parts[5] === '---' (a common "unknown heading" marker) sends NaN into heading.
lib/plugins/Label_10_Slash.ts:42 — ResultFormatter.altitude(decodeResult, 100 * Number(parts[6])) — this one is covered because altitude has its own isNaN guard.
lib/plugins/Label_44_Base.ts:52-53, Label_44_ETA.ts:37-38, Label_44_POS.ts:41-48 — ResultFormatter.month/day receive Number(data[4].substring(0, 2)); if data[4] is empty or non-numeric the result is NaN.
import{ResultFormatter}from'./utils/result_formatter';constdr: any={raw: {},formatted: {items: []}};ResultFormatter.heading(dr,Number('---'));// dr.raw.heading === NaN// dr.formatted.items[0].value === 'NaN'ResultFormatter.mach(dr,Number(''));// dr.raw.mach === 0 (Number('') → 0, so this one is "silent zero" — see #487)ResultFormatter.mach(dr,Number('***'));// dr.raw.mach === NaN, value === 'NaN mach'ResultFormatter.day(dr,Number(' '));// dr.raw.day === NaN, value === 'NaN'
End-to-end reproduction via a real plugin (Label_10_Slash needs 17 slash-separated fields; simplest way to hit the heading NaN path):
constdecoder=newMessageDecoder();decoder.decode({label: '10',text: '/N0000/E0000//-/---/000/KJFK/000000////WPT1/WPT2/000000/WPT3/000000/KLAX',});// result.formatted.items includes { code: 'HDG', value: 'NaN' }// result.raw.heading is NaN, so consumers can't distinguish "unknown" from "0".
Suggested fix
Match the pattern used by altitude (guard isNaN at the top and return without pushing), so callers can pass raw Number(field) results and unknown values disappear from formatted.items rather than surfacing as literal "NaN":
staticheading(decodeResult: DecodeResult,value: number){if(isNaN(value))return;decodeResult.raw.heading=value;decodeResult.formatted.items.push({type: 'heading',code: 'HDG',label: 'Heading',value: `${value}`,});}// Same shape for airspeed, groundspeed, mach, day, month, departureDay, arrivalDay.
The fuel formatters (currentFuel, burnedFuel, remainingFuel, outFuel, offFuel, onFuel, inFuel, startFuel) have the same shape and are worth guarding while touching this file — some callers do their own isNaN check first (e.g. Label_44_Base.parseFuel, Label_44_ETA:45-48), but not all, and centralising the guard removes the requirement.
Test coverage
Add unit tests for each affected formatter covering:
Number('---') / Number('***') → no item pushed, raw.<field> left unset
Summary
ResultFormatter.position(lib/utils/result_formatter.ts:51-65),ResultFormatter.altitude(67-78),ResultFormatter.flightNumber(80-91), andResultFormatter.temperature/totalAirTemp(331-359) all validate their input before pushing toformatted.items. Eight sibling formatters do not — they store whatevernumberthey receive (includingNaN) and then interpolate it into the formatted string, producing strings like"NaN knots","NaN mach", and"NaN"in downstream UI:NaNinputheadingraw.heading = NaN,value: "NaN"groundspeedraw.groundspeed = NaN,value: "NaN knots"airspeedraw.airspeed = NaN,value: "NaN knots"machraw.mach = NaN,value: "NaN mach"dayraw.day = NaN,value: "NaN"monthraw.month = NaN,value: "NaN"departureDayraw.departure_day = NaN,value: "NaN"arrivalDayraw.arrival_day = NaN,value: "NaN"Because JavaScript's
Number(...)returnsNaNfor common ACARS "unavailable" markers ('***','---','****') and any non-numeric text, every caller that passesNumber(field)orparseInt(field)straight to one of these formatters propagatesNaNall the way to the consumer.Affected callers (non-exhaustive)
Real call sites already in the codebase that can silently emit
NaN:lib/plugins/Label_10_Slash.ts:41—ResultFormatter.heading(decodeResult, Number(parts[5]))— any10message withparts[5] === '---'(a common "unknown heading" marker) sendsNaNintoheading.lib/plugins/Label_2P_FM4.ts:53—ResultFormatter.heading(decodeResult, Number(parts[7])).lib/plugins/Label_10_Slash.ts:42—ResultFormatter.altitude(decodeResult, 100 * Number(parts[6]))— this one is covered becausealtitudehas its ownisNaNguard.lib/plugins/Label_44_Base.ts:52-53,Label_44_ETA.ts:37-38,Label_44_POS.ts:41-48—ResultFormatter.month/dayreceiveNumber(data[4].substring(0, 2)); ifdata[4]is empty or non-numeric the result isNaN.lib/plugins/Label_4T_ETA.ts:31-33(before/after the fix for Bug: Label_4T_ETA dereferencesetaData[2].substringwith no length guard — short ETA payloads throw TypeError and abort MessageDecoder.decode() #482) —ResultFormatter.departureDay(...) / arrivalDay(...)onNumber(data[1])etc. can beNaN.Reproduction
End-to-end reproduction via a real plugin (Label_10_Slash needs 17 slash-separated fields; simplest way to hit the
headingNaN path):Suggested fix
Match the pattern used by
altitude(guardisNaNat the top and return without pushing), so callers can pass rawNumber(field)results and unknown values disappear fromformatted.itemsrather than surfacing as literal"NaN":The fuel formatters (
currentFuel,burnedFuel,remainingFuel,outFuel,offFuel,onFuel,inFuel,startFuel) have the same shape and are worth guarding while touching this file — some callers do their ownisNaNcheck first (e.g.Label_44_Base.parseFuel,Label_44_ETA:45-48), but not all, and centralising the guard removes the requirement.Test coverage
Add unit tests for each affected formatter covering:
Number('---')/Number('***')→ no item pushed,raw.<field>left unsetNumber('42')→ item pushed withvalue: '42'(or'42 knots', etc.)Number('')— document the current behaviour (silent0; that's a separate class, see Bug: Label_2P_FM3 else-branch reports position (0, 0) for empty lat/lon fields — silently places aircraft in the Gulf of Guinea #487)