A lint is a warning about a model the specification accepts: nothing in SysML v2 or KerML
makes the written model wrong, but it is almost always a slip. Each lint has a stable code,
carried as the diagnostic's code under -json, in the editor and over the wire, and each
can be switched off by that code. A lint is a warning in every mode:
-strict promotes notation no SysML v2
production admits, and a lint is not about notation, so it stays a warning and never changes
the exit status or blocks a check.
| Code | Reported on | Tier |
|---|---|---|
undeclared-signal |
a transition's when <name> whose name matches no declaration visible where it is written and no signal the model sends |
name resolution |
port-type-mismatch |
a connect, an interface usage or a flow joining two ports whose definitions are unrelated |
constraint |
deferred-keeper-unmarked |
an accept of a deferred signal at the root of the do action of a state annotated MigrationMetadata::DeferredEvent that is not marked MigrationMetadata::DeferredKeeper |
name resolution |
These constraint-level warnings report valid SysML models whose action-step multiplicities or
succession ordering the executor cannot safely implement. They are emitted by -validate with
source action-step-multiplicity; they are not lints and cannot be disabled with lint settings.
| Code | Reported when |
|---|---|
action-step-multiplicity-not-fixed |
An action-step or written succession-end multiplicity does not evaluate to a fixed exact count |
action-step-multiplicity-unsupported |
A repeated or zero-count step has an unsupported control-node, guard, pin, binding, connection, external-feature-read, state-behavior, part-level performed-action, or loop/conditional block-flow interaction, or a bound is beyond the 64-bit range (for example, 1180591620717411303424) |
action-step-order-unsatisfiable |
A succession end forces an endpoint count that it does not admit |
action-step-order-open |
The declared or defaulted succession ends do not force the endpoint counts under both readings of an unwritten end |
| Surface | Setting |
|---|---|
| CLI | -disable-lint <code>[,<code>…], repeatable; an unknown code is a usage error (exit 2) |
| REPL | %lint lists every lint, on or off; %lint <code> on|off switches one and reprints the session's diagnostics |
| Language server | disabledLints, a list of codes, in initializationOptions or workspace/didChangeConfiguration (LSP extensions) |
| Go | model.WithDisabledLints(codes...) when the workspace is made, or (*model.Workspace).SetDisabledLints(codes) |
A disabled lint is left out of what the workspace reports, not out of the analysis, so switching it back on needs no re-analysis. The gRPC service and the clients report both lints with their codes; a client that does not want one drops the diagnostics carrying its code.
OpenSysML accepts a signal trigger written without accept — transition first a when Ping then b;. The spelling is not standard: the pinned pilot grammar admits when only as a
change trigger over a Boolean expression inside an accept (SysML.xtext:1483-1485,
ChangeTriggerKind) (conformance audit). The name such a
trigger carries names an event, not a model element: it is left unresolved, and at run time it
matches a signal injected or sent by that name. A misspelled name therefore matches nothing
and the transition never fires, silently.
The lint reports the name when all of the following hold:
- no declaration of that name is visible where the trigger is written, by the ordinary scoping rules (KerML §7.2.5, §8.2.3.5);
- no
sendanywhere in the workspace sends a signal by that name, the name the runtime gives its message and compares the trigger's final segment with: the definition a payload names or constructs (send Ping to self;,send new Ping() to self;), through any alias to the definition it reaches, and the type of a payload feature's value (send reading to self;withattribute reading : Real = 1.0;sendsReal, notreading). A written message is the one sent; the body'spayloadparameter is read only where the send writes none. A name that resolves to nothing counts as written. A send invoking a calculation (send Ping() to self;withcalc def Ping) sends the calculation's value, as the runtime does, so it counts the result's type, notPing. A literal payload counts its scalar type, as the runtime names it:send "go" to self;sendsString, and integer, real and boolean literals sendInteger,RealandBoolean. A computed payload counts the scalar type its value is known to have (send 1 + 2 to self;sendsInteger,send "a" + "b" to self;String,send not ready to self;Boolean); a number whose kind is not known statically (send r * 2.0 to self;) counts bothIntegerandReal, the two names the runtime gives a number it sends. A document held as its interface record counts too: the record keeps the names its body sends.
The finding offers the resolver's nearest names in scope as did you mean …?:
m.sysml:3:73: warning: `when Pnig` names no declaration visible here and no signal the model sends, so only a signal injected by that name triggers it — did you mean Ping?
A signal the model only ever receives from outside (%send at the prompt, an injected
event over the wire) is reported, since nothing in the model says it exists; declare it
(attribute def Ping;) to say so, or switch the lint off. The trigger's resolution and
behavior are unchanged either way.
Two ports a connector joins are compatible (SysML v2 §7.12.2, §7.12.3) when one of these holds:
- one's definition specializes the other's, or both specialize a common definition of the
model (a library definition such as
Ports::Port, which every port specializes, does not count); - one port's directed features each have a feature of the other with the same name, the
conjugate direction (
inagainstout;inoutagainstinout) and a conforming type — a conjugated port (port p : ~P) reverses its definition's directions first; - the connector is typed by an interface definition with two ends, both typed by a port
definition, directly or through a model end it redefines: that interface decides what the
ends pair, and its own ends are judged by
port-conjugation. An interface leaving an end untyped decides nothing, and nothing judges a connection definition's or a many-ended interface's ends, so their usages' concrete ends are judged.
A port connected is typed by its own declaration, or else by the nearest model port it
redefines (port :>> p; keeps the type and conjugation of the p it redefines).
Otherwise the lint reports the connector at its first end:
m.sysml:9:13: warning: this connection connects port power : PowerOut to port fuel : FuelIn, whose definitions are unrelated and whose directed features are not conjugate; type one end by the conjugate port (~PowerOut) or by a common definition
It covers connect a.p to b.q; (and connection … connect), interface usages
(interface connect a.p to b.q;) and flow between two ports, whose syntax is
ConnectionUsage, InterfaceUsage and FlowUsage (SysML.xtext:1062, :1153, :1269).
An end that is not a port, or whose port type does not resolve, is not judged. The
specification states no constraint of this kind, so it is a lint rather than an error.
A SysML v1 state's deferred signal is migrated to the standard encoding described under
Deferred signals: the state is annotated
@MigrationMetadata::DeferredEvent { ref :>> signal : Sig; }, and its do action keeps each
occurrence of Sig through an accept loop whose accept is written
#MigrationMetadata::DeferredKeeper action receive accept kept : Sig;. The runtime knows the
keeping accept by that annotation alone — it is the accept that yields an occurrence to any
other accept of the state able to take it, and keeps what nothing else takes — so an accept of
the deferred signal written without it is an ordinary accept, which consumes the occurrence.
Output migrated before the marker existed wrote the loop's accept bare, and so does a model
written by hand after the pattern.
The lint reports each accept node at the root of the do action of a state annotated
MigrationMetadata::DeferredEvent whose payload is typed by the signal the annotation names,
when no accept of that signal there carries MigrationMetadata::DeferredKeeper: once one does,
the state has its keeping loop, and a bare accept of the signal beside it is the ordinary
consumer the marker exists to tell apart, which takes an occurrence first. Both annotations
are known by their resolved type, however the model spells them (through an import, an alias
or $::MigrationMetadata), and a metadata definition of the model that merely shares the
library name is not one of them.
m.sysml:12:5: warning: accept of deferred signal Ping is not marked #MigrationMetadata::DeferredKeeper, so it is an ordinary accept, not the keeping loop; re-migrate the model or mark it
The model still analyses and runs; the accept simply takes each occurrence rather than keeping
it. Re-migrate the model, which writes the marker on every keeping accept, or write
#MigrationMetadata::DeferredKeeper on the accept yourself. An accept of the signal nested
below the do action's root, one beside a marked keeper of the signal, or one under a state the
annotation does not name, is an ordinary accept by design and is not reported, nor is an
accept whose signal does not resolve, which name resolution reports.