mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-11-22 12:34:39 +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) =
|
let (import_usage, shadowed_imports, module_children) =
|
||||||
compute_import_usage(world, &definitions, ei);
|
compute_import_usage(world, &definitions, ei);
|
||||||
|
|
||||||
|
let mut seen_module_aliases = HashSet::new();
|
||||||
|
|
||||||
for def_info in definitions {
|
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) {
|
if shadowed_imports.contains(&def_info.decl) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -65,7 +72,7 @@ pub fn check_dead_code(
|
||||||
|
|
||||||
let is_unused = match def_info.decl.as_ref() {
|
let is_unused = match def_info.decl.as_ref() {
|
||||||
Decl::Import(_) | Decl::ImportAlias(_) => !import_usage.contains(&def_info.decl),
|
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| {
|
let children_used = module_children.get(&def_info.decl).is_some_and(|children| {
|
||||||
children.iter().any(|child| import_usage.contains(child))
|
children.iter().any(|child| import_usage.contains(child))
|
||||||
});
|
});
|
||||||
|
|
@ -99,6 +106,7 @@ fn compute_import_usage(
|
||||||
range: Range<usize>,
|
range: Range<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let text = ei.source.text();
|
||||||
let module_spans: Vec<ModuleSpan> = definitions
|
let module_spans: Vec<ModuleSpan> = definitions
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|def| match def.decl.as_ref() {
|
.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 alias_links: HashMap<Interned<Decl>, Interned<Decl>> = HashMap::new();
|
||||||
let mut shadowed = HashSet::new();
|
let mut shadowed = HashSet::new();
|
||||||
let mut module_children: HashMap<Interned<Decl>, HashSet<Interned<Decl>>> = HashMap::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 {
|
for def in definitions {
|
||||||
if matches!(def.decl.as_ref(), Decl::ImportAlias(_)) {
|
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 matches!(def.decl.as_ref(), Decl::Import(_) | Decl::ImportAlias(_)) {
|
||||||
if let Some(child_range) = world.source_range(def.span) {
|
if let Some(child_range) = world.source_range(def.span) {
|
||||||
if let Some(module) = module_spans
|
if let Some(module) = module_spans
|
||||||
|
|
@ -135,6 +154,16 @@ fn compute_import_usage(
|
||||||
.or_default()
|
.or_default()
|
||||||
.insert(def.decl.clone());
|
.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
|
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 {
|
fn should_skip_definition(def_info: &DefInfo, config: &DeadCodeConfig) -> bool {
|
||||||
if matches!(def_info.scope, DefScope::Exported) && !config.check_exported {
|
if matches!(def_info.scope, DefScope::Exported) && !config.check_exported {
|
||||||
return true;
|
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_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!(
|
let is_import_item = matches!(
|
||||||
def_info.decl.as_ref(),
|
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 {
|
let kind_str = match def_info.kind {
|
||||||
DefKind::Function => "function",
|
DefKind::Function => "function",
|
||||||
|
|
|
||||||
|
|
@ -447,7 +447,8 @@ impl<'a> CodeActionWorker<'a> {
|
||||||
// Calculate the range to remove, expand to cover the whole import item
|
// Calculate the range to remove, expand to cover the whole import item
|
||||||
// (e.g. `foo as bar`) and include trailing comma if present.
|
// (e.g. `foo as bar`) and include trailing comma if present.
|
||||||
let mut remove_range = self
|
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());
|
.unwrap_or_else(|| name_range.clone());
|
||||||
let bytes = self.source.text().as_bytes();
|
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.
|
/// Starts to work.
|
||||||
pub fn scoped(&mut self, root: &LinkedNode, range: &Range<usize>) -> Option<()> {
|
pub fn scoped(&mut self, root: &LinkedNode, range: &Range<usize>) -> Option<()> {
|
||||||
let cursor = (range.start + 1).min(self.source.text().len());
|
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
|
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 {
|
fn is_plain_identifier(name: &str) -> bool {
|
||||||
let mut chars = name.chars();
|
let mut chars = name.chars();
|
||||||
let Some(first) = chars.next() else {
|
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