|
|
|
|
@ -1,8 +1,8 @@
|
|
|
|
|
# 棘手的例子
|
|
|
|
|
|
|
|
|
|
在某些情况下,树状图的差异分析是具有挑战性的。本页展示了在开发过程中所观察到的困难情况。
|
|
|
|
|
树状差异分析有时具有挑战性。本页展示了开发过程中观察到的困难情况和处理结果。
|
|
|
|
|
|
|
|
|
|
并非所有这些情况在Difftastic中都能很好地工作。
|
|
|
|
|
并非所有这些情况在 Difftastic 中都能很好地工作。
|
|
|
|
|
|
|
|
|
|
## 添加定界符
|
|
|
|
|
|
|
|
|
|
@ -14,11 +14,15 @@ x
|
|
|
|
|
(x)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
理想输出: <code><span style="background-color: PaleGreen; color: #000">(</span>x<span style="background-color: PaleGreen; color: #000">)</span></code>
|
|
|
|
|
可能输出:<code><span style="background-color: PaleGreen; color: #000">(x)</span></code>
|
|
|
|
|
|
|
|
|
|
这个是十分棘手,因为`x`已经改变了它在树中的深度,但`x`本身却未发生改变。
|
|
|
|
|
理想输出:<code><span style="background-color: PaleGreen; color: #000">(</span>x<span style="background-color: PaleGreen; color: #000">)</span></code>
|
|
|
|
|
|
|
|
|
|
并不是所有的树形差异分析算法可以处理这个例子。同时仔细地展示出范例是具有挑战性的:我们希望去高亮出已改变的定界符,但不是他们的内容。这同样在更大的表达式是具有挑战性的。
|
|
|
|
|
这十分棘手,因为 `x` 改变了在树中的深度,但其本身却未发生改变。
|
|
|
|
|
|
|
|
|
|
并不是所有的树状差异分析算法可以处理这个例子。同时仔细地展示出范例是具有挑战性的:我们希望高亮已改变的定界符,而非他们的内容。这同样在更大的表达式中具有挑战性。
|
|
|
|
|
|
|
|
|
|
**Difftastic**:Difftastic 即使认为节点在不同深度,在这种情况下也能实现预期结果。
|
|
|
|
|
|
|
|
|
|
## 改变定界符
|
|
|
|
|
|
|
|
|
|
@ -30,9 +34,13 @@ x
|
|
|
|
|
[x]
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
正如这个包裹的例子,我们想要去高亮出定界符而不是`x`这个内容。
|
|
|
|
|
理想输出:<code><span style="background-color: #fbbd98; color: #000">(</span>x<span style="background-color: #fbbd98; color: #000">)</span></code>, <code><span style="background-color: PaleGreen; color: #000">[</span>x<span style="background-color: PaleGreen; color: #000">]</span></code>
|
|
|
|
|
|
|
|
|
|
正如这个例子,我们想要高亮定界符而非 `x` 这个内容。
|
|
|
|
|
|
|
|
|
|
**Difftastic**:通过树状差异分析,Difftastic 正确处理这个问题。
|
|
|
|
|
|
|
|
|
|
## 拓展定界符
|
|
|
|
|
## 扩展定界符
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
;; Before
|
|
|
|
|
@ -42,9 +50,13 @@ x
|
|
|
|
|
(x y)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
理想输出:<code>(x <span style="background-color: PaleGreen; color: #000">y</span>)</code>
|
|
|
|
|
可能输出 1:<code><span style="background-color: #fbbd98; color: #000">(</span>x<span style="background-color: #fbbd98; color: #000">)</span> y</code>, <code><span style="background-color: PaleGreen; color: #000">(</span>x y<span style="background-color: PaleGreen; color: #000">)</span></code>
|
|
|
|
|
|
|
|
|
|
在这个例子下,我们想要去高亮`y`。高亮显示定界符的话可能会让`x`看起来有所变化。
|
|
|
|
|
可能输出 2:<code>(x) <span style="background-color: #fbbd98; color: #000">y</span></code>, <code>(x <span style="background-color: PaleGreen; color: #000">y</span>)</code>
|
|
|
|
|
|
|
|
|
|
目前还不清楚在这种情况下,哪个结果更好。
|
|
|
|
|
|
|
|
|
|
**Difftastic**:Difftastic 目前显示结果 2,但这种情况下对成本模型很敏感。一些以前版本的 Difftastic 显示结果 1。
|
|
|
|
|
|
|
|
|
|
## 缩小定界符
|
|
|
|
|
|
|
|
|
|
@ -56,7 +68,7 @@ x
|
|
|
|
|
(x) y
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
这应该与扩展定界符的情况类似,去高亮定界符。
|
|
|
|
|
这与扩展定界符的情况类似。
|
|
|
|
|
|
|
|
|
|
## 使定界符不连贯
|
|
|
|
|
|
|
|
|
|
@ -70,7 +82,7 @@ x
|
|
|
|
|
|
|
|
|
|
理想输出:<code>(foo <span style="background-color:PaleGreen; color: #000">(novel)</span> (bar)</code>
|
|
|
|
|
|
|
|
|
|
很容易会变成:<code>(foo (<span style="background-color:PaleGreen; color: #000">novel</span>) <span style="background-color:PaleGreen; color: #000">(</span>bar<span style="background-color:PaleGreen; color: #000">)</span>)</code>,
|
|
|
|
|
很容易会变成 <code>(foo (<span style="background-color:PaleGreen; color: #000">novel</span>) <span style="background-color:PaleGreen; color: #000">(</span>bar<span style="background-color:PaleGreen; color: #000">)</span>)</code>,
|
|
|
|
|
其中后一组的定界符会被选中。
|
|
|
|
|
|
|
|
|
|
## 重新组织大节点
|
|
|
|
|
@ -84,8 +96,7 @@ x
|
|
|
|
|
([[foo]] x y)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
我们想高亮`[[foo]]`被移到括号内了。然而,一个简单的语法差异者更倾向于认为在前面删除`()`,在后面增加`()`,是最小的差异表现。
|
|
|
|
|
(见[issue 44](https://github.com/Wilfred/difftastic/issues/44)。)
|
|
|
|
|
我们想高亮 `[[foo]]` 被移到了括号内。然而,一个简单的语法差异者更倾向于认为在前面删除 `()` 并在后面增加 `()`,因为这是最小的差异表现(见[议题 #44](https://github.com/Wilfred/difftastic/issues/44))。
|
|
|
|
|
|
|
|
|
|
## 在列表内重新排列
|
|
|
|
|
|
|
|
|
|
@ -99,7 +110,7 @@ x
|
|
|
|
|
|
|
|
|
|
理想输出:<code>(<span style="background-color: PaleGreen; color: #000">y</span> <span style="background-color: PaleGreen; color: #000">x</span>)</code>
|
|
|
|
|
|
|
|
|
|
我们想突出显示列表的内容,而不是定界符。
|
|
|
|
|
我们想高亮列表内容,而非定界符。
|
|
|
|
|
|
|
|
|
|
## 中间插入
|
|
|
|
|
|
|
|
|
|
@ -113,7 +124,25 @@ foo(extra(bar(123)))
|
|
|
|
|
|
|
|
|
|
理想输出:<code>foo(<span style="background-color: PaleGreen; color: #000">extra(</span>bar(123)<span style="background-color: PaleGreen; color: #000">)</span>)</code>
|
|
|
|
|
|
|
|
|
|
我们想把`foo`和`bar`都看作是不变的。这种情况对于对树进行自下而上然后自上而下匹配的衍合算法来说是具有挑战性的。
|
|
|
|
|
我们想把 `foo` 和 `bar` 看作是未改变的。这种情况对树进行自下而上然后自上而下匹配的衍合算法具有挑战性。
|
|
|
|
|
|
|
|
|
|
## 标点符号元素
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
// Before
|
|
|
|
|
foo(1, bar)
|
|
|
|
|
|
|
|
|
|
// After
|
|
|
|
|
foo(bar, 2)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
可能输出:<code>foo(<span style="background-color: PaleGreen; color: #000">bar</span>, <span style="background-color: PaleGreen; color: #000">2</span>)</code>
|
|
|
|
|
|
|
|
|
|
理想输出:<code>foo(bar<span style="background-color: PaleGreen; color: #000">,</span> <span style="background-color: PaleGreen; color: #000">2</span>)</code>
|
|
|
|
|
|
|
|
|
|
`()` 内有两个元素,我们可以认为 `bar` 和 `,` 中有一个未改变(但不能认为两者都不改变,因为它们已经重新排序)。
|
|
|
|
|
|
|
|
|
|
我们想把 `bar` 看作是未改变的,因为它相比于 `,` 元素更加重要。在语言不可知的方式下完成这一点存在困难,所以 Difftastic 有一个小的低优先级标点符号元素列表。
|
|
|
|
|
|
|
|
|
|
## 滑块(平移)
|
|
|
|
|
|
|
|
|
|
@ -128,9 +157,9 @@ foo(extra(bar(123)))
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
git-diff有一些启发式方法来减少这种风险(比如说"patience diff"),但这个问题仍然可能发生。
|
|
|
|
|
git-diff 有一些启发式方法(比如 Patience Diff)降低这种风险,但这个问题仍可能发生。
|
|
|
|
|
|
|
|
|
|
接下来是一个在树状差异分析时常见的问题。
|
|
|
|
|
在树状差异分析时也有类似的问题。
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
;; Before
|
|
|
|
|
@ -143,7 +172,21 @@ A B
|
|
|
|
|
C D
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
理想情况下,我们更愿意将连续的节点标记为新的,所以我们强调`A B`而不是`B/nA`。从最长公序算法的角度来看,这两种选择是等价的。
|
|
|
|
|
可能输出:
|
|
|
|
|
|
|
|
|
|
<pre><code>A <span style="background-color: PaleGreen; color: #000">B</span>
|
|
|
|
|
<span style="background-color: PaleGreen; color: #000">A</span> B
|
|
|
|
|
C D
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
理想输出:
|
|
|
|
|
|
|
|
|
|
<pre><code>A B
|
|
|
|
|
<span style="background-color: PaleGreen; color: #000">A</span> <span style="background-color: PaleGreen; color: #000">B</span>
|
|
|
|
|
C D
|
|
|
|
|
</code></pre>
|
|
|
|
|
|
|
|
|
|
理想情况下,我们更期望将连续节点标记为新的。从最长共子序列算法(LCS)看,这两个选择等价。
|
|
|
|
|
|
|
|
|
|
## 滑块(嵌套)
|
|
|
|
|
|
|
|
|
|
@ -155,10 +198,11 @@ old1(old2)
|
|
|
|
|
old1(new1(old2))
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
这个应该是 <code>old1(<span style="background-color: PaleGreen; color: #000">new1(</span>old2<span style="background-color: PaleGreen; color: #000">)</span>)</code> 还是
|
|
|
|
|
<code>old1<span style="background-color: PaleGreen; color: #000">(new1</span>(old2)<span style="background-color: PaleGreen; color: #000">)</span></code>?
|
|
|
|
|
可能输出:<code>old1<span style="background-color: PaleGreen; color: #000">(new1</span>(old2)<span style="background-color: PaleGreen; color: #000">)</span></code>
|
|
|
|
|
|
|
|
|
|
理想输出:<code>old1(<span style="background-color: PaleGreen; color: #000">new1(</span>old2<span style="background-color: PaleGreen; color: #000">)</span>)</code>
|
|
|
|
|
|
|
|
|
|
正确的答案是取决于语言。大多数语言希望优先使用内部分隔符,而Lisps和JSON则喜欢使用外部分隔符。
|
|
|
|
|
正确的答案是取决于语言。大多数语言希望优先使用内部定界符,而 Lisps 与 JSON 则期望使用外部定界符。
|
|
|
|
|
|
|
|
|
|
## 最小化深度改变
|
|
|
|
|
|
|
|
|
|
@ -173,8 +217,7 @@ foo(456);
|
|
|
|
|
foo(789);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
我们认为`foo(123)`还是`foo(456)`与`foo(789)`匹配?
|
|
|
|
|
Difftastic优先考虑`foo(456)`,通过优先考虑相同嵌套深度的节点。
|
|
|
|
|
我们认为 `foo(123)` 与 `foo(456)` 中,哪个与 `foo(789)` 匹配?Difftastic 优先考虑 `foo(456)`,因为其优先考虑相同嵌套深度的节点。
|
|
|
|
|
|
|
|
|
|
## 有少量相似处的替代做法
|
|
|
|
|
|
|
|
|
|
@ -186,9 +229,11 @@ function foo(x) { return x + 1; }
|
|
|
|
|
function bar(y) { baz(y); }
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
在这个例子中,我们删除了一个函数,写了一个完全不同的函数。基于树状结构的差异可能会匹配 "函数 "和外部定界符,从而导致显示出许多令人困惑的小的变化。
|
|
|
|
|
可能结果:<code>function <span style="background-color: PaleGreen; color: #000">bar</span>(<span style="background-color: PaleGreen; color: #000">y</span>) { <span style="background-color: PaleGreen; color: #000">baz(y)</span>; }</code>
|
|
|
|
|
|
|
|
|
|
与滑块一样,替换问题也可能发生在基于文本的行差中。如果有少量的共同行,行差就会陷入困境。但树形差分的更精确、更细化的行为使这个问题更加普遍。
|
|
|
|
|
在这个例子中,我们删除了一个函数并写了一个完全不同的函数。树状结构差异分析可能会匹配 `function` 和外部定界符,从而导致高亮许多令人困惑的小变化。
|
|
|
|
|
|
|
|
|
|
与滑块一样,替代问题也可能发生在基于文本的行差中。如果有少量的共同行,行差就会陷入困境。但树状结构差异分析的精确、细化行为会使这个问题更加普遍。
|
|
|
|
|
|
|
|
|
|
## 匹配注释中的子字符串
|
|
|
|
|
|
|
|
|
|
@ -202,7 +247,7 @@ foobar();
|
|
|
|
|
foobaz();
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`foobar`和`foobaz`是完全不同的,它们的共同前缀`fooba`不应该被匹配起来。然而,为注释匹配共同的前缀或后缀是可取的。
|
|
|
|
|
`foobar` 和 `foobaz` 完全不同,它们的共同前缀 `fooba` 不应该被匹配。然而,为注释匹配共同的前缀或后缀是可取的。
|
|
|
|
|
|
|
|
|
|
## 多行注释
|
|
|
|
|
|
|
|
|
|
@ -218,7 +263,7 @@ if (x) {
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
这两个注释的内部内容在技术上是不同的。然而,我们想把它们当作是相同的。
|
|
|
|
|
这两个注释的内部内容在技术上是不同的。然而,我们期望把它们当作是相同的。
|
|
|
|
|
|
|
|
|
|
## 文档注释的换行
|
|
|
|
|
|
|
|
|
|
@ -234,7 +279,7 @@ if (x) {
|
|
|
|
|
* jumps over the lazy dog. */
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
里面的内容已经从 `jumps * over`变成了`immediately * jumps over`。然而,`*`是装饰性的,我们并不关心它的移动。
|
|
|
|
|
里面的内容已经从 `jumps * over` 变成了 `immediately * jumps over`。然而,`*` 是装饰性的,我们并不关心它的移动。
|
|
|
|
|
|
|
|
|
|
## 长字符串的小变化
|
|
|
|
|
|
|
|
|
|
@ -250,11 +295,11 @@ with lots of NOVEL words about
|
|
|
|
|
lots of stuff."""
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
将整个字符串字头突出显示为被删除并被一个新的字符串字头取代是正确的。然而,这让人很难看出实际改变了什么。
|
|
|
|
|
将整个字符串字头高亮为被删除并被一个新的字符串字头取代是正确的。然而,这让人很难看出实际改变了什么。
|
|
|
|
|
|
|
|
|
|
很明显,变量名应该被原子化处理,并且 注释是安全的,可以显示子字的变化。但不清楚如何处理一个20行字符串字面的小变化。
|
|
|
|
|
很明显,变量名应该被元素化处理,并且注释是安全的,可以显示子字符串的变化。但不清楚如何处理一个 20 行字符串字面值的小变化。
|
|
|
|
|
|
|
|
|
|
在空格上分割字符串并加以区别是很具有挑战的,但用户仍然想知道字符串内部的空白何时改变。`" "`和`" "`是不一样的。
|
|
|
|
|
在空格上分割字符串并加以区别很具有挑战性,但用户仍期望知道字符串内部的空白何时改变。`" "` 与 `" "` 是不同的。
|
|
|
|
|
|
|
|
|
|
## 自动格式化工具的拼写
|
|
|
|
|
|
|
|
|
|
@ -270,13 +315,13 @@ foo(
|
|
|
|
|
);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
自动格式化(例如[prettier](https://prettier.io/))有时会在格式化时添加或删除标点符号。逗号和括号是最常见的。
|
|
|
|
|
自动格式化(例如 [Prettier](https://prettier.io/))有时会在格式化时添加或删除标点符号,其中逗号和括号最常见。
|
|
|
|
|
|
|
|
|
|
语法差异可以忽略空白处的变化,但它必须假设标点符号是有意义的。这可能导致标点符号的变化被突出显示,而这可能与相关的内容变化相差甚远。
|
|
|
|
|
语法差异可以忽略空白处的变化,但它必须假定标点符号有意义。这可能导致标点符号的变化被突出显示,而这可能与相关的内容变化相差甚远。
|
|
|
|
|
|
|
|
|
|
## 新空行
|
|
|
|
|
|
|
|
|
|
空行对于句法差异来说是一种挑战。我们要比较的是语法标记,所以我们不会看到空行。
|
|
|
|
|
空行对于语法差异分析来说是一种挑战。我们要比较的是语法标记,所以我们不会看到空行。
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
// Before
|
|
|
|
|
@ -289,9 +334,9 @@ A
|
|
|
|
|
B
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
一般来说,我们希望语法差异能够忽略空行。在第一个例子中,这应该不会显示任何变化。
|
|
|
|
|
一般来说,我们期望语法差异能够忽略空行。在第一个例子中,这应该不会显示任何变化。
|
|
|
|
|
|
|
|
|
|
这有时是有问题的,因为它可以会意外地隐藏被重新格式化地代码。
|
|
|
|
|
这有时是有问题的,因为它可以会意外地隐藏被重新格式化的代码。
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
// Before
|
|
|
|
|
@ -306,7 +351,7 @@ Y
|
|
|
|
|
B
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
在这第二个例子中,我们插入了X和Y以及一个空行。我们想把空行作为一个补充来高亮。
|
|
|
|
|
在第二个例子中,我们插入了 X 和 Y 以及一个空行。我们想把空行作为一个补充来高亮。
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
// Before
|
|
|
|
|
@ -321,10 +366,18 @@ X
|
|
|
|
|
B
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
在这第三个例子中,语法上的差异只看到了一个增加。从用户的角度来看,也有两个空行被删除。
|
|
|
|
|
在第三个例子中,语法差异分析只看到了一个增加。从用户角度来看,也有两个空行被删除。
|
|
|
|
|
|
|
|
|
|
## 无效语法
|
|
|
|
|
|
|
|
|
|
我们不能保证我们得到的输入是有效的语法。即使代码是有效的,它也可能使用解析器不支持的语法。
|
|
|
|
|
|
|
|
|
|
Tree-sitter可以显示出显式的错误节点,而Difftastic会将它们视为原子,因此它可以不顾一切地运行相同的树形差异算法。
|
|
|
|
|
**Difftastic**:如果发生任何解析错误,Difftastic 将退回到基于文本的差异,以避免差异不完整的语法树。发生这种情况时,文件头会报告错误计数。
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
$ difft sample_files/syntax_error_before.js sample_files/syntax_error_after.js
|
|
|
|
|
sample_files/syntax_error_after.js --- Text (2 errors, exceeded DFT_PARSE_ERROR_LIMIT)
|
|
|
|
|
...
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
用户可以选择通过将 `DFT_PARSE_ERROR_LIMIT` 设置为一个更大的值,加入语法差异分析。在这种模式下,Difftastic 会将树状差异分析的错误节点看作元素,并像通常一样进行树状差异分析。
|
|
|
|
|
|