@line/ts-remove-unused
Advanced tools
Comparing version 0.6.1 to 0.6.2
110
dist/cli.js
@@ -44,2 +44,35 @@ #!/usr/bin/env node | ||
// lib/util/applyTextChanges.ts | ||
var regex = /\n{2,}$/; | ||
var pushClean = (list, value) => { | ||
if (value === "") { | ||
return; | ||
} | ||
const count = value.match(/^\n+/)?.[0].length || 0; | ||
if (count === 0) { | ||
list.push(value); | ||
return; | ||
} | ||
const last = list.pop() || ""; | ||
list.push(`${last}${value.slice(0, count)}`.replace(regex, "\n\n")); | ||
const rest = value.slice(count); | ||
if (rest) { | ||
list.push(rest); | ||
} | ||
}; | ||
var applyTextChanges = (oldContent, changes) => { | ||
const result = []; | ||
const sortedChanges = [...changes].sort( | ||
(a, b) => a.span.start - b.span.start | ||
); | ||
let currentPos = 0; | ||
for (const change of sortedChanges) { | ||
pushClean(result, oldContent.slice(currentPos, change.span.start)); | ||
pushClean(result, change.newText); | ||
currentPos = change.span.start + change.span.length; | ||
} | ||
pushClean(result, oldContent.slice(currentPos)); | ||
return result.join("").replace(/^\n+/, "").replace(/\n{1,}$/, "\n"); | ||
}; | ||
// lib/util/applyCodeFix.ts | ||
@@ -128,3 +161,6 @@ import ts from "typescript"; | ||
hasReexport: false, | ||
fromDynamic: /* @__PURE__ */ new Set() | ||
fromDynamic: /* @__PURE__ */ new Set(), | ||
// will not be updated when we delete a file, | ||
// but it's fine since we only use it to find the used specifier from a given file path. | ||
wholeReexportSpecifier: /* @__PURE__ */ new Map() | ||
})); | ||
@@ -164,6 +200,8 @@ } | ||
if (node.moduleSpecifier && ts3.isStringLiteral(node.moduleSpecifier)) { | ||
return { | ||
const result = { | ||
type: "reexport", | ||
specifier: node.moduleSpecifier.text | ||
specifier: node.moduleSpecifier.text, | ||
whole: !node.exportClause | ||
}; | ||
return result; | ||
} | ||
@@ -232,2 +270,5 @@ return { | ||
}); | ||
if (match.type === "reexport" && dest && match.whole) { | ||
graph.vertexes.get(file)?.data.wholeReexportSpecifier.set(dest, match.specifier); | ||
} | ||
if (dest && files.has(dest)) { | ||
@@ -418,2 +459,38 @@ graph.addEdge(sourceFile.fileName, dest); | ||
}; | ||
var removeWholeExportSpecifier = (content, specifier, target) => { | ||
const sourceFile = ts4.createSourceFile( | ||
"tmp.ts", | ||
content, | ||
target ?? ts4.ScriptTarget.Latest | ||
); | ||
const result = []; | ||
const visit = (node) => { | ||
if (result.length > 0) { | ||
return; | ||
} | ||
if (ts4.isExportDeclaration(node) && node.moduleSpecifier && ts4.isStringLiteral(node.moduleSpecifier) && !node.exportClause && node.moduleSpecifier.text === specifier) { | ||
result.push({ | ||
textChange: { | ||
newText: "", | ||
span: { | ||
start: node.getFullStart(), | ||
length: node.getFullWidth() | ||
} | ||
}, | ||
info: { | ||
position: node.getStart(sourceFile), | ||
code: node.getText(sourceFile) | ||
} | ||
}); | ||
} | ||
}; | ||
sourceFile.forEachChild(visit); | ||
if (!result[0]) { | ||
return null; | ||
} | ||
return { | ||
info: result[0].info, | ||
content: applyTextChanges(content, [result[0].textChange]) | ||
}; | ||
}; | ||
var removeUnusedExport = async ({ | ||
@@ -464,2 +541,3 @@ entrypoints, | ||
initialFiles.sort((a, b) => a.depth - b.depth); | ||
const wholeReexportsToBeDeleted = []; | ||
const taskManager = new TaskManager(async (c) => { | ||
@@ -516,2 +594,12 @@ if (!fileService.exists(c.file)) { | ||
if (vertex) { | ||
for (const v of vertex.from) { | ||
const target = dependencyGraph.vertexes.get(v); | ||
if (!target) { | ||
continue; | ||
} | ||
const specifier = target.data.wholeReexportSpecifier.get(c.file); | ||
if (target.data.hasReexport && specifier) { | ||
wholeReexportsToBeDeleted.push({ file: v, specifier }); | ||
} | ||
} | ||
dependencyGraph.deleteVertex(c.file); | ||
@@ -546,2 +634,16 @@ if (recursive) { | ||
await taskManager.execute(initialFiles.map((v) => v.file)); | ||
for (const item of wholeReexportsToBeDeleted) { | ||
if (!fileService.exists(item.file)) { | ||
continue; | ||
} | ||
const content = fileService.get(item.file); | ||
const result = removeWholeExportSpecifier(content, item.specifier); | ||
if (!result) { | ||
continue; | ||
} | ||
fileService.set(item.file, result.content); | ||
editTracker.start(item.file, content); | ||
editTracker.removeExport(item.file, result.info); | ||
editTracker.end(item.file); | ||
} | ||
}; | ||
@@ -893,3 +995,3 @@ | ||
const entrypoints = fileNames.filter( | ||
(fileName) => skip.some((regex) => regex.test(fileName)) | ||
(fileName) => skip.some((regex2) => regex2.test(fileName)) | ||
); | ||
@@ -896,0 +998,0 @@ editTracker.setTotal(fileNames.length - entrypoints.length); |
110
dist/main.js
@@ -39,2 +39,35 @@ // lib/remove.ts | ||
// lib/util/applyTextChanges.ts | ||
var regex = /\n{2,}$/; | ||
var pushClean = (list, value) => { | ||
if (value === "") { | ||
return; | ||
} | ||
const count = value.match(/^\n+/)?.[0].length || 0; | ||
if (count === 0) { | ||
list.push(value); | ||
return; | ||
} | ||
const last = list.pop() || ""; | ||
list.push(`${last}${value.slice(0, count)}`.replace(regex, "\n\n")); | ||
const rest = value.slice(count); | ||
if (rest) { | ||
list.push(rest); | ||
} | ||
}; | ||
var applyTextChanges = (oldContent, changes) => { | ||
const result = []; | ||
const sortedChanges = [...changes].sort( | ||
(a, b) => a.span.start - b.span.start | ||
); | ||
let currentPos = 0; | ||
for (const change of sortedChanges) { | ||
pushClean(result, oldContent.slice(currentPos, change.span.start)); | ||
pushClean(result, change.newText); | ||
currentPos = change.span.start + change.span.length; | ||
} | ||
pushClean(result, oldContent.slice(currentPos)); | ||
return result.join("").replace(/^\n+/, "").replace(/\n{1,}$/, "\n"); | ||
}; | ||
// lib/util/applyCodeFix.ts | ||
@@ -123,3 +156,6 @@ import ts from "typescript"; | ||
hasReexport: false, | ||
fromDynamic: /* @__PURE__ */ new Set() | ||
fromDynamic: /* @__PURE__ */ new Set(), | ||
// will not be updated when we delete a file, | ||
// but it's fine since we only use it to find the used specifier from a given file path. | ||
wholeReexportSpecifier: /* @__PURE__ */ new Map() | ||
})); | ||
@@ -159,6 +195,8 @@ } | ||
if (node.moduleSpecifier && ts3.isStringLiteral(node.moduleSpecifier)) { | ||
return { | ||
const result = { | ||
type: "reexport", | ||
specifier: node.moduleSpecifier.text | ||
specifier: node.moduleSpecifier.text, | ||
whole: !node.exportClause | ||
}; | ||
return result; | ||
} | ||
@@ -227,2 +265,5 @@ return { | ||
}); | ||
if (match.type === "reexport" && dest && match.whole) { | ||
graph.vertexes.get(file)?.data.wholeReexportSpecifier.set(dest, match.specifier); | ||
} | ||
if (dest && files.has(dest)) { | ||
@@ -413,2 +454,38 @@ graph.addEdge(sourceFile.fileName, dest); | ||
}; | ||
var removeWholeExportSpecifier = (content, specifier, target) => { | ||
const sourceFile = ts4.createSourceFile( | ||
"tmp.ts", | ||
content, | ||
target ?? ts4.ScriptTarget.Latest | ||
); | ||
const result = []; | ||
const visit = (node) => { | ||
if (result.length > 0) { | ||
return; | ||
} | ||
if (ts4.isExportDeclaration(node) && node.moduleSpecifier && ts4.isStringLiteral(node.moduleSpecifier) && !node.exportClause && node.moduleSpecifier.text === specifier) { | ||
result.push({ | ||
textChange: { | ||
newText: "", | ||
span: { | ||
start: node.getFullStart(), | ||
length: node.getFullWidth() | ||
} | ||
}, | ||
info: { | ||
position: node.getStart(sourceFile), | ||
code: node.getText(sourceFile) | ||
} | ||
}); | ||
} | ||
}; | ||
sourceFile.forEachChild(visit); | ||
if (!result[0]) { | ||
return null; | ||
} | ||
return { | ||
info: result[0].info, | ||
content: applyTextChanges(content, [result[0].textChange]) | ||
}; | ||
}; | ||
var removeUnusedExport = async ({ | ||
@@ -459,2 +536,3 @@ entrypoints, | ||
initialFiles.sort((a, b) => a.depth - b.depth); | ||
const wholeReexportsToBeDeleted = []; | ||
const taskManager = new TaskManager(async (c) => { | ||
@@ -511,2 +589,12 @@ if (!fileService.exists(c.file)) { | ||
if (vertex) { | ||
for (const v of vertex.from) { | ||
const target = dependencyGraph.vertexes.get(v); | ||
if (!target) { | ||
continue; | ||
} | ||
const specifier = target.data.wholeReexportSpecifier.get(c.file); | ||
if (target.data.hasReexport && specifier) { | ||
wholeReexportsToBeDeleted.push({ file: v, specifier }); | ||
} | ||
} | ||
dependencyGraph.deleteVertex(c.file); | ||
@@ -541,2 +629,16 @@ if (recursive) { | ||
await taskManager.execute(initialFiles.map((v) => v.file)); | ||
for (const item of wholeReexportsToBeDeleted) { | ||
if (!fileService.exists(item.file)) { | ||
continue; | ||
} | ||
const content = fileService.get(item.file); | ||
const result = removeWholeExportSpecifier(content, item.specifier); | ||
if (!result) { | ||
continue; | ||
} | ||
fileService.set(item.file, result.content); | ||
editTracker.start(item.file, content); | ||
editTracker.removeExport(item.file, result.info); | ||
editTracker.end(item.file); | ||
} | ||
}; | ||
@@ -888,3 +990,3 @@ | ||
const entrypoints = fileNames.filter( | ||
(fileName) => skip.some((regex) => regex.test(fileName)) | ||
(fileName) => skip.some((regex2) => regex2.test(fileName)) | ||
); | ||
@@ -891,0 +993,0 @@ editTracker.setTotal(fileNames.length - entrypoints.length); |
@@ -6,2 +6,3 @@ import { Graph } from './Graph.js'; | ||
fromDynamic: Set<string>; | ||
wholeReexportSpecifier: Map<string, string>; | ||
}> { | ||
@@ -8,0 +9,0 @@ constructor(); |
@@ -44,3 +44,3 @@ { | ||
}, | ||
"version": "0.6.1" | ||
"version": "0.6.2" | ||
} |
97484
2818