mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-11-20 11:55:34 +00:00
mod alias tmp fix
This commit is contained in:
parent
f527c3509f
commit
5676e481ca
9 changed files with 215 additions and 4 deletions
|
|
@ -55,7 +55,14 @@ pub fn check_dead_code(
|
|||
let (import_usage, shadowed_imports, module_children) =
|
||||
compute_import_usage(world, &definitions, ei);
|
||||
|
||||
let mut seen_module_aliases = HashSet::new();
|
||||
|
||||
for def_info in definitions {
|
||||
if matches!(def_info.decl.as_ref(), Decl::ModuleAlias(_))
|
||||
&& !seen_module_aliases.insert(def_info.decl.clone())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if shadowed_imports.contains(&def_info.decl) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -65,7 +72,7 @@ pub fn check_dead_code(
|
|||
|
||||
let is_unused = match def_info.decl.as_ref() {
|
||||
Decl::Import(_) | Decl::ImportAlias(_) => !import_usage.contains(&def_info.decl),
|
||||
Decl::ModuleImport(_) => {
|
||||
Decl::ModuleImport(_) | Decl::ModuleAlias(_) => {
|
||||
let children_used = module_children.get(&def_info.decl).is_some_and(|children| {
|
||||
children.iter().any(|child| import_usage.contains(child))
|
||||
});
|
||||
|
|
@ -99,6 +106,7 @@ fn compute_import_usage(
|
|||
range: Range<usize>,
|
||||
}
|
||||
|
||||
let text = ei.source.text();
|
||||
let module_spans: Vec<ModuleSpan> = definitions
|
||||
.iter()
|
||||
.filter_map(|def| match def.decl.as_ref() {
|
||||
|
|
@ -114,6 +122,7 @@ fn compute_import_usage(
|
|||
let mut alias_links: HashMap<Interned<Decl>, Interned<Decl>> = HashMap::new();
|
||||
let mut shadowed = HashSet::new();
|
||||
let mut module_children: HashMap<Interned<Decl>, HashSet<Interned<Decl>>> = HashMap::new();
|
||||
let mut alias_item_ranges: Vec<(Range<usize>, Interned<Decl>)> = Vec::new();
|
||||
|
||||
for def in definitions {
|
||||
if matches!(def.decl.as_ref(), Decl::ImportAlias(_)) {
|
||||
|
|
@ -124,6 +133,16 @@ fn compute_import_usage(
|
|||
}
|
||||
}
|
||||
}
|
||||
if matches!(def.decl.as_ref(), Decl::ModuleAlias(_)) {
|
||||
if let Some(alias_range) = world.source_range(def.span) {
|
||||
if let Some(items_range) = alias_items_range(text, &alias_range) {
|
||||
alias_item_ranges.push((items_range, def.decl.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for def in definitions {
|
||||
if matches!(def.decl.as_ref(), Decl::Import(_) | Decl::ImportAlias(_)) {
|
||||
if let Some(child_range) = world.source_range(def.span) {
|
||||
if let Some(module) = module_spans
|
||||
|
|
@ -135,6 +154,16 @@ fn compute_import_usage(
|
|||
.or_default()
|
||||
.insert(def.decl.clone());
|
||||
}
|
||||
|
||||
if let Some((_, alias_decl)) = alias_item_ranges
|
||||
.iter()
|
||||
.find(|(range, _)| range.contains(&child_range.start))
|
||||
{
|
||||
module_children
|
||||
.entry(alias_decl.clone())
|
||||
.or_default()
|
||||
.insert(def.decl.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -167,6 +196,26 @@ fn contains_range(outer: &Range<usize>, inner: &Range<usize>) -> bool {
|
|||
outer.start <= inner.start && outer.end >= inner.end
|
||||
}
|
||||
|
||||
fn alias_items_range(text: &str, alias_range: &Range<usize>) -> Option<Range<usize>> {
|
||||
let bytes = text.as_bytes();
|
||||
let mut idx = alias_range.end;
|
||||
while idx < bytes.len() && matches!(bytes[idx], b' ' | b'\t') {
|
||||
idx += 1;
|
||||
}
|
||||
if idx >= bytes.len() || bytes[idx] != b':' {
|
||||
return None;
|
||||
}
|
||||
idx += 1;
|
||||
while idx < bytes.len() && matches!(bytes[idx], b' ' | b'\t') {
|
||||
idx += 1;
|
||||
}
|
||||
let mut end = idx;
|
||||
while end < bytes.len() && bytes[end] != b'\n' && bytes[end] != b'\r' {
|
||||
end += 1;
|
||||
}
|
||||
Some(idx..end)
|
||||
}
|
||||
|
||||
fn should_skip_definition(def_info: &DefInfo, config: &DeadCodeConfig) -> bool {
|
||||
if matches!(def_info.scope, DefScope::Exported) && !config.check_exported {
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -24,11 +24,13 @@ pub fn generate_diagnostic(
|
|||
}
|
||||
|
||||
let is_module_import = matches!(def_info.decl.as_ref(), Decl::ModuleImport(..));
|
||||
let is_module_alias = matches!(def_info.decl.as_ref(), Decl::ModuleAlias(_));
|
||||
let is_import_item = matches!(
|
||||
def_info.decl.as_ref(),
|
||||
Decl::Import(_) | Decl::ImportAlias(_)
|
||||
Decl::Import(_) | Decl::ImportAlias(_) | Decl::ModuleAlias(_)
|
||||
);
|
||||
let is_module_like = is_module_import || matches!(def_info.kind, DefKind::Module);
|
||||
let is_module_like =
|
||||
is_module_import || matches!(def_info.kind, DefKind::Module) && !is_module_alias;
|
||||
|
||||
let kind_str = match def_info.kind {
|
||||
DefKind::Function => "function",
|
||||
|
|
|
|||
|
|
@ -447,7 +447,8 @@ impl<'a> CodeActionWorker<'a> {
|
|||
// Calculate the range to remove, expand to cover the whole import item
|
||||
// (e.g. `foo as bar`) and include trailing comma if present.
|
||||
let mut remove_range = self
|
||||
.find_import_item_range(root, name_range)
|
||||
.module_alias_remove_range(root, name_range)
|
||||
.or_else(|| self.find_import_item_range(root, name_range))
|
||||
.unwrap_or_else(|| name_range.clone());
|
||||
let bytes = self.source.text().as_bytes();
|
||||
|
||||
|
|
@ -496,6 +497,74 @@ impl<'a> CodeActionWorker<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
fn module_alias_remove_range(
|
||||
&self,
|
||||
root: &LinkedNode<'_>,
|
||||
name_range: &Range<usize>,
|
||||
) -> Option<Range<usize>> {
|
||||
if name_range.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let cursor = (name_range.start + name_range.end) / 2;
|
||||
let node = root.leaf_at_compat(cursor)?;
|
||||
|
||||
let mut in_module_import = false;
|
||||
for ancestor in node_ancestors(&node) {
|
||||
match ancestor.kind() {
|
||||
SyntaxKind::RenamedImportItem => return None,
|
||||
SyntaxKind::ModuleImport => {
|
||||
in_module_import = true;
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if !in_module_import {
|
||||
return None;
|
||||
}
|
||||
|
||||
let bytes = self.source.text().as_bytes();
|
||||
if name_range.end > bytes.len() || name_range.start > bytes.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut idx = name_range.start;
|
||||
while idx > 0 && matches!(bytes[idx - 1], b' ' | b'\t') {
|
||||
idx -= 1;
|
||||
}
|
||||
|
||||
if idx < 2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let as_end = idx;
|
||||
let as_start = as_end - 2;
|
||||
if &bytes[as_start..as_end] != b"as" {
|
||||
return None;
|
||||
}
|
||||
|
||||
if as_start > 0 && is_ascii_ident(bytes[as_start - 1]) {
|
||||
return None;
|
||||
}
|
||||
if as_end < bytes.len() && is_ascii_ident(bytes[as_end]) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut removal_start = as_start;
|
||||
while removal_start > 0 && matches!(bytes[removal_start - 1], b' ' | b'\t') {
|
||||
removal_start -= 1;
|
||||
}
|
||||
|
||||
let mut removal_end = name_range.end;
|
||||
while removal_end < bytes.len() && matches!(bytes[removal_end], b' ' | b'\t') {
|
||||
removal_end += 1;
|
||||
}
|
||||
|
||||
Some(removal_start..removal_end)
|
||||
}
|
||||
|
||||
/// Starts to work.
|
||||
pub fn scoped(&mut self, root: &LinkedNode, range: &Range<usize>) -> Option<()> {
|
||||
let cursor = (range.start + 1).min(self.source.text().len());
|
||||
|
|
@ -857,6 +926,10 @@ fn match_autofix_kind(source: &str, msg: &str) -> Option<AutofixKind> {
|
|||
None
|
||||
}
|
||||
|
||||
fn is_ascii_ident(ch: u8) -> bool {
|
||||
matches!(ch, b'_' | b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9')
|
||||
}
|
||||
|
||||
fn is_plain_identifier(name: &str) -> bool {
|
||||
let mut chars = name.chars();
|
||||
let Some(first) = chars.next() else {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
/// path: u.typ
|
||||
|
||||
#let foo() = "foo"
|
||||
#let bar() = "bar"
|
||||
|
||||
-----
|
||||
/// path: main.typ
|
||||
/// compile: true
|
||||
|
||||
#import "u.typ" as util: foo
|
||||
|
||||
#let value = foo()
|
||||
#value
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
/// path: u.typ
|
||||
|
||||
#let foo() = "foo"
|
||||
#let bar() = "bar"
|
||||
|
||||
-----
|
||||
/// path: main.typ
|
||||
/// compile: true
|
||||
|
||||
#import "u.typ" as util
|
||||
|
||||
#let answer = 42
|
||||
#answer
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/analysis.rs
|
||||
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
|
||||
input_file: crates/tinymist-query/src/fixtures/dead_code/import_module_alias_mixed.typ
|
||||
---
|
||||
{}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/analysis.rs
|
||||
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
|
||||
input_file: crates/tinymist-query/src/fixtures/dead_code/import_module_alias_unused.typ
|
||||
---
|
||||
{
|
||||
"main.typ": [
|
||||
{
|
||||
"message": "unused import: `util`\nHint: consider removing this unused import",
|
||||
"range": "2:19:2:23",
|
||||
"severity": 2,
|
||||
"source": "typst"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/code_action.rs
|
||||
description: Dead code code actions in /dummy-root/main.typ
|
||||
expression: "JsonRepr::new_pure(ordered_entries)"
|
||||
input_file: crates/tinymist-query/src/fixtures/dead_code/import_module_alias_mixed.typ
|
||||
---
|
||||
[]
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/code_action.rs
|
||||
description: Dead code code actions in /dummy-root/main.typ
|
||||
expression: "JsonRepr::new_pure(ordered_entries)"
|
||||
input_file: crates/tinymist-query/src/fixtures/dead_code/import_module_alias_unused.typ
|
||||
---
|
||||
[
|
||||
{
|
||||
"actions": [
|
||||
{
|
||||
"edit": {
|
||||
"changes": {
|
||||
"main.typ": [
|
||||
{
|
||||
"insertTextFormat": 1,
|
||||
"newText": "",
|
||||
"range": "2:15:2:23"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"kind": "quickfix",
|
||||
"title": "Remove unused import"
|
||||
}
|
||||
],
|
||||
"message": "unused import: `util`\nHint: consider removing this unused import",
|
||||
"range": "2:19:2:23"
|
||||
}
|
||||
]
|
||||
Loading…
Add table
Add a link
Reference in a new issue