@chenglou/freerange 0.0.2 → 0.0.4
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/CHANGELOG.md +14 -0
- package/README.md +232 -66
- package/dist/fr.js +627 -480
- package/package.json +1 -1
- package/src/audit.ts +2 -3
- package/src/domain/value-identity.ts +56 -0
- package/src/engine/analyze.ts +19 -12
- package/src/engine/state.ts +23 -11
- package/src/engine/transfer.ts +78 -29
- package/src/ir/instructions.ts +7 -1
- package/src/ir/program.ts +7 -3
- package/src/lower/accept.ts +6 -2
- package/src/lower/context.ts +14 -5
- package/src/lower/expression.ts +106 -88
- package/src/lower/function-unit.ts +64 -0
- package/src/lower/module.ts +28 -9
- package/src/lower/numeric-intersection.ts +15 -0
- package/src/lower/platform.ts +28 -12
- package/src/lower/program.ts +66 -41
- package/src/lower/statements.ts +5 -18
- package/src/lower/static-intrinsics.ts +9 -8
- package/src/project.ts +3 -3
- package/src/report/index.ts +2 -2
- package/src/requirements/infer.ts +76 -25
- package/src/typescript/check.ts +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.0.4 - 2026-07-31
|
|
6
|
+
|
|
7
|
+
- Allow `Date.now()` to represent dates before the Unix epoch.
|
|
8
|
+
- Support numeric phantom types (#6).
|
|
9
|
+
- Only recognize `Math`, `Number`, `Infinity`, parser functions, and browser globals when they resolve to TypeScript's standard libraries.
|
|
10
|
+
|
|
11
|
+
## 0.0.3 - 2026-07-28
|
|
12
|
+
|
|
13
|
+
- Analyze arrow functions and function expressions assigned directly to top-level `const` names.
|
|
14
|
+
- Preserve type narrowing through `&&` and `||` expressions.
|
|
15
|
+
- Analyze each `Math.random()` call as a fresh number from zero up to, but not including, one.
|
|
16
|
+
- Avoid quadratic work when tracking values through deep chains of same-file function calls (#3).
|
|
17
|
+
- Consolidate analysis limits and practical refactoring examples in the README.
|
|
18
|
+
|
|
5
19
|
## 0.0.2 - 2026-07-21
|
|
6
20
|
|
|
7
21
|
- Add node support (#2)
|
package/README.md
CHANGED
|
@@ -117,45 +117,249 @@ Every plain `number` parameter, including a number field in a fixed-shape object
|
|
|
117
117
|
|
|
118
118
|
## Writing Analyzable TypeScript
|
|
119
119
|
|
|
120
|
-
Freerange
|
|
121
|
-
|
|
120
|
+
Freerange deliberately analyzes a restricted part of TypeScript. When code leaves that scope, `fr --audit` says so instead of guessing what the code does. Freerange currently supports:
|
|
121
|
+
|
|
122
|
+
- Named, synchronous top-level functions. Both `function size(...) {}` and direct `const size = (...) => ...` declarations work. Freerange follows calls between functions in the same file
|
|
122
123
|
- Numbers, booleans, strings, nullable values, plain objects, tagged unions, dense arrays, and fixed tuples
|
|
123
124
|
- `if`/`else`, ternaries, non-fallthrough `switch`, `&&`, `||`, `!`, `??`, `for`, `while`, and `for...of` loops
|
|
124
125
|
- Arithmetic, comparisons, object field and array reads, selected `Math` operations, and `Number.isInteger`, `Number.isFinite`, and `Number.isNaN`
|
|
125
126
|
|
|
126
127
|
Freerange could theoretically support a much larger subset of TS, and did before its public release. Those patterns often made numeric inference and proofs much harder and slower, however, and some questions are undecidable in general. Now that AI agents write code, we strongly recommend asking agents to refactor important calculations into shapes that Freerange analyzes well, guided by `fr --audit`. Code that is easy to analyze tends to resemble functional programming: immutable data, explicit inputs and outputs, and clean, direct control flow.
|
|
127
128
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
129
|
+
Use precise TypeScript types. Avoid `any`, casts, and suppression comments, and parse external data before passing it to a numeric helper. A file containing `@ts-ignore`, `@ts-expect-error`, `@ts-nocheck`, or `eval` is rejected because its declared types cannot be trusted.
|
|
130
|
+
|
|
131
|
+
Before changing an input rule, decide what the application should do. If `columnCount` must be a positive integer, either require that with leading `console.assert` calls or normalize it with `Math.max(1, Math.floor(columnCount))`. Only normalize when the application wants that runtime behavior. Audit code: `[encode-input-rule]`.
|
|
132
|
+
|
|
133
|
+
### Numeric Analysis
|
|
134
|
+
|
|
135
|
+
For each `number`, Freerange remembers its lowest and highest possible values, whether it is an integer, whether it may be `NaN` or infinite, and at most one exact value that has been ruled out. It reasons about JavaScript floating-point numbers rather than ideal real numbers and does not use a general-purpose theorem prover.
|
|
136
|
+
|
|
137
|
+
Freerange also supports numeric phantom types, e.g. `type Pixels = number & {readonly __brand: 'pixels'}`.
|
|
138
|
+
|
|
139
|
+
#### No common-subexpression elimination
|
|
140
|
+
|
|
141
|
+
Freerange does not replace repeated calculations with one stored result, even when their source code is identical. This function checks one subtraction, then divides by a newly evaluated subtraction:
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
export function progressBar(value: number, start: number, end: number): number {
|
|
145
|
+
if (end - start === 0) return 0
|
|
146
|
+
return (value - start) / (end - start)
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Calculate the subtraction once when the check and division must use the same result:
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
export function progressBar(value: number, start: number, end: number): number {
|
|
154
|
+
const span = end - start
|
|
155
|
+
if (span === 0) return 0
|
|
156
|
+
return (value - start) / span
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Freerange recognizes aliases, repeated reads of the same immutable field or array position, and the same argument passed to multiple parameters. A newly evaluated calculation or function call is a new value. Audit code: `[guard-derived-value]`.
|
|
161
|
+
|
|
162
|
+
#### Ranges use inclusive endpoints
|
|
163
|
+
|
|
164
|
+
Freerange stores every range using its lowest and highest included values. JavaScript numbers are discrete, so the neighboring representable number can often express a strict endpoint: `Math.random()` is stored as `0..0.9999999999999999` and reported as `at least 0 and less than 1`. This needs no rewrite.
|
|
165
|
+
|
|
166
|
+
#### Branches merge into one continuous range
|
|
167
|
+
|
|
168
|
+
Freerange combines both branch results into one continuous range. Here `width` becomes `240..480`, which includes `300` even though neither branch returns it:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
export function previewRatio(compact: boolean): number {
|
|
172
|
+
const width = compact ? 240 : 480
|
|
173
|
+
return 100 / (width - 300)
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Keep a calculation inside each branch when it depends on the separate alternatives:
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
export function previewRatio(compact: boolean): number {
|
|
181
|
+
if (compact) return 100 / (240 - 300)
|
|
182
|
+
return 100 / (480 - 300)
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
A broad but safe range may need no rewrite.
|
|
187
|
+
|
|
188
|
+
#### A number remembers at most one excluded value
|
|
189
|
+
|
|
190
|
+
After `code !== 240` and `code !== 300`, Freerange may remember only the later exclusion. When an operation depends on a particular exclusion, check the value that the operation uses:
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
const divisor = code - 240
|
|
194
|
+
if (divisor === 0) return 0
|
|
195
|
+
return 100 / divisor
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Freerange does not retain arbitrary sets such as "every number except 240 and 300."
|
|
199
|
+
|
|
200
|
+
#### No transitive reasoning between comparisons
|
|
201
|
+
|
|
202
|
+
Freerange does not combine `left <= middle` and `middle <= right` to prove `left <= right`:
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
if (left > middle) throw new Error('out of order')
|
|
206
|
+
if (middle > right) throw new Error('out of order')
|
|
207
|
+
console.assert(left <= right) // unproven
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
When one value is built from another, keep the relationship visible in that calculation:
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
const gap = Math.max(0, requestedGap)
|
|
214
|
+
const left = navRight + gap
|
|
215
|
+
console.assert(navRight <= left)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
If the values arrive independently, Freerange may leave the relationship unproven.
|
|
219
|
+
|
|
220
|
+
#### No algebraic inversion of conditions
|
|
221
|
+
|
|
222
|
+
Freerange narrows the direct operands of a comparison but does not rearrange `width * 2 > 10` to derive `width > 5`. Check the value used by the later operation:
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
export function previewScale(width: number): number {
|
|
226
|
+
if (width <= 5) return 1
|
|
227
|
+
return 100 / width
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
#### No algebraic normalization
|
|
232
|
+
|
|
233
|
+
Freerange does not rewrite expressions using associativity, commutativity, or distributivity. Those rules do not always preserve JavaScript floating-point results:
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
const amount = 9_007_199_254_740_992
|
|
237
|
+
const first = 3 + amount + 2 // 9007199254740998
|
|
238
|
+
const second = 1 + amount + 4 // 9007199254740996
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Choose the operation order whose floating-point behavior the application wants. For example, `frameWidth / (imageWidth / imageHeight)` introduces a ratio that can round to zero; `(frameWidth * imageHeight) / imageWidth` avoids that particular problem, although the two expressions can still round differently. Audit code: `[use-direct-operands]`.
|
|
242
|
+
|
|
243
|
+
#### Numeric truthiness is unsupported
|
|
244
|
+
|
|
245
|
+
Freerange does not guess what a number used as a condition means. Write `width === 0` instead of relying on a truthy or falsy number such as `width || 1`. Audit code: `[write-explicit-condition]`.
|
|
246
|
+
|
|
247
|
+
### Functions
|
|
248
|
+
|
|
249
|
+
Put important calculations in named synchronous functions. A React component, callback, or async function can call a plain helper:
|
|
250
|
+
|
|
251
|
+
```tsx
|
|
252
|
+
export function fittedImageHeight(frameWidth: number, imageWidth: number, imageHeight: number): number {
|
|
253
|
+
return (frameWidth * Math.max(1, imageHeight)) / Math.max(1, imageWidth)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function ImageCard(props: {frameWidth: number; imageWidth: number; imageHeight: number}) {
|
|
257
|
+
const height = fittedImageHeight(props.frameWidth, props.imageWidth, props.imageHeight)
|
|
258
|
+
return <img style={{height}} />
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Literal default parameters and omitted optional parameters work in supported same-file calls. Object and calculated defaults do not. Passing more arguments than the implementation declares is unsupported.
|
|
135
263
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
264
|
+
#### A function return does not include how the value was calculated
|
|
265
|
+
|
|
266
|
+
Freerange evaluates supported same-file helpers using what the caller knows. It keeps what the helper may return, including numeric ranges and object fields, but not an equation such as "this result is exactly `end - start`":
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
function span(start: number, end: number): number {
|
|
270
|
+
return end - start
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export function progressBar(value: number, start: number, end: number): number {
|
|
274
|
+
return (value - start) / span(start, end)
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
When the caller needs that exact calculation, calculate and check it in the caller, then pass the result to a helper. The same limitation applies to booleans: `true` from `isValidIndex(values, index)` does not tell the caller which checks made it true, so write those checks where they protect the array read.
|
|
279
|
+
|
|
280
|
+
#### Imported function bodies are not analyzed
|
|
281
|
+
|
|
282
|
+
Freerange does not follow imported functions. Keep a numeric rule that needs analysis in a supported helper with explicit inputs:
|
|
283
|
+
|
|
284
|
+
```ts
|
|
285
|
+
export function labelWidthFromMeasurement(measuredWidth: number): number {
|
|
286
|
+
return Math.max(120, measuredWidth + 32)
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
An unsupported caller can pass `measureText(text)` into this helper, allowing Freerange to verify the rule but not the imported measurement. Imported constants work when their initializer resolves to a numeric literal such as `export const GAP = 24`. Runtime import cycles are outside Freerange's scope: an imported module must finish initializing before analyzed code reads its values. Type-only import cycles are fine.
|
|
291
|
+
|
|
292
|
+
#### No higher-order function analysis
|
|
293
|
+
|
|
294
|
+
Freerange does not analyze callbacks passed to higher-order functions such as `reduce`, `map`, or `filter`. For a simple scalar aggregation, write the loop directly:
|
|
295
|
+
|
|
296
|
+
```ts
|
|
297
|
+
export function totalWidth(widths: number[]): number {
|
|
298
|
+
let total = 0
|
|
299
|
+
for (let index = 0; index < widths.length; index += 1) {
|
|
300
|
+
total += widths[index]!
|
|
139
301
|
}
|
|
140
|
-
|
|
302
|
+
return total
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
This is not a general replacement for `map` or `filter`: object and array writes remain unsupported, and callback arguments, effects, and result allocation may matter. Audit code: `[use-loop-for-aggregation]`.
|
|
307
|
+
|
|
308
|
+
### Objects, Arrays, and Changing State
|
|
309
|
+
|
|
310
|
+
Freerange reads plain objects, fixed tuples, dense arrays, and tagged unions declared in the project through at most eight nested levels. Give each union case a tag, use an exhaustive non-fallthrough `switch`, and keep deeply nested or unclassifiable data outside important numeric helpers.
|
|
311
|
+
|
|
312
|
+
Freerange assumes that property reads are stable and perform no work during one analyzed synchronous call. A getter or Proxy that changes its answer or performs work is outside the scope.
|
|
313
|
+
|
|
314
|
+
#### No object and array writes
|
|
315
|
+
|
|
316
|
+
Freerange allows local variables to be reassigned but does not track writes through an object or array. Return a new value when the application does not require mutation or stable object identity:
|
|
317
|
+
|
|
318
|
+
```ts
|
|
319
|
+
export function moveRight(point: {x: number; y: number}, distance: number): {x: number; y: number} {
|
|
320
|
+
return {x: point.x + distance, y: point.y}
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Object spread is also unsupported because JavaScript copies only an object's own enumerable properties, which may not match the fields declared by its TypeScript type. List the fields explicitly. Rebuilding an object is not equivalent to mutation when other code observes its identity or the mutation.
|
|
325
|
+
|
|
326
|
+
#### Reads of changing state are not referentially transparent
|
|
327
|
+
|
|
328
|
+
Referential transparency means that evaluating the same expression again is equivalent to reusing its previous result. Freerange does not make that assumption for a clock, viewport, scroll position, or mutable module binding. Store one read when the check and later use should observe the same value:
|
|
141
329
|
|
|
142
|
-
|
|
330
|
+
```ts
|
|
331
|
+
export function viewportScale(): number {
|
|
332
|
+
const viewportWidth = window.innerWidth
|
|
333
|
+
if (viewportWidth === 0) return 0
|
|
334
|
+
return 100 / viewportWidth
|
|
335
|
+
}
|
|
336
|
+
```
|
|
143
337
|
|
|
144
|
-
|
|
338
|
+
Keep separate reads when two observations are intentional, such as two clock reads used to measure elapsed time.
|
|
145
339
|
|
|
146
|
-
|
|
340
|
+
#### Array reads require dense arrays and valid indexes
|
|
147
341
|
|
|
148
|
-
|
|
342
|
+
Use `values[index] ?? fallback` only when the application wants a fallback. Otherwise, prove that `index` is an integer from zero through `values.length - 1` before using `values[index]!`. A bounds check cannot detect a hole in a sparse array, so Freerange expects arrays to be dense. Audit codes: `[handle-missing-element]`, `[guard-array-index]`.
|
|
343
|
+
|
|
344
|
+
### Loops
|
|
149
345
|
|
|
150
|
-
|
|
346
|
+
#### Loops find stable ranges, not exact formulas
|
|
151
347
|
|
|
152
|
-
|
|
348
|
+
Freerange checks a loop until the possible values at the start of an iteration stop changing. It does not simulate the exact runtime iteration count or derive a formula for the final value:
|
|
153
349
|
|
|
154
|
-
|
|
350
|
+
```ts
|
|
351
|
+
export function fixedTotal(): number {
|
|
352
|
+
let total = 0
|
|
353
|
+
for (let index = 0; index < 3; index += 1) {
|
|
354
|
+
total += 2
|
|
355
|
+
}
|
|
356
|
+
return total
|
|
357
|
+
}
|
|
358
|
+
```
|
|
155
359
|
|
|
156
|
-
|
|
360
|
+
Freerange knows that the result is a nonnegative integer but does not derive that it is exactly `6`. Ordinary counting loops usually settle after two or three checks. If a range still changes after 16 checks, Freerange stops analyzing that path. Write a formula directly when it is the intended implementation, but do not replace repeated floating-point arithmetic with multiplication unless the different rounding behavior is acceptable.
|
|
157
361
|
|
|
158
|
-
|
|
362
|
+
Code outside this scope may make a result less precise or stop analysis. Freerange does not publish a stronger guarantee by pretending that unsupported code was understood. If an unsupported pattern is important and cannot be reasonably refactored, please file an issue.
|
|
159
363
|
|
|
160
364
|
## Recommended TypeScript Config
|
|
161
365
|
|
|
@@ -186,55 +390,17 @@ Freerange uses a few terms consistently:
|
|
|
186
390
|
- `partially supported`: Freerange can analyze some, but not all, of the function.
|
|
187
391
|
- `skipped`: some top-level statements in the modules weren't analyzed.
|
|
188
392
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
An `ensures` line assumes its `requires` and `assumes`. A requirement may be a real API rule, or it may expose a relationship Freerange cannot currently prove. An assumption may identify a real input boundary or an analysis limitation. Decide what the program should do before changing code to remove either one.
|
|
192
|
-
|
|
193
|
-
Always read the coverage line. No findings does not mean an unsupported file is safe. A derived guarantee becoming weaker, for example `at least 54` becoming `at least 0`, appears in the audit rather than the shorter findings output.
|
|
194
|
-
|
|
195
|
-
## Analysis Scope
|
|
196
|
-
|
|
197
|
-
Freerange deliberately analyzes a restricted part of TypeScript. When code leaves that scope, `fr --audit` says so instead of guessing what the code does. If an unsupported pattern is important in production code and cannot be reasonably refactored, please file an issue. We're open to expanding the scope when the added complexity proves worthwhile.
|
|
198
|
-
|
|
199
|
-
### Numbers
|
|
200
|
-
|
|
201
|
-
Freerange's numeric analysis is designed for layouts and other everyday application code. For each TypeScript `number`, Freerange remembers its lowest and highest possible values, whether it is an integer, whether it may be `NaN` or infinite, and at most one exact value that has been ruled out. For example, after `value !== 0`, Freerange remembers that `value` cannot be `0`.
|
|
393
|
+
### Caller Requirements
|
|
202
394
|
|
|
203
|
-
|
|
395
|
+
Every plain `number` parameter must be finite and not `NaN`. The same rule applies to numeric fields in fixed-shape object parameters, even when the function does not read them. Numeric literal types such as `1 | 2` already satisfy the rule. Nullable numbers, arrays, tuples, and tagged unions use more specific `assumes` lines instead. A supported literal default can satisfy the requirement when a caller omits an argument.
|
|
204
396
|
|
|
205
|
-
|
|
397
|
+
Division and array reads can create additional requirements. Freerange tries to express them using the function's parameters so that supported same-file callers can prove them, pass them to their own callers, or report a definitely invalid argument. If a condition cannot be expressed that way, `fr --audit` prints a local `assumes` line instead.
|
|
206
398
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
### Function calls
|
|
210
|
-
|
|
211
|
-
Freerange analyzes supported function calls in the same file using what it knows at each call. It does not analyze imported functions. It reads an imported constant only when the constant resolves to a numeric literal such as `export const GAP = 24`. Literal default parameters work in supported calls; object and calculated defaults do not. Passing more arguments than the implementation declares is also unsupported.
|
|
212
|
-
|
|
213
|
-
Freerange does not guess what an unknown function does, when a callback runs, which exceptions are caught, how another reference might change an object, or how a framework schedules work.
|
|
214
|
-
|
|
215
|
-
### Static assertions
|
|
216
|
-
|
|
217
|
-
Inside a `console.assert`, Freerange can prove several common UI calculations through named values: `Math.min` and `Math.max`, adding or subtracting a nonnegative value, multiplying both sides of a comparison by the same nonnegative value, `index % columnCount < columnCount` when `columnCount` is positive, and fields read from a newly created object. Freerange does not chain arbitrary comparisons: `left <= middle` and `middle <= right` do not by themselves prove `left <= right`.
|
|
218
|
-
|
|
219
|
-
### Loops
|
|
220
|
-
|
|
221
|
-
Freerange checks a loop repeatedly until the possible values at the start of an iteration stop changing. It does not simulate every runtime iteration or try to derive a formula for the final value. Ordinary counting loops usually settle after two or three checks. If the possible values still change after 16 checks, Freerange stops analyzing that path.
|
|
222
|
-
|
|
223
|
-
### Objects and arrays
|
|
224
|
-
|
|
225
|
-
Freerange reads plain objects, tuples, arrays, and tagged unions declared in your project through at most eight nested levels. A deeper property becomes unknown. A function is unsupported when one of its parameter types cannot be represented within this scope.
|
|
226
|
-
|
|
227
|
-
Freerange assumes that reading a property returns the same value and performs no work during one analyzed function call. A getter or Proxy that changes its answer or performs work is outside the scope. Property writes, including assignments that invoke setters, are unsupported. Object spread is also unsupported because JavaScript copies only an object's own enumerable properties, which may not match the fields declared by its TypeScript type.
|
|
228
|
-
|
|
229
|
-
### Caller requirements
|
|
230
|
-
|
|
231
|
-
Every plain `number` parameter must be finite and not `NaN`. The same rule applies to numeric fields in fixed-shape object parameters, even when the function does not read them. Numeric literal types such as `1 | 2` already satisfy the rule. Nullable numbers, arrays, tuples, and tagged unions use more specific `assumes` lines instead. When a caller omits an argument with a supported literal default, the default can satisfy the requirement automatically.
|
|
232
|
-
|
|
233
|
-
Calls to supported functions in the same file either prove these requirements, require their own callers to satisfy them, or report a definitely invalid argument. After a call proves that an argument is finite, later uses of that same stored value can reuse the result. Writing `console.assert(Number.isFinite(value))` at the start of a function is allowed but normally redundant. `Number.isInteger(value)` is stronger and replaces the finite requirement.
|
|
399
|
+
A caller requirement is not automatically a bug. For example, `requires: columns >= 1` means the function is safe under that condition; it does not mean Freerange found a caller passing zero. Freerange checks supported same-file calls, but it is not a repository-wide call-site verifier. Imported calls and unsupported callers may remain unchecked.
|
|
234
400
|
|
|
235
|
-
|
|
401
|
+
An `ensures` line assumes its `requires` and `assumes`. A requirement may be a real API rule, or it may expose a relationship Freerange cannot currently prove. An assumption may identify a real input boundary or an analysis limitation. Decide what the program should do before changing code to remove either one.
|
|
236
402
|
|
|
237
|
-
|
|
403
|
+
Always read the coverage line. No findings does not mean an unsupported file is safe. A derived guarantee becoming weaker, for example `at least 54` becoming `at least 0`, appears in the audit rather than the shorter findings output.
|
|
238
404
|
|
|
239
405
|
## Development
|
|
240
406
|
|