uilint 0.2.122 → 0.2.124
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/dist/index.js +714 -324
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -1407,9 +1407,9 @@ async function update(options) {
|
|
|
1407
1407
|
}
|
|
1408
1408
|
|
|
1409
1409
|
// src/commands/serve.ts
|
|
1410
|
-
import { existsSync as
|
|
1411
|
-
import { createRequire } from "module";
|
|
1412
|
-
import { dirname as
|
|
1410
|
+
import { existsSync as existsSync6, statSync as statSync3, readdirSync, readFileSync as readFileSync2, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
1411
|
+
import { createRequire as createRequire3 } from "module";
|
|
1412
|
+
import { dirname as dirname6, resolve as resolve6, relative as relative2, join as join4, parse as parse2 } from "path";
|
|
1413
1413
|
import { WebSocketServer, WebSocket } from "ws";
|
|
1414
1414
|
import { watch } from "chokidar";
|
|
1415
1415
|
import {
|
|
@@ -1792,7 +1792,563 @@ function completeBackgroundTask(id, successMessage, error) {
|
|
|
1792
1792
|
}
|
|
1793
1793
|
|
|
1794
1794
|
// src/commands/serve.ts
|
|
1795
|
-
import { ruleRegistry } from "uilint-eslint";
|
|
1795
|
+
import { ruleRegistry } from "uilint-eslint";
|
|
1796
|
+
|
|
1797
|
+
// src/utils/eslint-utils.ts
|
|
1798
|
+
import { existsSync as existsSync5, readFileSync } from "fs";
|
|
1799
|
+
import { createRequire as createRequire2 } from "module";
|
|
1800
|
+
import { dirname as dirname5, resolve as resolve5, relative, join as join3 } from "path";
|
|
1801
|
+
|
|
1802
|
+
// src/scope-extractor.ts
|
|
1803
|
+
import { createRequire } from "module";
|
|
1804
|
+
function isComponentName(name) {
|
|
1805
|
+
if (!name) return false;
|
|
1806
|
+
return /^[A-Z]/.test(name);
|
|
1807
|
+
}
|
|
1808
|
+
function isHookName(name) {
|
|
1809
|
+
if (!name) return false;
|
|
1810
|
+
return /^use[A-Z]/.test(name);
|
|
1811
|
+
}
|
|
1812
|
+
function containsJsxReturn(node) {
|
|
1813
|
+
if (!node) return false;
|
|
1814
|
+
if (node.type === "JSXElement" || node.type === "JSXFragment") {
|
|
1815
|
+
return true;
|
|
1816
|
+
}
|
|
1817
|
+
if (node.type === "BlockStatement" && node.body) {
|
|
1818
|
+
for (const stmt of node.body) {
|
|
1819
|
+
if (stmt.type === "ReturnStatement" && stmt.argument) {
|
|
1820
|
+
if (stmt.argument.type === "JSXElement" || stmt.argument.type === "JSXFragment") {
|
|
1821
|
+
return true;
|
|
1822
|
+
}
|
|
1823
|
+
if (stmt.argument.type === "ConditionalExpression") {
|
|
1824
|
+
if (stmt.argument.consequent?.type === "JSXElement" || stmt.argument.consequent?.type === "JSXFragment" || stmt.argument.alternate?.type === "JSXElement" || stmt.argument.alternate?.type === "JSXFragment") {
|
|
1825
|
+
return true;
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
return false;
|
|
1832
|
+
}
|
|
1833
|
+
function determineScopeType(name, isArrow, isMethod, returnsJsx, isExportDefault = false) {
|
|
1834
|
+
if (isMethod) return "method";
|
|
1835
|
+
if (isHookName(name)) return "hook";
|
|
1836
|
+
if (isExportDefault && returnsJsx) return "component";
|
|
1837
|
+
if (isComponentName(name) && returnsJsx) return "component";
|
|
1838
|
+
if (isArrow) return "arrow-function";
|
|
1839
|
+
return "function";
|
|
1840
|
+
}
|
|
1841
|
+
function getIdentifierName(node) {
|
|
1842
|
+
if (!node) return null;
|
|
1843
|
+
if (node.type === "Identifier") return node.name;
|
|
1844
|
+
if (node.type === "PrivateIdentifier") return `#${node.name}`;
|
|
1845
|
+
return null;
|
|
1846
|
+
}
|
|
1847
|
+
function getJsxElementType(node) {
|
|
1848
|
+
if (!node || node.type !== "JSXElement") return null;
|
|
1849
|
+
const opening = node.openingElement;
|
|
1850
|
+
if (!opening || !opening.name) return null;
|
|
1851
|
+
const name = opening.name;
|
|
1852
|
+
if (name.type === "JSXIdentifier") return name.name;
|
|
1853
|
+
if (name.type === "JSXMemberExpression") {
|
|
1854
|
+
const parts = [];
|
|
1855
|
+
let current = name;
|
|
1856
|
+
while (current) {
|
|
1857
|
+
if (current.type === "JSXMemberExpression") {
|
|
1858
|
+
parts.unshift(current.property.name);
|
|
1859
|
+
current = current.object;
|
|
1860
|
+
} else if (current.type === "JSXIdentifier") {
|
|
1861
|
+
parts.unshift(current.name);
|
|
1862
|
+
break;
|
|
1863
|
+
} else {
|
|
1864
|
+
break;
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
return parts.join(".");
|
|
1868
|
+
}
|
|
1869
|
+
return null;
|
|
1870
|
+
}
|
|
1871
|
+
function lineColToOffset(code, line, column) {
|
|
1872
|
+
const lines = code.split("\n");
|
|
1873
|
+
let offset = 0;
|
|
1874
|
+
for (let i = 0; i < line - 1 && i < lines.length; i++) {
|
|
1875
|
+
offset += lines[i].length + 1;
|
|
1876
|
+
}
|
|
1877
|
+
return offset + column;
|
|
1878
|
+
}
|
|
1879
|
+
function collectScopeBoundaries(ast) {
|
|
1880
|
+
const boundaries = [];
|
|
1881
|
+
function walk(node, parentName = null) {
|
|
1882
|
+
if (!node || typeof node !== "object") return;
|
|
1883
|
+
const range = node.range;
|
|
1884
|
+
const loc = node.loc;
|
|
1885
|
+
if (node.type === "FunctionDeclaration") {
|
|
1886
|
+
const name = getIdentifierName(node.id);
|
|
1887
|
+
const returnsJsx = containsJsxReturn(node.body);
|
|
1888
|
+
const scopeType = determineScopeType(name, false, false, returnsJsx);
|
|
1889
|
+
if (range && loc) {
|
|
1890
|
+
boundaries.push({
|
|
1891
|
+
name,
|
|
1892
|
+
type: scopeType,
|
|
1893
|
+
start: range[0],
|
|
1894
|
+
end: range[1],
|
|
1895
|
+
line: loc.start.line,
|
|
1896
|
+
column: loc.start.column
|
|
1897
|
+
});
|
|
1898
|
+
}
|
|
1899
|
+
walk(node.body, name);
|
|
1900
|
+
return;
|
|
1901
|
+
}
|
|
1902
|
+
if (node.type === "ArrowFunctionExpression") {
|
|
1903
|
+
let name = null;
|
|
1904
|
+
const returnsJsx = containsJsxReturn(node.body);
|
|
1905
|
+
if (range && loc) {
|
|
1906
|
+
const scopeType = determineScopeType(name, true, false, returnsJsx);
|
|
1907
|
+
boundaries.push({
|
|
1908
|
+
name,
|
|
1909
|
+
type: scopeType,
|
|
1910
|
+
start: range[0],
|
|
1911
|
+
end: range[1],
|
|
1912
|
+
line: loc.start.line,
|
|
1913
|
+
column: loc.start.column
|
|
1914
|
+
});
|
|
1915
|
+
}
|
|
1916
|
+
walk(node.body, name || parentName);
|
|
1917
|
+
return;
|
|
1918
|
+
}
|
|
1919
|
+
if (node.type === "FunctionExpression") {
|
|
1920
|
+
const name = getIdentifierName(node.id);
|
|
1921
|
+
const returnsJsx = containsJsxReturn(node.body);
|
|
1922
|
+
const scopeType = determineScopeType(name, false, false, returnsJsx);
|
|
1923
|
+
if (range && loc) {
|
|
1924
|
+
boundaries.push({
|
|
1925
|
+
name,
|
|
1926
|
+
type: scopeType,
|
|
1927
|
+
start: range[0],
|
|
1928
|
+
end: range[1],
|
|
1929
|
+
line: loc.start.line,
|
|
1930
|
+
column: loc.start.column
|
|
1931
|
+
});
|
|
1932
|
+
}
|
|
1933
|
+
walk(node.body, name || parentName);
|
|
1934
|
+
return;
|
|
1935
|
+
}
|
|
1936
|
+
if (node.type === "VariableDeclarator" && node.init) {
|
|
1937
|
+
const varName = getIdentifierName(node.id);
|
|
1938
|
+
const init = node.init;
|
|
1939
|
+
if (init.type === "ArrowFunctionExpression" || init.type === "FunctionExpression") {
|
|
1940
|
+
const funcOwnName = getIdentifierName(init.id);
|
|
1941
|
+
const effectiveName = funcOwnName || varName;
|
|
1942
|
+
const returnsJsx = containsJsxReturn(init.body);
|
|
1943
|
+
const isArrow = init.type === "ArrowFunctionExpression";
|
|
1944
|
+
const scopeType = determineScopeType(effectiveName, isArrow, false, returnsJsx);
|
|
1945
|
+
const initRange = init.range;
|
|
1946
|
+
const initLoc = init.loc;
|
|
1947
|
+
if (initRange && initLoc) {
|
|
1948
|
+
const existing = boundaries.find(
|
|
1949
|
+
(b) => b.start === initRange[0] && b.end === initRange[1]
|
|
1950
|
+
);
|
|
1951
|
+
if (existing) {
|
|
1952
|
+
existing.name = effectiveName;
|
|
1953
|
+
existing.type = scopeType;
|
|
1954
|
+
} else {
|
|
1955
|
+
boundaries.push({
|
|
1956
|
+
name: effectiveName,
|
|
1957
|
+
type: scopeType,
|
|
1958
|
+
start: initRange[0],
|
|
1959
|
+
end: initRange[1],
|
|
1960
|
+
line: initLoc.start.line,
|
|
1961
|
+
column: initLoc.start.column
|
|
1962
|
+
});
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
walk(init.body, effectiveName);
|
|
1966
|
+
return;
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
if (node.type === "MethodDefinition" || node.type === "Property") {
|
|
1970
|
+
const isMethod = node.type === "MethodDefinition" || node.type === "Property" && node.method;
|
|
1971
|
+
if (isMethod && node.value) {
|
|
1972
|
+
const name = getIdentifierName(node.key);
|
|
1973
|
+
const returnsJsx = containsJsxReturn(node.value.body);
|
|
1974
|
+
const scopeType = determineScopeType(name, false, true, returnsJsx);
|
|
1975
|
+
if (range && loc) {
|
|
1976
|
+
boundaries.push({
|
|
1977
|
+
name,
|
|
1978
|
+
type: scopeType,
|
|
1979
|
+
start: range[0],
|
|
1980
|
+
end: range[1],
|
|
1981
|
+
line: loc.start.line,
|
|
1982
|
+
column: loc.start.column
|
|
1983
|
+
});
|
|
1984
|
+
}
|
|
1985
|
+
walk(node.value.body, name);
|
|
1986
|
+
return;
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
if (node.type === "ClassDeclaration" || node.type === "ClassExpression") {
|
|
1990
|
+
const name = getIdentifierName(node.id);
|
|
1991
|
+
if (range && loc) {
|
|
1992
|
+
boundaries.push({
|
|
1993
|
+
name,
|
|
1994
|
+
type: "class",
|
|
1995
|
+
start: range[0],
|
|
1996
|
+
end: range[1],
|
|
1997
|
+
line: loc.start.line,
|
|
1998
|
+
column: loc.start.column
|
|
1999
|
+
});
|
|
2000
|
+
}
|
|
2001
|
+
if (node.body) {
|
|
2002
|
+
walk(node.body, name);
|
|
2003
|
+
}
|
|
2004
|
+
return;
|
|
2005
|
+
}
|
|
2006
|
+
if (node.type === "ExportDefaultDeclaration" && node.declaration) {
|
|
2007
|
+
const decl = node.declaration;
|
|
2008
|
+
if (decl.type === "FunctionDeclaration") {
|
|
2009
|
+
const name = getIdentifierName(decl.id);
|
|
2010
|
+
const returnsJsx = containsJsxReturn(decl.body);
|
|
2011
|
+
const scopeType = determineScopeType(name, false, false, returnsJsx, !name);
|
|
2012
|
+
const declRange = decl.range;
|
|
2013
|
+
const declLoc = decl.loc;
|
|
2014
|
+
if (declRange && declLoc) {
|
|
2015
|
+
boundaries.push({
|
|
2016
|
+
name: name || "default",
|
|
2017
|
+
type: scopeType,
|
|
2018
|
+
start: declRange[0],
|
|
2019
|
+
end: declRange[1],
|
|
2020
|
+
line: declLoc.start.line,
|
|
2021
|
+
column: declLoc.start.column
|
|
2022
|
+
});
|
|
2023
|
+
}
|
|
2024
|
+
walk(decl.body, name || "default");
|
|
2025
|
+
return;
|
|
2026
|
+
}
|
|
2027
|
+
if (decl.type === "ClassDeclaration") {
|
|
2028
|
+
walk(decl, parentName);
|
|
2029
|
+
return;
|
|
2030
|
+
}
|
|
2031
|
+
if (decl.type === "FunctionExpression" || decl.type === "ArrowFunctionExpression") {
|
|
2032
|
+
const funcOwnName = getIdentifierName(decl.id);
|
|
2033
|
+
const name = funcOwnName || "default";
|
|
2034
|
+
const returnsJsx = containsJsxReturn(decl.body);
|
|
2035
|
+
const isArrow = decl.type === "ArrowFunctionExpression";
|
|
2036
|
+
const scopeType = determineScopeType(name, isArrow, false, returnsJsx, !funcOwnName);
|
|
2037
|
+
const declRange = decl.range;
|
|
2038
|
+
const declLoc = decl.loc;
|
|
2039
|
+
if (declRange && declLoc) {
|
|
2040
|
+
boundaries.push({
|
|
2041
|
+
name,
|
|
2042
|
+
type: scopeType,
|
|
2043
|
+
start: declRange[0],
|
|
2044
|
+
end: declRange[1],
|
|
2045
|
+
line: declLoc.start.line,
|
|
2046
|
+
column: declLoc.start.column
|
|
2047
|
+
});
|
|
2048
|
+
}
|
|
2049
|
+
walk(decl.body, name);
|
|
2050
|
+
return;
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
for (const key of Object.keys(node)) {
|
|
2054
|
+
if (key === "loc" || key === "range" || key === "parent") continue;
|
|
2055
|
+
const child = node[key];
|
|
2056
|
+
if (Array.isArray(child)) {
|
|
2057
|
+
for (const item of child) {
|
|
2058
|
+
walk(item, parentName);
|
|
2059
|
+
}
|
|
2060
|
+
} else if (child && typeof child === "object" && child.type) {
|
|
2061
|
+
walk(child, parentName);
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
walk(ast);
|
|
2066
|
+
return boundaries;
|
|
2067
|
+
}
|
|
2068
|
+
function findContainingJsxElement(ast, offset) {
|
|
2069
|
+
const result = {
|
|
2070
|
+
innermost: null
|
|
2071
|
+
};
|
|
2072
|
+
function walk(node) {
|
|
2073
|
+
if (!node || typeof node !== "object") return;
|
|
2074
|
+
if (node.type === "JSXElement") {
|
|
2075
|
+
const range = node.range;
|
|
2076
|
+
if (range && range[0] <= offset && offset < range[1]) {
|
|
2077
|
+
const size = range[1] - range[0];
|
|
2078
|
+
if (!result.innermost || size < result.innermost.size) {
|
|
2079
|
+
const elementType = getJsxElementType(node);
|
|
2080
|
+
if (elementType) {
|
|
2081
|
+
result.innermost = { type: elementType, size };
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
for (const key of Object.keys(node)) {
|
|
2087
|
+
if (key === "loc" || key === "range" || key === "parent") continue;
|
|
2088
|
+
const child = node[key];
|
|
2089
|
+
if (Array.isArray(child)) {
|
|
2090
|
+
for (const item of child) walk(item);
|
|
2091
|
+
} else if (child && typeof child === "object" && child.type) {
|
|
2092
|
+
walk(child);
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
walk(ast);
|
|
2097
|
+
return result.innermost?.type ?? null;
|
|
2098
|
+
}
|
|
2099
|
+
function findEnclosingScopeBatch(source, positions, options) {
|
|
2100
|
+
const localRequire2 = createRequire(import.meta.url);
|
|
2101
|
+
const { parse: parse3 } = localRequire2("@typescript-eslint/typescript-estree");
|
|
2102
|
+
let ast;
|
|
2103
|
+
try {
|
|
2104
|
+
ast = parse3(source, {
|
|
2105
|
+
loc: true,
|
|
2106
|
+
range: true,
|
|
2107
|
+
jsx: true,
|
|
2108
|
+
comment: false,
|
|
2109
|
+
errorOnUnknownASTType: false
|
|
2110
|
+
});
|
|
2111
|
+
} catch {
|
|
2112
|
+
return positions.map(() => null);
|
|
2113
|
+
}
|
|
2114
|
+
const boundaries = collectScopeBoundaries(ast);
|
|
2115
|
+
return positions.map(({ line, column }) => {
|
|
2116
|
+
const offset = lineColToOffset(source, line, column);
|
|
2117
|
+
const containingScopes = boundaries.filter((b) => b.start <= offset && offset < b.end).sort((a, b) => a.end - a.start - (b.end - b.start));
|
|
2118
|
+
const jsxElementType = findContainingJsxElement(ast, offset);
|
|
2119
|
+
if (containingScopes.length === 0) {
|
|
2120
|
+
return {
|
|
2121
|
+
enclosingScope: null,
|
|
2122
|
+
scopeType: "module",
|
|
2123
|
+
jsxElementType: jsxElementType ?? void 0
|
|
2124
|
+
};
|
|
2125
|
+
}
|
|
2126
|
+
const innermost = containingScopes[0];
|
|
2127
|
+
const parent = containingScopes.length > 1 ? containingScopes[1] : null;
|
|
2128
|
+
const scopeName = innermost.name || "anonymous";
|
|
2129
|
+
return {
|
|
2130
|
+
enclosingScope: scopeName,
|
|
2131
|
+
scopeType: innermost.type,
|
|
2132
|
+
parentScope: parent?.name ?? void 0,
|
|
2133
|
+
jsxElementType: jsxElementType ?? void 0
|
|
2134
|
+
};
|
|
2135
|
+
});
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
// src/utils/eslint-utils.ts
|
|
2139
|
+
var ESLINT_CONFIG_FILES = [
|
|
2140
|
+
// Flat config (ESLint v9+)
|
|
2141
|
+
"eslint.config.js",
|
|
2142
|
+
"eslint.config.mjs",
|
|
2143
|
+
"eslint.config.cjs",
|
|
2144
|
+
"eslint.config.ts",
|
|
2145
|
+
// Legacy config
|
|
2146
|
+
".eslintrc",
|
|
2147
|
+
".eslintrc.js",
|
|
2148
|
+
".eslintrc.cjs",
|
|
2149
|
+
".eslintrc.json",
|
|
2150
|
+
".eslintrc.yaml",
|
|
2151
|
+
".eslintrc.yml"
|
|
2152
|
+
];
|
|
2153
|
+
function buildLineStarts(code) {
|
|
2154
|
+
const starts = [0];
|
|
2155
|
+
for (let i = 0; i < code.length; i++) {
|
|
2156
|
+
if (code.charCodeAt(i) === 10) starts.push(i + 1);
|
|
2157
|
+
}
|
|
2158
|
+
return starts;
|
|
2159
|
+
}
|
|
2160
|
+
function offsetFromLineCol(lineStarts, line1, col0, codeLength) {
|
|
2161
|
+
const lineIndex = Math.max(0, Math.min(lineStarts.length - 1, line1 - 1));
|
|
2162
|
+
const base = lineStarts[lineIndex] ?? 0;
|
|
2163
|
+
return Math.max(0, Math.min(codeLength, base + Math.max(0, col0)));
|
|
2164
|
+
}
|
|
2165
|
+
function buildJsxElementSpans(code, dataLocFile) {
|
|
2166
|
+
const localRequire2 = createRequire2(import.meta.url);
|
|
2167
|
+
const { parse: parse3 } = localRequire2("@typescript-eslint/typescript-estree");
|
|
2168
|
+
const ast = parse3(code, {
|
|
2169
|
+
loc: true,
|
|
2170
|
+
range: true,
|
|
2171
|
+
jsx: true,
|
|
2172
|
+
comment: false,
|
|
2173
|
+
errorOnUnknownASTType: false
|
|
2174
|
+
});
|
|
2175
|
+
const spans = [];
|
|
2176
|
+
function walk(node) {
|
|
2177
|
+
if (!node || typeof node !== "object") return;
|
|
2178
|
+
if (node.type === "JSXElement") {
|
|
2179
|
+
const range = node.range;
|
|
2180
|
+
const opening = node.openingElement;
|
|
2181
|
+
const loc = opening?.loc?.start;
|
|
2182
|
+
if (range && typeof range[0] === "number" && typeof range[1] === "number" && loc && typeof loc.line === "number" && typeof loc.column === "number") {
|
|
2183
|
+
const dataLoc = `${dataLocFile}:${loc.line}:${loc.column}`;
|
|
2184
|
+
spans.push({ start: range[0], end: range[1], dataLoc });
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
for (const key of Object.keys(node)) {
|
|
2188
|
+
const child = node[key];
|
|
2189
|
+
if (Array.isArray(child)) {
|
|
2190
|
+
for (const item of child) walk(item);
|
|
2191
|
+
} else if (child && typeof child === "object") {
|
|
2192
|
+
walk(child);
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
walk(ast);
|
|
2197
|
+
spans.sort((a, b) => a.end - a.start - (b.end - b.start));
|
|
2198
|
+
return spans;
|
|
2199
|
+
}
|
|
2200
|
+
function mapMessageToDataLoc(params) {
|
|
2201
|
+
const col0 = typeof params.messageCol1 === "number" ? Math.max(0, params.messageCol1 - 1) : 0;
|
|
2202
|
+
const offset = offsetFromLineCol(
|
|
2203
|
+
params.lineStarts,
|
|
2204
|
+
params.messageLine1,
|
|
2205
|
+
col0,
|
|
2206
|
+
params.codeLength
|
|
2207
|
+
);
|
|
2208
|
+
for (const s of params.spans) {
|
|
2209
|
+
if (s.start <= offset && offset < s.end) return s.dataLoc;
|
|
2210
|
+
}
|
|
2211
|
+
return void 0;
|
|
2212
|
+
}
|
|
2213
|
+
function normalizePathSlashes(p) {
|
|
2214
|
+
return p.replace(/\\/g, "/");
|
|
2215
|
+
}
|
|
2216
|
+
function normalizeDataLocFilePath(absoluteFilePath, projectCwd) {
|
|
2217
|
+
const abs = normalizePathSlashes(resolve5(absoluteFilePath));
|
|
2218
|
+
const cwd = normalizePathSlashes(resolve5(projectCwd));
|
|
2219
|
+
if (abs === cwd || abs.startsWith(cwd + "/")) {
|
|
2220
|
+
return normalizePathSlashes(relative(cwd, abs));
|
|
2221
|
+
}
|
|
2222
|
+
return abs;
|
|
2223
|
+
}
|
|
2224
|
+
function findESLintCwd(startDir) {
|
|
2225
|
+
let dir = startDir;
|
|
2226
|
+
for (let i = 0; i < 30; i++) {
|
|
2227
|
+
for (const cfg of ESLINT_CONFIG_FILES) {
|
|
2228
|
+
if (existsSync5(join3(dir, cfg))) return dir;
|
|
2229
|
+
}
|
|
2230
|
+
if (existsSync5(join3(dir, "package.json"))) return dir;
|
|
2231
|
+
const parent = dirname5(dir);
|
|
2232
|
+
if (parent === dir) break;
|
|
2233
|
+
dir = parent;
|
|
2234
|
+
}
|
|
2235
|
+
return startDir;
|
|
2236
|
+
}
|
|
2237
|
+
var eslintInstances = /* @__PURE__ */ new Map();
|
|
2238
|
+
async function getESLintForProject(projectCwd) {
|
|
2239
|
+
const cached = eslintInstances.get(projectCwd);
|
|
2240
|
+
if (cached) return cached;
|
|
2241
|
+
try {
|
|
2242
|
+
const req = createRequire2(join3(projectCwd, "package.json"));
|
|
2243
|
+
const mod = req("eslint");
|
|
2244
|
+
const ESLintCtor = mod?.ESLint ?? mod?.default?.ESLint ?? mod?.default ?? mod;
|
|
2245
|
+
if (!ESLintCtor) return null;
|
|
2246
|
+
const eslint = new ESLintCtor({ cwd: projectCwd });
|
|
2247
|
+
eslintInstances.set(projectCwd, eslint);
|
|
2248
|
+
return eslint;
|
|
2249
|
+
} catch {
|
|
2250
|
+
return null;
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
async function lintFileWithDataLoc(absolutePath, projectCwd, onProgress) {
|
|
2254
|
+
const progress = onProgress ?? (() => {
|
|
2255
|
+
});
|
|
2256
|
+
if (!existsSync5(absolutePath)) {
|
|
2257
|
+
progress(`File not found: ${absolutePath}`);
|
|
2258
|
+
return [];
|
|
2259
|
+
}
|
|
2260
|
+
progress(`Resolving ESLint project... ${projectCwd}`);
|
|
2261
|
+
const eslint = await getESLintForProject(projectCwd);
|
|
2262
|
+
if (!eslint) {
|
|
2263
|
+
progress("ESLint not available");
|
|
2264
|
+
return [];
|
|
2265
|
+
}
|
|
2266
|
+
try {
|
|
2267
|
+
progress("Running ESLint...");
|
|
2268
|
+
const results = await eslint.lintFiles([absolutePath]);
|
|
2269
|
+
const messages = Array.isArray(results) && results.length > 0 ? results[0].messages || [] : [];
|
|
2270
|
+
const dataLocFile = normalizeDataLocFilePath(absolutePath, projectCwd);
|
|
2271
|
+
let spans = [];
|
|
2272
|
+
let lineStarts = [];
|
|
2273
|
+
let codeLength = 0;
|
|
2274
|
+
let fileCode = null;
|
|
2275
|
+
try {
|
|
2276
|
+
progress("Building JSX map...");
|
|
2277
|
+
fileCode = readFileSync(absolutePath, "utf-8");
|
|
2278
|
+
codeLength = fileCode.length;
|
|
2279
|
+
lineStarts = buildLineStarts(fileCode);
|
|
2280
|
+
spans = buildJsxElementSpans(fileCode, dataLocFile);
|
|
2281
|
+
progress(`JSX map: ${spans.length} element(s)`);
|
|
2282
|
+
} catch (e) {
|
|
2283
|
+
progress("JSX map failed (falling back to unmapped issues)");
|
|
2284
|
+
spans = [];
|
|
2285
|
+
lineStarts = [];
|
|
2286
|
+
codeLength = 0;
|
|
2287
|
+
fileCode = null;
|
|
2288
|
+
}
|
|
2289
|
+
let issues = messages.filter((m) => typeof m?.message === "string").map((m) => {
|
|
2290
|
+
const line = typeof m.line === "number" ? m.line : 1;
|
|
2291
|
+
const column = typeof m.column === "number" ? m.column : void 0;
|
|
2292
|
+
const mappedDataLoc = spans.length > 0 && lineStarts.length > 0 && codeLength > 0 ? mapMessageToDataLoc({
|
|
2293
|
+
spans,
|
|
2294
|
+
lineStarts,
|
|
2295
|
+
codeLength,
|
|
2296
|
+
messageLine1: line,
|
|
2297
|
+
messageCol1: column
|
|
2298
|
+
}) : void 0;
|
|
2299
|
+
return {
|
|
2300
|
+
line,
|
|
2301
|
+
column,
|
|
2302
|
+
message: m.message,
|
|
2303
|
+
ruleId: typeof m.ruleId === "string" ? m.ruleId : void 0,
|
|
2304
|
+
dataLoc: mappedDataLoc
|
|
2305
|
+
};
|
|
2306
|
+
});
|
|
2307
|
+
const mappedCount = issues.filter((i) => Boolean(i.dataLoc)).length;
|
|
2308
|
+
if (issues.length > 0) {
|
|
2309
|
+
progress(`Mapped ${mappedCount}/${issues.length} issue(s) to JSX elements`);
|
|
2310
|
+
}
|
|
2311
|
+
if (fileCode && issues.length > 0) {
|
|
2312
|
+
progress("Extracting scope info...");
|
|
2313
|
+
issues = enrichIssuesWithScopeInfo(issues, fileCode);
|
|
2314
|
+
const scopeCount = issues.filter((i) => Boolean(i.scopeInfo)).length;
|
|
2315
|
+
progress(`Enriched ${scopeCount}/${issues.length} issue(s) with scope info`);
|
|
2316
|
+
}
|
|
2317
|
+
return issues;
|
|
2318
|
+
} catch (error) {
|
|
2319
|
+
progress(`ESLint failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
2320
|
+
return [];
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
function extractSourceSnippet(code, centerLine, contextLines = 3) {
|
|
2324
|
+
const allLines = code.split("\n");
|
|
2325
|
+
const startLine = Math.max(1, centerLine - contextLines);
|
|
2326
|
+
const endLine = Math.min(allLines.length, centerLine + contextLines);
|
|
2327
|
+
return {
|
|
2328
|
+
lines: allLines.slice(startLine - 1, endLine),
|
|
2329
|
+
startLine,
|
|
2330
|
+
endLine
|
|
2331
|
+
};
|
|
2332
|
+
}
|
|
2333
|
+
function enrichIssuesWithScopeInfo(issues, code) {
|
|
2334
|
+
if (issues.length === 0) {
|
|
2335
|
+
return issues;
|
|
2336
|
+
}
|
|
2337
|
+
const positions = issues.map((issue) => ({
|
|
2338
|
+
line: issue.line,
|
|
2339
|
+
column: issue.column ?? 0
|
|
2340
|
+
}));
|
|
2341
|
+
const scopeInfos = findEnclosingScopeBatch(code, positions);
|
|
2342
|
+
return issues.map((issue, index) => {
|
|
2343
|
+
const scopeInfo = scopeInfos[index];
|
|
2344
|
+
if (scopeInfo) {
|
|
2345
|
+
return { ...issue, scopeInfo };
|
|
2346
|
+
}
|
|
2347
|
+
return issue;
|
|
2348
|
+
});
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
// src/commands/serve.ts
|
|
1796
2352
|
function pickAppRoot(params) {
|
|
1797
2353
|
const { cwd, workspaceRoot } = params;
|
|
1798
2354
|
if (detectNextAppRouter(cwd)) return cwd;
|
|
@@ -1806,10 +2362,10 @@ function pickAppRoot(params) {
|
|
|
1806
2362
|
return matches[0].projectPath;
|
|
1807
2363
|
}
|
|
1808
2364
|
function detectPostToolUseHook(projectRoot) {
|
|
1809
|
-
const claudeSettingsPath =
|
|
1810
|
-
if (
|
|
2365
|
+
const claudeSettingsPath = join4(projectRoot, ".claude", "settings.json");
|
|
2366
|
+
if (existsSync6(claudeSettingsPath)) {
|
|
1811
2367
|
try {
|
|
1812
|
-
const content =
|
|
2368
|
+
const content = readFileSync2(claudeSettingsPath, "utf-8");
|
|
1813
2369
|
const settings = JSON.parse(content);
|
|
1814
2370
|
const hooks = settings.hooks?.PostToolUse;
|
|
1815
2371
|
if (Array.isArray(hooks)) {
|
|
@@ -1823,10 +2379,10 @@ function detectPostToolUseHook(projectRoot) {
|
|
|
1823
2379
|
} catch {
|
|
1824
2380
|
}
|
|
1825
2381
|
}
|
|
1826
|
-
const cursorHooksPath =
|
|
1827
|
-
if (
|
|
2382
|
+
const cursorHooksPath = join4(projectRoot, ".cursor", "hooks.json");
|
|
2383
|
+
if (existsSync6(cursorHooksPath)) {
|
|
1828
2384
|
try {
|
|
1829
|
-
const content =
|
|
2385
|
+
const content = readFileSync2(cursorHooksPath, "utf-8");
|
|
1830
2386
|
const hooks = JSON.parse(content);
|
|
1831
2387
|
if (hooks.hooks?.afterFileEdit?.length > 0) {
|
|
1832
2388
|
return { enabled: true, provider: "cursor" };
|
|
@@ -1837,7 +2393,7 @@ function detectPostToolUseHook(projectRoot) {
|
|
|
1837
2393
|
return { enabled: false, provider: null };
|
|
1838
2394
|
}
|
|
1839
2395
|
var cache = /* @__PURE__ */ new Map();
|
|
1840
|
-
var
|
|
2396
|
+
var eslintInstances2 = /* @__PURE__ */ new Map();
|
|
1841
2397
|
var visionAnalyzer = null;
|
|
1842
2398
|
function getVisionAnalyzerInstance() {
|
|
1843
2399
|
if (!visionAnalyzer) {
|
|
@@ -1854,20 +2410,20 @@ var resolvedPathCache = /* @__PURE__ */ new Map();
|
|
|
1854
2410
|
var subscriptions = /* @__PURE__ */ new Map();
|
|
1855
2411
|
var fileWatcher = null;
|
|
1856
2412
|
var connectedClients = 0;
|
|
1857
|
-
var localRequire =
|
|
1858
|
-
function
|
|
2413
|
+
var localRequire = createRequire3(import.meta.url);
|
|
2414
|
+
function buildLineStarts2(code) {
|
|
1859
2415
|
const starts = [0];
|
|
1860
2416
|
for (let i = 0; i < code.length; i++) {
|
|
1861
2417
|
if (code.charCodeAt(i) === 10) starts.push(i + 1);
|
|
1862
2418
|
}
|
|
1863
2419
|
return starts;
|
|
1864
2420
|
}
|
|
1865
|
-
function
|
|
2421
|
+
function offsetFromLineCol2(lineStarts, line1, col0, codeLength) {
|
|
1866
2422
|
const lineIndex = Math.max(0, Math.min(lineStarts.length - 1, line1 - 1));
|
|
1867
2423
|
const base = lineStarts[lineIndex] ?? 0;
|
|
1868
2424
|
return Math.max(0, Math.min(codeLength, base + Math.max(0, col0)));
|
|
1869
2425
|
}
|
|
1870
|
-
function
|
|
2426
|
+
function buildJsxElementSpans2(code, dataLocFile) {
|
|
1871
2427
|
const { parse: parse3 } = localRequire("@typescript-eslint/typescript-estree");
|
|
1872
2428
|
const ast = parse3(code, {
|
|
1873
2429
|
loc: true,
|
|
@@ -1901,9 +2457,9 @@ function buildJsxElementSpans(code, dataLocFile) {
|
|
|
1901
2457
|
spans.sort((a, b) => a.end - a.start - (b.end - b.start));
|
|
1902
2458
|
return spans;
|
|
1903
2459
|
}
|
|
1904
|
-
function
|
|
2460
|
+
function mapMessageToDataLoc2(params) {
|
|
1905
2461
|
const col0 = typeof params.messageCol1 === "number" ? Math.max(0, params.messageCol1 - 1) : 0;
|
|
1906
|
-
const offset =
|
|
2462
|
+
const offset = offsetFromLineCol2(
|
|
1907
2463
|
params.lineStarts,
|
|
1908
2464
|
params.messageLine1,
|
|
1909
2465
|
col0,
|
|
@@ -1914,7 +2470,7 @@ function mapMessageToDataLoc(params) {
|
|
|
1914
2470
|
}
|
|
1915
2471
|
return void 0;
|
|
1916
2472
|
}
|
|
1917
|
-
var
|
|
2473
|
+
var ESLINT_CONFIG_FILES2 = [
|
|
1918
2474
|
// Flat config (ESLint v9+)
|
|
1919
2475
|
"eslint.config.js",
|
|
1920
2476
|
"eslint.config.mjs",
|
|
@@ -1928,57 +2484,57 @@ var ESLINT_CONFIG_FILES = [
|
|
|
1928
2484
|
".eslintrc.yaml",
|
|
1929
2485
|
".eslintrc.yml"
|
|
1930
2486
|
];
|
|
1931
|
-
function
|
|
2487
|
+
function findESLintCwd2(startDir) {
|
|
1932
2488
|
let dir = startDir;
|
|
1933
2489
|
for (let i = 0; i < 30; i++) {
|
|
1934
|
-
for (const cfg of
|
|
1935
|
-
if (
|
|
2490
|
+
for (const cfg of ESLINT_CONFIG_FILES2) {
|
|
2491
|
+
if (existsSync6(join4(dir, cfg))) return dir;
|
|
1936
2492
|
}
|
|
1937
|
-
if (
|
|
1938
|
-
const parent =
|
|
2493
|
+
if (existsSync6(join4(dir, "package.json"))) return dir;
|
|
2494
|
+
const parent = dirname6(dir);
|
|
1939
2495
|
if (parent === dir) break;
|
|
1940
2496
|
dir = parent;
|
|
1941
2497
|
}
|
|
1942
2498
|
return startDir;
|
|
1943
2499
|
}
|
|
1944
|
-
function
|
|
2500
|
+
function normalizePathSlashes2(p) {
|
|
1945
2501
|
return p.replace(/\\/g, "/");
|
|
1946
2502
|
}
|
|
1947
|
-
function
|
|
1948
|
-
const abs =
|
|
1949
|
-
const cwd =
|
|
2503
|
+
function normalizeDataLocFilePath2(absoluteFilePath, projectCwd) {
|
|
2504
|
+
const abs = normalizePathSlashes2(resolve6(absoluteFilePath));
|
|
2505
|
+
const cwd = normalizePathSlashes2(resolve6(projectCwd));
|
|
1950
2506
|
if (abs === cwd || abs.startsWith(cwd + "/")) {
|
|
1951
|
-
return
|
|
2507
|
+
return normalizePathSlashes2(relative2(cwd, abs));
|
|
1952
2508
|
}
|
|
1953
2509
|
return abs;
|
|
1954
2510
|
}
|
|
1955
2511
|
function resolveRequestedFilePath(filePath) {
|
|
1956
2512
|
if (filePath.startsWith("/") || /^[A-Za-z]:[\\/]/.test(filePath)) {
|
|
1957
|
-
return
|
|
2513
|
+
return resolve6(filePath);
|
|
1958
2514
|
}
|
|
1959
2515
|
const cached = resolvedPathCache.get(filePath);
|
|
1960
2516
|
if (cached) return cached;
|
|
1961
2517
|
const cwd = process.cwd();
|
|
1962
|
-
const fromCwd =
|
|
1963
|
-
if (
|
|
2518
|
+
const fromCwd = resolve6(cwd, filePath);
|
|
2519
|
+
if (existsSync6(fromCwd)) {
|
|
1964
2520
|
resolvedPathCache.set(filePath, fromCwd);
|
|
1965
2521
|
return fromCwd;
|
|
1966
2522
|
}
|
|
1967
2523
|
const wsRoot = findWorkspaceRoot4(cwd);
|
|
1968
|
-
const fromWs =
|
|
1969
|
-
if (
|
|
2524
|
+
const fromWs = resolve6(wsRoot, filePath);
|
|
2525
|
+
if (existsSync6(fromWs)) {
|
|
1970
2526
|
resolvedPathCache.set(filePath, fromWs);
|
|
1971
2527
|
return fromWs;
|
|
1972
2528
|
}
|
|
1973
2529
|
for (const top of ["apps", "packages"]) {
|
|
1974
|
-
const base =
|
|
1975
|
-
if (!
|
|
2530
|
+
const base = join4(wsRoot, top);
|
|
2531
|
+
if (!existsSync6(base)) continue;
|
|
1976
2532
|
try {
|
|
1977
2533
|
const entries = readdirSync(base, { withFileTypes: true });
|
|
1978
2534
|
for (const ent of entries) {
|
|
1979
2535
|
if (!ent.isDirectory()) continue;
|
|
1980
|
-
const p =
|
|
1981
|
-
if (
|
|
2536
|
+
const p = resolve6(base, ent.name, filePath);
|
|
2537
|
+
if (existsSync6(p)) {
|
|
1982
2538
|
resolvedPathCache.set(filePath, p);
|
|
1983
2539
|
return p;
|
|
1984
2540
|
}
|
|
@@ -1989,16 +2545,16 @@ function resolveRequestedFilePath(filePath) {
|
|
|
1989
2545
|
resolvedPathCache.set(filePath, fromCwd);
|
|
1990
2546
|
return fromCwd;
|
|
1991
2547
|
}
|
|
1992
|
-
async function
|
|
1993
|
-
const cached =
|
|
2548
|
+
async function getESLintForProject2(projectCwd) {
|
|
2549
|
+
const cached = eslintInstances2.get(projectCwd);
|
|
1994
2550
|
if (cached) return cached;
|
|
1995
2551
|
try {
|
|
1996
|
-
const req =
|
|
2552
|
+
const req = createRequire3(join4(projectCwd, "package.json"));
|
|
1997
2553
|
const mod = req("eslint");
|
|
1998
2554
|
const ESLintCtor = mod?.ESLint ?? mod?.default?.ESLint ?? mod?.default ?? mod;
|
|
1999
2555
|
if (!ESLintCtor) return null;
|
|
2000
2556
|
const eslint = new ESLintCtor({ cwd: projectCwd });
|
|
2001
|
-
|
|
2557
|
+
eslintInstances2.set(projectCwd, eslint);
|
|
2002
2558
|
return eslint;
|
|
2003
2559
|
} catch {
|
|
2004
2560
|
return null;
|
|
@@ -2006,7 +2562,7 @@ async function getESLintForProject(projectCwd) {
|
|
|
2006
2562
|
}
|
|
2007
2563
|
async function lintFile(filePath, onProgress) {
|
|
2008
2564
|
const absolutePath = resolveRequestedFilePath(filePath);
|
|
2009
|
-
if (!
|
|
2565
|
+
if (!existsSync6(absolutePath)) {
|
|
2010
2566
|
onProgress(`File not found: ${pc.dim(absolutePath)}`);
|
|
2011
2567
|
return [];
|
|
2012
2568
|
}
|
|
@@ -2022,10 +2578,10 @@ async function lintFile(filePath, onProgress) {
|
|
|
2022
2578
|
onProgress("Cache hit (unchanged)");
|
|
2023
2579
|
return cached.issues;
|
|
2024
2580
|
}
|
|
2025
|
-
const fileDir =
|
|
2026
|
-
const projectCwd =
|
|
2581
|
+
const fileDir = dirname6(absolutePath);
|
|
2582
|
+
const projectCwd = findESLintCwd2(fileDir);
|
|
2027
2583
|
onProgress(`Resolving ESLint project... ${pc.dim(projectCwd)}`);
|
|
2028
|
-
const eslint = await
|
|
2584
|
+
const eslint = await getESLintForProject2(projectCwd);
|
|
2029
2585
|
if (!eslint) {
|
|
2030
2586
|
logWarning(
|
|
2031
2587
|
`ESLint not found in project. Install it in ${pc.dim(
|
|
@@ -2039,16 +2595,17 @@ async function lintFile(filePath, onProgress) {
|
|
|
2039
2595
|
onProgress("Running ESLint...");
|
|
2040
2596
|
const results = await eslint.lintFiles([absolutePath]);
|
|
2041
2597
|
const messages = Array.isArray(results) && results.length > 0 ? results[0].messages || [] : [];
|
|
2042
|
-
const dataLocFile =
|
|
2598
|
+
const dataLocFile = normalizeDataLocFilePath2(absolutePath, projectCwd);
|
|
2043
2599
|
let spans = [];
|
|
2044
2600
|
let lineStarts = [];
|
|
2045
2601
|
let codeLength = 0;
|
|
2602
|
+
let fileCode = null;
|
|
2046
2603
|
try {
|
|
2047
2604
|
onProgress("Building JSX map...");
|
|
2048
|
-
|
|
2049
|
-
codeLength =
|
|
2050
|
-
lineStarts =
|
|
2051
|
-
spans =
|
|
2605
|
+
fileCode = readFileSync2(absolutePath, "utf-8");
|
|
2606
|
+
codeLength = fileCode.length;
|
|
2607
|
+
lineStarts = buildLineStarts2(fileCode);
|
|
2608
|
+
spans = buildJsxElementSpans2(fileCode, dataLocFile);
|
|
2052
2609
|
onProgress(`JSX map: ${spans.length} element(s)`);
|
|
2053
2610
|
} catch (e) {
|
|
2054
2611
|
onProgress("JSX map failed (falling back to unmapped issues)");
|
|
@@ -2056,11 +2613,12 @@ async function lintFile(filePath, onProgress) {
|
|
|
2056
2613
|
spans = [];
|
|
2057
2614
|
lineStarts = [];
|
|
2058
2615
|
codeLength = 0;
|
|
2616
|
+
fileCode = null;
|
|
2059
2617
|
}
|
|
2060
|
-
|
|
2618
|
+
let issues = messages.filter((m) => typeof m?.message === "string").map((m) => {
|
|
2061
2619
|
const line = typeof m.line === "number" ? m.line : 1;
|
|
2062
2620
|
const column = typeof m.column === "number" ? m.column : void 0;
|
|
2063
|
-
const mappedDataLoc = spans.length > 0 && lineStarts.length > 0 && codeLength > 0 ?
|
|
2621
|
+
const mappedDataLoc = spans.length > 0 && lineStarts.length > 0 && codeLength > 0 ? mapMessageToDataLoc2({
|
|
2064
2622
|
spans,
|
|
2065
2623
|
lineStarts,
|
|
2066
2624
|
codeLength,
|
|
@@ -2081,6 +2639,12 @@ async function lintFile(filePath, onProgress) {
|
|
|
2081
2639
|
`Mapped ${mappedCount}/${issues.length} issue(s) to JSX elements`
|
|
2082
2640
|
);
|
|
2083
2641
|
}
|
|
2642
|
+
if (fileCode && issues.length > 0) {
|
|
2643
|
+
onProgress("Extracting scope info...");
|
|
2644
|
+
issues = enrichIssuesWithScopeInfo(issues, fileCode);
|
|
2645
|
+
const scopeCount = issues.filter((i) => Boolean(i.scopeInfo)).length;
|
|
2646
|
+
onProgress(`Enriched ${scopeCount}/${issues.length} issue(s) with scope info`);
|
|
2647
|
+
}
|
|
2084
2648
|
cache.set(absolutePath, { issues, mtimeMs, timestamp: Date.now() });
|
|
2085
2649
|
return issues;
|
|
2086
2650
|
} catch (error) {
|
|
@@ -2126,7 +2690,7 @@ async function handleMessage(ws, data) {
|
|
|
2126
2690
|
});
|
|
2127
2691
|
const startedAt = Date.now();
|
|
2128
2692
|
const resolved = resolveRequestedFilePath(filePath);
|
|
2129
|
-
if (!
|
|
2693
|
+
if (!existsSync6(resolved)) {
|
|
2130
2694
|
const cwd = process.cwd();
|
|
2131
2695
|
const wsRoot = findWorkspaceRoot4(cwd);
|
|
2132
2696
|
logServerWarning(
|
|
@@ -2278,14 +2842,14 @@ async function handleMessage(ws, data) {
|
|
|
2278
2842
|
screenshotFile
|
|
2279
2843
|
);
|
|
2280
2844
|
} else {
|
|
2281
|
-
const screenshotsDir =
|
|
2845
|
+
const screenshotsDir = join4(
|
|
2282
2846
|
serverAppRootForVision,
|
|
2283
2847
|
".uilint",
|
|
2284
2848
|
"screenshots"
|
|
2285
2849
|
);
|
|
2286
|
-
const imagePath =
|
|
2850
|
+
const imagePath = join4(screenshotsDir, screenshotFile);
|
|
2287
2851
|
try {
|
|
2288
|
-
if (!
|
|
2852
|
+
if (!existsSync6(imagePath)) {
|
|
2289
2853
|
logServerWarning(
|
|
2290
2854
|
`Skipping vision report write: screenshot file not found`,
|
|
2291
2855
|
imagePath
|
|
@@ -2377,7 +2941,7 @@ async function handleMessage(ws, data) {
|
|
|
2377
2941
|
case "source:fetch": {
|
|
2378
2942
|
const { filePath, requestId } = message;
|
|
2379
2943
|
const absolutePath = resolveRequestedFilePath(filePath);
|
|
2380
|
-
if (!
|
|
2944
|
+
if (!existsSync6(absolutePath)) {
|
|
2381
2945
|
sendMessage(ws, {
|
|
2382
2946
|
type: "source:error",
|
|
2383
2947
|
filePath,
|
|
@@ -2387,9 +2951,9 @@ async function handleMessage(ws, data) {
|
|
|
2387
2951
|
break;
|
|
2388
2952
|
}
|
|
2389
2953
|
try {
|
|
2390
|
-
const content =
|
|
2954
|
+
const content = readFileSync2(absolutePath, "utf-8");
|
|
2391
2955
|
const totalLines = content.split("\n").length;
|
|
2392
|
-
const relativePath =
|
|
2956
|
+
const relativePath = normalizeDataLocFilePath2(absolutePath, serverAppRootForVision);
|
|
2393
2957
|
sendMessage(ws, {
|
|
2394
2958
|
type: "source:result",
|
|
2395
2959
|
filePath,
|
|
@@ -2411,8 +2975,8 @@ async function handleMessage(ws, data) {
|
|
|
2411
2975
|
case "coverage:request": {
|
|
2412
2976
|
const { requestId } = message;
|
|
2413
2977
|
try {
|
|
2414
|
-
const coveragePath =
|
|
2415
|
-
if (!
|
|
2978
|
+
const coveragePath = join4(serverAppRootForVision, "coverage", "coverage-final.json");
|
|
2979
|
+
if (!existsSync6(coveragePath)) {
|
|
2416
2980
|
sendMessage(ws, {
|
|
2417
2981
|
type: "coverage:error",
|
|
2418
2982
|
error: "Coverage data not found. Run tests with coverage first (e.g., `vitest run --coverage`)",
|
|
@@ -2420,7 +2984,7 @@ async function handleMessage(ws, data) {
|
|
|
2420
2984
|
});
|
|
2421
2985
|
break;
|
|
2422
2986
|
}
|
|
2423
|
-
const coverageData = JSON.parse(
|
|
2987
|
+
const coverageData = JSON.parse(readFileSync2(coveragePath, "utf-8"));
|
|
2424
2988
|
logCoverageResult(Object.keys(coverageData).length);
|
|
2425
2989
|
sendMessage(ws, {
|
|
2426
2990
|
type: "coverage:result",
|
|
@@ -2470,15 +3034,15 @@ async function handleMessage(ws, data) {
|
|
|
2470
3034
|
});
|
|
2471
3035
|
break;
|
|
2472
3036
|
}
|
|
2473
|
-
const screenshotsDir =
|
|
2474
|
-
if (!
|
|
3037
|
+
const screenshotsDir = join4(serverAppRootForVision, ".uilint", "screenshots");
|
|
3038
|
+
if (!existsSync6(screenshotsDir)) {
|
|
2475
3039
|
mkdirSync4(screenshotsDir, { recursive: true });
|
|
2476
3040
|
}
|
|
2477
|
-
const imagePath =
|
|
3041
|
+
const imagePath = join4(screenshotsDir, filename);
|
|
2478
3042
|
const imageBuffer = Buffer.from(base64Data, "base64");
|
|
2479
3043
|
writeFileSync4(imagePath, imageBuffer);
|
|
2480
3044
|
const sidecarFilename = filename.replace(/\.png$/, ".json");
|
|
2481
|
-
const sidecarPath =
|
|
3045
|
+
const sidecarPath = join4(screenshotsDir, sidecarFilename);
|
|
2482
3046
|
const sidecarData = {
|
|
2483
3047
|
route,
|
|
2484
3048
|
timestamp,
|
|
@@ -2532,7 +3096,7 @@ function handleFileChange(filePath) {
|
|
|
2532
3096
|
}
|
|
2533
3097
|
function handleCoverageFileChange(filePath) {
|
|
2534
3098
|
try {
|
|
2535
|
-
const coverageData = JSON.parse(
|
|
3099
|
+
const coverageData = JSON.parse(readFileSync2(filePath, "utf-8"));
|
|
2536
3100
|
logCoverageResult(Object.keys(coverageData).length);
|
|
2537
3101
|
broadcast({
|
|
2538
3102
|
type: "coverage:result",
|
|
@@ -2716,7 +3280,7 @@ function handleRuleConfigSet(ws, ruleId, severity, options, requestId) {
|
|
|
2716
3280
|
}
|
|
2717
3281
|
if (result.success) {
|
|
2718
3282
|
logServerInfo(`Updated uilint/${normalizedRuleId} -> ${severity}`);
|
|
2719
|
-
|
|
3283
|
+
eslintInstances2.clear();
|
|
2720
3284
|
cache.clear();
|
|
2721
3285
|
updateCacheCount(0);
|
|
2722
3286
|
sendMessage(ws, {
|
|
@@ -2759,7 +3323,7 @@ async function serve(options) {
|
|
|
2759
3323
|
ignoreInitial: true
|
|
2760
3324
|
});
|
|
2761
3325
|
fileWatcher.on("change", (path) => {
|
|
2762
|
-
const resolvedPath =
|
|
3326
|
+
const resolvedPath = resolve6(path);
|
|
2763
3327
|
if (resolvedPath.endsWith("coverage-final.json")) {
|
|
2764
3328
|
handleCoverageFileChange(resolvedPath);
|
|
2765
3329
|
return;
|
|
@@ -2767,8 +3331,8 @@ async function serve(options) {
|
|
|
2767
3331
|
handleFileChange(resolvedPath);
|
|
2768
3332
|
scheduleReindex(appRoot, resolvedPath);
|
|
2769
3333
|
});
|
|
2770
|
-
const coveragePath =
|
|
2771
|
-
if (
|
|
3334
|
+
const coveragePath = join4(appRoot, "coverage", "coverage-final.json");
|
|
3335
|
+
if (existsSync6(coveragePath)) {
|
|
2772
3336
|
fileWatcher.add(coveragePath);
|
|
2773
3337
|
logServerInfo(`Watching coverage`, coveragePath);
|
|
2774
3338
|
}
|
|
@@ -2862,10 +3426,10 @@ async function serve(options) {
|
|
|
2862
3426
|
}
|
|
2863
3427
|
|
|
2864
3428
|
// src/commands/vision.ts
|
|
2865
|
-
import { dirname as
|
|
3429
|
+
import { dirname as dirname7, resolve as resolve7, join as join5 } from "path";
|
|
2866
3430
|
import {
|
|
2867
|
-
existsSync as
|
|
2868
|
-
readFileSync as
|
|
3431
|
+
existsSync as existsSync7,
|
|
3432
|
+
readFileSync as readFileSync3,
|
|
2869
3433
|
readdirSync as readdirSync2
|
|
2870
3434
|
} from "fs";
|
|
2871
3435
|
import {
|
|
@@ -2892,7 +3456,7 @@ function debugDumpPath3(options) {
|
|
|
2892
3456
|
const v = options.debugDump ?? process.env.UILINT_DEBUG_DUMP;
|
|
2893
3457
|
if (!v) return null;
|
|
2894
3458
|
if (v === "1" || v.toLowerCase() === "true" || v.toLowerCase() === "yes") {
|
|
2895
|
-
return
|
|
3459
|
+
return resolve7(process.cwd(), ".uilint");
|
|
2896
3460
|
}
|
|
2897
3461
|
return v;
|
|
2898
3462
|
}
|
|
@@ -2911,17 +3475,17 @@ function debugLog3(enabled, message, obj) {
|
|
|
2911
3475
|
function findScreenshotsDirUpwards(startDir) {
|
|
2912
3476
|
let dir = startDir;
|
|
2913
3477
|
for (let i = 0; i < 20; i++) {
|
|
2914
|
-
const candidate =
|
|
2915
|
-
if (
|
|
2916
|
-
const parent =
|
|
3478
|
+
const candidate = join5(dir, ".uilint", "screenshots");
|
|
3479
|
+
if (existsSync7(candidate)) return candidate;
|
|
3480
|
+
const parent = dirname7(dir);
|
|
2917
3481
|
if (parent === dir) break;
|
|
2918
3482
|
dir = parent;
|
|
2919
3483
|
}
|
|
2920
3484
|
return null;
|
|
2921
3485
|
}
|
|
2922
3486
|
function listScreenshotSidecars(dirPath) {
|
|
2923
|
-
if (!
|
|
2924
|
-
const entries = readdirSync2(dirPath).filter((f) => f.endsWith(".json")).map((f) =>
|
|
3487
|
+
if (!existsSync7(dirPath)) return [];
|
|
3488
|
+
const entries = readdirSync2(dirPath).filter((f) => f.endsWith(".json")).map((f) => join5(dirPath, f));
|
|
2925
3489
|
const out = [];
|
|
2926
3490
|
for (const p of entries) {
|
|
2927
3491
|
try {
|
|
@@ -2950,11 +3514,11 @@ function listScreenshotSidecars(dirPath) {
|
|
|
2950
3514
|
return out;
|
|
2951
3515
|
}
|
|
2952
3516
|
function readImageAsBase64(imagePath) {
|
|
2953
|
-
const bytes =
|
|
3517
|
+
const bytes = readFileSync3(imagePath);
|
|
2954
3518
|
return { base64: bytes.toString("base64"), sizeBytes: bytes.byteLength };
|
|
2955
3519
|
}
|
|
2956
3520
|
function loadJsonFile(filePath) {
|
|
2957
|
-
const raw =
|
|
3521
|
+
const raw = readFileSync3(filePath, "utf-8");
|
|
2958
3522
|
return JSON.parse(raw);
|
|
2959
3523
|
}
|
|
2960
3524
|
function formatIssuesText(issues) {
|
|
@@ -3028,13 +3592,13 @@ async function vision(options) {
|
|
|
3028
3592
|
await flushLangfuse();
|
|
3029
3593
|
process.exit(1);
|
|
3030
3594
|
}
|
|
3031
|
-
if (imagePath && !
|
|
3595
|
+
if (imagePath && !existsSync7(imagePath)) {
|
|
3032
3596
|
throw new Error(`Image not found: ${imagePath}`);
|
|
3033
3597
|
}
|
|
3034
|
-
if (sidecarPath && !
|
|
3598
|
+
if (sidecarPath && !existsSync7(sidecarPath)) {
|
|
3035
3599
|
throw new Error(`Sidecar not found: ${sidecarPath}`);
|
|
3036
3600
|
}
|
|
3037
|
-
if (manifestFilePath && !
|
|
3601
|
+
if (manifestFilePath && !existsSync7(manifestFilePath)) {
|
|
3038
3602
|
throw new Error(`Manifest file not found: ${manifestFilePath}`);
|
|
3039
3603
|
}
|
|
3040
3604
|
const sidecar = sidecarPath ? loadJsonFile(sidecarPath) : null;
|
|
@@ -3059,7 +3623,7 @@ async function vision(options) {
|
|
|
3059
3623
|
const resolved = await resolveVisionStyleGuide({
|
|
3060
3624
|
projectPath,
|
|
3061
3625
|
styleguide: options.styleguide,
|
|
3062
|
-
startDir: startPath ?
|
|
3626
|
+
startDir: startPath ? dirname7(startPath) : projectPath
|
|
3063
3627
|
});
|
|
3064
3628
|
styleGuide = resolved.styleGuide;
|
|
3065
3629
|
styleguideLocation = resolved.styleguideLocation;
|
|
@@ -3108,8 +3672,8 @@ async function vision(options) {
|
|
|
3108
3672
|
const resolvedImagePath = imagePath || (() => {
|
|
3109
3673
|
const screenshotFile = typeof sidecar?.screenshotFile === "string" ? sidecar.screenshotFile : typeof sidecar?.filename === "string" ? sidecar.filename : void 0;
|
|
3110
3674
|
if (!screenshotFile) return null;
|
|
3111
|
-
const baseDir = sidecarPath ?
|
|
3112
|
-
const abs =
|
|
3675
|
+
const baseDir = sidecarPath ? dirname7(sidecarPath) : projectPath;
|
|
3676
|
+
const abs = resolve7(baseDir, screenshotFile);
|
|
3113
3677
|
return abs;
|
|
3114
3678
|
})();
|
|
3115
3679
|
if (!resolvedImagePath) {
|
|
@@ -3117,7 +3681,7 @@ async function vision(options) {
|
|
|
3117
3681
|
"No image path could be resolved. Provide --image or a sidecar with `screenshotFile`/`filename`."
|
|
3118
3682
|
);
|
|
3119
3683
|
}
|
|
3120
|
-
if (!
|
|
3684
|
+
if (!existsSync7(resolvedImagePath)) {
|
|
3121
3685
|
throw new Error(`Image not found: ${resolvedImagePath}`);
|
|
3122
3686
|
}
|
|
3123
3687
|
const { base64, sizeBytes } = readImageAsBase64(resolvedImagePath);
|
|
@@ -3667,14 +4231,17 @@ function indexCommand() {
|
|
|
3667
4231
|
|
|
3668
4232
|
// src/commands/duplicates/find.ts
|
|
3669
4233
|
import { Command as Command2 } from "commander";
|
|
3670
|
-
import { relative as
|
|
4234
|
+
import { relative as relative3 } from "path";
|
|
3671
4235
|
import chalk3 from "chalk";
|
|
3672
4236
|
function findCommand() {
|
|
3673
4237
|
return new Command2("find").description("Find semantic duplicate groups in the codebase").option(
|
|
3674
4238
|
"--threshold <n>",
|
|
3675
|
-
"Similarity threshold 0-1 (default: 0.
|
|
4239
|
+
"Similarity threshold 0-1 (default: 0.75)",
|
|
3676
4240
|
parseFloat
|
|
3677
|
-
).option("--min-size <n>", "Minimum group size (default: 2)", parseInt).option("--kind <type>", "Filter: component, hook, function").option(
|
|
4241
|
+
).option("--min-size <n>", "Minimum group size (default: 2)", parseInt).option("--kind <type>", "Filter: component, hook, function").option(
|
|
4242
|
+
"--confidence <level>",
|
|
4243
|
+
"Minimum confidence: high, medium, low (default: low)"
|
|
4244
|
+
).option("--no-structural", "Disable structural similarity boost").option("--same-file", "Include duplicates within the same file").option("-o, --output <format>", "Output format: text or json", "text").action(async (options) => {
|
|
3678
4245
|
const { findDuplicates } = await import("uilint-duplicates");
|
|
3679
4246
|
const projectRoot = process.cwd();
|
|
3680
4247
|
const isJson = options.output === "json";
|
|
@@ -3683,7 +4250,10 @@ function findCommand() {
|
|
|
3683
4250
|
path: projectRoot,
|
|
3684
4251
|
threshold: options.threshold,
|
|
3685
4252
|
minGroupSize: options.minSize,
|
|
3686
|
-
kind: options.kind
|
|
4253
|
+
kind: options.kind,
|
|
4254
|
+
confidenceLevel: options.confidence,
|
|
4255
|
+
useStructuralBoost: options.structural !== false,
|
|
4256
|
+
includeSameFile: options.sameFile ?? false
|
|
3687
4257
|
});
|
|
3688
4258
|
if (isJson) {
|
|
3689
4259
|
console.log(JSON.stringify({ groups }, null, 2));
|
|
@@ -3701,24 +4271,23 @@ function findCommand() {
|
|
|
3701
4271
|
);
|
|
3702
4272
|
groups.forEach((group, idx) => {
|
|
3703
4273
|
const similarity = Math.round(group.avgSimilarity * 100);
|
|
4274
|
+
const confidenceEmoji = group.confidence === "high" ? "\u{1F534}" : group.confidence === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
4275
|
+
const confidenceColor = group.confidence === "high" ? chalk3.red : group.confidence === "medium" ? chalk3.yellow : chalk3.green;
|
|
3704
4276
|
console.log(
|
|
3705
|
-
chalk3.
|
|
3706
|
-
|
|
4277
|
+
chalk3.bold(
|
|
4278
|
+
`${confidenceEmoji} Duplicate Group ${idx + 1} (${confidenceColor(`${similarity}% - ${group.confidence} confidence`)}, ${group.members.length} occurrences):`
|
|
3707
4279
|
)
|
|
3708
4280
|
);
|
|
3709
4281
|
group.members.forEach((member) => {
|
|
3710
|
-
const relPath =
|
|
4282
|
+
const relPath = relative3(projectRoot, member.filePath);
|
|
3711
4283
|
const location = `${relPath}:${member.startLine}-${member.endLine}`;
|
|
3712
4284
|
const name = member.name || "(anonymous)";
|
|
3713
4285
|
const score = member.score === 1 ? "" : chalk3.dim(` (${Math.round(member.score * 100)}%)`);
|
|
3714
4286
|
console.log(` ${chalk3.cyan(location.padEnd(50))} ${name}${score}`);
|
|
3715
4287
|
});
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
`
|
|
3720
|
-
)
|
|
3721
|
-
);
|
|
4288
|
+
const suggestion = group.confidence === "high" ? `Strongly recommend consolidating into a single reusable ${group.kind}` : group.confidence === "medium" ? `Consider extracting shared logic into a reusable ${group.kind}` : `Optional: Review if a common abstraction makes sense`;
|
|
4289
|
+
console.log(chalk3.dim(` \u2192 ${suggestion}
|
|
4290
|
+
`));
|
|
3722
4291
|
});
|
|
3723
4292
|
} catch (error) {
|
|
3724
4293
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -3734,7 +4303,7 @@ function findCommand() {
|
|
|
3734
4303
|
|
|
3735
4304
|
// src/commands/duplicates/search.ts
|
|
3736
4305
|
import { Command as Command3 } from "commander";
|
|
3737
|
-
import { relative as
|
|
4306
|
+
import { relative as relative4 } from "path";
|
|
3738
4307
|
import chalk4 from "chalk";
|
|
3739
4308
|
function searchCommand() {
|
|
3740
4309
|
return new Command3("search").description("Semantic search for similar code").argument("<query>", "Search query (natural language)").option("-k, --top <n>", "Number of results (default: 10)", parseInt).option("--threshold <n>", "Minimum similarity (default: 0.5)", parseFloat).option("-o, --output <format>", "Output format: text or json", "text").action(async (query, options) => {
|
|
@@ -3758,7 +4327,7 @@ function searchCommand() {
|
|
|
3758
4327
|
console.log(chalk4.bold(`Found ${results.length} matching results:
|
|
3759
4328
|
`));
|
|
3760
4329
|
results.forEach((result, idx) => {
|
|
3761
|
-
const relPath =
|
|
4330
|
+
const relPath = relative4(projectRoot, result.filePath);
|
|
3762
4331
|
const location = `${relPath}:${result.startLine}-${result.endLine}`;
|
|
3763
4332
|
const name = result.name || "(anonymous)";
|
|
3764
4333
|
const score = Math.round(result.score * 100);
|
|
@@ -3784,7 +4353,7 @@ function searchCommand() {
|
|
|
3784
4353
|
|
|
3785
4354
|
// src/commands/duplicates/similar.ts
|
|
3786
4355
|
import { Command as Command4 } from "commander";
|
|
3787
|
-
import { relative as
|
|
4356
|
+
import { relative as relative5, resolve as resolve8, isAbsolute as isAbsolute2 } from "path";
|
|
3788
4357
|
import chalk5 from "chalk";
|
|
3789
4358
|
function similarCommand() {
|
|
3790
4359
|
return new Command4("similar").description("Find code similar to a specific location").argument("<location>", "File location in format file:line (e.g., src/Button.tsx:15)").option("-k, --top <n>", "Number of results (default: 10)", parseInt).option("--threshold <n>", "Minimum similarity (default: 0.7)", parseFloat).option("-o, --output <format>", "Output format: text or json", "text").action(async (location, options) => {
|
|
@@ -3813,7 +4382,7 @@ function similarCommand() {
|
|
|
3813
4382
|
}
|
|
3814
4383
|
process.exit(1);
|
|
3815
4384
|
}
|
|
3816
|
-
const filePath = isAbsolute2(filePart) ? filePart :
|
|
4385
|
+
const filePath = isAbsolute2(filePart) ? filePart : resolve8(projectRoot, filePart);
|
|
3817
4386
|
try {
|
|
3818
4387
|
const results = await findSimilarAtLocation({
|
|
3819
4388
|
path: projectRoot,
|
|
@@ -3832,12 +4401,12 @@ function similarCommand() {
|
|
|
3832
4401
|
}
|
|
3833
4402
|
console.log(
|
|
3834
4403
|
chalk5.bold(
|
|
3835
|
-
`Found ${results.length} similar code locations to ${
|
|
4404
|
+
`Found ${results.length} similar code locations to ${relative5(projectRoot, filePath)}:${line}:
|
|
3836
4405
|
`
|
|
3837
4406
|
)
|
|
3838
4407
|
);
|
|
3839
4408
|
results.forEach((result, idx) => {
|
|
3840
|
-
const relPath =
|
|
4409
|
+
const relPath = relative5(projectRoot, result.filePath);
|
|
3841
4410
|
const locationStr = `${relPath}:${result.startLine}-${result.endLine}`;
|
|
3842
4411
|
const name = result.name || "(anonymous)";
|
|
3843
4412
|
const score = Math.round(result.score * 100);
|
|
@@ -3888,199 +4457,6 @@ import { glob } from "glob";
|
|
|
3888
4457
|
import { execSync } from "child_process";
|
|
3889
4458
|
import { findWorkspaceRoot as findWorkspaceRoot5 } from "uilint-core/node";
|
|
3890
4459
|
import { ruleRegistry as ruleRegistry2 } from "uilint-eslint";
|
|
3891
|
-
|
|
3892
|
-
// src/utils/eslint-utils.ts
|
|
3893
|
-
import { existsSync as existsSync7, readFileSync as readFileSync3 } from "fs";
|
|
3894
|
-
import { createRequire as createRequire2 } from "module";
|
|
3895
|
-
import { dirname as dirname7, resolve as resolve8, relative as relative5, join as join5 } from "path";
|
|
3896
|
-
var ESLINT_CONFIG_FILES2 = [
|
|
3897
|
-
// Flat config (ESLint v9+)
|
|
3898
|
-
"eslint.config.js",
|
|
3899
|
-
"eslint.config.mjs",
|
|
3900
|
-
"eslint.config.cjs",
|
|
3901
|
-
"eslint.config.ts",
|
|
3902
|
-
// Legacy config
|
|
3903
|
-
".eslintrc",
|
|
3904
|
-
".eslintrc.js",
|
|
3905
|
-
".eslintrc.cjs",
|
|
3906
|
-
".eslintrc.json",
|
|
3907
|
-
".eslintrc.yaml",
|
|
3908
|
-
".eslintrc.yml"
|
|
3909
|
-
];
|
|
3910
|
-
function buildLineStarts2(code) {
|
|
3911
|
-
const starts = [0];
|
|
3912
|
-
for (let i = 0; i < code.length; i++) {
|
|
3913
|
-
if (code.charCodeAt(i) === 10) starts.push(i + 1);
|
|
3914
|
-
}
|
|
3915
|
-
return starts;
|
|
3916
|
-
}
|
|
3917
|
-
function offsetFromLineCol2(lineStarts, line1, col0, codeLength) {
|
|
3918
|
-
const lineIndex = Math.max(0, Math.min(lineStarts.length - 1, line1 - 1));
|
|
3919
|
-
const base = lineStarts[lineIndex] ?? 0;
|
|
3920
|
-
return Math.max(0, Math.min(codeLength, base + Math.max(0, col0)));
|
|
3921
|
-
}
|
|
3922
|
-
function buildJsxElementSpans2(code, dataLocFile) {
|
|
3923
|
-
const localRequire2 = createRequire2(import.meta.url);
|
|
3924
|
-
const { parse: parse3 } = localRequire2("@typescript-eslint/typescript-estree");
|
|
3925
|
-
const ast = parse3(code, {
|
|
3926
|
-
loc: true,
|
|
3927
|
-
range: true,
|
|
3928
|
-
jsx: true,
|
|
3929
|
-
comment: false,
|
|
3930
|
-
errorOnUnknownASTType: false
|
|
3931
|
-
});
|
|
3932
|
-
const spans = [];
|
|
3933
|
-
function walk(node) {
|
|
3934
|
-
if (!node || typeof node !== "object") return;
|
|
3935
|
-
if (node.type === "JSXElement") {
|
|
3936
|
-
const range = node.range;
|
|
3937
|
-
const opening = node.openingElement;
|
|
3938
|
-
const loc = opening?.loc?.start;
|
|
3939
|
-
if (range && typeof range[0] === "number" && typeof range[1] === "number" && loc && typeof loc.line === "number" && typeof loc.column === "number") {
|
|
3940
|
-
const dataLoc = `${dataLocFile}:${loc.line}:${loc.column}`;
|
|
3941
|
-
spans.push({ start: range[0], end: range[1], dataLoc });
|
|
3942
|
-
}
|
|
3943
|
-
}
|
|
3944
|
-
for (const key of Object.keys(node)) {
|
|
3945
|
-
const child = node[key];
|
|
3946
|
-
if (Array.isArray(child)) {
|
|
3947
|
-
for (const item of child) walk(item);
|
|
3948
|
-
} else if (child && typeof child === "object") {
|
|
3949
|
-
walk(child);
|
|
3950
|
-
}
|
|
3951
|
-
}
|
|
3952
|
-
}
|
|
3953
|
-
walk(ast);
|
|
3954
|
-
spans.sort((a, b) => a.end - a.start - (b.end - b.start));
|
|
3955
|
-
return spans;
|
|
3956
|
-
}
|
|
3957
|
-
function mapMessageToDataLoc2(params) {
|
|
3958
|
-
const col0 = typeof params.messageCol1 === "number" ? Math.max(0, params.messageCol1 - 1) : 0;
|
|
3959
|
-
const offset = offsetFromLineCol2(
|
|
3960
|
-
params.lineStarts,
|
|
3961
|
-
params.messageLine1,
|
|
3962
|
-
col0,
|
|
3963
|
-
params.codeLength
|
|
3964
|
-
);
|
|
3965
|
-
for (const s of params.spans) {
|
|
3966
|
-
if (s.start <= offset && offset < s.end) return s.dataLoc;
|
|
3967
|
-
}
|
|
3968
|
-
return void 0;
|
|
3969
|
-
}
|
|
3970
|
-
function normalizePathSlashes2(p) {
|
|
3971
|
-
return p.replace(/\\/g, "/");
|
|
3972
|
-
}
|
|
3973
|
-
function normalizeDataLocFilePath2(absoluteFilePath, projectCwd) {
|
|
3974
|
-
const abs = normalizePathSlashes2(resolve8(absoluteFilePath));
|
|
3975
|
-
const cwd = normalizePathSlashes2(resolve8(projectCwd));
|
|
3976
|
-
if (abs === cwd || abs.startsWith(cwd + "/")) {
|
|
3977
|
-
return normalizePathSlashes2(relative5(cwd, abs));
|
|
3978
|
-
}
|
|
3979
|
-
return abs;
|
|
3980
|
-
}
|
|
3981
|
-
function findESLintCwd2(startDir) {
|
|
3982
|
-
let dir = startDir;
|
|
3983
|
-
for (let i = 0; i < 30; i++) {
|
|
3984
|
-
for (const cfg of ESLINT_CONFIG_FILES2) {
|
|
3985
|
-
if (existsSync7(join5(dir, cfg))) return dir;
|
|
3986
|
-
}
|
|
3987
|
-
if (existsSync7(join5(dir, "package.json"))) return dir;
|
|
3988
|
-
const parent = dirname7(dir);
|
|
3989
|
-
if (parent === dir) break;
|
|
3990
|
-
dir = parent;
|
|
3991
|
-
}
|
|
3992
|
-
return startDir;
|
|
3993
|
-
}
|
|
3994
|
-
var eslintInstances2 = /* @__PURE__ */ new Map();
|
|
3995
|
-
async function getESLintForProject2(projectCwd) {
|
|
3996
|
-
const cached = eslintInstances2.get(projectCwd);
|
|
3997
|
-
if (cached) return cached;
|
|
3998
|
-
try {
|
|
3999
|
-
const req = createRequire2(join5(projectCwd, "package.json"));
|
|
4000
|
-
const mod = req("eslint");
|
|
4001
|
-
const ESLintCtor = mod?.ESLint ?? mod?.default?.ESLint ?? mod?.default ?? mod;
|
|
4002
|
-
if (!ESLintCtor) return null;
|
|
4003
|
-
const eslint = new ESLintCtor({ cwd: projectCwd });
|
|
4004
|
-
eslintInstances2.set(projectCwd, eslint);
|
|
4005
|
-
return eslint;
|
|
4006
|
-
} catch {
|
|
4007
|
-
return null;
|
|
4008
|
-
}
|
|
4009
|
-
}
|
|
4010
|
-
async function lintFileWithDataLoc(absolutePath, projectCwd, onProgress) {
|
|
4011
|
-
const progress = onProgress ?? (() => {
|
|
4012
|
-
});
|
|
4013
|
-
if (!existsSync7(absolutePath)) {
|
|
4014
|
-
progress(`File not found: ${absolutePath}`);
|
|
4015
|
-
return [];
|
|
4016
|
-
}
|
|
4017
|
-
progress(`Resolving ESLint project... ${projectCwd}`);
|
|
4018
|
-
const eslint = await getESLintForProject2(projectCwd);
|
|
4019
|
-
if (!eslint) {
|
|
4020
|
-
progress("ESLint not available");
|
|
4021
|
-
return [];
|
|
4022
|
-
}
|
|
4023
|
-
try {
|
|
4024
|
-
progress("Running ESLint...");
|
|
4025
|
-
const results = await eslint.lintFiles([absolutePath]);
|
|
4026
|
-
const messages = Array.isArray(results) && results.length > 0 ? results[0].messages || [] : [];
|
|
4027
|
-
const dataLocFile = normalizeDataLocFilePath2(absolutePath, projectCwd);
|
|
4028
|
-
let spans = [];
|
|
4029
|
-
let lineStarts = [];
|
|
4030
|
-
let codeLength = 0;
|
|
4031
|
-
try {
|
|
4032
|
-
progress("Building JSX map...");
|
|
4033
|
-
const code = readFileSync3(absolutePath, "utf-8");
|
|
4034
|
-
codeLength = code.length;
|
|
4035
|
-
lineStarts = buildLineStarts2(code);
|
|
4036
|
-
spans = buildJsxElementSpans2(code, dataLocFile);
|
|
4037
|
-
progress(`JSX map: ${spans.length} element(s)`);
|
|
4038
|
-
} catch (e) {
|
|
4039
|
-
progress("JSX map failed (falling back to unmapped issues)");
|
|
4040
|
-
spans = [];
|
|
4041
|
-
lineStarts = [];
|
|
4042
|
-
codeLength = 0;
|
|
4043
|
-
}
|
|
4044
|
-
const issues = messages.filter((m) => typeof m?.message === "string").map((m) => {
|
|
4045
|
-
const line = typeof m.line === "number" ? m.line : 1;
|
|
4046
|
-
const column = typeof m.column === "number" ? m.column : void 0;
|
|
4047
|
-
const mappedDataLoc = spans.length > 0 && lineStarts.length > 0 && codeLength > 0 ? mapMessageToDataLoc2({
|
|
4048
|
-
spans,
|
|
4049
|
-
lineStarts,
|
|
4050
|
-
codeLength,
|
|
4051
|
-
messageLine1: line,
|
|
4052
|
-
messageCol1: column
|
|
4053
|
-
}) : void 0;
|
|
4054
|
-
return {
|
|
4055
|
-
line,
|
|
4056
|
-
column,
|
|
4057
|
-
message: m.message,
|
|
4058
|
-
ruleId: typeof m.ruleId === "string" ? m.ruleId : void 0,
|
|
4059
|
-
dataLoc: mappedDataLoc
|
|
4060
|
-
};
|
|
4061
|
-
});
|
|
4062
|
-
const mappedCount = issues.filter((i) => Boolean(i.dataLoc)).length;
|
|
4063
|
-
if (issues.length > 0) {
|
|
4064
|
-
progress(`Mapped ${mappedCount}/${issues.length} issue(s) to JSX elements`);
|
|
4065
|
-
}
|
|
4066
|
-
return issues;
|
|
4067
|
-
} catch (error) {
|
|
4068
|
-
progress(`ESLint failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
4069
|
-
return [];
|
|
4070
|
-
}
|
|
4071
|
-
}
|
|
4072
|
-
function extractSourceSnippet(code, centerLine, contextLines = 3) {
|
|
4073
|
-
const allLines = code.split("\n");
|
|
4074
|
-
const startLine = Math.max(1, centerLine - contextLines);
|
|
4075
|
-
const endLine = Math.min(allLines.length, centerLine + contextLines);
|
|
4076
|
-
return {
|
|
4077
|
-
lines: allLines.slice(startLine - 1, endLine),
|
|
4078
|
-
startLine,
|
|
4079
|
-
endLine
|
|
4080
|
-
};
|
|
4081
|
-
}
|
|
4082
|
-
|
|
4083
|
-
// src/commands/manifest/generator.ts
|
|
4084
4460
|
var DEFAULT_INCLUDE = ["**/*.tsx", "**/*.jsx"];
|
|
4085
4461
|
var DEFAULT_EXCLUDE = [
|
|
4086
4462
|
"node_modules/**",
|
|
@@ -4161,36 +4537,50 @@ async function generateManifest(options = {}) {
|
|
|
4161
4537
|
const relativePath = relative6(cwd, absolutePath);
|
|
4162
4538
|
onProgress(`Linting ${relativePath}...`, i + 1, files.length);
|
|
4163
4539
|
const fileDir = dirname8(absolutePath);
|
|
4164
|
-
const projectCwd =
|
|
4540
|
+
const projectCwd = findESLintCwd(fileDir);
|
|
4165
4541
|
const issues = await lintFileWithDataLoc(absolutePath, projectCwd);
|
|
4166
4542
|
if (issues.length === 0) continue;
|
|
4167
|
-
const
|
|
4543
|
+
const filteredIssues = issues.filter(
|
|
4544
|
+
(issue) => Boolean(issue.dataLoc)
|
|
4545
|
+
);
|
|
4546
|
+
if (filteredIssues.length === 0) continue;
|
|
4547
|
+
let fileContent;
|
|
4548
|
+
let snippets;
|
|
4549
|
+
try {
|
|
4550
|
+
fileContent = readFileSync4(absolutePath, "utf-8");
|
|
4551
|
+
} catch {
|
|
4552
|
+
fileContent = void 0;
|
|
4553
|
+
}
|
|
4554
|
+
let scopeInfos = [];
|
|
4555
|
+
if (fileContent) {
|
|
4556
|
+
try {
|
|
4557
|
+
const positions = filteredIssues.map((issue) => ({
|
|
4558
|
+
line: issue.line,
|
|
4559
|
+
column: issue.column ?? 0
|
|
4560
|
+
}));
|
|
4561
|
+
scopeInfos = findEnclosingScopeBatch(fileContent, positions);
|
|
4562
|
+
} catch {
|
|
4563
|
+
scopeInfos = filteredIssues.map(() => null);
|
|
4564
|
+
}
|
|
4565
|
+
}
|
|
4566
|
+
const manifestIssues = filteredIssues.map((issue, index) => ({
|
|
4168
4567
|
line: issue.line,
|
|
4169
4568
|
column: issue.column,
|
|
4170
4569
|
message: issue.message,
|
|
4171
4570
|
ruleId: issue.ruleId,
|
|
4172
|
-
dataLoc: issue.dataLoc
|
|
4571
|
+
dataLoc: issue.dataLoc,
|
|
4572
|
+
scopeInfo: scopeInfos[index] ?? void 0
|
|
4173
4573
|
}));
|
|
4174
|
-
if (
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
if (includeSnippets) {
|
|
4181
|
-
snippets = {};
|
|
4182
|
-
const issuesByDataLoc = /* @__PURE__ */ new Map();
|
|
4183
|
-
for (const issue of manifestIssues) {
|
|
4184
|
-
if (!issuesByDataLoc.has(issue.dataLoc)) {
|
|
4185
|
-
issuesByDataLoc.set(issue.dataLoc, issue);
|
|
4186
|
-
}
|
|
4187
|
-
}
|
|
4188
|
-
for (const [dataLoc, issue] of issuesByDataLoc) {
|
|
4189
|
-
snippets[dataLoc] = extractSourceSnippet(fileContent, issue.line, snippetContextLines);
|
|
4190
|
-
}
|
|
4574
|
+
if (includeSnippets && fileContent) {
|
|
4575
|
+
snippets = {};
|
|
4576
|
+
const issuesByDataLoc = /* @__PURE__ */ new Map();
|
|
4577
|
+
for (const issue of manifestIssues) {
|
|
4578
|
+
if (!issuesByDataLoc.has(issue.dataLoc)) {
|
|
4579
|
+
issuesByDataLoc.set(issue.dataLoc, issue);
|
|
4191
4580
|
}
|
|
4192
|
-
}
|
|
4193
|
-
|
|
4581
|
+
}
|
|
4582
|
+
for (const [dataLoc, issue] of issuesByDataLoc) {
|
|
4583
|
+
snippets[dataLoc] = extractSourceSnippet(fileContent, issue.line, snippetContextLines);
|
|
4194
4584
|
}
|
|
4195
4585
|
}
|
|
4196
4586
|
for (const issue of manifestIssues) {
|
|
@@ -4207,7 +4597,7 @@ async function generateManifest(options = {}) {
|
|
|
4207
4597
|
}
|
|
4208
4598
|
}
|
|
4209
4599
|
totalIssues += manifestIssues.length;
|
|
4210
|
-
const dataLocFilePath =
|
|
4600
|
+
const dataLocFilePath = normalizeDataLocFilePath(absolutePath, projectCwd);
|
|
4211
4601
|
manifestFiles.push({
|
|
4212
4602
|
filePath: dataLocFilePath,
|
|
4213
4603
|
issues: manifestIssues,
|