diff --git a/src/Bicep.Core.Samples/Files/Completions/childCompletions.json b/src/Bicep.Core.Samples/Files/Completions/childCompletions.json new file mode 100644 index 00000000000..2f491fe1c5f --- /dev/null +++ b/src/Bicep.Core.Samples/Files/Completions/childCompletions.json @@ -0,0 +1,44 @@ +[ + { + "label": "empty.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_empty.bicep", + "filterText": "'ChildModules/empty.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'ChildModules/empty.bicep'" + } + }, + { + "label": "main.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_main.bicep", + "filterText": "'ChildModules/main.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'ChildModules/main.bicep'" + } + }, + { + "label": "modulea.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_modulea.bicep", + "filterText": "'ChildModules/modulea.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'ChildModules/modulea.bicep'" + } + } +] \ No newline at end of file diff --git a/src/Bicep.Core.Samples/Files/Completions/childDotCompletions.json b/src/Bicep.Core.Samples/Files/Completions/childDotCompletions.json new file mode 100644 index 00000000000..0d6bdf03872 --- /dev/null +++ b/src/Bicep.Core.Samples/Files/Completions/childDotCompletions.json @@ -0,0 +1,44 @@ +[ + { + "label": "empty.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_empty.bicep", + "filterText": "'./ChildModules/empty.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'./ChildModules/empty.bicep'" + } + }, + { + "label": "main.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_main.bicep", + "filterText": "'./ChildModules/main.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'./ChildModules/main.bicep'" + } + }, + { + "label": "modulea.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_modulea.bicep", + "filterText": "'./ChildModules/modulea.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'./ChildModules/modulea.bicep'" + } + } +] \ No newline at end of file diff --git a/src/Bicep.Core.Samples/Files/Completions/childECompletions.json b/src/Bicep.Core.Samples/Files/Completions/childECompletions.json new file mode 100644 index 00000000000..2f491fe1c5f --- /dev/null +++ b/src/Bicep.Core.Samples/Files/Completions/childECompletions.json @@ -0,0 +1,44 @@ +[ + { + "label": "empty.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_empty.bicep", + "filterText": "'ChildModules/empty.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'ChildModules/empty.bicep'" + } + }, + { + "label": "main.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_main.bicep", + "filterText": "'ChildModules/main.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'ChildModules/main.bicep'" + } + }, + { + "label": "modulea.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_modulea.bicep", + "filterText": "'ChildModules/modulea.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'ChildModules/modulea.bicep'" + } + } +] \ No newline at end of file diff --git a/src/Bicep.Core.Samples/Files/Completions/childFileCompletions.json b/src/Bicep.Core.Samples/Files/Completions/childFileCompletions.json new file mode 100644 index 00000000000..4c514557cb3 --- /dev/null +++ b/src/Bicep.Core.Samples/Files/Completions/childFileCompletions.json @@ -0,0 +1,44 @@ +[ + { + "label": "empty.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_empty.bicep", + "filterText": "'ChildModules/empty.bicep'", + "insertTextFormat": "snippet", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'ChildModules/empty.bicep'" + } + }, + { + "label": "main.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_main.bicep", + "filterText": "'ChildModules/main.bicep'", + "insertTextFormat": "snippet", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'ChildModules/main.bicep'" + } + }, + { + "label": "modulea.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_modulea.bicep", + "filterText": "'ChildModules/modulea.bicep'", + "insertTextFormat": "snippet", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'ChildModules/modulea.bicep'" + } + } +] \ No newline at end of file diff --git a/src/Bicep.Core.Samples/Files/Completions/childMCompletions.json b/src/Bicep.Core.Samples/Files/Completions/childMCompletions.json new file mode 100644 index 00000000000..0d6bdf03872 --- /dev/null +++ b/src/Bicep.Core.Samples/Files/Completions/childMCompletions.json @@ -0,0 +1,44 @@ +[ + { + "label": "empty.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_empty.bicep", + "filterText": "'./ChildModules/empty.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'./ChildModules/empty.bicep'" + } + }, + { + "label": "main.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_main.bicep", + "filterText": "'./ChildModules/main.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'./ChildModules/main.bicep'" + } + }, + { + "label": "modulea.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_modulea.bicep", + "filterText": "'./ChildModules/modulea.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'./ChildModules/modulea.bicep'" + } + } +] \ No newline at end of file diff --git a/src/Bicep.Core.Samples/Files/Completions/cwdMCompletions.json b/src/Bicep.Core.Samples/Files/Completions/cwdMCompletions.json new file mode 100644 index 00000000000..c7f3b993b1a --- /dev/null +++ b/src/Bicep.Core.Samples/Files/Completions/cwdMCompletions.json @@ -0,0 +1,162 @@ +[ + { + "label": "ChildModules/", + "kind": "folder", + "deprecated": false, + "preselect": false, + "sortText": "2_ChildModules/", + "filterText": "'ChildModules/'", + "insertTextFormat": "snippet", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'ChildModules/$0'" + }, + "command": { + "command": "editor.action.triggerSuggest" + } + }, + { + "label": "Completions/", + "kind": "folder", + "deprecated": false, + "preselect": false, + "sortText": "2_Completions/", + "filterText": "'Completions/'", + "insertTextFormat": "snippet", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'Completions/$0'" + }, + "command": { + "command": "editor.action.triggerSuggest" + } + }, + { + "label": "cycle.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_cycle.bicep", + "filterText": "'cycle.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'cycle.bicep'" + } + }, + { + "label": "empty.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_empty.bicep", + "filterText": "'empty.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'empty.bicep'" + } + }, + { + "label": "main.diagnostics.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_main.diagnostics.bicep", + "filterText": "'main.diagnostics.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'main.diagnostics.bicep'" + } + }, + { + "label": "main.formatted.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_main.formatted.bicep", + "filterText": "'main.formatted.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'main.formatted.bicep'" + } + }, + { + "label": "main.symbols.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_main.symbols.bicep", + "filterText": "'main.symbols.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'main.symbols.bicep'" + } + }, + { + "label": "main.syntax.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_main.syntax.bicep", + "filterText": "'main.syntax.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'main.syntax.bicep'" + } + }, + { + "label": "main.tokens.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_main.tokens.bicep", + "filterText": "'main.tokens.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'main.tokens.bicep'" + } + }, + { + "label": "modulea.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_modulea.bicep", + "filterText": "'modulea.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'modulea.bicep'" + } + }, + { + "label": "subscription_empty.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_subscription_empty.bicep", + "filterText": "'subscription_empty.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'subscription_empty.bicep'" + } + } +] \ No newline at end of file diff --git a/src/Bicep.Core.Samples/Files/InvalidModules_LF/ChildModules/empty.bicep b/src/Bicep.Core.Samples/Files/InvalidModules_LF/ChildModules/empty.bicep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/Bicep.Core.Samples/Files/InvalidModules_LF/ChildModules/main.bicep b/src/Bicep.Core.Samples/Files/InvalidModules_LF/ChildModules/main.bicep new file mode 100644 index 00000000000..942e7146d35 --- /dev/null +++ b/src/Bicep.Core.Samples/Files/InvalidModules_LF/ChildModules/main.bicep @@ -0,0 +1 @@ +param stringParamA string = 'test' \ No newline at end of file diff --git a/src/Bicep.Core.Samples/Files/InvalidModules_LF/ChildModules/modulea.bicep b/src/Bicep.Core.Samples/Files/InvalidModules_LF/ChildModules/modulea.bicep new file mode 100644 index 00000000000..f17db98a4a7 --- /dev/null +++ b/src/Bicep.Core.Samples/Files/InvalidModules_LF/ChildModules/modulea.bicep @@ -0,0 +1,3 @@ +module './main.bicep' = { + +} \ No newline at end of file diff --git a/src/Bicep.Core.Samples/Files/InvalidModules_LF/Completions/cwdCompletions.json b/src/Bicep.Core.Samples/Files/InvalidModules_LF/Completions/cwdCompletions.json new file mode 100644 index 00000000000..c7f3b993b1a --- /dev/null +++ b/src/Bicep.Core.Samples/Files/InvalidModules_LF/Completions/cwdCompletions.json @@ -0,0 +1,162 @@ +[ + { + "label": "ChildModules/", + "kind": "folder", + "deprecated": false, + "preselect": false, + "sortText": "2_ChildModules/", + "filterText": "'ChildModules/'", + "insertTextFormat": "snippet", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'ChildModules/$0'" + }, + "command": { + "command": "editor.action.triggerSuggest" + } + }, + { + "label": "Completions/", + "kind": "folder", + "deprecated": false, + "preselect": false, + "sortText": "2_Completions/", + "filterText": "'Completions/'", + "insertTextFormat": "snippet", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'Completions/$0'" + }, + "command": { + "command": "editor.action.triggerSuggest" + } + }, + { + "label": "cycle.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_cycle.bicep", + "filterText": "'cycle.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'cycle.bicep'" + } + }, + { + "label": "empty.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_empty.bicep", + "filterText": "'empty.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'empty.bicep'" + } + }, + { + "label": "main.diagnostics.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_main.diagnostics.bicep", + "filterText": "'main.diagnostics.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'main.diagnostics.bicep'" + } + }, + { + "label": "main.formatted.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_main.formatted.bicep", + "filterText": "'main.formatted.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'main.formatted.bicep'" + } + }, + { + "label": "main.symbols.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_main.symbols.bicep", + "filterText": "'main.symbols.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'main.symbols.bicep'" + } + }, + { + "label": "main.syntax.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_main.syntax.bicep", + "filterText": "'main.syntax.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'main.syntax.bicep'" + } + }, + { + "label": "main.tokens.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_main.tokens.bicep", + "filterText": "'main.tokens.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'main.tokens.bicep'" + } + }, + { + "label": "modulea.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_modulea.bicep", + "filterText": "'modulea.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'modulea.bicep'" + } + }, + { + "label": "subscription_empty.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_subscription_empty.bicep", + "filterText": "'subscription_empty.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'subscription_empty.bicep'" + } + } +] \ No newline at end of file diff --git a/src/Bicep.Core.Samples/Files/InvalidModules_LF/Completions/cwdFileCompletions.json b/src/Bicep.Core.Samples/Files/InvalidModules_LF/Completions/cwdFileCompletions.json new file mode 100644 index 00000000000..c7f3b993b1a --- /dev/null +++ b/src/Bicep.Core.Samples/Files/InvalidModules_LF/Completions/cwdFileCompletions.json @@ -0,0 +1,162 @@ +[ + { + "label": "ChildModules/", + "kind": "folder", + "deprecated": false, + "preselect": false, + "sortText": "2_ChildModules/", + "filterText": "'ChildModules/'", + "insertTextFormat": "snippet", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'ChildModules/$0'" + }, + "command": { + "command": "editor.action.triggerSuggest" + } + }, + { + "label": "Completions/", + "kind": "folder", + "deprecated": false, + "preselect": false, + "sortText": "2_Completions/", + "filterText": "'Completions/'", + "insertTextFormat": "snippet", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'Completions/$0'" + }, + "command": { + "command": "editor.action.triggerSuggest" + } + }, + { + "label": "cycle.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_cycle.bicep", + "filterText": "'cycle.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'cycle.bicep'" + } + }, + { + "label": "empty.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_empty.bicep", + "filterText": "'empty.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'empty.bicep'" + } + }, + { + "label": "main.diagnostics.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_main.diagnostics.bicep", + "filterText": "'main.diagnostics.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'main.diagnostics.bicep'" + } + }, + { + "label": "main.formatted.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_main.formatted.bicep", + "filterText": "'main.formatted.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'main.formatted.bicep'" + } + }, + { + "label": "main.symbols.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_main.symbols.bicep", + "filterText": "'main.symbols.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'main.symbols.bicep'" + } + }, + { + "label": "main.syntax.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_main.syntax.bicep", + "filterText": "'main.syntax.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'main.syntax.bicep'" + } + }, + { + "label": "main.tokens.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_main.tokens.bicep", + "filterText": "'main.tokens.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'main.tokens.bicep'" + } + }, + { + "label": "modulea.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_modulea.bicep", + "filterText": "'modulea.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'modulea.bicep'" + } + }, + { + "label": "subscription_empty.bicep", + "kind": "file", + "deprecated": false, + "preselect": false, + "sortText": "1_subscription_empty.bicep", + "filterText": "'subscription_empty.bicep'", + "insertTextFormat": "plainText", + "insertTextMode": "asIs", + "textEdit": { + "range": {}, + "newText": "'subscription_empty.bicep'" + } + } +] \ No newline at end of file diff --git a/src/Bicep.Core.Samples/Files/InvalidModules_LF/main.bicep b/src/Bicep.Core.Samples/Files/InvalidModules_LF/main.bicep index 4d2f195171b..468f34652c5 100644 --- a/src/Bicep.Core.Samples/Files/InvalidModules_LF/main.bicep +++ b/src/Bicep.Core.Samples/Files/InvalidModules_LF/main.bicep @@ -231,3 +231,38 @@ module moduleWithDuplicateName1 './empty.bicep' = { module moduleWithDuplicateName2 './empty.bicep' = { name: 'moduleWithDuplicateName' } + +// #completionTest(19, 20, 21) -> cwdCompletions +module completionB '' + +// #completionTest(19, 20, 21) -> cwdCompletions +module completionC '' = + +// #completionTest(19, 20, 21) -> cwdCompletions +module completionD '' = {} + +// #completionTest(19, 20, 21) -> cwdCompletions +module completionE '' = { + name: 'hello' +} + +// #completionTest(26, 27, 28, 29) -> cwdFileCompletions +module cwdFileCompletionA '.' + +// #completionTest(26, 27) -> cwdMCompletions +module cwdFileCompletionB m + +// #completionTest(26, 27, 28, 29) -> cwdMCompletions +module cwdFileCompletionC 'm' + +// #completionTest(24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39) -> childCompletions +module childCompletionA 'ChildModules/' + +// #completionTest(24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39) -> childDotCompletions +module childCompletionB './ChildModules/' + +// #completionTest(24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40) -> childMCompletions +module childCompletionC './ChildModules/m' + +// #completionTest(24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40) -> childECompletions +module childCompletionD 'ChildModules/e' \ No newline at end of file diff --git a/src/Bicep.Core.Samples/Files/InvalidModules_LF/main.diagnostics.bicep b/src/Bicep.Core.Samples/Files/InvalidModules_LF/main.diagnostics.bicep index dc1d23ed059..14db2028e6a 100644 --- a/src/Bicep.Core.Samples/Files/InvalidModules_LF/main.diagnostics.bicep +++ b/src/Bicep.Core.Samples/Files/InvalidModules_LF/main.diagnostics.bicep @@ -1,17 +1,17 @@ module nonExistentFileRef './nonExistent.bicep' = { -//@[26:47) [BCP091 (Error)] An error occurred reading file. Could not find file '${TEST_OUTPUT_DIR}nonExistent.bicep'. |'./nonExistent.bicep'| +//@[26:47) [BCP091 (Error)] An error occurred reading file. Could not find file '${TEST_OUTPUT_DIR}/nonExistent.bicep'. |'./nonExistent.bicep'| } // we should only look this file up once, but should still return the same failure module nonExistentFileRefDuplicate './nonExistent.bicep' = { -//@[35:56) [BCP091 (Error)] An error occurred reading file. Could not find file '${TEST_OUTPUT_DIR}nonExistent.bicep'. |'./nonExistent.bicep'| +//@[35:56) [BCP091 (Error)] An error occurred reading file. Could not find file '${TEST_OUTPUT_DIR}/nonExistent.bicep'. |'./nonExistent.bicep'| } // we should only look this file up once, but should still return the same failure module nonExistentFileRefEquivalentPath 'abc/def/../../nonExistent.bicep' = { -//@[40:73) [BCP091 (Error)] An error occurred reading file. Could not find file '${TEST_OUTPUT_DIR}nonExistent.bicep'. |'abc/def/../../nonExistent.bicep'| +//@[40:73) [BCP091 (Error)] An error occurred reading file. Could not find file '${TEST_OUTPUT_DIR}/nonExistent.bicep'. |'abc/def/../../nonExistent.bicep'| } @@ -199,7 +199,7 @@ var unspecifiedOutput = modAUnspecifiedInputs.outputs.test //@[54:58) [BCP053 (Error)] The type "outputs" does not contain property "test". Available properties include "arrayOutput", "objOutput", "stringOutputA", "stringOutputB". |test| module modCycle './cycle.bicep' = { -//@[16:31) [BCP095 (Error)] The module is involved in a cycle ("${TEST_OUTPUT_DIR}cycle.bicep" -> "${TEST_OUTPUT_DIR}main.bicep"). |'./cycle.bicep'| +//@[16:31) [BCP095 (Error)] The module is involved in a cycle ("${TEST_OUTPUT_DIR}/cycle.bicep" -> "${TEST_OUTPUT_DIR}/main.bicep"). |'./cycle.bicep'| } @@ -307,3 +307,58 @@ module moduleWithDuplicateName2 './empty.bicep' = { //@[8:33) [BCP122 (Error)] Modules: "moduleWithDuplicateName1", "moduleWithDuplicateName2" are defined with this same name and this same scope in a file. Rename them or split into different modules. |'moduleWithDuplicateName'| } +// #completionTest(19, 20, 21) -> cwdCompletions +module completionB '' +//@[19:21) [BCP050 (Error)] The specified module path is empty. |''| +//@[21:21) [BCP018 (Error)] Expected the "=" character at this location. || + +// #completionTest(19, 20, 21) -> cwdCompletions +module completionC '' = +//@[19:21) [BCP050 (Error)] The specified module path is empty. |''| +//@[23:23) [BCP118 (Error)] Expected the "{" character or the "if" keyword at this location. || + +// #completionTest(19, 20, 21) -> cwdCompletions +module completionD '' = {} +//@[19:21) [BCP050 (Error)] The specified module path is empty. |''| + +// #completionTest(19, 20, 21) -> cwdCompletions +module completionE '' = { +//@[19:21) [BCP050 (Error)] The specified module path is empty. |''| + name: 'hello' +} + +// #completionTest(26, 27, 28, 29) -> cwdFileCompletions +module cwdFileCompletionA '.' +//@[26:29) [BCP086 (Error)] The specified module path ends with an invalid character. The following are not permitted: " ", ".". |'.'| +//@[29:29) [BCP018 (Error)] Expected the "=" character at this location. || + +// #completionTest(26, 27) -> cwdMCompletions +module cwdFileCompletionB m +//@[26:27) [BCP097 (Error)] Expected a module path string. This should be a relative path to another bicep file, e.g. 'myModule.bicep' or '../parent/myModule.bicep' |m| +//@[26:27) [BCP090 (Error)] This module declaration is missing a file path reference. |m| +//@[27:27) [BCP018 (Error)] Expected the "=" character at this location. || + +// #completionTest(26, 27, 28, 29) -> cwdMCompletions +module cwdFileCompletionC 'm' +//@[26:29) [BCP091 (Error)] An error occurred reading file. Could not find file '${TEST_OUTPUT_DIR}/m'. |'m'| +//@[29:29) [BCP018 (Error)] Expected the "=" character at this location. || + +// #completionTest(24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39) -> childCompletions +module childCompletionA 'ChildModules/' +//@[24:39) [BCP091 (Error)] An error occurred reading file. Access to the path '${TEST_OUTPUT_DIR}/ChildModules/' is denied. |'ChildModules/'| +//@[39:39) [BCP018 (Error)] Expected the "=" character at this location. || + +// #completionTest(24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39) -> childDotCompletions +module childCompletionB './ChildModules/' +//@[24:41) [BCP091 (Error)] An error occurred reading file. Access to the path '${TEST_OUTPUT_DIR}/ChildModules/' is denied. |'./ChildModules/'| +//@[41:41) [BCP018 (Error)] Expected the "=" character at this location. || + +// #completionTest(24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40) -> childMCompletions +module childCompletionC './ChildModules/m' +//@[24:42) [BCP091 (Error)] An error occurred reading file. Could not find file '${TEST_OUTPUT_DIR}/ChildModules/m'. |'./ChildModules/m'| +//@[42:42) [BCP018 (Error)] Expected the "=" character at this location. || + +// #completionTest(24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40) -> childECompletions +module childCompletionD 'ChildModules/e' +//@[24:40) [BCP091 (Error)] An error occurred reading file. Could not find file '${TEST_OUTPUT_DIR}/ChildModules/e'. |'ChildModules/e'| +//@[40:40) [BCP018 (Error)] Expected the "=" character at this location. || diff --git a/src/Bicep.Core.Samples/Files/InvalidModules_LF/main.formatted.bicep b/src/Bicep.Core.Samples/Files/InvalidModules_LF/main.formatted.bicep index 5cecbc1209b..1cae7260c50 100644 --- a/src/Bicep.Core.Samples/Files/InvalidModules_LF/main.formatted.bicep +++ b/src/Bicep.Core.Samples/Files/InvalidModules_LF/main.formatted.bicep @@ -199,3 +199,38 @@ module moduleWithDuplicateName1 './empty.bicep' = { module moduleWithDuplicateName2 './empty.bicep' = { name: 'moduleWithDuplicateName' } + +// #completionTest(19, 20, 21) -> cwdCompletions +module completionB '' + +// #completionTest(19, 20, 21) -> cwdCompletions +module completionC '' = + +// #completionTest(19, 20, 21) -> cwdCompletions +module completionD '' = {} + +// #completionTest(19, 20, 21) -> cwdCompletions +module completionE '' = { + name: 'hello' +} + +// #completionTest(26, 27, 28, 29) -> cwdFileCompletions +module cwdFileCompletionA '.' + +// #completionTest(26, 27) -> cwdMCompletions +module cwdFileCompletionB m + +// #completionTest(26, 27, 28, 29) -> cwdMCompletions +module cwdFileCompletionC 'm' + +// #completionTest(24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39) -> childCompletions +module childCompletionA 'ChildModules/' + +// #completionTest(24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39) -> childDotCompletions +module childCompletionB './ChildModules/' + +// #completionTest(24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40) -> childMCompletions +module childCompletionC './ChildModules/m' + +// #completionTest(24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40) -> childECompletions +module childCompletionD 'ChildModules/e' diff --git a/src/Bicep.Core.Samples/Files/InvalidModules_LF/main.symbols.bicep b/src/Bicep.Core.Samples/Files/InvalidModules_LF/main.symbols.bicep index 259ea299b3c..c91653f9643 100644 --- a/src/Bicep.Core.Samples/Files/InvalidModules_LF/main.symbols.bicep +++ b/src/Bicep.Core.Samples/Files/InvalidModules_LF/main.symbols.bicep @@ -288,3 +288,48 @@ module moduleWithDuplicateName2 './empty.bicep' = { name: 'moduleWithDuplicateName' } +// #completionTest(19, 20, 21) -> cwdCompletions +module completionB '' +//@[7:18) Module completionB. Type: error. Declaration start char: 0, length: 21 + +// #completionTest(19, 20, 21) -> cwdCompletions +module completionC '' = +//@[7:18) Module completionC. Type: error. Declaration start char: 0, length: 23 + +// #completionTest(19, 20, 21) -> cwdCompletions +module completionD '' = {} +//@[7:18) Module completionD. Type: error. Declaration start char: 0, length: 26 + +// #completionTest(19, 20, 21) -> cwdCompletions +module completionE '' = { +//@[7:18) Module completionE. Type: error. Declaration start char: 0, length: 43 + name: 'hello' +} + +// #completionTest(26, 27, 28, 29) -> cwdFileCompletions +module cwdFileCompletionA '.' +//@[7:25) Module cwdFileCompletionA. Type: error. Declaration start char: 0, length: 29 + +// #completionTest(26, 27) -> cwdMCompletions +module cwdFileCompletionB m +//@[7:25) Module cwdFileCompletionB. Type: error. Declaration start char: 0, length: 27 + +// #completionTest(26, 27, 28, 29) -> cwdMCompletions +module cwdFileCompletionC 'm' +//@[7:25) Module cwdFileCompletionC. Type: error. Declaration start char: 0, length: 29 + +// #completionTest(24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39) -> childCompletions +module childCompletionA 'ChildModules/' +//@[7:23) Module childCompletionA. Type: error. Declaration start char: 0, length: 39 + +// #completionTest(24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39) -> childDotCompletions +module childCompletionB './ChildModules/' +//@[7:23) Module childCompletionB. Type: error. Declaration start char: 0, length: 41 + +// #completionTest(24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40) -> childMCompletions +module childCompletionC './ChildModules/m' +//@[7:23) Module childCompletionC. Type: error. Declaration start char: 0, length: 42 + +// #completionTest(24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40) -> childECompletions +module childCompletionD 'ChildModules/e' +//@[7:23) Module childCompletionD. Type: error. Declaration start char: 0, length: 40 diff --git a/src/Bicep.Core.Samples/Files/InvalidModules_LF/main.syntax.bicep b/src/Bicep.Core.Samples/Files/InvalidModules_LF/main.syntax.bicep index 53496f8be1a..a1a1733d0a1 100644 --- a/src/Bicep.Core.Samples/Files/InvalidModules_LF/main.syntax.bicep +++ b/src/Bicep.Core.Samples/Files/InvalidModules_LF/main.syntax.bicep @@ -1463,6 +1463,170 @@ module moduleWithDuplicateName2 './empty.bicep' = { //@[33:34) NewLine |\n| } //@[0:1) RightBrace |}| -//@[1:2) NewLine |\n| +//@[1:3) NewLine |\n\n| + +// #completionTest(19, 20, 21) -> cwdCompletions +//@[48:49) NewLine |\n| +module completionB '' +//@[0:21) ModuleDeclarationSyntax +//@[0:6) Identifier |module| +//@[7:18) IdentifierSyntax +//@[7:18) Identifier |completionB| +//@[19:21) StringSyntax +//@[19:21) StringComplete |''| +//@[21:21) SkippedTriviaSyntax +//@[21:21) SkippedTriviaSyntax +//@[21:21) SkippedTriviaSyntax +//@[21:23) NewLine |\n\n| + +// #completionTest(19, 20, 21) -> cwdCompletions +//@[48:49) NewLine |\n| +module completionC '' = +//@[0:23) ModuleDeclarationSyntax +//@[0:6) Identifier |module| +//@[7:18) IdentifierSyntax +//@[7:18) Identifier |completionC| +//@[19:21) StringSyntax +//@[19:21) StringComplete |''| +//@[22:23) Assignment |=| +//@[23:23) SkippedTriviaSyntax +//@[23:23) SkippedTriviaSyntax +//@[23:25) NewLine |\n\n| -//@[0:0) EndOfFile || +// #completionTest(19, 20, 21) -> cwdCompletions +//@[48:49) NewLine |\n| +module completionD '' = {} +//@[0:26) ModuleDeclarationSyntax +//@[0:6) Identifier |module| +//@[7:18) IdentifierSyntax +//@[7:18) Identifier |completionD| +//@[19:21) StringSyntax +//@[19:21) StringComplete |''| +//@[22:23) Assignment |=| +//@[24:26) ObjectSyntax +//@[24:25) LeftBrace |{| +//@[25:26) RightBrace |}| +//@[26:28) NewLine |\n\n| + +// #completionTest(19, 20, 21) -> cwdCompletions +//@[48:49) NewLine |\n| +module completionE '' = { +//@[0:43) ModuleDeclarationSyntax +//@[0:6) Identifier |module| +//@[7:18) IdentifierSyntax +//@[7:18) Identifier |completionE| +//@[19:21) StringSyntax +//@[19:21) StringComplete |''| +//@[22:23) Assignment |=| +//@[24:43) ObjectSyntax +//@[24:25) LeftBrace |{| +//@[25:26) NewLine |\n| + name: 'hello' +//@[2:15) ObjectPropertySyntax +//@[2:6) IdentifierSyntax +//@[2:6) Identifier |name| +//@[6:7) Colon |:| +//@[8:15) StringSyntax +//@[8:15) StringComplete |'hello'| +//@[15:16) NewLine |\n| +} +//@[0:1) RightBrace |}| +//@[1:3) NewLine |\n\n| + +// #completionTest(26, 27, 28, 29) -> cwdFileCompletions +//@[56:57) NewLine |\n| +module cwdFileCompletionA '.' +//@[0:29) ModuleDeclarationSyntax +//@[0:6) Identifier |module| +//@[7:25) IdentifierSyntax +//@[7:25) Identifier |cwdFileCompletionA| +//@[26:29) StringSyntax +//@[26:29) StringComplete |'.'| +//@[29:29) SkippedTriviaSyntax +//@[29:29) SkippedTriviaSyntax +//@[29:29) SkippedTriviaSyntax +//@[29:31) NewLine |\n\n| + +// #completionTest(26, 27) -> cwdMCompletions +//@[45:46) NewLine |\n| +module cwdFileCompletionB m +//@[0:27) ModuleDeclarationSyntax +//@[0:6) Identifier |module| +//@[7:25) IdentifierSyntax +//@[7:25) Identifier |cwdFileCompletionB| +//@[26:27) SkippedTriviaSyntax +//@[26:27) Identifier |m| +//@[27:27) SkippedTriviaSyntax +//@[27:27) SkippedTriviaSyntax +//@[27:27) SkippedTriviaSyntax +//@[27:29) NewLine |\n\n| + +// #completionTest(26, 27, 28, 29) -> cwdMCompletions +//@[53:54) NewLine |\n| +module cwdFileCompletionC 'm' +//@[0:29) ModuleDeclarationSyntax +//@[0:6) Identifier |module| +//@[7:25) IdentifierSyntax +//@[7:25) Identifier |cwdFileCompletionC| +//@[26:29) StringSyntax +//@[26:29) StringComplete |'m'| +//@[29:29) SkippedTriviaSyntax +//@[29:29) SkippedTriviaSyntax +//@[29:29) SkippedTriviaSyntax +//@[29:31) NewLine |\n\n| + +// #completionTest(24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39) -> childCompletions +//@[102:103) NewLine |\n| +module childCompletionA 'ChildModules/' +//@[0:39) ModuleDeclarationSyntax +//@[0:6) Identifier |module| +//@[7:23) IdentifierSyntax +//@[7:23) Identifier |childCompletionA| +//@[24:39) StringSyntax +//@[24:39) StringComplete |'ChildModules/'| +//@[39:39) SkippedTriviaSyntax +//@[39:39) SkippedTriviaSyntax +//@[39:39) SkippedTriviaSyntax +//@[39:41) NewLine |\n\n| + +// #completionTest(24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39) -> childDotCompletions +//@[105:106) NewLine |\n| +module childCompletionB './ChildModules/' +//@[0:41) ModuleDeclarationSyntax +//@[0:6) Identifier |module| +//@[7:23) IdentifierSyntax +//@[7:23) Identifier |childCompletionB| +//@[24:41) StringSyntax +//@[24:41) StringComplete |'./ChildModules/'| +//@[41:41) SkippedTriviaSyntax +//@[41:41) SkippedTriviaSyntax +//@[41:41) SkippedTriviaSyntax +//@[41:43) NewLine |\n\n| + +// #completionTest(24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40) -> childMCompletions +//@[107:108) NewLine |\n| +module childCompletionC './ChildModules/m' +//@[0:42) ModuleDeclarationSyntax +//@[0:6) Identifier |module| +//@[7:23) IdentifierSyntax +//@[7:23) Identifier |childCompletionC| +//@[24:42) StringSyntax +//@[24:42) StringComplete |'./ChildModules/m'| +//@[42:42) SkippedTriviaSyntax +//@[42:42) SkippedTriviaSyntax +//@[42:42) SkippedTriviaSyntax +//@[42:44) NewLine |\n\n| + +// #completionTest(24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40) -> childECompletions +//@[107:108) NewLine |\n| +module childCompletionD 'ChildModules/e' +//@[0:40) ModuleDeclarationSyntax +//@[0:6) Identifier |module| +//@[7:23) IdentifierSyntax +//@[7:23) Identifier |childCompletionD| +//@[24:40) StringSyntax +//@[24:40) StringComplete |'ChildModules/e'| +//@[40:40) SkippedTriviaSyntax +//@[40:40) SkippedTriviaSyntax +//@[40:40) SkippedTriviaSyntax +//@[40:40) EndOfFile || diff --git a/src/Bicep.Core.Samples/Files/InvalidModules_LF/main.tokens.bicep b/src/Bicep.Core.Samples/Files/InvalidModules_LF/main.tokens.bicep index 00e956216e1..334577325e8 100644 --- a/src/Bicep.Core.Samples/Files/InvalidModules_LF/main.tokens.bicep +++ b/src/Bicep.Core.Samples/Files/InvalidModules_LF/main.tokens.bicep @@ -962,6 +962,106 @@ module moduleWithDuplicateName2 './empty.bicep' = { //@[33:34) NewLine |\n| } //@[0:1) RightBrace |}| -//@[1:2) NewLine |\n| +//@[1:3) NewLine |\n\n| + +// #completionTest(19, 20, 21) -> cwdCompletions +//@[48:49) NewLine |\n| +module completionB '' +//@[0:6) Identifier |module| +//@[7:18) Identifier |completionB| +//@[19:21) StringComplete |''| +//@[21:23) NewLine |\n\n| + +// #completionTest(19, 20, 21) -> cwdCompletions +//@[48:49) NewLine |\n| +module completionC '' = +//@[0:6) Identifier |module| +//@[7:18) Identifier |completionC| +//@[19:21) StringComplete |''| +//@[22:23) Assignment |=| +//@[23:25) NewLine |\n\n| -//@[0:0) EndOfFile || +// #completionTest(19, 20, 21) -> cwdCompletions +//@[48:49) NewLine |\n| +module completionD '' = {} +//@[0:6) Identifier |module| +//@[7:18) Identifier |completionD| +//@[19:21) StringComplete |''| +//@[22:23) Assignment |=| +//@[24:25) LeftBrace |{| +//@[25:26) RightBrace |}| +//@[26:28) NewLine |\n\n| + +// #completionTest(19, 20, 21) -> cwdCompletions +//@[48:49) NewLine |\n| +module completionE '' = { +//@[0:6) Identifier |module| +//@[7:18) Identifier |completionE| +//@[19:21) StringComplete |''| +//@[22:23) Assignment |=| +//@[24:25) LeftBrace |{| +//@[25:26) NewLine |\n| + name: 'hello' +//@[2:6) Identifier |name| +//@[6:7) Colon |:| +//@[8:15) StringComplete |'hello'| +//@[15:16) NewLine |\n| +} +//@[0:1) RightBrace |}| +//@[1:3) NewLine |\n\n| + +// #completionTest(26, 27, 28, 29) -> cwdFileCompletions +//@[56:57) NewLine |\n| +module cwdFileCompletionA '.' +//@[0:6) Identifier |module| +//@[7:25) Identifier |cwdFileCompletionA| +//@[26:29) StringComplete |'.'| +//@[29:31) NewLine |\n\n| + +// #completionTest(26, 27) -> cwdMCompletions +//@[45:46) NewLine |\n| +module cwdFileCompletionB m +//@[0:6) Identifier |module| +//@[7:25) Identifier |cwdFileCompletionB| +//@[26:27) Identifier |m| +//@[27:29) NewLine |\n\n| + +// #completionTest(26, 27, 28, 29) -> cwdMCompletions +//@[53:54) NewLine |\n| +module cwdFileCompletionC 'm' +//@[0:6) Identifier |module| +//@[7:25) Identifier |cwdFileCompletionC| +//@[26:29) StringComplete |'m'| +//@[29:31) NewLine |\n\n| + +// #completionTest(24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39) -> childCompletions +//@[102:103) NewLine |\n| +module childCompletionA 'ChildModules/' +//@[0:6) Identifier |module| +//@[7:23) Identifier |childCompletionA| +//@[24:39) StringComplete |'ChildModules/'| +//@[39:41) NewLine |\n\n| + +// #completionTest(24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39) -> childDotCompletions +//@[105:106) NewLine |\n| +module childCompletionB './ChildModules/' +//@[0:6) Identifier |module| +//@[7:23) Identifier |childCompletionB| +//@[24:41) StringComplete |'./ChildModules/'| +//@[41:43) NewLine |\n\n| + +// #completionTest(24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40) -> childMCompletions +//@[107:108) NewLine |\n| +module childCompletionC './ChildModules/m' +//@[0:6) Identifier |module| +//@[7:23) Identifier |childCompletionC| +//@[24:42) StringComplete |'./ChildModules/m'| +//@[42:44) NewLine |\n\n| + +// #completionTest(24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40) -> childECompletions +//@[107:108) NewLine |\n| +module childCompletionD 'ChildModules/e' +//@[0:6) Identifier |module| +//@[7:23) Identifier |childCompletionD| +//@[24:40) StringComplete |'ChildModules/e'| +//@[40:40) EndOfFile || diff --git a/src/Bicep.Core.UnitTests/FileSystem/FileResolverTests.cs b/src/Bicep.Core.UnitTests/FileSystem/FileResolverTests.cs index 62816115584..8d8b60760c9 100644 --- a/src/Bicep.Core.UnitTests/FileSystem/FileResolverTests.cs +++ b/src/Bicep.Core.UnitTests/FileSystem/FileResolverTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; +using System.Linq; using System.Collections.Generic; using System.IO; using Bicep.Core.FileSystem; @@ -61,5 +62,74 @@ public void TryRead_should_return_expected_results() fileContents.Should().BeNull(); failureMessage.Should().NotBeNull(); } + + [TestMethod] + [ExpectedException(typeof(IOException), AllowDerivedTypes=true)] + public void GetDirectories_should_return_expected_results() + { + var fileResolver = new FileResolver(); + var tempDir = Path.Combine(Path.GetTempPath(), $"BICEP_TESTDIR_{Guid.NewGuid()}"); + var tempFile = Path.Combine(tempDir, $"BICEP_TEST_{Guid.NewGuid()}"); + var tempChildDir = Path.Combine(tempDir, $"BICEP_TESTCHILDDIR_{Guid.NewGuid()}"); + + // make parent dir + Directory.CreateDirectory(tempDir); + fileResolver.GetDirectories(PathHelper.FilePathToFileUrl(tempDir)).Should().HaveCount(0); + // make child dir + Directory.CreateDirectory(tempChildDir); + fileResolver.GetDirectories(PathHelper.FilePathToFileUrl(tempDir)).Should().HaveCount(1); + // add a file to parent dir + File.WriteAllText(tempFile, "abcd\r\ndef"); + fileResolver.GetDirectories(PathHelper.FilePathToFileUrl(tempDir)).Should().HaveCount(1); + // check child dir + fileResolver.GetDirectories(PathHelper.FilePathToFileUrl(tempChildDir)).Should().HaveCount(0); + // should throw an IOException when called with a file path + fileResolver.GetDirectories(PathHelper.FilePathToFileUrl(Path.Join(Path.GetTempPath(), tempFile))); + } + + [TestMethod] + [ExpectedException(typeof(IOException), AllowDerivedTypes=true)] + public void GetFiles_should_return_expected_results() + { + var fileResolver = new FileResolver(); + var tempDir = Path.Combine(Path.GetTempPath(), $"BICEP_TESTDIR_{Guid.NewGuid()}"); + var tempFile = Path.Combine(tempDir, $"BICEP_TEST_{Guid.NewGuid()}"); + var tempChildDir = Path.Combine(tempDir, $"BICEP_TESTCHILDDIR_{Guid.NewGuid()}"); + + // make parent dir + Directory.CreateDirectory(tempDir); + fileResolver.GetFiles(PathHelper.FilePathToFileUrl(tempDir)).Should().HaveCount(0); + // add a file to parent dir + File.WriteAllText(tempFile, "abcd\r\ndef"); + fileResolver.GetFiles(PathHelper.FilePathToFileUrl(tempDir)).Should().HaveCount(1); + // make child dir + Directory.CreateDirectory(tempChildDir); + fileResolver.GetFiles(PathHelper.FilePathToFileUrl(tempDir)).Should().HaveCount(1); + // check child dir + fileResolver.GetFiles(PathHelper.FilePathToFileUrl(tempChildDir)).Should().HaveCount(0); + // should throw an IOException when called with a file path + fileResolver.GetDirectories(PathHelper.FilePathToFileUrl(Path.Join(Path.GetTempPath(), tempFile))); + } + + [TestMethod] + public void DirExists_should_return_expected_results() + { + var fileResolver = new FileResolver(); + var tempDir = Path.Combine(Path.GetTempPath(), $"BICEP_TESTDIR_{Guid.NewGuid()}"); + var tempFile = Path.Combine(tempDir, $"BICEP_TEST_{Guid.NewGuid()}"); + var tempChildDir = Path.Combine(tempDir, $"BICEP_TESTCHILDDIR_{Guid.NewGuid()}"); + + // make parent dir + Directory.CreateDirectory(tempDir); + fileResolver.TryDirExists(PathHelper.FilePathToFileUrl(tempDir)).Should().BeTrue(); + fileResolver.TryDirExists(PathHelper.FilePathToFileUrl(tempFile)).Should().BeFalse(); + // add a file to parent dir + File.WriteAllText(tempFile, "abcd\r\ndef"); + fileResolver.TryDirExists(PathHelper.FilePathToFileUrl(tempDir)).Should().BeTrue(); + fileResolver.TryDirExists(PathHelper.FilePathToFileUrl(tempFile)).Should().BeFalse(); + // make child dir + Directory.CreateDirectory(tempChildDir); + fileResolver.TryDirExists(PathHelper.FilePathToFileUrl(tempChildDir)).Should().BeTrue(); + } } } \ No newline at end of file diff --git a/src/Bicep.Core.UnitTests/Utils/OutputHelper.cs b/src/Bicep.Core.UnitTests/Utils/OutputHelper.cs index 88a518b1819..1b63969ac03 100644 --- a/src/Bicep.Core.UnitTests/Utils/OutputHelper.cs +++ b/src/Bicep.Core.UnitTests/Utils/OutputHelper.cs @@ -2,6 +2,8 @@ // Licensed under the MIT License. using System; using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; using System.IO; using System.Linq; using System.Text; @@ -63,8 +65,12 @@ public static string GetSpanText(string sourceText, IPositionable positionable) public static string GetDiagLoggingString(string sourceText, string outputDirectory, Diagnostic diagnostic) { var spanText = GetSpanText(sourceText, diagnostic); - var message = diagnostic.Message.Replace($"{outputDirectory}{Path.DirectorySeparatorChar}", "${TEST_OUTPUT_DIR}"); - + var message = diagnostic.Message.Replace($"{outputDirectory}{Path.DirectorySeparatorChar}", "${TEST_OUTPUT_DIR}/"); + // Normalize file path seperators across OS + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + message = Regex.Replace(message, @"'\${TEST_OUTPUT_DIR}.*?'", new MatchEvaluator((match) => match.Value.Replace('\\', '/'))); + } return $"[{diagnostic.Code} ({diagnostic.Level})] {message} |{spanText}|"; } } diff --git a/src/Bicep.Core/FileSystem/FileResolver.cs b/src/Bicep.Core/FileSystem/FileResolver.cs index 61b03174f5e..53f07aab5ed 100644 --- a/src/Bicep.Core/FileSystem/FileResolver.cs +++ b/src/Bicep.Core/FileSystem/FileResolver.cs @@ -1,8 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Linq; +using System.Runtime.InteropServices; using Bicep.Core.Diagnostics; namespace Bicep.Core.FileSystem @@ -21,6 +24,12 @@ public bool TryRead(Uri fileUri, [NotNullWhen(true)] out string? fileContents, [ try { failureBuilder = null; + if (Directory.Exists(fileUri.LocalPath)) + { + // Docs suggest this is the error to throw when we give a directory. + // A trailing backslash causes windows not to throw this exception. + throw new UnauthorizedAccessException($"Access to the path '{fileUri.LocalPath}' is denied."); + } fileContents = File.ReadAllText(fileUri.LocalPath); return true; } @@ -43,5 +52,28 @@ public bool TryRead(Uri fileUri, [NotNullWhen(true)] out string? fileContents, [ return relativeUri; } + + public IEnumerable GetDirectories(Uri fileUri, string pattern="") + { + if (!fileUri.IsFile) + { + return Enumerable.Empty(); + } + return Directory.GetDirectories(fileUri.LocalPath, pattern).Select(s => new Uri(s + "/")); + } + + public IEnumerable GetFiles(Uri fileUri, string pattern="") + { + if (!fileUri.IsFile) + { + return Enumerable.Empty(); + } + return Directory.GetFiles(fileUri.LocalPath, pattern).Select(s => new Uri(s)); + } + + public bool TryDirExists(Uri fileUri) + { + return fileUri.IsFile && Directory.Exists(fileUri.LocalPath); + } } } \ No newline at end of file diff --git a/src/Bicep.Core/FileSystem/IFileResolver.cs b/src/Bicep.Core/FileSystem/IFileResolver.cs index ccf1abe30e0..c56943dda68 100644 --- a/src/Bicep.Core/FileSystem/IFileResolver.cs +++ b/src/Bicep.Core/FileSystem/IFileResolver.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Bicep.Core.Diagnostics; @@ -22,5 +23,26 @@ public interface IFileResolver /// The file URI of the parent. /// The file path of the child. Uri? TryResolveModulePath(Uri parentFileUri, string childFilePath); + + + /// + /// Tries to get Directories given a uri and pattern. Both argument and returned URIs MUST have a trailing '/' + /// + /// The base fileUri + /// optional pattern to filter the dirs + IEnumerable GetDirectories(Uri fileUri, string pattern); + + /// + /// Tries to get Files given a uri and pattern. fileUri MUST have a trailing '/' + /// + /// The base fileUri + /// optional pattern to filter the resulting files + IEnumerable GetFiles(Uri fileUri, string pattern); + + /// + /// Check whether specified URI exsists (depends on URI types). fileUri MUST have a trailing '/' + /// + /// The fileUri to test + bool TryDirExists(Uri fileUri); } } \ No newline at end of file diff --git a/src/Bicep.Core/FileSystem/InMemoryFileResolver.cs b/src/Bicep.Core/FileSystem/InMemoryFileResolver.cs index 48c6d394af6..c942e60b61b 100644 --- a/src/Bicep.Core/FileSystem/InMemoryFileResolver.cs +++ b/src/Bicep.Core/FileSystem/InMemoryFileResolver.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; +using System.Linq; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Bicep.Core.Diagnostics; @@ -40,5 +41,21 @@ public bool TryRead(Uri fileUri, [NotNullWhen(true)] out string? fileContents, [ return relativeUri; } + + public bool TryDirExists(Uri fileUri) + { + return this.fileLookup.Keys + .Any(key => key.ToString().StartsWith(fileUri.ToString())); + } + + public IEnumerable GetDirectories(Uri fileUri, string pattern) + { + return Enumerable.Empty(); + } + + public IEnumerable GetFiles(Uri fileUri, string pattern) + { + return fileLookup.Keys; + } } } \ No newline at end of file diff --git a/src/Bicep.Core/FileSystem/PathHelper.cs b/src/Bicep.Core/FileSystem/PathHelper.cs index cca6f35f100..1f036dabf18 100644 --- a/src/Bicep.Core/FileSystem/PathHelper.cs +++ b/src/Bicep.Core/FileSystem/PathHelper.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; +using System.Linq; using System.IO; namespace Bicep.Core.FileSystem diff --git a/src/Bicep.LangServer.UnitTests/BicepCompletionProviderTests.cs b/src/Bicep.LangServer.UnitTests/BicepCompletionProviderTests.cs index 02f5f030d1a..32481d47648 100644 --- a/src/Bicep.LangServer.UnitTests/BicepCompletionProviderTests.cs +++ b/src/Bicep.LangServer.UnitTests/BicepCompletionProviderTests.cs @@ -6,6 +6,7 @@ using System.Linq; using Bicep.Core; using Bicep.Core.Extensions; +using Bicep.Core.FileSystem; using Bicep.Core.Navigation; using Bicep.Core.Parsing; using Bicep.Core.Semantics; @@ -33,7 +34,7 @@ public void DeclarationSnippetsShouldBeValid() var compilation = new Compilation(TestResourceTypeProvider.Create(), grouping); compilation.GetEntrypointSemanticModel().GetAllDiagnostics().Should().BeEmpty(); - var provider = new BicepCompletionProvider(); + var provider = new BicepCompletionProvider(new FileResolver()); var completions = provider.GetFilteredCompletions(compilation, BicepCompletionContext.Create(grouping.EntryPoint, 0)); @@ -99,7 +100,7 @@ public void DeclarationContextShouldReturnKeywordCompletions() var compilation = new Compilation(TestResourceTypeProvider.Create(), grouping); compilation.GetEntrypointSemanticModel().GetAllDiagnostics().Should().BeEmpty(); - var provider = new BicepCompletionProvider(); + var provider = new BicepCompletionProvider(new FileResolver()); var completions = provider.GetFilteredCompletions(compilation, BicepCompletionContext.Create(grouping.EntryPoint, 0)); @@ -179,7 +180,7 @@ param p string var offset = grouping.EntryPoint.ProgramSyntax.Declarations.OfType().Single().Value.Span.Position; var compilation = new Compilation(TestResourceTypeProvider.Create(), grouping); - var provider = new BicepCompletionProvider(); + var provider = new BicepCompletionProvider(new FileResolver()); var context = BicepCompletionContext.Create(grouping.EntryPoint, offset); var completions = provider.GetFilteredCompletions(compilation, context).ToList(); @@ -218,7 +219,7 @@ public void CompletionsForOneLinerParameterDefaultValueShouldIncludeFunctionsVal var offset = ((ParameterDefaultValueSyntax) grouping.EntryPoint.ProgramSyntax.Declarations.OfType().Single().Modifier!).DefaultValue.Span.Position; - var provider = new BicepCompletionProvider(); + var provider = new BicepCompletionProvider(new FileResolver()); var completions = provider.GetFilteredCompletions( compilation, BicepCompletionContext.Create(grouping.EntryPoint, offset)).ToList(); @@ -249,7 +250,7 @@ public void CompletionsForModifierDefaultValuesShouldIncludeFunctionsValidInDefa var compilation = new Compilation(TestResourceTypeProvider.Create(), grouping); var context = BicepCompletionContext.Create(grouping.EntryPoint, offset); - var provider = new BicepCompletionProvider(); + var provider = new BicepCompletionProvider(new FileResolver()); var completions = provider.GetFilteredCompletions(compilation, context).ToList(); AssertExpectedFunctions(completions, expectParamDefaultFunctions: true); @@ -280,7 +281,7 @@ param concat string var offset = grouping.EntryPoint.ProgramSyntax.Declarations.OfType().Single().Value.Span.Position; var compilation = new Compilation(TestResourceTypeProvider.Create(), grouping); - var provider = new BicepCompletionProvider(); + var provider = new BicepCompletionProvider(new FileResolver()); var context = BicepCompletionContext.Create(grouping.EntryPoint, offset); var completions = provider.GetFilteredCompletions(compilation, context).ToList(); @@ -322,7 +323,7 @@ public void OutputTypeContextShouldReturnDeclarationTypeCompletions() { var grouping = SyntaxFactory.CreateFromText("output test "); var compilation = new Compilation(TestResourceTypeProvider.Create(), grouping); - var provider = new BicepCompletionProvider(); + var provider = new BicepCompletionProvider(new FileResolver()); var offset = grouping.EntryPoint.ProgramSyntax.Declarations.OfType().Single().Type.Span.Position; @@ -339,7 +340,7 @@ public void ParameterTypeContextShouldReturnDeclarationTypeCompletions() { var grouping = SyntaxFactory.CreateFromText("param foo "); var compilation = new Compilation(TestResourceTypeProvider.Create(), grouping); - var provider = new BicepCompletionProvider(); + var provider = new BicepCompletionProvider(new FileResolver()); var offset = grouping.EntryPoint.ProgramSyntax.Declarations.OfType().Single().Type.Span.Position; @@ -383,7 +384,7 @@ public void CommentShouldNotGiveAnyCompletions(string codeFragment) { var grouping = SyntaxFactory.CreateFromText(codeFragment); var compilation = new Compilation(TestResourceTypeProvider.Create(), grouping); - var provider = new BicepCompletionProvider(); + var provider = new BicepCompletionProvider(new FileResolver()); var offset = codeFragment.IndexOf('|'); diff --git a/src/Bicep.LangServer/Completions/BicepCompletionContext.cs b/src/Bicep.LangServer/Completions/BicepCompletionContext.cs index e53860e7345..ed167dbbd4e 100644 --- a/src/Bicep.LangServer/Completions/BicepCompletionContext.cs +++ b/src/Bicep.LangServer/Completions/BicepCompletionContext.cs @@ -169,7 +169,8 @@ private static BicepCompletionContextKind GetDeclarationTypeFlags(IList(matchingNodes, module => CheckTypeIsExpected(module.Name, module.Path)) || - SyntaxMatcher.IsTailMatch(matchingNodes, (_, _, token) => token.Type == TokenType.StringComplete)) + SyntaxMatcher.IsTailMatch(matchingNodes, (_, _, token) => token.Type == TokenType.StringComplete) || + SyntaxMatcher.IsTailMatch(matchingNodes, (_, _, token) => token.Type == TokenType.Identifier)) { // the most specific matching node is a module declaration // the declaration syntax is "module '' ..." diff --git a/src/Bicep.LangServer/Completions/BicepCompletionProvider.cs b/src/Bicep.LangServer/Completions/BicepCompletionProvider.cs index a617928e22d..3ee5bc3d92e 100644 --- a/src/Bicep.LangServer/Completions/BicepCompletionProvider.cs +++ b/src/Bicep.LangServer/Completions/BicepCompletionProvider.cs @@ -7,6 +7,7 @@ using System.Text; using Bicep.Core; using Bicep.Core.Extensions; +using Bicep.Core.FileSystem; using Bicep.Core.Parsing; using Bicep.Core.Resources; using Bicep.Core.Semantics; @@ -28,6 +29,13 @@ public class BicepCompletionProvider : ICompletionProvider private static readonly Container PropertyAccessCommitChars = new Container("."); + private IFileResolver FileResolver; + + public BicepCompletionProvider(IFileResolver fileResolver) + { + this.FileResolver = fileResolver; + } + public IEnumerable GetFilteredCompletions(Compilation compilation, BicepCompletionContext context) { var model = compilation.GetEntrypointSemanticModel(); @@ -41,6 +49,7 @@ public IEnumerable GetFilteredCompletions(Compilation compilatio .Concat(GetPropertyValueCompletions(model, context)) .Concat(GetArrayItemCompletions(model, context)) .Concat(GetResourceTypeCompletions(model, context)) + .Concat(GetModulePathCompletions(model, context)) .Concat(GetResourceOrModuleBodyCompletions(context)) .Concat(GetTargetScopeCompletions(model, context)); } @@ -50,7 +59,6 @@ private IEnumerable GetDeclarationCompletions(BicepCompletionCon if (context.Kind.HasFlag(BicepCompletionContextKind.DeclarationStart)) { yield return CreateKeywordCompletion(LanguageConstants.ParameterKeyword, "Parameter keyword", context.ReplacementRange); - yield return CreateContextualSnippetCompletion(LanguageConstants.ParameterKeyword, "Parameter declaration", "param ${1:Identifier} ${2:Type}", context.ReplacementRange); yield return CreateContextualSnippetCompletion(LanguageConstants.ParameterKeyword, "Parameter declaration with default value", "param ${1:Identifier} ${2:Type} = ${3:DefaultValue}", context.ReplacementRange); yield return CreateContextualSnippetCompletion(LanguageConstants.ParameterKeyword, "Parameter declaration with default and allowed values", @"param ${1:Identifier} ${2:Type} { @@ -108,7 +116,7 @@ private IEnumerable GetDeclarationCompletions(BicepCompletionCon private IEnumerable GetTargetScopeCompletions(SemanticModel model, BicepCompletionContext context) { - return context.Kind.HasFlag(BicepCompletionContextKind.TargetScope) && context.TargetScope is {} targetScope + return context.Kind.HasFlag(BicepCompletionContextKind.TargetScope) && context.TargetScope is { } targetScope ? GetValueCompletionsForType(model.GetDeclaredType(targetScope), context.ReplacementRange) : Enumerable.Empty(); } @@ -168,6 +176,69 @@ private IEnumerable GetResourceTypeCompletions(SemanticModel mod .ToList(); } + private IEnumerable GetModulePathCompletions(SemanticModel model, BicepCompletionContext context) + { + if (!context.Kind.HasFlag(BicepCompletionContextKind.ModulePath)) + { + return Enumerable.Empty(); + } + + // To provide intellisense before the quotes are typed + if (context.EnclosingDeclaration is not ModuleDeclarationSyntax declarationSyntax + || declarationSyntax.Path is not StringSyntax stringSyntax + || stringSyntax.TryGetLiteralValue() is not string entered) + { + entered = ""; + } + + // These should only fail if we're not able to resolve cwd path or the entered string + if (FileResolver.TryResolveModulePath(model.SyntaxTree.FileUri, ".") is not {} cwdUri + || FileResolver.TryResolveModulePath(cwdUri, entered) is not {} query) + { + return Enumerable.Empty(); + } + + var files = Enumerable.Empty(); + var dirs = Enumerable.Empty(); + + + // technically bicep files do not have to follow the bicep extension, so + // we are not enforcing *.bicep get files command + if (FileResolver.TryDirExists(query)) + { + files = FileResolver.GetFiles(query, string.Empty); + dirs = FileResolver.GetDirectories(query, string.Empty); + } + else if (FileResolver.TryResolveModulePath(query, ".") is {} queryParent) + { + files = FileResolver.GetFiles(queryParent, ""); + dirs = FileResolver.GetDirectories(queryParent,""); + } + // "./" will not be preserved when making relative Uris. We have to go and manually add it. + // Prioritize .bicep files higher than other files. + var fileItems = files + .Where(file => file != model.SyntaxTree.FileUri) + .Where(file => file.Segments.Last().EndsWith(LanguageServerConstants.LanguageFileExtension)) + .Select(file => CreateModulePathCompletion( + file.Segments.Last(), + (entered.StartsWith("./") ? "./" : "") + cwdUri.MakeRelativeUri(file).ToString(), + context.ReplacementRange, + CompletionItemKind.File, + file.Segments.Last().EndsWith(LanguageServerConstants.LanguageId) ? CompletionPriority.High : CompletionPriority.Medium)) + .ToList(); + + var dirItems = dirs + .Select(dir => CreateModulePathCompletion( + dir.Segments.Last(), + (entered.StartsWith("./") ? "./" : "") + cwdUri.MakeRelativeUri(dir).ToString(), + context.ReplacementRange, + CompletionItemKind.Folder, + CompletionPriority.Medium) + .WithCommand(new Command {Name = EditorCommands.RequestCompletions })) + .ToList(); + return fileItems.Concat(dirItems); + } + private static IEnumerable GetParameterTypeSnippets(Range replacementRange) { yield return CreateContextualSnippetCompletion("secureObject", "Secure object", @"object { @@ -192,7 +263,7 @@ private static IEnumerable GetAccessibleSymbolCompletions(Semant // maps insert text to the completion item var completions = new Dictionary(); - var enclosingDeclarationSymbol = context.EnclosingDeclaration == null + var enclosingDeclarationSymbol = context.EnclosingDeclaration == null ? null : model.GetSymbolInfo(context.EnclosingDeclaration); @@ -347,7 +418,7 @@ private IEnumerable GetPropertyValueCompletions(SemanticModel mo } var declaredTypeAssignment = GetDeclaredTypeAssignment(model, context.Property); - if(declaredTypeAssignment == null) + if (declaredTypeAssignment == null) { return Enumerable.Empty(); } @@ -378,7 +449,7 @@ private static IEnumerable GetValueCompletionsForType(TypeSymbol case PrimitiveType _ when ReferenceEquals(propertyType, LanguageConstants.Bool): yield return CreateKeywordCompletion(LanguageConstants.TrueKeyword, LanguageConstants.TrueKeyword, replacementRange, preselect: true, CompletionPriority.High); yield return CreateKeywordCompletion(LanguageConstants.FalseKeyword, LanguageConstants.FalseKeyword, replacementRange, preselect: true, CompletionPriority.High); - + break; case StringLiteralType stringLiteral: @@ -399,7 +470,7 @@ private static IEnumerable GetValueCompletionsForType(TypeSymbol .WithDetail(arrayLabel) .Preselect() .WithSortText(GetSortText(arrayLabel, CompletionPriority.High)); - + break; case DiscriminatedObjectType _: @@ -509,6 +580,25 @@ private static CompletionItem CreateResourceTypeCompletion(ResourceTypeReference .WithSortText(index.ToString("x8")); } + private static CompletionItem CreateModulePathCompletion(string name, string path, Range replacementRange, CompletionItemKind completionItemKind, CompletionPriority priority) + { + path = StringUtils.EscapeBicepString(path); + var item = CompletionItemBuilder.Create(completionItemKind) + .WithLabel(name) + .WithFilterText(path) + .WithSortText(GetSortText(name, priority)); + // Folder completions should keep us within the completion string + if (completionItemKind.Equals(CompletionItemKind.Folder)) + { + item.WithSnippetEdit(replacementRange, $"{path.Substring(0, path.Length - 1)}$0'"); + } + else + { + item = item.WithPlainTextEdit(replacementRange, path); + } + return item; + } + /// /// Creates a completion with a contextual snippet. This will look like a snippet to the user. /// @@ -532,7 +622,7 @@ private static CompletionItem CreateSymbolCompletion(Symbol symbol, Range replac if (symbol is FunctionSymbol function) { - // for functions withouth any parameters on all the overloads, we should be placing the cursor after the parentheses + // for functions without any parameters on all the overloads, we should be placing the cursor after the parentheses // for all other functions, the cursor should land between the parentheses so the user can specify the arguments bool hasParameters = function.Overloads.Any(overload => overload.HasParameters); var snippet = hasParameters diff --git a/src/Bicep.LangServer/Completions/CompletionItemBuilder.cs b/src/Bicep.LangServer/Completions/CompletionItemBuilder.cs index 115748896de..ba56559495e 100644 --- a/src/Bicep.LangServer/Completions/CompletionItemBuilder.cs +++ b/src/Bicep.LangServer/Completions/CompletionItemBuilder.cs @@ -10,9 +10,37 @@ public static class CompletionItemBuilder { public static CompletionItem Create(CompletionItemKind kind) => new CompletionItem {Kind = kind}; - public static CompletionItem WithLabel(this CompletionItem item, string label) + public static CompletionItem WithAdditionalEdits(this CompletionItem item, TextEditContainer editContainer) { - item.Label = label; + item.AdditionalTextEdits = editContainer; + return item; + } + + public static CompletionItem WithCommitCharacters(this CompletionItem item, Container commitCharacters) + { + item.CommitCharacters = commitCharacters; + return item; + } + + public static CompletionItem WithDetail(this CompletionItem item, string detail) + { + item.Detail = detail; + return item; + } + + public static CompletionItem WithDocumentation(this CompletionItem item, string markdown) + { + item.Documentation = new StringOrMarkupContent(new MarkupContent + { + Kind = MarkupKind.Markdown, + Value = markdown + }); + return item; + } + + public static CompletionItem WithFilterText(this CompletionItem item, string filterText) + { + item.FilterText = filterText; return item; } @@ -27,6 +55,19 @@ public static CompletionItem WithInsertText(this CompletionItem item, string ins return item; } + public static CompletionItem WithLabel(this CompletionItem item, string label) + { + item.Label = label; + return item; + } + + public static CompletionItem WithPlainTextEdit(this CompletionItem item, Range range, string text, InsertTextMode insertTextMode = InsertTextMode.AsIs) + { + AssertNoInsertText(item); + SetTextEditInternal(item, range, InsertTextFormat.PlainText, text, insertTextMode); + return item; + } + public static CompletionItem WithSnippet(this CompletionItem item, string snippet, InsertTextMode insertTextMode = InsertTextMode.AsIs) { AssertNoTextEdit(item); @@ -38,12 +79,6 @@ public static CompletionItem WithSnippet(this CompletionItem item, string snippe return item; } - public static CompletionItem WithPlainTextEdit(this CompletionItem item, Range range, string text, InsertTextMode insertTextMode = InsertTextMode.AsIs) - { - AssertNoInsertText(item); - SetTextEditInternal(item, range, InsertTextFormat.PlainText, text, insertTextMode); - return item; - } public static CompletionItem WithSnippetEdit(this CompletionItem item, Range range, string snippet, InsertTextMode insertTextMode = InsertTextMode.AsIs) { @@ -52,27 +87,8 @@ public static CompletionItem WithSnippetEdit(this CompletionItem item, Range ran return item; } - public static CompletionItem WithAdditionalEdits(this CompletionItem item, TextEditContainer editContainer) - { - item.AdditionalTextEdits = editContainer; - return item; - } - public static CompletionItem WithDetail(this CompletionItem item, string detail) - { - item.Detail = detail; - return item; - } - public static CompletionItem WithDocumentation(this CompletionItem item, string markdown) - { - item.Documentation = new StringOrMarkupContent(new MarkupContent - { - Kind = MarkupKind.Markdown, - Value = markdown - }); - return item; - } public static CompletionItem WithSortText(this CompletionItem item, string sortText) { @@ -88,11 +104,6 @@ public static CompletionItem Preselect(this CompletionItem item, bool preselect) return item; } - public static CompletionItem WithCommitCharacters(this CompletionItem item, Container commitCharacters) - { - item.CommitCharacters = commitCharacters; - return item; - } public static CompletionItem WithCommand(this CompletionItem item, Command command) { diff --git a/src/Bicep.LangServer/Handlers/BicepCompletionHandler.cs b/src/Bicep.LangServer/Handlers/BicepCompletionHandler.cs index f880ac970fe..d8a6d400eee 100644 --- a/src/Bicep.LangServer/Handlers/BicepCompletionHandler.cs +++ b/src/Bicep.LangServer/Handlers/BicepCompletionHandler.cs @@ -1,11 +1,14 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Bicep.LanguageServer.CompilationManager; using Bicep.LanguageServer.Completions; using Bicep.LanguageServer.Utils; +using Microsoft.Extensions.Logging; using OmniSharp.Extensions.LanguageServer.Protocol.Document; using OmniSharp.Extensions.LanguageServer.Protocol.Models; @@ -13,12 +16,14 @@ namespace Bicep.LanguageServer.Handlers { public class BicepCompletionHandler : CompletionHandler { + private readonly ILogger logger; private readonly ICompilationManager compilationManager; private readonly ICompletionProvider completionProvider; - public BicepCompletionHandler(ICompilationManager compilationManager, ICompletionProvider completionProvider) + public BicepCompletionHandler(ILogger logger, ICompilationManager compilationManager, ICompletionProvider completionProvider) : base(CreateRegistrationOptions()) { + this.logger = logger; this.compilationManager = compilationManager; this.completionProvider = completionProvider; } @@ -33,7 +38,15 @@ public override Task Handle(CompletionParams request, Cancellati int offset = PositionHelper.GetOffset(compilationContext.LineStarts, request.Position); var completionContext = BicepCompletionContext.Create(compilationContext.Compilation.SyntaxTreeGrouping.EntryPoint, offset); - var completions = this.completionProvider.GetFilteredCompletions(compilationContext.Compilation, completionContext); + var completions = Enumerable.Empty(); + try + { + completions = this.completionProvider.GetFilteredCompletions(compilationContext.Compilation, completionContext); + } + catch (Exception e) + { + this.logger.LogError("Error with Completion in file {Uri} with {Context}. Underlying exception is: {Exception}", request.TextDocument.Uri, completionContext, e.ToString()); + } return Task.FromResult(new CompletionList(completions, isIncomplete: false)); } @@ -48,7 +61,7 @@ public override Task Handle(CompletionItem request, Cancellation DocumentSelector = DocumentSelectorFactory.Create(), AllCommitCharacters = new Container(), ResolveProvider = false, - TriggerCharacters = new Container(":", " ", ".") + TriggerCharacters = new Container(":", " ", ".", "/") }; } } diff --git a/src/Bicep.LangServer/LanguageServerConstants.cs b/src/Bicep.LangServer/LanguageServerConstants.cs index d6113908765..c21256de7fd 100644 --- a/src/Bicep.LangServer/LanguageServerConstants.cs +++ b/src/Bicep.LangServer/LanguageServerConstants.cs @@ -5,6 +5,8 @@ namespace Bicep.LanguageServer public static class LanguageServerConstants { public const string LanguageId = "bicep"; + + public const string LanguageFileExtension = ".bicep"; } }