yoda-language-server 0.8.0 → 0.10.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -0
  3. data/README.md +22 -27
  4. data/client/vscode/README.md +17 -2
  5. data/client/vscode/package-lock.json +908 -817
  6. data/client/vscode/package.json +17 -7
  7. data/client/vscode/src/check-versions.ts +49 -0
  8. data/client/vscode/src/config.ts +8 -1
  9. data/client/vscode/src/extension.ts +4 -3
  10. data/client/vscode/src/install-tools.ts +56 -16
  11. data/client/vscode/src/language-server.ts +5 -0
  12. data/client/vscode/src/test/runTest.ts +1 -1
  13. data/client/vscode/src/test/suite/hover.test.ts +13 -25
  14. data/client/vscode/src/test/suite/index.ts +1 -2
  15. data/client/vscode/src/utils.ts +11 -0
  16. data/images/hover-method.png +0 -0
  17. data/images/method-complete.png +0 -0
  18. data/lib/yoda/cli/analyze_deps.rb +46 -25
  19. data/lib/yoda/id_mask.rb +84 -0
  20. data/lib/yoda/instrument.rb +7 -0
  21. data/lib/yoda/model/descriptions/require_path_description.rb +45 -0
  22. data/lib/yoda/model/descriptions.rb +1 -0
  23. data/lib/yoda/model/node_signatures/node.rb +9 -1
  24. data/lib/yoda/model/values/literal_value.rb +3 -0
  25. data/lib/yoda/parsing/location.rb +9 -0
  26. data/lib/yoda/server/concurrent_writer.rb +1 -1
  27. data/lib/yoda/server/lifecycle_handler.rb +101 -78
  28. data/lib/yoda/server/notifier.rb +2 -55
  29. data/lib/yoda/services/loadable_path_resolver.rb +40 -0
  30. data/lib/yoda/services.rb +1 -0
  31. data/lib/yoda/store/actions/read_project_files.rb +9 -7
  32. data/lib/yoda/store/adapters/gdbm_adapter/namespace_accessor.rb +1 -1
  33. data/lib/yoda/store/adapters/memory_adapter.rb +1 -1
  34. data/lib/yoda/store/objects/libraries_status.rb +1 -1
  35. data/lib/yoda/store/objects/library/core.rb +8 -0
  36. data/lib/yoda/store/objects/library/gem.rb +14 -3
  37. data/lib/yoda/store/objects/library/path_resolvable.rb +29 -0
  38. data/lib/yoda/store/objects/library/std.rb +9 -0
  39. data/lib/yoda/store/objects/library.rb +1 -0
  40. data/lib/yoda/store/objects/patch.rb +1 -1
  41. data/lib/yoda/store/objects/patch_set.rb +2 -2
  42. data/lib/yoda/store/project/dependency.rb +22 -4
  43. data/lib/yoda/store/project/file_finder.rb +20 -0
  44. data/lib/yoda/store/project.rb +2 -0
  45. data/lib/yoda/store/registry/cache.rb +2 -2
  46. data/lib/yoda/store/registry/composer.rb +9 -7
  47. data/lib/yoda/store/registry/index.rb +14 -10
  48. data/lib/yoda/store/registry/library_registry.rb +3 -1
  49. data/lib/yoda/store/registry.rb +1 -1
  50. data/lib/yoda/typing/constant_resolver/code_query.rb +25 -0
  51. data/lib/yoda/typing/constant_resolver/query.rb +12 -1
  52. data/lib/yoda/typing/constant_resolver.rb +13 -8
  53. data/lib/yoda/typing/inferencer/load_resolver.rb +37 -0
  54. data/lib/yoda/typing/inferencer/tracer.rb +32 -0
  55. data/lib/yoda/typing/inferencer.rb +3 -2
  56. data/lib/yoda/typing/node_info.rb +5 -0
  57. data/lib/yoda/typing/tree/{defined.rb → ask_defined.rb} +3 -2
  58. data/lib/yoda/typing/tree/base.rb +65 -20
  59. data/lib/yoda/typing/tree/begin.rb +5 -5
  60. data/lib/yoda/typing/tree/block_call.rb +26 -0
  61. data/lib/yoda/typing/tree/case.rb +8 -19
  62. data/lib/yoda/typing/tree/class_tree.rb +10 -18
  63. data/lib/yoda/typing/tree/conditional_loop.rb +15 -0
  64. data/lib/yoda/typing/tree/constant.rb +19 -0
  65. data/lib/yoda/typing/tree/constant_assignment.rb +2 -2
  66. data/lib/yoda/typing/tree/ensure.rb +17 -0
  67. data/lib/yoda/typing/tree/for.rb +7 -0
  68. data/lib/yoda/typing/tree/hash_tree.rb +32 -0
  69. data/lib/yoda/typing/tree/if.rb +10 -5
  70. data/lib/yoda/typing/tree/interpolation_text.rb +21 -0
  71. data/lib/yoda/typing/tree/literal.rb +8 -36
  72. data/lib/yoda/typing/tree/literal_inferable.rb +48 -0
  73. data/lib/yoda/typing/tree/local_exit.rb +15 -0
  74. data/lib/yoda/typing/tree/logical_assignment.rb +5 -5
  75. data/lib/yoda/typing/tree/logical_operator.rb +6 -5
  76. data/lib/yoda/typing/tree/method_def.rb +41 -0
  77. data/lib/yoda/typing/tree/method_inferable.rb +51 -0
  78. data/lib/yoda/typing/tree/module_tree.rb +7 -20
  79. data/lib/yoda/typing/tree/multiple_assignment.rb +6 -10
  80. data/lib/yoda/typing/tree/namespace_inferable.rb +20 -0
  81. data/lib/yoda/typing/tree/rescue.rb +18 -0
  82. data/lib/yoda/typing/tree/rescue_clause.rb +42 -0
  83. data/lib/yoda/typing/tree/self.rb +2 -1
  84. data/lib/yoda/typing/tree/send.rb +8 -60
  85. data/lib/yoda/typing/tree/send_inferable.rb +89 -0
  86. data/lib/yoda/typing/tree/singleton_class_tree.rb +24 -0
  87. data/lib/yoda/typing/tree/singleton_method_def.rb +41 -0
  88. data/lib/yoda/typing/tree/super.rb +9 -2
  89. data/lib/yoda/typing/tree/variable.rb +5 -10
  90. data/lib/yoda/typing/tree/variable_assignment.rb +11 -8
  91. data/lib/yoda/typing/tree/yield.rb +9 -2
  92. data/lib/yoda/typing/tree.rb +55 -22
  93. data/lib/yoda/typing.rb +1 -0
  94. data/lib/yoda/version.rb +1 -1
  95. data/lib/yoda.rb +1 -0
  96. data/yoda-language-server.gemspec +1 -1
  97. metadata +35 -18
  98. data/lib/yoda/typing/inferencer/ast_traverser.rb +0 -408
  99. data/lib/yoda/typing/tree/block.rb +0 -12
  100. data/lib/yoda/typing/tree/const.rb +0 -12
  101. data/lib/yoda/typing/tree/escape.rb +0 -12
  102. data/lib/yoda/typing/tree/hash_body.rb +0 -36
  103. data/lib/yoda/typing/tree/literal_with_interpolation.rb +0 -21
  104. data/lib/yoda/typing/tree/method.rb +0 -43
  105. data/lib/yoda/typing/tree/rescue_body.rb +0 -12
  106. data/lib/yoda/typing/tree/singleton_method.rb +0 -47
  107. data/lib/yoda/typing/tree/while.rb +0 -12
@@ -30,6 +30,12 @@
30
30
  ],
31
31
  "default": null,
32
32
  "description": "Specifies the path of yoda."
33
+ },
34
+ "yoda.trace.server": {
35
+ "type": "string",
36
+ "enum": ["off", "messages", "compact", "verbose"],
37
+ "default": "off",
38
+ "description": "Message level of yoda server."
33
39
  }
34
40
  }
35
41
  }
@@ -42,16 +48,20 @@
42
48
  "package": "vsce package"
43
49
  },
44
50
  "dependencies": {
51
+ "semver": "^7.3.5",
45
52
  "vscode-languageclient": "^7.0"
46
53
  },
47
54
  "devDependencies": {
48
- "@types/mocha": "^5.2.6",
49
- "@types/node": "^8.10.66",
50
- "@types/vscode": "^1.52.0",
51
- "glob": "^7.1.4",
52
- "mocha": "^9.1.3",
55
+ "@types/chai": "^4.3",
56
+ "@types/mocha": "^9.1",
57
+ "@types/node": "^8.10",
58
+ "@types/semver": "^7.3.9",
59
+ "@types/vscode": "^1.52",
60
+ "@vscode/test-electron": "^2.1",
61
+ "chai": "^4.3",
62
+ "glob": "^7.1",
63
+ "mocha": "^9.1",
53
64
  "typescript": "^4",
54
- "vsce": "^2.6.0",
55
- "vscode-test": "^1.5"
65
+ "vsce": "^2.6.0"
56
66
  }
57
67
  }
@@ -0,0 +1,49 @@
1
+
2
+ import { execSync } from 'child_process'
3
+ import { cmp, maxSatisfying } from 'semver'
4
+ import { asyncExec } from './utils'
5
+
6
+ interface CheckResult {
7
+ shouldUpdate: boolean
8
+ localVersion: string
9
+ remoteVersion: string
10
+ }
11
+
12
+ export async function checkVersions(): Promise<CheckResult> {
13
+ const { stdout: localStdout } = await asyncExec("gem list --local --exact yoda-language-server")
14
+ const localVersion = extractVersion(localStdout)
15
+
16
+ const { stdout: remoteStdout } = await asyncExec("gem list --remote --no-prerelease --exact yoda-language-server")
17
+ const remoteVersion = extractVersion(remoteStdout)
18
+
19
+ return {
20
+ shouldUpdate: shouldUpdate(localVersion, remoteVersion),
21
+ localVersion: localVersion,
22
+ remoteVersion: remoteVersion,
23
+ }
24
+ }
25
+
26
+ function shouldUpdate(localVersion: string, remoteVersion: string): boolean {
27
+ if (!localVersion) {
28
+ return true
29
+ }
30
+
31
+ if (!remoteVersion) {
32
+ return false
33
+ }
34
+
35
+ return cmp(localVersion, "<", remoteVersion)
36
+ }
37
+
38
+ function extractVersion(text: string): string {
39
+ const lines = text.split("\n")
40
+ for (const line of lines) {
41
+ const matchData = line.match(/^yoda-language-server\s*\((.+)\)/);
42
+ if (matchData) {
43
+ const versions = matchData[1].split(/,\s*/)
44
+ return maxSatisfying(versions, '*')
45
+ }
46
+ }
47
+
48
+ return null
49
+ }
@@ -8,6 +8,13 @@ export function calcExecutionConfiguration() {
8
8
  return { command }
9
9
  }
10
10
 
11
- export function getTraceConfiguration (): string | null {
11
+ export function isCustomExecutionPathConfigured(): boolean {
12
+ const yodaPathEnv = process.env.YODA_EXECUTABLE_PATH
13
+ const yodaPathConfiguration = workspace.getConfiguration("yoda").get("path") as (string | null);
14
+
15
+ return !!(yodaPathEnv || yodaPathConfiguration)
16
+ }
17
+
18
+ export function getTraceConfiguration(): string | null {
12
19
  return workspace.getConfiguration("yoda").get("trace.server") as (string | null);
13
20
  }
@@ -1,5 +1,6 @@
1
1
  import { ExtensionContext, Disposable } from 'vscode'
2
- import { isLanguageServerInstalled, promptForInstallTool } from './install-tools'
2
+ import { isCustomExecutionPathConfigured } from './config'
3
+ import { tryInstallOrUpdate } from './install-tools'
3
4
  import { configureLanguageServer } from './language-server'
4
5
 
5
6
  let disposable: Disposable
@@ -11,8 +12,8 @@ export async function activate(context: ExtensionContext) {
11
12
  // This line of code will only be executed once when your extension is activated
12
13
  // console.log('Congratulations, your extension "yoda" is now active!');
13
14
 
14
- if (!isLanguageServerInstalled()) {
15
- await promptForInstallTool()
15
+ if (!isCustomExecutionPathConfigured()) {
16
+ await tryInstallOrUpdate()
16
17
  }
17
18
 
18
19
  const languageServer = configureLanguageServer()
@@ -1,17 +1,10 @@
1
1
  import * as child_process from 'child_process'
2
2
 
3
3
  import { window } from 'vscode'
4
+ import { checkVersions } from './check-versions'
4
5
  import { calcExecutionConfiguration, getTraceConfiguration } from './config'
5
6
  import { outputChannel } from './status'
6
- import { promisify } from 'util'
7
-
8
- function execCommand(command: string, onMessage: (stdout: string | null, stderr: string | null) => void, callback: (error: Error) => void) {
9
- const process = child_process.exec(command, callback)
10
- process.stdout.on('data', (data) => onMessage(data.toString(), null))
11
- process.stderr.on('data', (data) => onMessage(null, data.toString()))
12
- }
13
-
14
- const asyncExecCommand = promisify(execCommand)
7
+ import { asyncExec, asyncExecPipeline } from './utils'
15
8
 
16
9
  export function isLanguageServerInstalled(): boolean {
17
10
  const { command } = calcExecutionConfiguration()
@@ -23,11 +16,41 @@ export function isLanguageServerInstalled(): boolean {
23
16
  }
24
17
  }
25
18
 
26
- export async function promptForInstallTool() {
27
- const choises = ['Install']
28
- const selected = await window.showInformationMessage('yoda command is not available. Please install.', ...choises)
19
+ export async function tryInstallOrUpdate() {
20
+ try {
21
+ if (!isLanguageServerInstalled()) {
22
+ outputChannel.appendLine(`Yoda is not installed. Ask to install.`)
23
+ await promptForInstallTool(false)
24
+ return
25
+ }
26
+
27
+ const { shouldUpdate, localVersion, remoteVersion } = await checkVersions()
28
+
29
+ console.log(`Local version: ${localVersion}`)
30
+ console.log(`Available version: ${remoteVersion}`)
31
+ console.log(`shouldUpdate: ${shouldUpdate}`)
32
+
33
+ if (shouldUpdate) {
34
+ await promptForInstallTool(localVersion !== null, remoteVersion)
35
+ }
36
+ } catch (e) {
37
+ outputChannel.appendLine(`An error occured on update: ${e}`)
38
+ }
39
+ }
40
+
41
+ export async function promptForInstallTool(update: boolean, newVersion?: string) {
42
+ const choises = [update ? 'Update' : 'Install']
43
+
44
+ const newVersionLabel = newVersion ? ` (${newVersion})` : ''
45
+
46
+ const message = update ?
47
+ `A newer version of yoda${newVersionLabel} is available.` :
48
+ 'yoda command is not available. Please install.'
49
+
50
+ const selected = await window.showInformationMessage(message, ...choises)
29
51
  switch (selected) {
30
52
  case 'Install':
53
+ case 'Update':
31
54
  await installTool()
32
55
  break;
33
56
  default:
@@ -41,11 +64,29 @@ async function installTool() {
41
64
 
42
65
  outputChannel.appendLine('Installing yoda...')
43
66
 
67
+ await installGemFromRubygems()
68
+
69
+ outputChannel.appendLine('yoda is installed.')
70
+ }
71
+
72
+ async function installGemFromRubygems() {
73
+ outputChannel.appendLine('gem install yoda-language-server')
74
+ await asyncExecPipeline("yes | gem install yoda-language-server", (stdout, stderr) => {
75
+ if (stdout) {
76
+ outputChannel.append(stdout)
77
+ }
78
+ if (stderr) {
79
+ outputChannel.append(stderr)
80
+ }
81
+ })
82
+ }
83
+
84
+ async function installGemFromRepository() {
44
85
  try {
45
- child_process.execSync("gem list --installed --exact specific_install")
86
+ await asyncExec("gem list --installed --exact specific_install")
46
87
  } catch (e) {
47
88
  outputChannel.appendLine('gem install specific_install')
48
- await asyncExecCommand("gem install specific_install", (stdout, stderr) => {
89
+ await asyncExecPipeline("gem install specific_install", (stdout, stderr) => {
49
90
  if (stdout) {
50
91
  outputChannel.append(stdout)
51
92
  }
@@ -57,7 +98,7 @@ async function installTool() {
57
98
  }
58
99
 
59
100
  outputChannel.appendLine('gem specific_install tomoasleep/yoda')
60
- await asyncExecCommand("gem specific_install tomoasleep/yoda", (stdout, stderr) => {
101
+ await asyncExecPipeline("gem specific_install tomoasleep/yoda", (stdout, stderr) => {
61
102
  if (stdout) {
62
103
  outputChannel.append(stdout)
63
104
  }
@@ -65,5 +106,4 @@ async function installTool() {
65
106
  outputChannel.append(stderr)
66
107
  }
67
108
  })
68
- outputChannel.appendLine('yoda is installed.')
69
109
  }
@@ -19,6 +19,7 @@ export function configureLanguageServer(): LanguageClient {
19
19
  }
20
20
 
21
21
  const clientOptions : LanguageClientOptions = {
22
+ progressOnInitialization: true,
22
23
  documentSelector: [{ scheme: 'file', language: 'ruby' }],
23
24
  synchronize: {
24
25
  configurationSection: 'yoda',
@@ -37,6 +38,10 @@ export function configureLanguageServer(): LanguageClient {
37
38
  outputChannel.appendLine(value);
38
39
  console.log(value);
39
40
  },
41
+ replace(value: string) {
42
+ outputChannel.replace(value);
43
+ console.log(value);
44
+ },
40
45
  clear() { outputChannel.clear() },
41
46
  show() { outputChannel.show() },
42
47
  hide() { outputChannel.hide() },
@@ -1,6 +1,6 @@
1
1
  import * as path from 'path';
2
2
 
3
- import { runTests } from 'vscode-test';
3
+ import { runTests } from '@vscode/test-electron';
4
4
 
5
5
  async function main() {
6
6
  try {
@@ -1,43 +1,31 @@
1
1
  import * as vscode from 'vscode';
2
- import * as assert from 'assert';
2
+ import { expect } from 'chai';
3
3
  import { getDocUri, activate } from '../helper';
4
4
 
5
5
  describe('Should provide hover', () => {
6
6
  const docUri = getDocUri('completion.rb');
7
7
 
8
8
  it('show hover', async () => {
9
- await testCompletion(docUri, new vscode.Position(0, 2), {
10
- contents: [
11
- {"language":"ruby","value":"Object # Object.module"},
12
- "**Object.class**\n\n\n",
13
- ],
14
- range: new vscode.Range(
15
- new vscode.Position(0, 0),
16
- new vscode.Position(0, 6),
17
- ),
18
- });
19
- })
9
+ await activate(docUri);
10
+
11
+ const actualHovers = await requestComplete(docUri, new vscode.Position(0, 2));
20
12
 
13
+ console.log("hovers: ", actualHovers);
21
14
 
15
+ expect((actualHovers[0].contents[0] as vscode.MarkdownString).value).to.include("Object # singleton(::Object)");
16
+ expect((actualHovers[0].contents[1] as vscode.MarkdownString).value).to.include("**Object**")
17
+ })
22
18
  });
23
19
 
24
- async function testCompletion(
20
+ async function requestComplete(
25
21
  docUri: vscode.Uri,
26
22
  position: vscode.Position,
27
- expectedHover: vscode.Hover
28
- ) {
29
- await activate(docUri);
30
-
31
- // Executing the command `vscode.executeCompletionItemProvider` to simulate triggering completion
32
- // See: https://code.visualstudio.com/api/references/commands
33
- const [actualHover] = await vscode.commands.executeCommand<vscode.Hover[]>(
23
+ ): Promise<vscode.Hover[]> {
24
+ const hovers = await vscode.commands.executeCommand<vscode.Hover[]>(
34
25
  'vscode.executeHoverProvider',
35
26
  docUri,
36
- position
27
+ position,
37
28
  );
38
29
 
39
- // assert.equal(actualHover.range, expectedHover.range);
40
- expectedHover.contents.forEach((expectedItem, i) => {
41
- assert.equal(actualHover.contents[i], expectedItem);
42
- });
30
+ return hovers
43
31
  }
@@ -7,10 +7,9 @@ export function run(testsRoot: string, callback: (error: any, failures?: number)
7
7
  const mocha = new Mocha({
8
8
  ui: 'bdd',
9
9
  timeout: 600000,
10
+ color: true,
10
11
  });
11
12
 
12
- mocha.useColors(true);
13
-
14
13
  glob('**/**.test.js', { cwd: testsRoot }, (error, files) => {
15
14
  if (error) {
16
15
  return callback(error);
@@ -0,0 +1,11 @@
1
+ import { exec } from 'child_process'
2
+ import { promisify } from 'util'
3
+
4
+ function execPipeline(command: string, onMessage: (stdout: string | null, stderr: string | null) => void, callback: (error: Error) => void) {
5
+ const process = exec(command, callback)
6
+ process.stdout.on('data', (data) => onMessage(data.toString(), null))
7
+ process.stderr.on('data', (data) => onMessage(null, data.toString()))
8
+ }
9
+
10
+ export const asyncExecPipeline = promisify(execPipeline)
11
+ export const asyncExec = promisify(exec)
Binary file
Binary file
@@ -16,7 +16,7 @@ module Yoda
16
16
 
17
17
  # @param root_path [String]
18
18
  def initialize(root_path)
19
- @root_path = root_path
19
+ @root_path = File.expand_path(root_path)
20
20
  end
21
21
 
22
22
  # @return [Array<Yoda::Store::Objects::Library::Gem>]
@@ -28,6 +28,7 @@ module Yoda
28
28
  {
29
29
  path: root_path,
30
30
  dependencies: gems.map(&:to_h),
31
+ autoload_dependency_ids: autoload_gem_ids,
31
32
  }
32
33
  end
33
34
 
@@ -45,39 +46,47 @@ module Yoda
45
46
  end
46
47
  end
47
48
 
48
- # @return [Array<Bundler::LazySpecification>, nil]
49
+ # @return [Array<String>]
50
+ def autoload_gem_ids
51
+ if has_gemfile?
52
+ gems.map(&:id)
53
+ else
54
+ []
55
+ end
56
+ end
57
+
58
+ # @return [Array<Bundler::LazySpecification, Gem::Specification>, nil]
49
59
  def gem_specs
50
60
  @gem_specs ||= begin
51
- Dir.chdir(root_path) do
52
- with_unbundled_env do
53
- # Suppress bundler outputs to stdout.
54
- Bundler.ui = Bundler::UI::Silent.new
55
-
56
- Bundler.reset!
57
-
58
- if File.exists?("Gemfile")
59
- # Resolve dependencies of uninstalled gems and ensure remote sources are available.
60
- Bundler.definition.resolve_remotely!
61
- spec_set = Bundler.definition.resolve
62
-
63
- if Gem::Version.new(Bundler::VERSION) >= Gem::Version.new('2.2.25')
64
- deps = Bundler.definition.requested_dependencies
65
- spec_set.materialize(deps).to_a
66
- else
67
- # For backward compatibility (Ref: https://github.com/rubygems/rubygems/pull/4788)
68
- deps = Bundler.definition.send(:requested_dependencies)
69
- missing = []
70
- materialized = spec_set.materialize(deps, missing)
71
- materialized.to_a + missing
72
- end
61
+ with_project_env do
62
+ if has_gemfile?
63
+ # Resolve dependencies of uninstalled gems and ensure remote sources are available.
64
+ Bundler.definition.resolve_remotely!
65
+ spec_set = Bundler.definition.resolve
66
+
67
+ if Gem::Version.new(Bundler::VERSION) >= Gem::Version.new('2.2.25')
68
+ deps = Bundler.definition.requested_dependencies
69
+ spec_set.materialize(deps).to_a
73
70
  else
74
- []
71
+ # For backward compatibility (Ref: https://github.com/rubygems/rubygems/pull/4788)
72
+ deps = Bundler.definition.send(:requested_dependencies)
73
+ missing = []
74
+ materialized = spec_set.materialize(deps, missing)
75
+
76
+ materialized.to_a + missing
75
77
  end
78
+ else
79
+ [] # Gem::Specification.latest_specs(true)
76
80
  end
77
81
  end
78
82
  end
79
83
  end
80
84
 
85
+ # @return [Boolean]
86
+ def has_gemfile?
87
+ File.exists?(File.expand_path("Gemfile", root_path))
88
+ end
89
+
81
90
  # @param [Gem::Specification]
82
91
  def metadata?(spec)
83
92
  spec.source.is_a?(Bundler::Source::Metadata)
@@ -88,6 +97,18 @@ module Yoda
88
97
  spec.source.is_a?(Bundler::Source::Path) && (File.expand_path(spec.source.path) == File.expand_path(root_path))
89
98
  end
90
99
 
100
+ def with_project_env
101
+ Dir.chdir(root_path) do
102
+ with_unbundled_env do
103
+ # Suppress bundler outputs to stdout.
104
+ Bundler.ui = Bundler::UI::Silent.new
105
+ Bundler.reset!
106
+
107
+ yield
108
+ end
109
+ end
110
+ end
111
+
91
112
  def with_unbundled_env(&block)
92
113
  if Bundler.respond_to?(:with_unbundled_env)
93
114
  Bundler.with_unbundled_env(&block)
@@ -0,0 +1,84 @@
1
+ require 'set'
2
+
3
+ module Yoda
4
+ class IdMask
5
+ # @return [Hash<Symbol>, nil]
6
+ attr_reader :pattern
7
+
8
+ # @param pattern [IdMask, Set<Symbol>, Array<Symbol>, Hash<Symbol>, nil]
9
+ # @return [IdMask]
10
+ def self.build(pattern)
11
+ if pattern.is_a?(IdMask)
12
+ pattern
13
+ elsif pattern.nil?
14
+ new(pattern)
15
+ elsif pattern.is_a?(Hash)
16
+ new(pattern.map { |k, v| [k.to_sym, v] }.to_h)
17
+ else
18
+ new(pattern.to_a.to_h { |id| [id.to_sym, nil] })
19
+ end
20
+ end
21
+
22
+ # @param pattern [Hash<Symbol>, nil]
23
+ def initialize(pattern)
24
+ fail TypeError, "pattern must be a Hash or nil" unless pattern.is_a?(Hash) || pattern.nil?
25
+ @pattern = pattern
26
+ end
27
+
28
+ # @param id [Symbol, String]
29
+ # @return [Boolean]
30
+ def cover?(id)
31
+ return true if any?
32
+ pattern.has_key?(id.to_sym)
33
+ end
34
+
35
+ # @param another [IdMasklia, Set<Symbol>, Array<Symbol>, Hash<Symbol>, nil]
36
+ # @return [IdMask]
37
+ def intersection(another)
38
+ another_mask = IdMask.build(another)
39
+ return another_mask if any?
40
+ return self if another_mask.any?
41
+
42
+ ids_intersection = covering_ids & another_mask.covering_ids
43
+
44
+ intersection_pattern = ids_intersection.map do |id|
45
+ [id, nesting_mask(id) & another_mask.nesting_mask(id)]
46
+ end.to_h
47
+
48
+ IdMask.build(intersection_pattern)
49
+ end
50
+
51
+ alias_method :&, :intersection
52
+
53
+ # @return [Boolean]
54
+ def any?
55
+ pattern.nil?
56
+ end
57
+
58
+ # @return [Set<Symbol>, nil]
59
+ def covering_ids
60
+ @covering_ids ||= begin
61
+ if any?
62
+ nil
63
+ else
64
+ Set.new(pattern.keys)
65
+ end
66
+ end
67
+ end
68
+
69
+ # @param id [String, Symbol]
70
+ # @return [IdMask]
71
+ def nesting_mask(id)
72
+ if pattern.is_a?(Hash)
73
+ IdMask.build(pattern[id.to_sym])
74
+ else
75
+ IdMask.build(nil)
76
+ end
77
+ end
78
+
79
+ # @return [Hash, nil]
80
+ def to_pattern
81
+ pattern&.map { |k, v| [k, v&.to_pattern] }&.to_h
82
+ end
83
+ end
84
+ end
@@ -130,5 +130,12 @@ module Yoda
130
130
  def registry_dump(index: nil, length: nil)
131
131
  emit(:registry_dump, index: index, length: length)
132
132
  end
133
+
134
+ # @param name [String]
135
+ # @param version [String]
136
+ # @param message [String]
137
+ def build_library_registry(message:, name:, version:)
138
+ emit(:build_library_registry, name: name, version: version, message: message)
139
+ end
133
140
  end
134
141
  end
@@ -0,0 +1,45 @@
1
+ module Yoda
2
+ module Model
3
+ module Descriptions
4
+ # @abstract
5
+ class RequirePathDescription
6
+ # @return [String]
7
+ attr_reader :path
8
+
9
+ # @param path [String]
10
+ def initialize(path)
11
+ @path = path
12
+ end
13
+
14
+ # @abstract
15
+ # @return [String]
16
+ def title
17
+ path
18
+ end
19
+
20
+ # @abstract
21
+ # @return [String]
22
+ def sort_text
23
+ path
24
+ end
25
+
26
+ # @return [String]
27
+ def label
28
+ sort_text
29
+ end
30
+
31
+ # @abstract
32
+ # @return [String]
33
+ def to_markdown
34
+ path
35
+ end
36
+
37
+ # Return an LSP MarkedString content for description
38
+ # @return [String, Hash]
39
+ def markup_content
40
+ to_markdown
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -7,6 +7,7 @@ module Yoda
7
7
  require 'yoda/model/descriptions/value_description'
8
8
  require 'yoda/model/descriptions/word_description'
9
9
  require 'yoda/model/descriptions/node_description'
10
+ require 'yoda/model/descriptions/require_path_description'
10
11
  require 'yoda/model/descriptions/variable_description'
11
12
  end
12
13
  end
@@ -3,7 +3,15 @@ module Yoda
3
3
  module NodeSignatures
4
4
  class Node < Base
5
5
  def descriptions
6
- [node_type_description, *type_descriptions]
6
+ if node_info.require_paths.empty?
7
+ [node_type_description, *type_descriptions]
8
+ else
9
+ node_info.require_paths.map { |path| Descriptions::RequirePathDescription.new(path) }
10
+ end
11
+ end
12
+
13
+ def defined_files
14
+ node_info.require_paths.map { |path| [path, Parsing::Location.first_row, Parsing::Location.first_column] }
7
15
  end
8
16
  end
9
17
  end
@@ -11,6 +11,9 @@ module Yoda
11
11
  # @return [Base]
12
12
  attr_reader :value
13
13
 
14
+ # @return [Object]
15
+ attr_reader :literal
16
+
14
17
  # @param value [Base]
15
18
  # @param literal [Object]
16
19
  def initialize(value:, literal:)
@@ -17,6 +17,15 @@ module Yoda
17
17
  @column = column
18
18
  end
19
19
 
20
+ def self.first_row
21
+ 1
22
+ end
23
+
24
+ def self.first_column
25
+ 0
26
+ end
27
+
28
+
20
29
  # @param ast_location [Parser::Source::Map, Parser::Source::Range]
21
30
  # @return [Location, nil]
22
31
  def self.of_ast_location(ast_location)
@@ -2,7 +2,7 @@ module Yoda
2
2
  class Server
3
3
  # Wrapper class for writer to make thread safe
4
4
  class ConcurrentWriter
5
- # @param [::LanguageServer::Protocol::Transport::Stdio::Writer]
5
+ # @param channel [::LanguageServer::Protocol::Transport::Stdio::Writer]
6
6
  def initialize(channel)
7
7
  @channel = channel
8
8
  @mutex = Mutex.new