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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/README.md +22 -27
- data/client/vscode/README.md +17 -2
- data/client/vscode/package-lock.json +908 -817
- data/client/vscode/package.json +17 -7
- data/client/vscode/src/check-versions.ts +49 -0
- data/client/vscode/src/config.ts +8 -1
- data/client/vscode/src/extension.ts +4 -3
- data/client/vscode/src/install-tools.ts +56 -16
- data/client/vscode/src/language-server.ts +5 -0
- data/client/vscode/src/test/runTest.ts +1 -1
- data/client/vscode/src/test/suite/hover.test.ts +13 -25
- data/client/vscode/src/test/suite/index.ts +1 -2
- data/client/vscode/src/utils.ts +11 -0
- data/images/hover-method.png +0 -0
- data/images/method-complete.png +0 -0
- data/lib/yoda/cli/analyze_deps.rb +46 -25
- data/lib/yoda/id_mask.rb +84 -0
- data/lib/yoda/instrument.rb +7 -0
- data/lib/yoda/model/descriptions/require_path_description.rb +45 -0
- data/lib/yoda/model/descriptions.rb +1 -0
- data/lib/yoda/model/node_signatures/node.rb +9 -1
- data/lib/yoda/model/values/literal_value.rb +3 -0
- data/lib/yoda/parsing/location.rb +9 -0
- data/lib/yoda/server/concurrent_writer.rb +1 -1
- data/lib/yoda/server/lifecycle_handler.rb +101 -78
- data/lib/yoda/server/notifier.rb +2 -55
- data/lib/yoda/services/loadable_path_resolver.rb +40 -0
- data/lib/yoda/services.rb +1 -0
- data/lib/yoda/store/actions/read_project_files.rb +9 -7
- data/lib/yoda/store/adapters/gdbm_adapter/namespace_accessor.rb +1 -1
- data/lib/yoda/store/adapters/memory_adapter.rb +1 -1
- data/lib/yoda/store/objects/libraries_status.rb +1 -1
- data/lib/yoda/store/objects/library/core.rb +8 -0
- data/lib/yoda/store/objects/library/gem.rb +14 -3
- data/lib/yoda/store/objects/library/path_resolvable.rb +29 -0
- data/lib/yoda/store/objects/library/std.rb +9 -0
- data/lib/yoda/store/objects/library.rb +1 -0
- data/lib/yoda/store/objects/patch.rb +1 -1
- data/lib/yoda/store/objects/patch_set.rb +2 -2
- data/lib/yoda/store/project/dependency.rb +22 -4
- data/lib/yoda/store/project/file_finder.rb +20 -0
- data/lib/yoda/store/project.rb +2 -0
- data/lib/yoda/store/registry/cache.rb +2 -2
- data/lib/yoda/store/registry/composer.rb +9 -7
- data/lib/yoda/store/registry/index.rb +14 -10
- data/lib/yoda/store/registry/library_registry.rb +3 -1
- data/lib/yoda/store/registry.rb +1 -1
- data/lib/yoda/typing/constant_resolver/code_query.rb +25 -0
- data/lib/yoda/typing/constant_resolver/query.rb +12 -1
- data/lib/yoda/typing/constant_resolver.rb +13 -8
- data/lib/yoda/typing/inferencer/load_resolver.rb +37 -0
- data/lib/yoda/typing/inferencer/tracer.rb +32 -0
- data/lib/yoda/typing/inferencer.rb +3 -2
- data/lib/yoda/typing/node_info.rb +5 -0
- data/lib/yoda/typing/tree/{defined.rb → ask_defined.rb} +3 -2
- data/lib/yoda/typing/tree/base.rb +65 -20
- data/lib/yoda/typing/tree/begin.rb +5 -5
- data/lib/yoda/typing/tree/block_call.rb +26 -0
- data/lib/yoda/typing/tree/case.rb +8 -19
- data/lib/yoda/typing/tree/class_tree.rb +10 -18
- data/lib/yoda/typing/tree/conditional_loop.rb +15 -0
- data/lib/yoda/typing/tree/constant.rb +19 -0
- data/lib/yoda/typing/tree/constant_assignment.rb +2 -2
- data/lib/yoda/typing/tree/ensure.rb +17 -0
- data/lib/yoda/typing/tree/for.rb +7 -0
- data/lib/yoda/typing/tree/hash_tree.rb +32 -0
- data/lib/yoda/typing/tree/if.rb +10 -5
- data/lib/yoda/typing/tree/interpolation_text.rb +21 -0
- data/lib/yoda/typing/tree/literal.rb +8 -36
- data/lib/yoda/typing/tree/literal_inferable.rb +48 -0
- data/lib/yoda/typing/tree/local_exit.rb +15 -0
- data/lib/yoda/typing/tree/logical_assignment.rb +5 -5
- data/lib/yoda/typing/tree/logical_operator.rb +6 -5
- data/lib/yoda/typing/tree/method_def.rb +41 -0
- data/lib/yoda/typing/tree/method_inferable.rb +51 -0
- data/lib/yoda/typing/tree/module_tree.rb +7 -20
- data/lib/yoda/typing/tree/multiple_assignment.rb +6 -10
- data/lib/yoda/typing/tree/namespace_inferable.rb +20 -0
- data/lib/yoda/typing/tree/rescue.rb +18 -0
- data/lib/yoda/typing/tree/rescue_clause.rb +42 -0
- data/lib/yoda/typing/tree/self.rb +2 -1
- data/lib/yoda/typing/tree/send.rb +8 -60
- data/lib/yoda/typing/tree/send_inferable.rb +89 -0
- data/lib/yoda/typing/tree/singleton_class_tree.rb +24 -0
- data/lib/yoda/typing/tree/singleton_method_def.rb +41 -0
- data/lib/yoda/typing/tree/super.rb +9 -2
- data/lib/yoda/typing/tree/variable.rb +5 -10
- data/lib/yoda/typing/tree/variable_assignment.rb +11 -8
- data/lib/yoda/typing/tree/yield.rb +9 -2
- data/lib/yoda/typing/tree.rb +55 -22
- data/lib/yoda/typing.rb +1 -0
- data/lib/yoda/version.rb +1 -1
- data/lib/yoda.rb +1 -0
- data/yoda-language-server.gemspec +1 -1
- metadata +35 -18
- data/lib/yoda/typing/inferencer/ast_traverser.rb +0 -408
- data/lib/yoda/typing/tree/block.rb +0 -12
- data/lib/yoda/typing/tree/const.rb +0 -12
- data/lib/yoda/typing/tree/escape.rb +0 -12
- data/lib/yoda/typing/tree/hash_body.rb +0 -36
- data/lib/yoda/typing/tree/literal_with_interpolation.rb +0 -21
- data/lib/yoda/typing/tree/method.rb +0 -43
- data/lib/yoda/typing/tree/rescue_body.rb +0 -12
- data/lib/yoda/typing/tree/singleton_method.rb +0 -47
- data/lib/yoda/typing/tree/while.rb +0 -12
data/client/vscode/package.json
CHANGED
@@ -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/
|
49
|
-
"@types/
|
50
|
-
"@types/
|
51
|
-
"
|
52
|
-
"
|
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
|
+
}
|
data/client/vscode/src/config.ts
CHANGED
@@ -8,6 +8,13 @@ export function calcExecutionConfiguration() {
|
|
8
8
|
return { command }
|
9
9
|
}
|
10
10
|
|
11
|
-
export function
|
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 {
|
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 (!
|
15
|
-
await
|
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 {
|
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
|
27
|
-
|
28
|
-
|
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
|
-
|
86
|
+
await asyncExec("gem list --installed --exact specific_install")
|
46
87
|
} catch (e) {
|
47
88
|
outputChannel.appendLine('gem install specific_install')
|
48
|
-
await
|
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
|
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,43 +1,31 @@
|
|
1
1
|
import * as vscode from 'vscode';
|
2
|
-
import
|
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
|
-
|
10
|
-
|
11
|
-
|
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
|
20
|
+
async function requestComplete(
|
25
21
|
docUri: vscode.Uri,
|
26
22
|
position: vscode.Position,
|
27
|
-
|
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
|
-
|
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<
|
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
|
-
|
52
|
-
|
53
|
-
#
|
54
|
-
Bundler.
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
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)
|
data/lib/yoda/id_mask.rb
ADDED
@@ -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
|
data/lib/yoda/instrument.rb
CHANGED
@@ -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
|
-
|
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
|
@@ -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
|