Skip to content

Commit

Permalink
fix(vscode): static completions for Json. wrong/missing after other…
Browse files Browse the repository at this point in the history
… expressions (#3137)

I believe this is a regression from #3128. See the new test cases for the issues this covers. This issue is kinda specific to Json because it has its own tree-sitter node for a single identifier unlike others.

Also renamed a symbol in the completion visitor because `location` was a bad/wrong name.

## Checklist

- [x] Title matches [Winglang's style guide](https://docs.winglang.io/contributing/pull_requests#how-are-pull-request-titles-formatted)
- [x] Description explains motivation and solution
- [x] Tests added (always)
- [ ] Docs updated (only required for features)
- [ ] Added `pr/e2e-full` label if this feature requires end-to-end testing

*By submitting this pull request, I confirm that my contribution is made under the terms of the [Monada Contribution License](https://docs.winglang.io/terms-and-policies/contribution-license.html)*.
  • Loading branch information
MarkMcCulloh authored Jun 29, 2023
1 parent 799fb95 commit 396c012
Show file tree
Hide file tree
Showing 4 changed files with 381 additions and 15 deletions.
76 changes: 61 additions & 15 deletions libs/wingc/src/lsp/completions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,17 @@ pub fn on_completion(params: lsp_types::CompletionParams) -> CompletionResponse
);
let node_to_complete_kind = node_to_complete.kind();

let file_id = file.to_str().expect("File path must be valid utf8");
let mut scope_visitor = ScopeVisitor::new(
node_to_complete.parent().map(|parent| WingSpan {
start: parent.start_position().into(),
end: parent.end_position().into(),
file_id: file_id.to_string(),
}),
WingSpan {
start: node_to_complete.start_position().into(),
end: node_to_complete.end_position().into(),
file_id: file.to_str().expect("File path must be valid utf8").to_string(),
file_id: file_id.to_string(),
},
&root_scope,
);
Expand Down Expand Up @@ -603,8 +609,10 @@ fn should_exclude_symbol(symbol: &str) -> bool {
/// This visitor is used to find the scope
/// and relevant expression that contains a given location.
pub struct ScopeVisitor<'a> {
/// The area surrounding the location we're looking for
pub parent_span: Option<WingSpan>,
/// The target location we're looking for
pub location: WingSpan,
pub target_span: WingSpan,
/// The index of the statement that contains the target location
/// or, the last valid statement before the target location
pub found_stmt_index: Option<usize>,
Expand All @@ -617,9 +625,10 @@ pub struct ScopeVisitor<'a> {
}

impl<'a> ScopeVisitor<'a> {
pub fn new(location: WingSpan, starting_scope: &'a Scope) -> Self {
pub fn new(parent_span: Option<WingSpan>, target_span: WingSpan, starting_scope: &'a Scope) -> Self {
Self {
location,
parent_span,
target_span,
found_stmt_index: None,
nearest_expr: None,
found_scope: starting_scope,
Expand All @@ -634,11 +643,11 @@ impl<'a> ScopeVisitor<'a> {

impl<'a> Visit<'a> for ScopeVisitor<'a> {
fn visit_scope(&mut self, node: &'a Scope) {
if node.span.file_id != "" && node.span.contains(&self.location.start.into()) {
if node.span.file_id != "" && node.span.contains(&self.target_span.start.into()) {
self.found_scope = node;

for (i, statement) in node.statements.iter().enumerate() {
if statement.span <= self.location {
if statement.span <= self.target_span {
self.visit_stmt(&statement);
} else if self.found_stmt_index.is_none() {
self.found_stmt_index = Some(max(i as i64 - 1, 0) as usize);
Expand All @@ -654,16 +663,23 @@ impl<'a> Visit<'a> for ScopeVisitor<'a> {
fn visit_expr(&mut self, node: &'a Expr) {
// We want to find the nearest expression to our target location
// i.e we want the expression that is to the left of it
if node.span.end == self.location.start {
if node.span.end == self.target_span.start {
self.nearest_expr = Some(node);
} else if node.span.end <= self.location.start {
if let Some(nearest_expr) = self.nearest_expr {
// If we already have a nearest expression, we want to find the one that is closest to our target location
if node.span.end > nearest_expr.span.end {
self.nearest_expr = Some(node);
}
// if we can't get an exact match, we want to find the nearest expression within the same node-ish
// (this is for cases like `(Json 5).`, since parentheses are not part of the span)
else if node.span.end <= self.target_span.start {
if let Some(parent) = &self.parent_span {
if parent.contains(&node.span.end.into()) {
if let Some(nearest_expr) = self.nearest_expr {
// If we already have a nearest expression, we want to find the one that is closest to our target location
if node.span.end > nearest_expr.span.end {
self.nearest_expr = Some(node);
}
} else {
self.nearest_expr = Some(node);
}
}
} else {
self.nearest_expr = Some(node);
}
}

Expand All @@ -676,7 +692,7 @@ impl<'a> Visit<'a> for ScopeVisitor<'a> {
}

fn visit_type_annotation(&mut self, node: &'a TypeAnnotation) {
if node.span.end == self.location.end {
if node.span.end == self.target_span.end {
self.nearest_type_annotation = Some(node);
}

Expand Down Expand Up @@ -932,6 +948,36 @@ let x: cloud.
assert!(!variable_type_annotation_namespace.is_empty())
);

test_completion_list!(
parentheses_expression,
r#"
(Json {}).tryGet("t")?.
//^
"#,
assert!(!parentheses_expression.is_empty())
);

test_completion_list!(
static_json_after_expression,
r#"
bring cloud;
let b = new cloud.Bucket();
Json.
//^
"#,
assert!(!static_json_after_expression.is_empty())
);

test_completion_list!(
static_json_after_expression_statement,
r#"
bring cloud;
let b = new cloud.Bucket();Json.
//^
"#,
assert!(!static_json_after_expression_statement.is_empty())
);

test_completion_list!(
optional_chaining,
r#"
Expand Down
114 changes: 114 additions & 0 deletions libs/wingc/src/lsp/snapshots/completions/parentheses_expression.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
---
source: libs/wingc/src/lsp/completions.rs
---
- label: asBool
kind: 2
documentation:
kind: markdown
value: "```wing\n asBool: (): bool\n```\n---\nConvert Json element to number if possible.\n\n### Returns\na string."
sortText: ff|asBool
insertText: asBool($0)
insertTextFormat: 2
command:
title: triggerParameterHints
command: editor.action.triggerParameterHints
- label: asNum
kind: 2
documentation:
kind: markdown
value: "```wing\n asNum: (): num\n```\n---\nConvert Json element to number if possible.\n\n### Returns\na string."
sortText: ff|asNum
insertText: asNum($0)
insertTextFormat: 2
command:
title: triggerParameterHints
command: editor.action.triggerParameterHints
- label: asStr
kind: 2
documentation:
kind: markdown
value: "```wing\n asStr: (): str\n```\n---\nConvert Json element to string if possible.\n\n### Returns\na string."
sortText: ff|asStr
insertText: asStr($0)
insertTextFormat: 2
command:
title: triggerParameterHints
command: editor.action.triggerParameterHints
- label: get
kind: 2
documentation:
kind: markdown
value: "```wing\n get: (key: str): Json\n```\n---\nReturns a specified element from the Json.\n\n### Parameters\n - *key* - The key of the element to return.\n\n### Returns\nThe element associated with the specified key, or undefined if the key can't be found"
sortText: ff|get
insertText: get($0)
insertTextFormat: 2
command:
title: triggerParameterHints
command: editor.action.triggerParameterHints
- label: getAt
kind: 2
documentation:
kind: markdown
value: "```wing\n getAt: (index: num): Json\n```\n---\nReturns a specified element at a given index from Json Array.\n\n### Parameters\n - *index* - The index of the element in the Json Array to return.\n\n### Returns\nThe element at given index in Json Array, or undefined if index is not valid"
sortText: ff|getAt
insertText: getAt($0)
insertTextFormat: 2
command:
title: triggerParameterHints
command: editor.action.triggerParameterHints
- label: tryAsBool
kind: 2
documentation:
kind: markdown
value: "```wing\n tryAsBool: (): bool?\n```\n---\nConvert Json element to boolean if possible.\n\n### Returns\na string."
sortText: ff|tryAsBool
insertText: tryAsBool($0)
insertTextFormat: 2
command:
title: triggerParameterHints
command: editor.action.triggerParameterHints
- label: tryAsNum
kind: 2
documentation:
kind: markdown
value: "```wing\n tryAsNum: (): num?\n```\n---\nConvert Json element to string if possible.\n\n### Returns\na string."
sortText: ff|tryAsNum
insertText: tryAsNum($0)
insertTextFormat: 2
command:
title: triggerParameterHints
command: editor.action.triggerParameterHints
- label: tryAsStr
kind: 2
documentation:
kind: markdown
value: "```wing\n tryAsStr: (): str?\n```\n---\nConvert Json element to string if possible.\n\n### Returns\na string."
sortText: ff|tryAsStr
insertText: tryAsStr($0)
insertTextFormat: 2
command:
title: triggerParameterHints
command: editor.action.triggerParameterHints
- label: tryGet
kind: 2
documentation:
kind: markdown
value: "```wing\n tryGet: (key: str): Json?\n```\n---\nOptionally returns an specified element from the Json.\n\n### Parameters\n - *key* - The key of the element to return.\n\n### Returns\nThe element associated with the specified key, or undefined if the key can't be found"
sortText: ff|tryGet
insertText: tryGet($0)
insertTextFormat: 2
command:
title: triggerParameterHints
command: editor.action.triggerParameterHints
- label: tryGetAt
kind: 2
documentation:
kind: markdown
value: "```wing\n tryGetAt: (index: num): Json?\n```\n---\nOptionally returns a specified element at a given index from Json Array.\n\n### Parameters\n - *index* - The index of the element in the Json Array to return.\n\n### Returns\nThe element at given index in Json Array, or undefined if index is not valid"
sortText: ff|tryGetAt
insertText: tryGetAt($0)
insertTextFormat: 2
command:
title: triggerParameterHints
command: editor.action.triggerParameterHints

Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
---
source: libs/wingc/src/lsp/completions.rs
---
- label: deepCopy
kind: 2
documentation:
kind: markdown
value: "```wing\nstatic deepCopy: (json: MutJson): Json\n```\n---\nCreates an immutable deep copy of the Json.\n\n### Parameters\n - *json* - to copy.\n\n### Returns\nthe immutable copy of the Json"
sortText: ff|deepCopy
insertText: deepCopy($0)
insertTextFormat: 2
command:
title: triggerParameterHints
command: editor.action.triggerParameterHints
- label: deepCopyMut
kind: 2
documentation:
kind: markdown
value: "```wing\nstatic deepCopyMut: (json: Json): MutJson\n```\n---\nCreates a mutable deep copy of the Json.\n\n### Parameters\n - *json* - to copy.\n\n### Returns\nthe mutable copy of the Json"
sortText: ff|deepCopyMut
insertText: deepCopyMut($0)
insertTextFormat: 2
command:
title: triggerParameterHints
command: editor.action.triggerParameterHints
- label: delete
kind: 2
documentation:
kind: markdown
value: "```wing\nstatic delete: (json: MutJson, key: str): void\n```\n---\nDeletes a key in a given Json.\n\n### Parameters\n - *json* - to delete key from.\n - *key* - the key to delete."
sortText: ff|delete
insertText: delete($0)
insertTextFormat: 2
command:
title: triggerParameterHints
command: editor.action.triggerParameterHints
- label: has
kind: 2
documentation:
kind: markdown
value: "```wing\nstatic has: (json: Json, key: str): bool\n```\n---\nChecks if a Json object has a given key.\n\n### Parameters\n - *json* - The json object to inspect.\n - *key* - The key to check.\n\n### Returns\nBoolean value corresponding to whether the key exists"
sortText: ff|has
insertText: has($0)
insertTextFormat: 2
command:
title: triggerParameterHints
command: editor.action.triggerParameterHints
- label: keys
kind: 2
documentation:
kind: markdown
value: "```wing\nstatic keys: (json: any): Array<str>\n```\n---\nReturns the keys from the Json object.\n\n### Parameters\n - *json* - to get keys from.\n\n### Returns\nthe keys from the Json object as string array"
sortText: ff|keys
insertText: keys($0)
insertTextFormat: 2
command:
title: triggerParameterHints
command: editor.action.triggerParameterHints
- label: parse
kind: 2
documentation:
kind: markdown
value: "```wing\nstatic parse: (str: str): Json\n```\n---\nParse a string into a Json.\n\n### Parameters\n - *str* - to parse as Json.\n\n### Returns\nJson representation of the string"
sortText: ff|parse
insertText: parse($0)
insertTextFormat: 2
command:
title: triggerParameterHints
command: editor.action.triggerParameterHints
- label: stringify
kind: 2
documentation:
kind: markdown
value: "```wing\nstatic stringify: (json: any, indent: num?): str\n```\n---\nFormats Json as string.\n\n### Parameters\n - *json* - to format as string.\n - *indent*\n\n### Returns\nstring representation of the Json\n\n### Remarks\n(JSON.stringify($args$))"
sortText: ff|stringify
insertText: stringify($0)
insertTextFormat: 2
command:
title: triggerParameterHints
command: editor.action.triggerParameterHints
- label: tryParse
kind: 2
documentation:
kind: markdown
value: "```wing\nstatic tryParse: (str: str): Json?\n```\n---\nTry to parse a string into a Json.\n\n### Parameters\n - *str* - to parse as Json.\n\n### Returns\nJson representation of the string or undefined if string is not parsable"
sortText: ff|tryParse
insertText: tryParse($0)
insertTextFormat: 2
command:
title: triggerParameterHints
command: editor.action.triggerParameterHints
- label: values
kind: 2
documentation:
kind: markdown
value: "```wing\nstatic values: (json: Json): Array<Json>\n```\n---\nReturns the values from the Json.\n\n### Parameters\n - *json* - to get values from.\n\n### Returns\nthe values from the Json as array of Json"
sortText: ff|values
insertText: values($0)
insertTextFormat: 2
command:
title: triggerParameterHints
command: editor.action.triggerParameterHints

Loading

0 comments on commit 396c012

Please sign in to comment.