@spaceflow/review 2.0.0 → 3.0.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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.0.0](https://github.com/Lydanne/spaceflow/compare/@spaceflow/review@1.0.0...@spaceflow/review@2.0.0) (2026-04-13)
|
|
4
|
+
|
|
5
|
+
### 代码重构
|
|
6
|
+
|
|
7
|
+
* **review:** 优化 Example 标题和描述解析逻辑 ([decb54f](https://github.com/Lydanne/spaceflow/commit/decb54f6832acaeddd83ccfb6a3439c47294f11d))
|
|
8
|
+
|
|
9
|
+
### 其他修改
|
|
10
|
+
|
|
11
|
+
* **review-summary:** released version 1.0.0 [no ci] ([742d53e](https://github.com/Lydanne/spaceflow/commit/742d53efb7f16e33c50d9b1c4b9e31a7c0e8da21))
|
|
12
|
+
|
|
3
13
|
## [1.0.0](https://github.com/Lydanne/spaceflow/compare/@spaceflow/review@0.83.0...@spaceflow/review@1.0.0) (2026-04-13)
|
|
4
14
|
|
|
5
15
|
### ⚠ BREAKING CHANGES
|
package/dist/index.js
CHANGED
|
@@ -988,12 +988,12 @@ class ReviewSpecService {
|
|
|
988
988
|
* 如果 spec 没有 includes 配置,则保留该 spec 的所有 issues
|
|
989
989
|
* 支持 `added|`/`modified|`/`deleted|` 前缀语法
|
|
990
990
|
*/ filterIssuesByIncludes(issues, specs, changedFiles) {
|
|
991
|
-
// 构建
|
|
991
|
+
// 构建 rule.id -> includes 的映射(规则级优先,文件级兜底)
|
|
992
992
|
const specIncludesMap = new Map();
|
|
993
993
|
for (const spec of specs){
|
|
994
|
-
// 从规则 ID 前缀推断 spec filename
|
|
995
994
|
for (const rule of spec.rules){
|
|
996
|
-
|
|
995
|
+
// 规则级 includes 覆盖文件级 includes,与 severity 优先级一致
|
|
996
|
+
specIncludesMap.set(rule.id, rule.includes ?? spec.includes);
|
|
997
997
|
}
|
|
998
998
|
}
|
|
999
999
|
return issues.filter((issue)=>{
|
package/package.json
CHANGED
|
@@ -1674,8 +1674,8 @@ const bad_name = 1;
|
|
|
1674
1674
|
});
|
|
1675
1675
|
});
|
|
1676
1676
|
|
|
1677
|
-
describe("filterIssuesByIncludes -
|
|
1678
|
-
it("should use rule-level includes
|
|
1677
|
+
describe("filterIssuesByIncludes - includes priority", () => {
|
|
1678
|
+
it("should use rule-level includes over file-level includes", () => {
|
|
1679
1679
|
const specs = [
|
|
1680
1680
|
{
|
|
1681
1681
|
filename: "nest.md",
|
|
@@ -1706,12 +1706,301 @@ const bad_name = 1;
|
|
|
1706
1706
|
];
|
|
1707
1707
|
const issues = [
|
|
1708
1708
|
{ file: "user.model.ts", ruleId: "JsTs.Nest.Model" },
|
|
1709
|
+
{ file: "order.model.ts", ruleId: "JsTs.Nest.Model" },
|
|
1709
1710
|
{ file: "user.controller.ts", ruleId: "JsTs.Nest" },
|
|
1711
|
+
{ file: "order.controller.ts", ruleId: "JsTs.Nest" },
|
|
1712
|
+
{ file: "user.service.ts", ruleId: "JsTs.Nest" },
|
|
1713
|
+
{ file: "user.module.ts", ruleId: "JsTs.Nest" },
|
|
1714
|
+
{ file: "user.model.ts", ruleId: "JsTs.Nest" }, // model 文件但 ruleId 是 Nest(文件级)
|
|
1710
1715
|
];
|
|
1711
1716
|
const result = service.filterIssuesByIncludes(issues, specs);
|
|
1712
|
-
//
|
|
1713
|
-
|
|
1714
|
-
|
|
1717
|
+
// JsTs.Nest.Model → 规则级 *.model.ts,user.model.ts / order.model.ts 匹配 → 保留
|
|
1718
|
+
// JsTs.Nest → 文件级 *.controller.ts,user.controller.ts / order.controller.ts 匹配 → 保留
|
|
1719
|
+
// user.service.ts / user.module.ts 不匹配文件级 *.controller.ts → 过滤
|
|
1720
|
+
// user.model.ts(ruleId=Nest) → 文件级 *.controller.ts,不匹配 → 过滤
|
|
1721
|
+
expect(result).toHaveLength(4);
|
|
1722
|
+
expect(result.map((i) => i.file)).toEqual([
|
|
1723
|
+
"user.model.ts",
|
|
1724
|
+
"order.model.ts",
|
|
1725
|
+
"user.controller.ts",
|
|
1726
|
+
"order.controller.ts",
|
|
1727
|
+
]);
|
|
1728
|
+
});
|
|
1729
|
+
|
|
1730
|
+
it("should fall back to file-level includes when rule has no includes", () => {
|
|
1731
|
+
const specs = [
|
|
1732
|
+
{
|
|
1733
|
+
filename: "nest.md",
|
|
1734
|
+
extensions: ["ts"],
|
|
1735
|
+
type: "nest",
|
|
1736
|
+
content: "",
|
|
1737
|
+
overrides: [],
|
|
1738
|
+
severity: "error" as const,
|
|
1739
|
+
includes: ["*.controller.ts"],
|
|
1740
|
+
rules: [
|
|
1741
|
+
{
|
|
1742
|
+
id: "JsTs.Nest",
|
|
1743
|
+
title: "Nest",
|
|
1744
|
+
description: "",
|
|
1745
|
+
examples: [],
|
|
1746
|
+
overrides: [],
|
|
1747
|
+
},
|
|
1748
|
+
{
|
|
1749
|
+
id: "JsTs.Nest.DirStructure",
|
|
1750
|
+
title: "DirStructure",
|
|
1751
|
+
description: "",
|
|
1752
|
+
examples: [],
|
|
1753
|
+
overrides: [],
|
|
1754
|
+
// 无 rule.includes → 回退到 spec.includes
|
|
1755
|
+
},
|
|
1756
|
+
],
|
|
1757
|
+
},
|
|
1758
|
+
];
|
|
1759
|
+
const issues = [
|
|
1760
|
+
{ file: "user.controller.ts", ruleId: "JsTs.Nest.DirStructure" },
|
|
1761
|
+
{ file: "order.controller.ts", ruleId: "JsTs.Nest.DirStructure" },
|
|
1762
|
+
{ file: "user.service.ts", ruleId: "JsTs.Nest.DirStructure" },
|
|
1763
|
+
{ file: "user.module.ts", ruleId: "JsTs.Nest.DirStructure" },
|
|
1764
|
+
{ file: "user.controller.ts", ruleId: "JsTs.Nest" },
|
|
1765
|
+
{ file: "user.service.ts", ruleId: "JsTs.Nest" },
|
|
1766
|
+
];
|
|
1767
|
+
const result = service.filterIssuesByIncludes(issues, specs);
|
|
1768
|
+
// DirStructure 无规则级 includes → 回退文件级 *.controller.ts
|
|
1769
|
+
// user.controller.ts / order.controller.ts 匹配 → 保留
|
|
1770
|
+
// user.service.ts / user.module.ts 不匹配 → 过滤
|
|
1771
|
+
// JsTs.Nest 同样回退文件级,user.controller.ts 匹配 → 保留,user.service.ts 不匹配 → 过滤
|
|
1772
|
+
expect(result).toHaveLength(3);
|
|
1773
|
+
expect(result.map((i) => i.file)).toEqual([
|
|
1774
|
+
"user.controller.ts",
|
|
1775
|
+
"order.controller.ts",
|
|
1776
|
+
"user.controller.ts",
|
|
1777
|
+
]);
|
|
1778
|
+
});
|
|
1779
|
+
|
|
1780
|
+
it("should use rule-level includes even when it narrows the file-level scope", () => {
|
|
1781
|
+
const specs = [
|
|
1782
|
+
{
|
|
1783
|
+
filename: "js&ts.md",
|
|
1784
|
+
extensions: ["ts"],
|
|
1785
|
+
type: "base",
|
|
1786
|
+
content: "",
|
|
1787
|
+
overrides: [],
|
|
1788
|
+
severity: "error" as const,
|
|
1789
|
+
includes: ["*.ts"], // 文件级:所有 ts 文件
|
|
1790
|
+
rules: [
|
|
1791
|
+
{
|
|
1792
|
+
id: "JsTs.Base",
|
|
1793
|
+
title: "Base",
|
|
1794
|
+
description: "",
|
|
1795
|
+
examples: [],
|
|
1796
|
+
overrides: [],
|
|
1797
|
+
},
|
|
1798
|
+
{
|
|
1799
|
+
id: "JsTs.Base.TestRule",
|
|
1800
|
+
title: "TestRule",
|
|
1801
|
+
description: "",
|
|
1802
|
+
examples: [],
|
|
1803
|
+
overrides: [],
|
|
1804
|
+
includes: ["*.spec.ts"], // 规则级:仅 spec 文件(收窄)
|
|
1805
|
+
},
|
|
1806
|
+
],
|
|
1807
|
+
},
|
|
1808
|
+
];
|
|
1809
|
+
const issues = [
|
|
1810
|
+
{ file: "app.spec.ts", ruleId: "JsTs.Base.TestRule" },
|
|
1811
|
+
{ file: "utils.spec.ts", ruleId: "JsTs.Base.TestRule" },
|
|
1812
|
+
{ file: "app.ts", ruleId: "JsTs.Base.TestRule" },
|
|
1813
|
+
{ file: "utils.ts", ruleId: "JsTs.Base.TestRule" },
|
|
1814
|
+
{ file: "app.ts", ruleId: "JsTs.Base" },
|
|
1815
|
+
{ file: "utils.ts", ruleId: "JsTs.Base" },
|
|
1816
|
+
{ file: "app.spec.ts", ruleId: "JsTs.Base" },
|
|
1817
|
+
];
|
|
1818
|
+
const result = service.filterIssuesByIncludes(issues, specs);
|
|
1819
|
+
// JsTs.Base.TestRule → 规则级 *.spec.ts,app.spec.ts / utils.spec.ts 匹配 → 保留
|
|
1820
|
+
// app.ts / utils.ts 不匹配 *.spec.ts → 过滤
|
|
1821
|
+
// JsTs.Base → 文件级 *.ts,app.ts / utils.ts / app.spec.ts 匹配 → 保留
|
|
1822
|
+
expect(result).toHaveLength(5);
|
|
1823
|
+
expect(result.map((i) => i.file)).toEqual([
|
|
1824
|
+
"app.spec.ts",
|
|
1825
|
+
"utils.spec.ts",
|
|
1826
|
+
"app.ts",
|
|
1827
|
+
"utils.ts",
|
|
1828
|
+
"app.spec.ts",
|
|
1829
|
+
]);
|
|
1830
|
+
});
|
|
1831
|
+
|
|
1832
|
+
it("should use rule-level includes even when it widens the file-level scope", () => {
|
|
1833
|
+
const specs = [
|
|
1834
|
+
{
|
|
1835
|
+
filename: "nest.md",
|
|
1836
|
+
extensions: ["ts"],
|
|
1837
|
+
type: "nest",
|
|
1838
|
+
content: "",
|
|
1839
|
+
overrides: [],
|
|
1840
|
+
severity: "error" as const,
|
|
1841
|
+
includes: ["*.controller.ts"], // 文件级:仅 controller
|
|
1842
|
+
rules: [
|
|
1843
|
+
{
|
|
1844
|
+
id: "JsTs.Nest",
|
|
1845
|
+
title: "Nest",
|
|
1846
|
+
description: "",
|
|
1847
|
+
examples: [],
|
|
1848
|
+
overrides: [],
|
|
1849
|
+
},
|
|
1850
|
+
{
|
|
1851
|
+
id: "JsTs.Nest.AllFiles",
|
|
1852
|
+
title: "AllFiles",
|
|
1853
|
+
description: "",
|
|
1854
|
+
examples: [],
|
|
1855
|
+
overrides: [],
|
|
1856
|
+
includes: ["*.ts"], // 规则级:所有 ts 文件(放宽)
|
|
1857
|
+
},
|
|
1858
|
+
],
|
|
1859
|
+
},
|
|
1860
|
+
];
|
|
1861
|
+
const issues = [
|
|
1862
|
+
{ file: "user.service.ts", ruleId: "JsTs.Nest.AllFiles" },
|
|
1863
|
+
{ file: "user.controller.ts", ruleId: "JsTs.Nest.AllFiles" },
|
|
1864
|
+
{ file: "user.module.ts", ruleId: "JsTs.Nest.AllFiles" },
|
|
1865
|
+
{ file: "user.controller.ts", ruleId: "JsTs.Nest" },
|
|
1866
|
+
{ file: "order.controller.ts", ruleId: "JsTs.Nest" },
|
|
1867
|
+
{ file: "user.service.ts", ruleId: "JsTs.Nest" },
|
|
1868
|
+
{ file: "user.module.ts", ruleId: "JsTs.Nest" },
|
|
1869
|
+
];
|
|
1870
|
+
const result = service.filterIssuesByIncludes(issues, specs);
|
|
1871
|
+
// JsTs.Nest.AllFiles → 规则级 *.ts,全部匹配 → 保留 3 个
|
|
1872
|
+
// JsTs.Nest → 文件级 *.controller.ts,user.controller.ts / order.controller.ts 匹配 → 保留
|
|
1873
|
+
// user.service.ts / user.module.ts 不匹配 → 过滤
|
|
1874
|
+
expect(result).toHaveLength(5);
|
|
1875
|
+
expect(result.map((i) => i.file)).toEqual([
|
|
1876
|
+
"user.service.ts",
|
|
1877
|
+
"user.controller.ts",
|
|
1878
|
+
"user.module.ts",
|
|
1879
|
+
"user.controller.ts",
|
|
1880
|
+
"order.controller.ts",
|
|
1881
|
+
]);
|
|
1882
|
+
});
|
|
1883
|
+
|
|
1884
|
+
it("should support status prefix in rule-level includes", () => {
|
|
1885
|
+
const specs = [
|
|
1886
|
+
{
|
|
1887
|
+
filename: "nest.md",
|
|
1888
|
+
extensions: ["ts"],
|
|
1889
|
+
type: "nest",
|
|
1890
|
+
content: "",
|
|
1891
|
+
overrides: [],
|
|
1892
|
+
severity: "error" as const,
|
|
1893
|
+
includes: ["*.controller.ts"], // 文件级:无前缀
|
|
1894
|
+
rules: [
|
|
1895
|
+
{
|
|
1896
|
+
id: "JsTs.Nest",
|
|
1897
|
+
title: "Nest",
|
|
1898
|
+
description: "",
|
|
1899
|
+
examples: [],
|
|
1900
|
+
overrides: [],
|
|
1901
|
+
},
|
|
1902
|
+
{
|
|
1903
|
+
id: "JsTs.Nest.Model",
|
|
1904
|
+
title: "Model",
|
|
1905
|
+
description: "",
|
|
1906
|
+
examples: [],
|
|
1907
|
+
overrides: [],
|
|
1908
|
+
includes: ["added|*.model.ts", "modified|*.dto.ts"], // 规则级:added 的 model + modified 的 dto
|
|
1909
|
+
},
|
|
1910
|
+
],
|
|
1911
|
+
},
|
|
1912
|
+
];
|
|
1913
|
+
const changedFiles = {
|
|
1914
|
+
getStatus: (file: string) => {
|
|
1915
|
+
if (file === "user.model.ts") return "added";
|
|
1916
|
+
if (file === "order.model.ts") return "modified";
|
|
1917
|
+
if (file === "user.dto.ts") return "modified";
|
|
1918
|
+
if (file === "order.dto.ts") return "added";
|
|
1919
|
+
return "modified";
|
|
1920
|
+
},
|
|
1921
|
+
} as any;
|
|
1922
|
+
const issues = [
|
|
1923
|
+
{ file: "user.model.ts", ruleId: "JsTs.Nest.Model" }, // added + *.model.ts → 匹配
|
|
1924
|
+
{ file: "order.model.ts", ruleId: "JsTs.Nest.Model" }, // modified + *.model.ts → 不匹配 added|
|
|
1925
|
+
{ file: "user.dto.ts", ruleId: "JsTs.Nest.Model" }, // modified + *.dto.ts → 匹配 modified|
|
|
1926
|
+
{ file: "order.dto.ts", ruleId: "JsTs.Nest.Model" }, // added + *.dto.ts → 不匹配 modified|
|
|
1927
|
+
{ file: "user.controller.ts", ruleId: "JsTs.Nest" }, // 文件级 *.controller.ts → 匹配
|
|
1928
|
+
{ file: "user.service.ts", ruleId: "JsTs.Nest" }, // 文件级 *.controller.ts → 不匹配
|
|
1929
|
+
];
|
|
1930
|
+
const result = service.filterIssuesByIncludes(issues, specs, changedFiles);
|
|
1931
|
+
expect(result).toHaveLength(3);
|
|
1932
|
+
expect(result.map((i) => i.file)).toEqual([
|
|
1933
|
+
"user.model.ts",
|
|
1934
|
+
"user.dto.ts",
|
|
1935
|
+
"user.controller.ts",
|
|
1936
|
+
]);
|
|
1937
|
+
|
|
1938
|
+
// 全部 modified 时,added|*.model.ts 不匹配,modified|*.dto.ts 匹配
|
|
1939
|
+
const changedFiles2 = {
|
|
1940
|
+
getStatus: () => "modified",
|
|
1941
|
+
} as any;
|
|
1942
|
+
const result2 = service.filterIssuesByIncludes(issues, specs, changedFiles2);
|
|
1943
|
+
// user.model.ts: modified, added|*.model.ts 不匹配 → 过滤
|
|
1944
|
+
// order.model.ts: modified, added|*.model.ts 不匹配 → 过滤
|
|
1945
|
+
// user.dto.ts: modified, modified|*.dto.ts 匹配 → 保留
|
|
1946
|
+
// order.dto.ts: modified, modified|*.dto.ts 匹配 → 保留
|
|
1947
|
+
// user.controller.ts: 文件级匹配 → 保留
|
|
1948
|
+
// user.service.ts: 文件级不匹配 → 过滤
|
|
1949
|
+
expect(result2).toHaveLength(3);
|
|
1950
|
+
expect(result2.map((i) => i.file)).toEqual([
|
|
1951
|
+
"user.dto.ts",
|
|
1952
|
+
"order.dto.ts",
|
|
1953
|
+
"user.controller.ts",
|
|
1954
|
+
]);
|
|
1955
|
+
});
|
|
1956
|
+
|
|
1957
|
+
it("should use empty rule-level includes to remove file-level restriction", () => {
|
|
1958
|
+
const specs = [
|
|
1959
|
+
{
|
|
1960
|
+
filename: "nest.md",
|
|
1961
|
+
extensions: ["ts"],
|
|
1962
|
+
type: "nest",
|
|
1963
|
+
content: "",
|
|
1964
|
+
overrides: [],
|
|
1965
|
+
severity: "error" as const,
|
|
1966
|
+
includes: ["*.controller.ts"], // 文件级:仅 controller
|
|
1967
|
+
rules: [
|
|
1968
|
+
{
|
|
1969
|
+
id: "JsTs.Nest",
|
|
1970
|
+
title: "Nest",
|
|
1971
|
+
description: "",
|
|
1972
|
+
examples: [],
|
|
1973
|
+
overrides: [],
|
|
1974
|
+
},
|
|
1975
|
+
{
|
|
1976
|
+
id: "JsTs.Nest.Global",
|
|
1977
|
+
title: "Global",
|
|
1978
|
+
description: "",
|
|
1979
|
+
examples: [],
|
|
1980
|
+
overrides: [],
|
|
1981
|
+
includes: [], // 规则级:空数组 = 无限制(覆盖文件级)
|
|
1982
|
+
},
|
|
1983
|
+
],
|
|
1984
|
+
},
|
|
1985
|
+
];
|
|
1986
|
+
const issues = [
|
|
1987
|
+
{ file: "user.service.ts", ruleId: "JsTs.Nest.Global" },
|
|
1988
|
+
{ file: "user.controller.ts", ruleId: "JsTs.Nest.Global" },
|
|
1989
|
+
{ file: "user.module.ts", ruleId: "JsTs.Nest.Global" },
|
|
1990
|
+
{ file: "user.service.ts", ruleId: "JsTs.Nest" },
|
|
1991
|
+
{ file: "user.controller.ts", ruleId: "JsTs.Nest" },
|
|
1992
|
+
{ file: "user.module.ts", ruleId: "JsTs.Nest" },
|
|
1993
|
+
];
|
|
1994
|
+
const result = service.filterIssuesByIncludes(issues, specs);
|
|
1995
|
+
// JsTs.Nest.Global → 规则级 includes=[] (空=无限制),全部保留 3 个
|
|
1996
|
+
// JsTs.Nest → 文件级 *.controller.ts,仅 user.controller.ts 匹配 → 保留
|
|
1997
|
+
expect(result).toHaveLength(4);
|
|
1998
|
+
expect(result.map((i) => i.file)).toEqual([
|
|
1999
|
+
"user.service.ts",
|
|
2000
|
+
"user.controller.ts",
|
|
2001
|
+
"user.module.ts",
|
|
2002
|
+
"user.controller.ts",
|
|
2003
|
+
]);
|
|
1715
2004
|
});
|
|
1716
2005
|
});
|
|
1717
2006
|
|
|
@@ -799,12 +799,12 @@ export class ReviewSpecService {
|
|
|
799
799
|
specs: ReviewSpec[],
|
|
800
800
|
changedFiles?: ChangedFileCollection,
|
|
801
801
|
): T[] {
|
|
802
|
-
// 构建
|
|
802
|
+
// 构建 rule.id -> includes 的映射(规则级优先,文件级兜底)
|
|
803
803
|
const specIncludesMap = new Map<string, string[]>();
|
|
804
804
|
for (const spec of specs) {
|
|
805
|
-
// 从规则 ID 前缀推断 spec filename
|
|
806
805
|
for (const rule of spec.rules) {
|
|
807
|
-
|
|
806
|
+
// 规则级 includes 覆盖文件级 includes,与 severity 优先级一致
|
|
807
|
+
specIncludesMap.set(rule.id, rule.includes ?? spec.includes);
|
|
808
808
|
}
|
|
809
809
|
}
|
|
810
810
|
|