0
0
Fork 0
Codesys-MCP-SP21-plus/tests/unit/var-block-parse.test.ts
Karstein Phobic Nyvold Kvistad 614a8458f0 feat(live-values): depth-1 sub-property descent
Extends the pump beyond top-level vars: when a var's declared type
resolves to another mirror .st file, we descend one level and read
each of that type's vars as <var>.<member>.

Parser: parseVarDecls(text) now returns name + declared type per
decl. ARRAY/POINTER/REFERENCE wrappers stripped to the inner type.
parseVarNames is kept as a thin wrapper for the existing callers.

Pump: new resolveTypeMirror(typeName, deviceRoot) dep. Pump itself
doesn't know how to find a type's source -- the caller plugs in a
strategy. server.ts wires a recursive walk under the device root
looking for '<typeName>.st'; the mirror layout guarantees stable
filenames for POU/FB/DUT (every code-bearing object).

Pump's deviceRootFor(absPath) parses the abs path back to the
device root by locating the '/mcp-mirror/<device>/' segment. If
that fails we fall back to the file's parent dir (still works for
same-folder type lookups, just won't find types in sibling folders).

Constructor now accepts partial deps and fills in safe defaults
(no-op resolveTypeMirror -> never descend, matching v0.3
top-level-only behaviour). Existing tests don't need to change.

407/407 tests pass; new coverage:
  - parseVarDecls: name+type extraction, ARRAY/POINTER/REFERENCE
    stripping, AT %loc prefix, missing-type pathological case
  - LiveValuesPump.tick: descends when resolver returns content;
    doesn't descend when resolver returns null (primitives)
2026-04-29 13:52:35 +02:00

104 lines
3 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { parseVarNames, parseVarDecls } from '../../src/live-values-pump';
describe('parseVarNames', () => {
it('extracts vars from a single VAR block', () => {
const text = [
'PROGRAM PLC_PRG',
'VAR',
' counter : INT := 0;',
' bRunning : BOOL;',
' rTemperature : REAL;',
'END_VAR',
].join('\n');
expect(parseVarNames(text)).toEqual(['counter', 'bRunning', 'rTemperature']);
});
it('handles VAR_INPUT / VAR_OUTPUT / VAR_GLOBAL too', () => {
const text = [
'FUNCTION_BLOCK FB_X',
'VAR_INPUT',
' x : INT;',
'END_VAR',
'VAR_OUTPUT',
' y : BOOL;',
'END_VAR',
'VAR',
' internal : DINT;',
'END_VAR',
].join('\n');
expect(parseVarNames(text).sort()).toEqual(['internal', 'x', 'y']);
});
it('ignores lines inside (* ... *) blocks and after //', () => {
const text = [
'VAR',
' alpha : INT;',
' (* commentedOut : BOOL; *)',
' beta : INT; // tail comment',
'END_VAR',
].join('\n');
expect(parseVarNames(text)).toEqual(['alpha', 'beta']);
});
it('returns [] when no VAR block is present', () => {
expect(parseVarNames('PROGRAM X\nEND_PROGRAM')).toEqual([]);
});
it('handles AT %X10.0 location prefix and := initializer', () => {
const text = [
'VAR',
' bRelay AT %QX0.1 : BOOL := FALSE;',
' iCount : INT := 42;',
'END_VAR',
].join('\n');
expect(parseVarNames(text)).toEqual(['bRelay', 'iCount']);
});
});
describe('parseVarDecls', () => {
it('returns name + declared type per decl', () => {
const text = [
'PROGRAM PLC_PRG',
'VAR',
' counter : INT := 0;',
' bRunning : BOOL;',
' fb : FB_Test;',
'END_VAR',
].join('\n');
expect(parseVarDecls(text)).toEqual([
{ name: 'counter', type: 'INT' },
{ name: 'bRunning', type: 'BOOL' },
{ name: 'fb', type: 'FB_Test' },
]);
});
it('strips ARRAY [...] OF wrapper', () => {
const text = ['VAR', ' buf : ARRAY [0..9] OF INT;', 'END_VAR'].join('\n');
expect(parseVarDecls(text)).toEqual([{ name: 'buf', type: 'INT' }]);
});
it('strips POINTER TO and REFERENCE TO', () => {
const text = [
'VAR',
' p : POINTER TO BOOL;',
' r : REFERENCE TO MyStruct;',
'END_VAR',
].join('\n');
expect(parseVarDecls(text)).toEqual([
{ name: 'p', type: 'BOOL' },
{ name: 'r', type: 'MyStruct' },
]);
});
it('handles AT %loc prefix', () => {
const text = ['VAR', ' bRelay AT %QX0.1 : BOOL := FALSE;', 'END_VAR'].join('\n');
expect(parseVarDecls(text)).toEqual([{ name: 'bRelay', type: 'BOOL' }]);
});
it('returns type=null when there is no `: <type>` clause on the line', () => {
// Pathological: missing type. Don't crash; just emit name with null type.
const text = ['VAR', ' weird;', 'END_VAR'].join('\n');
expect(parseVarDecls(text)).toEqual([{ name: 'weird', type: null }]);
});
});