ucn 5.5.2 → 5.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/ucn/SKILL.md +35 -12
- package/.claude/skills/ucn/references/commands.md +21 -17
- package/.claude/skills/ucn/references/resolution-rules.md +4 -0
- package/.claude/skills/ucn/references/trust-contract.md +4 -4
- package/CHANGELOG.md +474 -0
- package/README.md +71 -29
- package/cli/flags.js +184 -0
- package/cli/index.js +138 -324
- package/core/account.js +44 -87
- package/core/analysis.js +273 -107
- package/core/ast-analysis.js +2 -0
- package/core/bridge.js +29 -65
- package/core/cache.js +101 -16
- package/core/callers.js +352 -70
- package/core/capture.js +331 -0
- package/core/change-kinds.js +888 -0
- package/core/check.js +1117 -139
- package/core/client-receivers.js +189 -0
- package/core/command-contracts.js +7 -6
- package/core/constructed-class.js +1195 -0
- package/core/contract-membership.js +142 -0
- package/core/deadcode.js +388 -85
- package/core/deletion.js +401 -0
- package/core/discovery.js +104 -9
- package/core/entrypoints.js +12 -6
- package/core/evidence-bands.js +68 -0
- package/core/evidence-bands.json +44 -0
- package/core/execute.js +172 -161
- package/core/hot-summary.js +241 -0
- package/core/index-ir.js +3 -1
- package/core/inprocess-clients.js +41 -15
- package/core/ir.js +5 -1
- package/core/member-signatures.js +789 -0
- package/core/output/analysis-ext.js +20 -10
- package/core/output/analysis.js +98 -60
- package/core/output/check.js +98 -37
- package/core/output/doctor.js +34 -1
- package/core/output/find.js +2 -1
- package/core/output/lines.js +63 -3
- package/core/output/public.js +30 -11
- package/core/output/refactoring.js +32 -0
- package/core/output/reporting.js +21 -2
- package/core/output/search.js +11 -2
- package/core/output/tracing.js +35 -7
- package/core/output-budget.js +124 -57
- package/core/parallel-build.js +3 -3
- package/core/project.js +106 -23
- package/core/provenance-type.js +12 -5
- package/core/public-command.js +35 -9
- package/core/python-modules.js +6 -0
- package/core/reflection.js +166 -23
- package/core/registry.js +623 -72
- package/core/reporting.js +69 -34
- package/core/request-helpers.js +63 -7
- package/core/review-items.js +75 -0
- package/core/search.js +89 -128
- package/core/shared.js +0 -45
- package/core/snapshot.js +350 -0
- package/core/source-universe.js +707 -0
- package/core/test-units.js +3624 -0
- package/core/tracing.js +150 -96
- package/core/verify.js +27 -11
- package/languages/adapter.js +8 -3
- package/languages/c-family.js +12 -0
- package/languages/csharp.js +634 -0
- package/languages/go.js +120 -0
- package/languages/index.js +422 -4
- package/languages/java.js +712 -0
- package/languages/javascript.js +1460 -2
- package/languages/lexical-scope.js +68 -0
- package/languages/python.js +1672 -1
- package/languages/rust.js +91 -0
- package/mcp/server.js +44 -110
- package/package.json +11 -6
package/core/capture.js
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/capture.js - Can an ADDED declaration change what existing code means?
|
|
3
|
+
*
|
|
4
|
+
* Adding code is inert when nothing that existed before can reach it. Two
|
|
5
|
+
* questions decide that:
|
|
6
|
+
*
|
|
7
|
+
* novelty does the name occur anywhere outside the lines the diff
|
|
8
|
+
* added? A name with no earlier occurrence cannot have been
|
|
9
|
+
* called, imported, shadowed or looked up before, so nothing
|
|
10
|
+
* that existed binds to the new declaration.
|
|
11
|
+
* capture even under a new name, a member added to a type that already
|
|
12
|
+
* existed can take part in something existing code relies on:
|
|
13
|
+
* it can override or be overridden, become a requirement every
|
|
14
|
+
* implementer must now supply, change how the type is
|
|
15
|
+
* constructed or laid out, be called by the runtime or a
|
|
16
|
+
* framework without being named.
|
|
17
|
+
*
|
|
18
|
+
* Each hazard is named. None of them is a proven break: they are the
|
|
19
|
+
* reasons an addition is review and not ready.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
'use strict';
|
|
23
|
+
|
|
24
|
+
const { hierarchySlotClosure } = require('./contract-membership');
|
|
25
|
+
const { NON_CALLABLE_TYPES, isValueDefinition } = require('./shared');
|
|
26
|
+
const { langTraits } = require('../languages');
|
|
27
|
+
|
|
28
|
+
const HAZARD_TEXT = Object.freeze({
|
|
29
|
+
'name-in-use': 'the name already occurs outside this change: existing code may now bind to the new declaration',
|
|
30
|
+
'novelty-unknown': 'part of the project could not be searched for the name, so it cannot be shown to be new',
|
|
31
|
+
'new-requirement': 'a new requirement of an existing contract: every implementer must now supply it',
|
|
32
|
+
'overrides-inherited': 'overrides a member the type inherited: existing calls on this type now reach it',
|
|
33
|
+
'overridden-by-descendant': 'a descendant already declares this member: it now overrides the new one',
|
|
34
|
+
'constructor-of-existing-type': 'changes how an existing type is constructed',
|
|
35
|
+
'field-of-existing-type': 'a new member of an existing type: construction, positional initialisation and layout can depend on its members',
|
|
36
|
+
'shape-of-existing-type': 'a new member of a type whose members are compared and enumerated by shape: values used as the type or as its constructor, and types computed from its members, are held to it',
|
|
37
|
+
'literal-lacks-new-member': 'an object literal declared as this type does not have the new member',
|
|
38
|
+
'protocol-member': 'a member the language runtime or the compiler invokes by name',
|
|
39
|
+
'registered': 'decorated or registered with a framework, which may invoke it without naming it',
|
|
40
|
+
'overrides-external': 'implements or overrides something defined outside the project',
|
|
41
|
+
'reflection-reachable': 'reflection in the project can reach this name',
|
|
42
|
+
'syntax-invoked': 'invoked by operator, conversion or indexer syntax that never contains its name: expressions that exist may now resolve to it',
|
|
43
|
+
'registry-member': 'member of a registry that is called through a computed key',
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Name-level reasons a deletion assessment withholds a claim, read as a
|
|
47
|
+
// property of an addition: what can reach a name can reach a new one.
|
|
48
|
+
const HAZARD_OF_SHIELD = Object.freeze({
|
|
49
|
+
'reflection': 'reflection-reachable',
|
|
50
|
+
'dynamic-dispatch': 'registry-member',
|
|
51
|
+
'operator-surface': 'syntax-invoked',
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Where an occurrence of a new declaration's name could mean it.
|
|
56
|
+
*
|
|
57
|
+
* a function-local declaration the lines of its enclosing scope;
|
|
58
|
+
* a module-level declaration of its own file, the files that import the
|
|
59
|
+
* a module (a file with its own name from it, and the files that take
|
|
60
|
+
* scope) everything the module has (a namespace
|
|
61
|
+
* or module import, a star import);
|
|
62
|
+
* anything else (a member, a the whole project: a receiver, a
|
|
63
|
+
* name in a shared scope) package, an inclusion can reach it
|
|
64
|
+
* from anywhere.
|
|
65
|
+
*
|
|
66
|
+
* Returns a predicate over (relative path, line), or null for "anywhere".
|
|
67
|
+
* An occurrence elsewhere is another name that happens to be spelled the
|
|
68
|
+
* same: it could not have meant this declaration before, and cannot now.
|
|
69
|
+
*/
|
|
70
|
+
function reachOf(index, definition) {
|
|
71
|
+
if (!definition || definition.className) return null;
|
|
72
|
+
const entry = index.files.get(definition.file);
|
|
73
|
+
if (!entry) return null;
|
|
74
|
+
const own = entry.relativePath;
|
|
75
|
+
if (definition.lexicalScopeStartLine != null) {
|
|
76
|
+
return (file, line) => file === own &&
|
|
77
|
+
line >= definition.lexicalScopeStartLine && line <= definition.lexicalScopeEndLine;
|
|
78
|
+
}
|
|
79
|
+
const traits = langTraits(entry.language) || {};
|
|
80
|
+
if (traits.packageScope !== 'file' || traits.textualIncludes) return null;
|
|
81
|
+
// A script without module syntax shares one global scope with the
|
|
82
|
+
// others (trait `scriptGlobalScope`).
|
|
83
|
+
if (traits.scriptGlobalScope && (entry.imports || []).length === 0 && (entry.exports || []).length === 0) return null;
|
|
84
|
+
const files = new Set([own]);
|
|
85
|
+
const exported = new Set(entry.exports || []);
|
|
86
|
+
if (traits.moduleNamesImportable) {
|
|
87
|
+
for (const symbol of entry.symbols || []) if (!symbol.className) exported.add(symbol.name);
|
|
88
|
+
}
|
|
89
|
+
// A name the module does not export stays in the module.
|
|
90
|
+
if (!exported.has(definition.name)) return file => file === own;
|
|
91
|
+
for (const other of index.files.values()) {
|
|
92
|
+
const resolved = other.moduleResolved || {};
|
|
93
|
+
const takesAll = (other.importDetails || []).some(detail =>
|
|
94
|
+
resolved[detail.module] === own && (detail.names || []).includes('*'));
|
|
95
|
+
const reExportsAll = (other.exportDetails || []).some(detail =>
|
|
96
|
+
detail && (detail.name === '*' || detail.type === 're-export-all') && resolved[detail.source] === own);
|
|
97
|
+
// Everything the module exports is passed on: who receives the
|
|
98
|
+
// name is no longer written here.
|
|
99
|
+
if (reExportsAll) return null;
|
|
100
|
+
// A binding of one of the module's exports takes that name only.
|
|
101
|
+
// Any other binding of the module holds the module itself.
|
|
102
|
+
const binds = (other.importBindings || []).some(binding => resolved[binding.module] === own &&
|
|
103
|
+
(binding.name === definition.name || !exported.has(binding.name) ||
|
|
104
|
+
binding.kind === 'namespace' || binding.kind === 'default'));
|
|
105
|
+
if (takesAll || binds) files.add(other.relativePath);
|
|
106
|
+
}
|
|
107
|
+
return file => files.has(file);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Occurrences, in code, of a new declaration's name outside the lines the
|
|
112
|
+
* diff added, among the places where the name could mean the declaration.
|
|
113
|
+
*
|
|
114
|
+
* @param {object} index
|
|
115
|
+
* @param {string} name
|
|
116
|
+
* @param {Map<string, Set<number>>} addedLinesByFile - relative path -> lines
|
|
117
|
+
* @param {object} [definition] - the new declaration (its reach narrows
|
|
118
|
+
* the search; without it every occurrence counts)
|
|
119
|
+
* @returns {{outside: {file, line}[], total: number}}
|
|
120
|
+
*/
|
|
121
|
+
function occurrencesOutsideChange(index, name, addedLinesByFile, definition = null) {
|
|
122
|
+
const rows = index.usages(name, { includeTests: true, codeOnly: true }) || [];
|
|
123
|
+
const reaches = reachOf(index, definition);
|
|
124
|
+
const outside = [];
|
|
125
|
+
for (const row of rows) {
|
|
126
|
+
const file = row.relativePath || row.file;
|
|
127
|
+
if (addedLinesByFile.get(file)?.has(row.line)) continue;
|
|
128
|
+
if (reaches && !reaches(file, row.line)) continue;
|
|
129
|
+
outside.push({ file, line: row.line });
|
|
130
|
+
}
|
|
131
|
+
outside.sort((a, b) => (a.file === b.file ? a.line - b.line : (a.file < b.file ? -1 : 1)));
|
|
132
|
+
return { outside, total: rows.length };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** The type definitions that own a member: same file, containing it. */
|
|
136
|
+
function ownersOf(index, definition) {
|
|
137
|
+
return (index.symbols.get(definition.className) || []).filter(type =>
|
|
138
|
+
type.file === definition.file && !type.className &&
|
|
139
|
+
type.startLine <= definition.startLine && (type.endLine || type.startLine) >= definition.startLine);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** One syntax tree per file for the questions below. */
|
|
143
|
+
function treeReader(index) {
|
|
144
|
+
const { parseRevision } = require('./change-kinds');
|
|
145
|
+
const roots = new Map();
|
|
146
|
+
return file => {
|
|
147
|
+
if (!roots.has(file)) {
|
|
148
|
+
let root;
|
|
149
|
+
try { root = parseRevision(index._readFile(file), index.files.get(file)?.language); } catch (_) { root = null; }
|
|
150
|
+
roots.set(file, root);
|
|
151
|
+
}
|
|
152
|
+
return roots.get(file);
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* What a new member does to the SHAPE of the type that owns it: the member
|
|
158
|
+
* set existing code is held to, whether or not it names the member.
|
|
159
|
+
*
|
|
160
|
+
* Where classes are compared by shape (traits `classComparedByShape`,
|
|
161
|
+
* `classMemberRole`), a class's members are a contract in two ways. Values
|
|
162
|
+
* used as the class must have them: an object literal or any class, and
|
|
163
|
+
* where the class only INHERITS a private or protected member, any class
|
|
164
|
+
* that inherits the same one. And types computed from its members
|
|
165
|
+
* (`keyof`, mapped types, `Pick`) are held to them even when the class
|
|
166
|
+
* declares a private member of its own and so has no other values.
|
|
167
|
+
*
|
|
168
|
+
* 'required' every value used as the type must now have the member: an
|
|
169
|
+
* instance member of a class that declares no private or
|
|
170
|
+
* protected member of its own. A private one included: no
|
|
171
|
+
* other value can ever have it;
|
|
172
|
+
* 'shape' the type is something else to existing code, with no value
|
|
173
|
+
* that can be shown to lack the member: a public member of a
|
|
174
|
+
* class held by its own declaration (seen by `keyof` only),
|
|
175
|
+
* a static member, hidden or not (the constructor is
|
|
176
|
+
* compared by shape whatever its instances are held to), an
|
|
177
|
+
* optional member, a member whose role cannot be read; and a
|
|
178
|
+
* virtual member where that changes what the type is (trait
|
|
179
|
+
* `virtualMemberChangesShape`);
|
|
180
|
+
* null nothing: a private or protected INSTANCE member of a class
|
|
181
|
+
* that already declares one, which no other code can hold,
|
|
182
|
+
* name or enumerate.
|
|
183
|
+
*
|
|
184
|
+
* "Declares one of its own" is asked of the class AS IT WAS: members on
|
|
185
|
+
* lines the change added are left out (the first private member of a class
|
|
186
|
+
* is what makes every structural value of it stop being one).
|
|
187
|
+
*
|
|
188
|
+
* @param {Map<string, Set<number>>} [addedLinesByFile] by relative path
|
|
189
|
+
*/
|
|
190
|
+
function shapeChangeOf(index, definition, addedLinesByFile = null) {
|
|
191
|
+
const entry = index.files.get(definition.file);
|
|
192
|
+
const traits = langTraits(entry && entry.language) || {};
|
|
193
|
+
if (traits.virtualMemberChangesShape && (definition.modifiers || []).includes('virtual')) return 'shape';
|
|
194
|
+
if (typeof traits.classComparedByShape !== 'function' || typeof traits.classMemberRole !== 'function') return null;
|
|
195
|
+
const rootOf = treeReader(index);
|
|
196
|
+
let change = null;
|
|
197
|
+
for (const owner of ownersOf(index, definition)) {
|
|
198
|
+
if (owner.type !== 'class') continue; // an interface member is a requirement, asked about elsewhere
|
|
199
|
+
const root = rootOf(owner.file);
|
|
200
|
+
const node = root && classNodeAt(root, owner);
|
|
201
|
+
const member = node ? traits.classMemberRole(node, definition.name, definition.startLine) : null;
|
|
202
|
+
if (!member) { change = 'shape'; continue; }
|
|
203
|
+
const added = (addedLinesByFile && addedLinesByFile.get(owner.relativePath)) || null;
|
|
204
|
+
const ownDeclaration = !traits.classComparedByShape(node, added);
|
|
205
|
+
// The constructor's type is its own contract: a static member is
|
|
206
|
+
// part of it whatever the instances are held to, private included
|
|
207
|
+
// (anything with a matching construct signature was a `typeof T`).
|
|
208
|
+
if (member.role === 'static') { change = 'shape'; continue; }
|
|
209
|
+
if (member.hidden && ownDeclaration) continue;
|
|
210
|
+
if (member.hidden || (member.role === 'instance' && !ownDeclaration)) return 'required';
|
|
211
|
+
change = 'shape';
|
|
212
|
+
}
|
|
213
|
+
return change;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** The class declaration node a type definition names. */
|
|
217
|
+
function classNodeAt(root, type) {
|
|
218
|
+
let found = null;
|
|
219
|
+
const visit = node => {
|
|
220
|
+
const start = node.startPosition.row + 1;
|
|
221
|
+
const end = node.endPosition.row + 1;
|
|
222
|
+
if (end < type.startLine || start > (type.endLine || type.startLine)) return;
|
|
223
|
+
if (node.childForFieldName('body') && node.childForFieldName('name')?.text === type.name &&
|
|
224
|
+
/class/.test(node.type)) {
|
|
225
|
+
if (!found || node.startIndex < found.startIndex) found = node;
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
for (const child of node.namedChildren) visit(child);
|
|
229
|
+
};
|
|
230
|
+
visit(root);
|
|
231
|
+
return found;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// A member name that is exactly what a literal's key would spell.
|
|
235
|
+
const PLAIN_MEMBER_NAME = /^#?[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Object literals declared as the type that owns `definition` and that do
|
|
239
|
+
* not have a member of its name: `const box: Box = { size: () => 1 }` once
|
|
240
|
+
* `Box` gains `brandNew()`. This is a claim of a break, so only what can
|
|
241
|
+
* be read is reported:
|
|
242
|
+
* - the member's name is a plain name (a quoted or escaped one may be
|
|
243
|
+
* spelled differently in the literal);
|
|
244
|
+
* - the file declares one type of that name, and the literal's
|
|
245
|
+
* annotation is that name, resolved to that file (a qualified name, a
|
|
246
|
+
* union or an array is another type);
|
|
247
|
+
* - the literal is the declared value itself and every member it has is
|
|
248
|
+
* a key that can be read, counting the members every object has
|
|
249
|
+
* (trait `typedLiteralMembers`: open on a spread, a computed, escaped
|
|
250
|
+
* or numeric key, an assertion or call around the literal, a comment
|
|
251
|
+
* that tells the checker to skip it);
|
|
252
|
+
* - the project does not add members to every object (trait
|
|
253
|
+
* `universalObjectInterface`).
|
|
254
|
+
* Sorted (file, line) of the literal.
|
|
255
|
+
*/
|
|
256
|
+
function literalsLackingMember(index, definition) {
|
|
257
|
+
if (!PLAIN_MEMBER_NAME.test(String(definition.name))) return [];
|
|
258
|
+
if (ownersOf(index, definition).length === 0) return [];
|
|
259
|
+
const namesakes = (index.symbols.get(definition.className) || []).filter(type =>
|
|
260
|
+
type.file === definition.file && !type.className && NON_CALLABLE_TYPES.has(type.type));
|
|
261
|
+
if (namesakes.length !== 1) return [];
|
|
262
|
+
const rootOf = treeReader(index);
|
|
263
|
+
const lacking = new Map(); // file\0line -> site
|
|
264
|
+
for (const entry of index.files.values()) {
|
|
265
|
+
const traits = langTraits(entry.language) || {};
|
|
266
|
+
const members = traits.typedLiteralMembers;
|
|
267
|
+
if (typeof members !== 'function') continue;
|
|
268
|
+
if (traits.universalObjectInterface && (index.symbols.get(traits.universalObjectInterface) || []).length > 0) continue;
|
|
269
|
+
for (const symbol of entry.symbols || []) {
|
|
270
|
+
if (!symbol.registryContainerType || symbol.className) continue;
|
|
271
|
+
if (String(symbol.registryContainerType).trim().replace(/<.*$/s, '').trim() !== definition.className) continue;
|
|
272
|
+
const line = symbol.objectLiteralLine || symbol.startLine;
|
|
273
|
+
const key = `${entry.relativePath}\0${line}`;
|
|
274
|
+
if (lacking.has(key)) continue;
|
|
275
|
+
lacking.set(key, null);
|
|
276
|
+
const declaredIn = index._resolveClassFile ? index._resolveClassFile(definition.className, symbol.file) : null;
|
|
277
|
+
if (declaredIn !== definition.file) continue;
|
|
278
|
+
const root = rootOf(symbol.file);
|
|
279
|
+
const literal = root ? members(root, line) : null;
|
|
280
|
+
if (!literal || literal.open || literal.names.has(definition.name)) continue;
|
|
281
|
+
lacking.set(key, { file: entry.relativePath, line, kind: 'literal' });
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return [...lacking.values()].filter(Boolean)
|
|
285
|
+
.sort((a, b) => (a.file === b.file ? a.line - b.line : (a.file < b.file ? -1 : 1)));
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Hazards of adding `definition`, a declaration the index now holds.
|
|
290
|
+
*
|
|
291
|
+
* @param {object} index
|
|
292
|
+
* @param {object} definition - the indexed symbol record
|
|
293
|
+
* @param {object} facts - { ownerExisted: the owner type predates the change,
|
|
294
|
+
* contract: what holds the declaration in place whatever names it
|
|
295
|
+
* (core/deadcode.js contractFactsOf), shield: the reason a deletion
|
|
296
|
+
* assessment of it names, or null, shape: what the member does to its
|
|
297
|
+
* type's shape (shapeChangeOf) }
|
|
298
|
+
* @returns {string[]} hazard codes, in a fixed order
|
|
299
|
+
*/
|
|
300
|
+
function captureHazards(index, definition, facts = {}) {
|
|
301
|
+
const hazards = [];
|
|
302
|
+
if (definition.className && facts.ownerExisted) {
|
|
303
|
+
const type = definition.type || definition.memberType;
|
|
304
|
+
if (definition.requiredMember) hazards.push('new-requirement');
|
|
305
|
+
if (definition.isConstructor || type === 'constructor') hazards.push('constructor-of-existing-type');
|
|
306
|
+
// A value member (a field, a constant, an enum variant) is part of
|
|
307
|
+
// what the type IS, whichever list the diff reported it in.
|
|
308
|
+
else if (facts.nonCallable || isValueDefinition(definition) ||
|
|
309
|
+
(NON_CALLABLE_TYPES.has(type) && !definition.isMethod)) hazards.push('field-of-existing-type');
|
|
310
|
+
else if (!definition.requiredMember && facts.shape) hazards.push('shape-of-existing-type');
|
|
311
|
+
let closure;
|
|
312
|
+
try { closure = hierarchySlotClosure(index, definition); } catch (_) { closure = null; }
|
|
313
|
+
if (closure) {
|
|
314
|
+
if ((closure.ancestors || []).some(ancestor => (ancestor.members || []).length > 0)) {
|
|
315
|
+
hazards.push('overrides-inherited');
|
|
316
|
+
}
|
|
317
|
+
if ((closure.descendants || []).some(descendant => (descendant.members || []).length > 0)) {
|
|
318
|
+
hazards.push('overridden-by-descendant');
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
const contract = facts.contract || {};
|
|
323
|
+
if (contract.runtimeContract) hazards.push('protocol-member');
|
|
324
|
+
if (contract.registered) hazards.push('registered');
|
|
325
|
+
if (contract.externalContract) hazards.push('overrides-external');
|
|
326
|
+
const fromShield = HAZARD_OF_SHIELD[facts.shield];
|
|
327
|
+
if (fromShield) hazards.push(fromShield);
|
|
328
|
+
return hazards;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
module.exports = { occurrencesOutsideChange, captureHazards, shapeChangeOf, literalsLackingMember, HAZARD_TEXT };
|