离线
Freerange 显示每个区域的范围 number 在您的 TypeScript 代码库中,让你找到潜力 NaN, (中文(简体) ), Infinity,除以零,出界数组指数,等等.
- 使用正式类型脚本 API。不是新语言,也不是叉子。 没有说明, 没有库功能 .
- 静态Freerang在编译(构建)时间工作,如TS. 不需要启动你的应用程序。 AI代理现在可以保证UI的布局,而从未碰过浏览器!
- 快点。使用TypeScript分析时间的可忽略分数.
- 强壮由特工对数千个边缘病例进行反常测试.
Freerang被刻意地设计为迎合TypeScript的有用(并正在成长)子集,并为将重要计算移入该子集提供具体指导,这样你的代码和数学就可以在中间相会以解锁最能证明的功率,而不会出现太多的ergonomics缺陷. AI特工特别适合重构这种代码,我们强烈地建议你要求他们这样做. 然而,如果你/他们确实找到一个不支持的TS特性真正有价值,请提交一个问题!
安装
bun install --dev @chenglou/freerange # npm install works too of course
API 密码
没有API=. 您的 TypeScript 代码为 Freerang 分析提供了足够的信息 。 我们建议你的特工们 以下列方便分析的方式 塑造代码.
图标
fr: 打印工程错误和警告fr --audit:打印每个函数的合同,外加重构建议来帮助Freerange更好地分析. 对特工来说很好
传出一个文件路径到任一命令以过滤到文件的报告.
fr 在引擎盖下直接使用 TypeScript, 因此它自然尊重您 tsconfig。我们在分析前输出 TS 错误,所以在技术上,你可以互换你的清晰 tsc --noEmit 命令用于 fr 没有改变!
实例
1: 抓取 UI 缩放臭虫
function gridColumnCount(containerWidth: number) {
return Math.floor(containerWidth / 240)
}
function gridItemWidth(containerWidth: number) {
return containerWidth / gridColumnCount(containerWidth)
}
gridItemWidth(200)
bun fr (或 npx fr产出:
index.ts:9:1 - error [inferred-requirement]: call to gridItemWidth violates its nonzero divisor requirement (division at index.ts:6:10)
如何运作: Freerang 遵循 200 输入 gridItemWidth,然后通过呼吁 gridColumnCount。它的工作结果 Math.floor(200 / 240) 实值 0,然后通过这个结果捕捉到后一个师。 TypeScript只知道这些值是数字;Freerang通过两个函数跟踪它们的区域.
2:静态 console.assert
你知道吗 console.log 有个不太出名的弟弟: console.assert当断言是真的, 它保持沉默。 如果断言是虚假的,则报告失败.
单靠自己, console.assert 不如普世实用 console.log。通过分析自由离线系统的变化 console.assert 静态数字 :
export function itemColumn(itemIndex: number, columnCount: number): number {
console.assert(Number.isInteger(columnCount))
console.assert(columnCount >= 1)
const index = Math.max(0, Math.floor(itemIndex))
const column = index % columnCount
console.assert(column < columnCount)
return column
}
在上述例子中 itemColumn(0, 2.2) 产生出错( e)columnCount 应为整数) 编译时间不要在跑步时间! 无需启动浏览器即可知道这里的代码是错误的.
console.assert 在任何其它语句之前,一个函数开始时的呼叫是呼叫者的要求。 和参数类型一样,每个呼叫者必须满足它们.
任意 console.assert 稍后在函数中会被Freerange验证为函数本身. 否则,Freerange报告错误.
主要的断言可以将两种投入与 ===, (中文(简体) ), <, (中文(简体) ), <=, (中文(简体) ), >,或 >=。 。 。 . !== 仍然需要固定的有限数目。 证明比较的打电话者立即满足了要求;否则,同样的要求会传给其打电话者:
function clamp(minimum: number, value: number, maximum: number): number {
console.assert(minimum <= maximum)
return Math.min(maximum, Math.max(minimum, value))
}
譬如说, clamp(0, opacity, 1) 满足要求,同时 clamp(1, opacity, 0) 是一个错误.
为了简单和可预测, console.assert 目前只工作于命名的顶级函数并接受简单的数字检查:
Number.isInteger, (中文(简体) ),Number.isFinite, (中文(简体) ),Number.isNaN- 比较(
===, (中文(简体) ),!==, (中文(简体) ),<, (中文(简体) ),>, (中文(简体) ),<=, (中文(简体) ),>=使用数字字元、对象路径和array.length - 模块常数参考. 对于调用者的要求,常数必须解决为数字字数
我们也不支持化名 console.assert,比如说. const assert = console.assert。 。 。 .
对于更复杂的断言,如内在计算,将其取入变量:
const availableWidth = frame.right - frame.left
console.assert(availableWidth >= 0)
(你可以脱去) console.assert 在生产中使用捆绑机或邦的起落特性,就像你已经做的那样。)
值得一提的东西
有许多可以断言的事情。 这里有一些好的,没有噪音的:
- 保证两个UI项目不重叠:
console.assert(input.bottom < content.top) - 保证虚拟清单永远不使项目超出预期:
const visibleItemCount = endIndex - startIndex console.assert(visibleItemCount <= MAX_VISIBLE_ITEM_COUNT) - 确保两个单独计算的数值相等:
const frame = { input: {bottom: inputBottom}, inputTray: {bottom: inputBottom}, } console.assert(frame.inputTray.bottom === frame.input.bottom)
每个平地 number 参数已要求有限度, 非NaN 数值。 同一要求也适用于从固定形状对象参数中选择的数字字段。 Freerange 也检查一下 divisor 是否 0 和所显示的其他条件 fr --audit. 你不需要明确声明同样的信息.
写入可分析类型脚本
Freerang 刻意分析 TypeScript 中的一个受限制的部分. 当代码离开范围, fr --audit 而不是猜测代码的作用 Freerang目前支持:
- 命名, 同步的顶级函数 。 两者
function size(...) {}直接const size = (...) => ...宣言发挥作用。 Freerange 跟踪同一文件中函数之间的调用 - 数字、布林斯、字符串、无效值、平面对象、被标记的结合、稠密的阵列和固定的Tuple
if编号 :else,三角形,不坠入switch, (中文(简体) ),&&, (中文(简体) ),||, (中文(简体) ),!, (中文(简体) ),??, (中文(简体) ),for, (中文(简体) ),while,以及for...of循环- 算术、比较、对象字段和数组读取、选择
Math业务,以及Number.isInteger, (中文(简体) ),Number.isFinite,以及Number.isNaN
Freerang在理论上可以支持一个更大的子集TS,并且是在它公开发布之前做的. 然而,这些模式往往使数字推论和证明变得更加困难和缓慢,有些问题一般是无法确定的。 现在AI代理写代码,我们强烈建议请代理将重要计算重构为Freerange分析良好的外形,以 fr --audit. 易于分析的代码往往类似于功能编程:不可移动的数据,明确的输入和输出,以及干净的直接控制流.
使用精确的脚本类型 。 避免 any,投出和压制注释,并分析外部数据,然后将其传递给一个数字助手。 文件包含 @ts-ignore, (中文(简体) ), @ts-expect-error, (中文(简体) ), @ts-nocheck,或 eval 被拒绝,因为其宣布的类型无法信任.
在修改输入规则之前,先决定应用程序应当做什么. 若为 columnCount 必须是正整数,或者要求使用正整数 console.assert 呼叫或使其正常化 Math.max(1, Math.floor(columnCount))。只有当应用程序想要运行时的行为时,才会恢复正常。 审计代码: [encode-input-rule]。 。 。 .
数字分析
每笔 number, Freerange 记住它的最低和最高可能值, 无论它是整数, 也可能是 NaN 或无限, 最多一个被排除的确切值。 它解释了JavaScript浮点数而不是理想真实数的原因,没有使用通用定理证明.
Freerange也支持数字幻象类型,例如. type Pixels = number & {readonly __brand: 'pixels'}。 。 。 .
没有普通子表达式消除
Freerange不以一个存储结果来取代重复计算,即使其源代码相同. 此函数检查一个减法,然后被新评估的减法除以:
export function progressBar(value: number, start: number, end: number): number {
if (end - start === 0) return 0
return (value - start) / (end - start)
}
当检查和除法必须使用相同结果时计算一次减值:
export function progressBar(value: number, start: number, end: number): number {
const span = end - start
if (span === 0) return 0
return (value - start) / span
}
Freerang 识别别名,重复读取相同的不可变字段,重复的数组读取使用相同的存储索引,同样的参数传递到多个参数. 新评价的计算或函数调用是一个新值. 审计代码: [guard-derived-value]。 。 。 .
范围使用包容性终点
Freerange使用其最低和最高的数值存储每个区域。 JavaScript数字是离散的,所以相邻的可代表数字往往可以表达出严格的终点: Math.random() 保存为 0..0.9999999999999999 报告为 at least 0 and less than 1这不需要重写.
分支合并为一个连续区域
Freerange将两个分支结果合并为一个连续范围. 给 width 变成 240..480,包括 300 即使没有分支返回它:
export function previewRatio(compact: boolean): number {
const width = compact ? 240 : 480
return 100 / (width - 300)
}
取决于不同替代品时,在每个分支内部保留一个计算:
export function previewRatio(compact: boolean): number {
if (compact) return 100 / (240 - 300)
return 100 / (480 - 300)
}
宽而安全的范围可能不需要重写.
最多一个数字记得被排除在外的值
之后 code !== 240 财务报告和审定财务报表 code !== 300,Freerang可能只记得后来的排除. 当操作依赖于特定的排除时,检查操作使用的值:
const divisor = code - 240
if (divisor === 0) return 0
return 100 / divisor
Freerange没有保留"除了240和300之外的所有数字"等任意设定
比较之间没有过渡性推理
Freerange保留了每条入行路径建立时相同值之间的直接命令比较,包括通过同一文件中的支持帮助者. 例如,从 left <= right 财务报告和审定财务报表 lowerOffset <= upperOffset,它可以证明 left - upperOffset <= right - lowerOffset。不组合 left <= middle 财务报告和审定财务报表 middle <= right 证明 left <= right数字 :
if (left > middle) throw new Error('out of order')
if (middle > right) throw new Error('out of order')
console.assert(left <= right) // unproven
从 left === right弗里兰奇可以证明 left - right === 0 并在双方增减相同值时维护平等。 当双方以相同值相乘或相分而其标志已知时,它也起作用. Freerang 不另替换 left 与 right 在更大的计算中.
由同一种非负值相乘两相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相 分会有相应的规则,但排除了0:正分会维护秩序而负分会逆转. 未知的标志或不同因素仍未证实:
if (left > right || divisor <= 0) return
const dividedLeft = left / divisor
const dividedRight = right / divisor
console.assert(dividedLeft <= dividedRight)
这些规则只适用于两个结果都无法产生的情况 NaN。这些证明是非限制性的,因为浮点四舍五入可能使两个结果相等.
当一个值从另一个值中构建时, 使关系在计算中可见 :
const gap = Math.max(0, requestedGap)
const left = navRight + gap
console.assert(navRight <= left)
如果数值独立到达,Freerange可能会使关系得不到证实.
分会结果保持范围,不是每个分会的关系
Freerange 不将不同关系从每个分支转移到已合并的结果:
console.assert(minimum <= maximum)
const result = value > maximum ? maximum : value < minimum ? minimum : value
console.assert(minimum <= result) // unproven
当预期操作是 Math.min编号 :Math.max 夹住,写下这个操作 直接让Freerange看到它的界限。 Freerang没有将关系转移到分支结果或被循环所取代的值上.
没有代数条件倒置
Freerange 缩小了比较的直接操作,但不重排 width * 2 > 10 生成 width > 5。检查后期操作所用的值 :
export function previewScale(width: number): number {
if (width <= 5) return 1
return 100 / width
}
没有代数正常化
Freerange不重写表达式使用关联性,共通性,或分布性. 这些规则并不总是保留 JavaScript 浮点结果:
const amount = 9_007_199_254_740_992
const first = 3 + amount + 2 // 9007199254740998
const second = 1 + amount + 4 // 9007199254740996
选择应用程序想要的浮点行为操作顺序 。 譬如说, frameWidth / (imageWidth / imageHeight) 引入一个可四至零的比例; (frameWidth * imageHeight) / imageWidth 避免了这个特殊的问题,尽管这两个表达方式仍然可以不同地圆形. 审计代码: [use-direct-operands]。 。 。 .
数字真实性不支持
Freerange 无法猜测一个数字作为条件意味着什么. 写入 width === 0 而不是依赖一个真实或虚幻的数字,例如: width || 1审计代码: [write-explicit-condition]。 。 。 .
职能
在命名的同步函数中放入重要的计算. React 组件,回调,或async函数可以调用普通帮助器:
export function fittedImageHeight(frameWidth: number, imageWidth: number, imageHeight: number): number {
return (frameWidth * Math.max(1, imageHeight)) / Math.max(1, imageWidth)
}
function ImageCard(props: {frameWidth: number; imageWidth: number; imageHeight: number}) {
const height = fittedImageHeight(props.frameWidth, props.imageWidth, props.imageHeight)
return <img style={{height}} />
}
在支持的同文件调用中,literal默认参数并省略了可选参数的工作. 对象和计算出的默认值没有. 传递比执行声明更多的论据是不支持的.
函数返回不包括数值是如何计算的
Freerang评价支持使用调用者所知的同名文件帮助者. 它保留了帮助者可能返回的东西,包括数值范围与对象字段,但并没有像"这个结果正是 end - start" :
function span(start: number, end: number): number {
return end - start
}
export function progressBar(value: number, start: number, end: number): number {
return (value - start) / span(start, end)
}
当呼叫者需要准确的计算时,计算并在呼叫者中检查,然后将结果传递给帮助者. 同样的限制也适用于布尔人: true 从 isValidIndex(values, index) 不告诉呼叫者哪个检查是真实的,所以写下那些保护数组的检查.
未分析导入的函数机构
Freerange 不遵循导入的函数 。 保留一条需要在有明确输入的辅助工具中进行分析的数字规则:
export function labelWidthFromMeasurement(measuredWidth: number): number {
return Math.max(120, measuredWidth + 32)
}
不支持的呼叫者可以通过 measureText(text) 输入此帮助器,允许 Freerang 验证规则而不是导入的测量。 导入的常数在初始化器解为数字字元时有效, 如 export const GAP = 24。运行时导入周期超出了 Freerange 的范围:一个导入的模块必须在分析代码读取其值之前完成初始化。 只类型导入周期很好 .
没有高序函数分析
Freerange 不分析传递给更高排序函数的回调, 例如 reduce, (中文(简体) ), map,或 filter。对于简单的分级聚合,请直接写入循环:
export function totalWidth(widths: number[]): number {
let total = 0
for (let index = 0; index < widths.length; index += 1) {
total += widths[index]!
}
return total
}
这不是一般替代 map 或者说 filter:对象和数组写入仍然不支持,召回参数,效果和结果分配可能很重要. 审计代码: [use-loop-for-aggregation]。 。 。 .
对象、阵列和变化状态
Freerange读取平地物体,固定的Tuple,稠密的阵列,以及被标记的结合在最多8个嵌入级. 对于被打出的对象,它分析文件中使用的字段,并通过任务,返回,阵列,以及文件中函数之间的调用来跟踪. 您项目定义的数字字段成为调用符要求; 库字段, 如 MouseEvent.clientX 是假设 对象字节仍然评估和保留它们构建的每个字段.
当有条件或无效的表达式在不同的对象类型中选择时,在读取其一个字段前指定所选值,例如. const point = useFallback ? fallback : measured; return point.x。给每个工会案例一个标签,使用一个详尽无遗的不倒 switch,并在重要的数字辅助器外,保持深层嵌入或不可分类的数据.
Freerang 假设属性读取是稳定的,在一次分析同步调用时不做任何工作. 改变答案或执行工作的获取器或代理设备不在范围之内.
没有对象和数组写入
Freerange 允许本地变量被重新分配,但不通过对象或阵列跟踪写入. 当应用程序不需要突变或稳定对象身份时返回新值:
export function moveRight(point: {x: number; y: number}, distance: number): {x: number; y: number} {
return {x: point.x + distance, y: point.y}
}
对象分布也是不支持的,因为JavaScript复制只复制一个对象自己的可假设属性,这可能与它的TypeScript类型所声明的字段不符. 明确列出字段。 当其他代码观察物体的特性或变异时,重建物体不等同于变异.
状态变化的读取不优于透明
参考性透明度是指,再次评价同一表达方式相当于重新使用其先前的结果。 Freerang没有为时钟,取景口,滚动位置,或可变模块装订设定了这个假设. 当检查和以后使用时, 存储一个读取相同值 :
export function viewportScale(): number {
const viewportWidth = window.innerWidth
if (viewportWidth === 0) return 0
return 100 / viewportWidth
}
当两个观察是故意的时,例如用两个钟来测量已过时间时,就保持单独的读取.
阵列读取需要密集数组和有效索引
使用 values[index] ?? fallback 只有在应用程序想要倒置时。 否则,证明 index 是零到零的整数 values.length - 1 在使用前 values[index]!。边框检查无法检测到一个稀有阵列中的空洞,因此Freerange期望阵列是密集的。 审计代码: [handle-missing-element], (中文(简体) ), [guard-array-index]。 。 。 .
循环
循环找到稳定的范围,而不是精确的公式
Freerange检查一个循环,直到迭代开始时可能存在的值停止改变. 它不模拟精确的运行时间迭代计数或得出最终值的公式:
export function fixedTotal(): number {
let total = 0
for (let index = 0; index < 3; index += 1) {
total += 2
}
return total
}
Freerange知道结果是一个非负整数,但并不得出它是完全的 6。普通计数循环通常在两到三次检查后结算。 如果一个范围在16次检查后仍然有变化,Freerange停止分析这条路径. 当是预定执行时直接写入一个公式,但不要用乘法来取代重复的浮点算法,除非不同的四舍五入行为是可以接受的.
这一范围以外的守则可能会使结果更准确或停止分析。 Freerang不会因为假装不支持的代码被理解而发表更强有力的保证. 如果一个不支持的模式很重要,无法合理重构,请提交问题.
推荐类型脚本配置
Freerange 唯一授权的 TypeScript 编译器选项 strictNullChecks (否则分析太不安全),当 strict 开始 我们一般也建议采用以下备选办法。 它们对于Freerange的分析来说并不必要,但它们帮助AI特工和人类写出更安全的代码,这些代码更容易被分析:
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
}
}
fr --audit 产出
Freerange 统一使用几个术语:
requires:呼叫者必须满足的条件. 该功能的保证假设条件属实.ensures: 函数返回时返回值的保证.assumes:一个输入条件 Freerange 未经证明即接受,例如一个阵列是密集的,或者一个阵列的每个元素number[]是有限的.proves: 一个成功的静态console.assert检查.unsupported: Freerang 无法分析函数,因为它在被分析子集之外使用代码. Freerang 显示第一个可以重构的阻塞器 .partially supported: Freerang可以分析部分,但不是全部功能.skipped:模块中的一些顶级语句没有被分析.
呼叫要求
每个平地 number 参数必须是有限的,而不是 NaN,即使该函数不读取。 同样的规则适用于从固定形状对象参数中选择的数字字段. 数字字形类型,例如 1 | 2 已经满足了规则。 可忽略数字、阵列、拖曳和有标记的结合使用更具体的 assumes 线条代替。 支持的字面默认可以满足调用者省略参数时的要求.
分区和阵列读取可以产生额外的要求. Freerang试图使用函数的参数来表达它们,这样支持同名调用者就可以证明它们,把它们传给自己的调用者,或者报告一个绝对无效的参数. 如果一个条件不能以这种方式表达, fr --audit 打印一个本地 assumes 线条代替.
呼叫要求不是自动错误 。 譬如说, requires: columns >= 1 表示该函数在此条件下是安全的;并不意味着Freerang发现一个呼叫器通过0. Freerange检查支持同一文件的调用,但它不是一个全库的呼叫站点验证器. 进口的电话和不支持的电话可能仍然得不到控制.
一个 ensures 线条假定 requires 财务报告和审定财务报表 assumes。要求可能是真正的API规则,也可能暴露出目前无法证明的一段关系。 假设可以识别出真正的输入边界或分析限制. 决定程序在更改代码以删除其中之一之前应做什么.
总是读到覆盖线。 没有发现并不意味着一个不支持的文件是安全的 。 例如,衍生担保变得较弱 at least 54 成为 at least 0,而不是较短的审计结果产出.
发展
bun install
bun run check