Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

bash-language-server

Package Overview
Dependencies
Maintainers
1
Versions
107
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

bash-language-server - npm Package Compare versions

Comparing version 1.11.3 to 1.12.0

4

CHANGELOG.md
# Bash Language Server
## 1.12.0
* Completion handler improvements: remove duplicates, include symbols from other files, ensure that programs found on the paths are actually executable (https://github.com/bash-lsp/bash-language-server/pull/215)
## 1.11.3

@@ -4,0 +8,0 @@

12

out/analyser.d.ts
import * as LSP from 'vscode-languageserver';
import * as Parser from 'web-tree-sitter';
import { BashCompletionItem } from './types';
/**

@@ -52,7 +51,11 @@ * The Analyzer uses the Abstract Syntax Trees (ASTs) that are provided by

*/
findSymbols(uri: string): LSP.SymbolInformation[];
findSymbolsForFile({ uri }: {
uri: string;
}): LSP.SymbolInformation[];
/**
* Find unique symbol completions for the given file.
* Find symbol completions for the given word.
*/
findSymbolCompletions(uri: string): BashCompletionItem[];
findSymbolsMatchingWord({ word }: {
word: string;
}): LSP.SymbolInformation[];
/**

@@ -71,3 +74,2 @@ * Analyze the given document, cache the tree-sitter AST, and iterate over the

private getAllSymbols;
private symbolKindToCompletionKind;
}

@@ -16,6 +16,5 @@ "use strict";

const URI = require("urijs");
const util_1 = require("util");
const LSP = require("vscode-languageserver");
const config_1 = require("./config");
const types_1 = require("./types");
const array_1 = require("./util/array");
const flatten_1 = require("./util/flatten");

@@ -25,2 +24,3 @@ const fs_1 = require("./util/fs");

const TreeSitterUtil = require("./util/tree-sitter");
const readFileAsync = util_1.promisify(fs.readFile);
/**

@@ -64,11 +64,11 @@ * The Analyzer uses the Abstract Syntax Trees (ASTs) that are provided by

connection.console.log(`Glob resolved with ${filePaths.length} files after ${getTimePassed()}`);
filePaths.forEach(filePath => {
for (const filePath of filePaths) {
const uri = `file://${filePath}`;
connection.console.log(`Analyzing ${uri}`);
try {
const fileContent = fs.readFileSync(filePath, 'utf8');
const fileContent = yield readFileAsync(filePath, 'utf8');
const shebang = shebang_1.getShebang(fileContent);
if (shebang && !shebang_1.isBashShebang(shebang)) {
connection.console.log(`Skipping file ${uri} with shebang "${shebang}"`);
return;
continue;
}

@@ -80,3 +80,3 @@ analyzer.analyze(uri, LSP.TextDocument.create(uri, 'shell', 1, fileContent));

}
});
}
connection.console.log(`Analyzer finished after ${getTimePassed()}`);

@@ -187,3 +187,3 @@ }

*/
findSymbols(uri) {
findSymbolsForFile({ uri }) {
const declarationsInFile = this.uriToDeclarations[uri] || {};

@@ -193,14 +193,15 @@ return flatten_1.flattenObjectValues(declarationsInFile);

/**
* Find unique symbol completions for the given file.
* Find symbol completions for the given word.
*/
findSymbolCompletions(uri) {
const hashFunction = ({ name, kind }) => `${name}${kind}`;
return array_1.uniqueBasedOnHash(this.findSymbols(uri), hashFunction).map((symbol) => ({
label: symbol.name,
kind: this.symbolKindToCompletionKind(symbol.kind),
data: {
name: symbol.name,
type: types_1.CompletionItemDataType.Symbol,
},
}));
findSymbolsMatchingWord({ word }) {
const symbols = [];
Object.keys(this.uriToDeclarations).forEach(uri => {
const declarationsInFile = this.uriToDeclarations[uri] || {};
Object.keys(declarationsInFile).map(name => {
if (name.startsWith(word)) {
declarationsInFile[name].forEach(symbol => symbols.push(symbol));
}
});
});
return symbols;
}

@@ -286,55 +287,4 @@ /**

}
symbolKindToCompletionKind(s) {
switch (s) {
case LSP.SymbolKind.File:
return LSP.CompletionItemKind.File;
case LSP.SymbolKind.Module:
case LSP.SymbolKind.Namespace:
case LSP.SymbolKind.Package:
return LSP.CompletionItemKind.Module;
case LSP.SymbolKind.Class:
return LSP.CompletionItemKind.Class;
case LSP.SymbolKind.Method:
return LSP.CompletionItemKind.Method;
case LSP.SymbolKind.Property:
return LSP.CompletionItemKind.Property;
case LSP.SymbolKind.Field:
return LSP.CompletionItemKind.Field;
case LSP.SymbolKind.Constructor:
return LSP.CompletionItemKind.Constructor;
case LSP.SymbolKind.Enum:
return LSP.CompletionItemKind.Enum;
case LSP.SymbolKind.Interface:
return LSP.CompletionItemKind.Interface;
case LSP.SymbolKind.Function:
return LSP.CompletionItemKind.Function;
case LSP.SymbolKind.Variable:
return LSP.CompletionItemKind.Variable;
case LSP.SymbolKind.Constant:
return LSP.CompletionItemKind.Constant;
case LSP.SymbolKind.String:
case LSP.SymbolKind.Number:
case LSP.SymbolKind.Boolean:
case LSP.SymbolKind.Array:
case LSP.SymbolKind.Key:
case LSP.SymbolKind.Null:
return LSP.CompletionItemKind.Text;
case LSP.SymbolKind.Object:
return LSP.CompletionItemKind.Module;
case LSP.SymbolKind.EnumMember:
return LSP.CompletionItemKind.EnumMember;
case LSP.SymbolKind.Struct:
return LSP.CompletionItemKind.Struct;
case LSP.SymbolKind.Event:
return LSP.CompletionItemKind.Event;
case LSP.SymbolKind.Operator:
return LSP.CompletionItemKind.Operator;
case LSP.SymbolKind.TypeParameter:
return LSP.CompletionItemKind.TypeParameter;
default:
return LSP.CompletionItemKind.Text;
}
}
}
exports.default = Analyzer;
//# sourceMappingURL=analyser.js.map
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const Fs = require("fs");
const Path = require("path");
const fs = require("fs");
const path_1 = require("path");
const util_1 = require("util");
const ArrayUtil = require("./util/array");
const FsUtil = require("./util/fs");
const ShUtil = require("./util/sh");
const lstatAsync = util_1.promisify(fs.lstat);
const readdirAsync = util_1.promisify(fs.readdir);
/**

@@ -56,33 +68,36 @@ * Provides information based on the programs on your PATH

function findExecutablesInPath(path) {
path = FsUtil.untildify(path);
return new Promise((resolve, _) => {
Fs.lstat(path, (err, stat) => {
if (err) {
resolve([]);
}
else {
if (stat.isDirectory()) {
Fs.readdir(path, (readDirErr, paths) => {
if (readDirErr) {
resolve([]);
return __awaiter(this, void 0, void 0, function* () {
path = FsUtil.untildify(path);
try {
const pathStats = yield lstatAsync(path);
if (pathStats.isDirectory()) {
const childrenPaths = yield readdirAsync(path);
const files = [];
for (const childrenPath of childrenPaths) {
try {
const stats = yield lstatAsync(path_1.join(path, childrenPath));
if (isExecutableFile(stats)) {
files.push(path_1.basename(childrenPath));
}
else {
const files = paths.map(p => FsUtil.getStats(Path.join(path, p))
.then(s => (s.isFile() ? [Path.basename(p)] : []))
.catch(() => []));
resolve(Promise.all(files).then(ArrayUtil.flatten));
}
});
}
catch (error) {
// Ignore error
}
}
else if (stat.isFile()) {
resolve([Path.basename(path)]);
}
else {
// Something else.
resolve([]);
}
return files;
}
});
else if (isExecutableFile(pathStats)) {
return [path_1.basename(path)];
}
}
catch (error) {
// Ignore error
}
return [];
});
}
function isExecutableFile(stats) {
const isExecutable = !!(1 & parseInt((stats.mode & parseInt('777', 8)).toString(8)[0]));
return stats.isFile() && isExecutable;
}
//# sourceMappingURL=executables.js.map

@@ -12,2 +12,3 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
const path = require("path");
const TurndownService = require("turndown");

@@ -22,2 +23,3 @@ const LSP = require("vscode-languageserver");

const types_1 = require("./types");
const array_1 = require("./util/array");
/**

@@ -149,2 +151,3 @@ * The BashServer glues together the separate components to implement

}
// FIXME: could also be a symbol
return null;

@@ -160,3 +163,3 @@ });

this.connection.console.log(`onDocumentSymbol`);
return this.analyzer.findSymbols(params.textDocument.uri);
return this.analyzer.findSymbolsForFile({ uri: params.textDocument.uri });
}

@@ -185,4 +188,9 @@ onWorkspaceSymbol(params) {

this.logRequest({ request: 'onCompletion', params, word });
const symbolCompletions = this.analyzer.findSymbolCompletions(params.textDocument.uri);
// TODO: we could do some caching here...
const currentUri = params.textDocument.uri;
const symbolCompletions = getCompletionItemsForSymbols({
symbols: this.analyzer.findSymbolsMatchingWord({
word,
}),
currentUri,
});
const reservedWordsCompletions = ReservedWords.LIST.map(reservedWord => ({

@@ -196,8 +204,11 @@ label: reservedWord,

}));
const programCompletions = this.executables.list().map((s) => {
const programCompletions = this.executables
.list()
.filter(executable => !Builtins.isBuiltin(executable))
.map(executable => {
return {
label: s,
label: executable,
kind: LSP.SymbolKind.Function,
data: {
name: s,
name: executable,
type: types_1.CompletionItemDataType.Executable,

@@ -215,3 +226,2 @@ },

}));
// TODO: we have duplicates here (e.g. echo is both a builtin AND have a man page)
const allCompletions = [

@@ -268,2 +278,83 @@ ...reservedWordsCompletions,

exports.default = BashServer;
function getCompletionItemsForSymbols({ symbols, currentUri, }) {
const isCurrentFile = ({ location: { uri } }) => uri === currentUri;
const getSymbolId = ({ name, kind }) => `${name}${kind}`;
const symbolsCurrentFile = symbols.filter(s => isCurrentFile(s));
const symbolsOtherFiles = symbols
.filter(s => !isCurrentFile(s))
// Remove identical symbols
.filter(symbolOtherFiles => !symbolsCurrentFile.some(symbolCurrentFile => getSymbolId(symbolCurrentFile) === getSymbolId(symbolOtherFiles)));
return array_1.uniqueBasedOnHash([...symbolsCurrentFile, ...symbolsOtherFiles], getSymbolId).map((symbol) => ({
label: symbol.name,
kind: symbolKindToCompletionKind(symbol.kind),
data: {
name: symbol.name,
type: types_1.CompletionItemDataType.Symbol,
},
documentation: symbol.location.uri !== currentUri
? `${symbolKindToDescription(symbol.kind)} defined in ${path.relative(currentUri, symbol.location.uri)}`
: undefined,
}));
}
function symbolKindToCompletionKind(s) {
switch (s) {
case LSP.SymbolKind.File:
return LSP.CompletionItemKind.File;
case LSP.SymbolKind.Module:
case LSP.SymbolKind.Namespace:
case LSP.SymbolKind.Package:
return LSP.CompletionItemKind.Module;
case LSP.SymbolKind.Class:
return LSP.CompletionItemKind.Class;
case LSP.SymbolKind.Method:
return LSP.CompletionItemKind.Method;
case LSP.SymbolKind.Property:
return LSP.CompletionItemKind.Property;
case LSP.SymbolKind.Field:
return LSP.CompletionItemKind.Field;
case LSP.SymbolKind.Constructor:
return LSP.CompletionItemKind.Constructor;
case LSP.SymbolKind.Enum:
return LSP.CompletionItemKind.Enum;
case LSP.SymbolKind.Interface:
return LSP.CompletionItemKind.Interface;
case LSP.SymbolKind.Function:
return LSP.CompletionItemKind.Function;
case LSP.SymbolKind.Variable:
return LSP.CompletionItemKind.Variable;
case LSP.SymbolKind.Constant:
return LSP.CompletionItemKind.Constant;
case LSP.SymbolKind.String:
case LSP.SymbolKind.Number:
case LSP.SymbolKind.Boolean:
case LSP.SymbolKind.Array:
case LSP.SymbolKind.Key:
case LSP.SymbolKind.Null:
return LSP.CompletionItemKind.Text;
case LSP.SymbolKind.Object:
return LSP.CompletionItemKind.Module;
case LSP.SymbolKind.EnumMember:
return LSP.CompletionItemKind.EnumMember;
case LSP.SymbolKind.Struct:
return LSP.CompletionItemKind.Struct;
case LSP.SymbolKind.Event:
return LSP.CompletionItemKind.Event;
case LSP.SymbolKind.Operator:
return LSP.CompletionItemKind.Operator;
case LSP.SymbolKind.TypeParameter:
return LSP.CompletionItemKind.TypeParameter;
default:
return LSP.CompletionItemKind.Text;
}
}
function symbolKindToDescription(s) {
switch (s) {
case LSP.SymbolKind.Function:
return 'Function';
case LSP.SymbolKind.Variable:
return 'Variable';
default:
return 'Keyword';
}
}
//# sourceMappingURL=server.js.map

@@ -1,4 +0,1 @@

/// <reference types="node" />
import * as Fs from 'fs';
export declare function getStats(path: string): Promise<Fs.Stats>;
export declare function untildify(pathWithTilde: string): string;

@@ -5,0 +2,0 @@ export declare function getFilePaths({ globPattern, rootPath, }: {

@@ -12,18 +12,4 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
const Fs = require("fs");
const glob = require("glob");
const Os = require("os");
function getStats(path) {
return new Promise((resolve, reject) => {
Fs.lstat(path, (err, stat) => {
if (err) {
reject(err);
}
else {
resolve(stat);
}
});
});
}
exports.getStats = getStats;
// from https://github.com/sindresorhus/untildify/blob/f85a087418aeaa2beb56fe2684fe3b64fc8c588d/index.js#L11

@@ -30,0 +16,0 @@ function untildify(pathWithTilde) {

@@ -6,3 +6,3 @@ {

"license": "MIT",
"version": "1.11.3",
"version": "1.12.0",
"publisher": "mads-hartmann",

@@ -19,3 +19,3 @@ "main": "./out/server.js",

"engines": {
"node": "*"
"node": ">=8.0.0"
},

@@ -22,0 +22,0 @@ "dependencies": {

@@ -64,6 +64,6 @@ import FIXTURES, { FIXTURE_FOLDER } from '../../../testing/fixtures'

describe('findSymbols', () => {
describe('findSymbolsForFile', () => {
it('returns empty list if uri is not found', () => {
analyzer.analyze(CURRENT_URI, FIXTURES.INSTALL)
const result = analyzer.findSymbols('foobar.sh')
const result = analyzer.findSymbolsForFile({ uri: 'foobar.sh' })
expect(result).toEqual([])

@@ -74,3 +74,3 @@ })

analyzer.analyze(CURRENT_URI, FIXTURES.INSTALL)
const result = analyzer.findSymbols(CURRENT_URI)
const result = analyzer.findSymbolsForFile({ uri: CURRENT_URI })
expect(result).not.toEqual([])

@@ -82,3 +82,3 @@ expect(result).toMatchSnapshot()

analyzer.analyze(CURRENT_URI, FIXTURES.ISSUE101)
const result = analyzer.findSymbols(CURRENT_URI)
const result = analyzer.findSymbolsForFile({ uri: CURRENT_URI })
expect(result).not.toEqual([])

@@ -108,5 +108,87 @@ expect(result).toMatchSnapshot()

describe('findSymbolCompletions', () => {
it('return a list of symbols', () => {
analyzer.analyze(CURRENT_URI, FIXTURES.INSTALL)
expect(analyzer.findSymbolCompletions(CURRENT_URI)).toMatchSnapshot()
it('return a list of symbols across the workspace', () => {
analyzer.analyze('install.sh', FIXTURES.INSTALL)
analyzer.analyze('sourcing-sh', FIXTURES.SOURCING)
expect(analyzer.findSymbolsMatchingWord({ word: 'npm_config_logl' }))
.toMatchInlineSnapshot(`
Array [
Object {
"kind": 13,
"location": Object {
"range": Object {
"end": Object {
"character": 27,
"line": 40,
},
"start": Object {
"character": 0,
"line": 40,
},
},
"uri": "dummy-uri.sh",
},
"name": "npm_config_loglevel",
},
Object {
"kind": 13,
"location": Object {
"range": Object {
"end": Object {
"character": 31,
"line": 48,
},
"start": Object {
"character": 2,
"line": 48,
},
},
"uri": "dummy-uri.sh",
},
"name": "npm_config_loglevel",
},
Object {
"kind": 13,
"location": Object {
"range": Object {
"end": Object {
"character": 27,
"line": 40,
},
"start": Object {
"character": 0,
"line": 40,
},
},
"uri": "install.sh",
},
"name": "npm_config_loglevel",
},
Object {
"kind": 13,
"location": Object {
"range": Object {
"end": Object {
"character": 31,
"line": 48,
},
"start": Object {
"character": 2,
"line": 48,
},
},
"uri": "install.sh",
},
"name": "npm_config_loglevel",
},
]
`)
expect(analyzer.findSymbolsMatchingWord({ word: 'xxxxxxxx' })).toMatchInlineSnapshot(
`Array []`,
)
expect(analyzer.findSymbolsMatchingWord({ word: 'BLU' })).toMatchInlineSnapshot(
`Array []`,
)
})

@@ -113,0 +195,0 @@ })

@@ -222,3 +222,3 @@ import * as lsp from 'vscode-languageserver'

// Limited set
// Limited set (not using snapshot due to different executables on CI and locally)
expect('length' in result && result.length < 5).toBe(true)

@@ -285,2 +285,100 @@ expect(result).toEqual(

})
it('responds to onCompletion when word is found in another file', async () => {
const { connection, server } = await initializeServer()
server.register(connection)
const onCompletion = connection.onCompletion.mock.calls[0][0]
const resultVariable = await onCompletion(
{
textDocument: {
uri: FIXTURE_URI.SOURCING,
},
position: {
// $BLU (variable)
line: 6,
character: 7,
},
},
{} as any,
)
expect(resultVariable).toMatchInlineSnapshot(`
Array [
Object {
"data": Object {
"name": "BLUE",
"type": 3,
},
"documentation": "Variable defined in ../extension.inc",
"kind": 6,
"label": "BLUE",
},
]
`)
const resultFunction = await onCompletion(
{
textDocument: {
uri: FIXTURE_URI.SOURCING,
},
position: {
// add_a_us (function)
line: 8,
character: 7,
},
},
{} as any,
)
expect(resultFunction).toMatchInlineSnapshot(`
Array [
Object {
"data": Object {
"name": "add_a_user",
"type": 3,
},
"documentation": "Function defined in ../issue101.sh",
"kind": 3,
"label": "add_a_user",
},
]
`)
})
it('responds to onCompletion with local symbol when word is found in multiple files', async () => {
const { connection, server } = await initializeServer()
server.register(connection)
const onCompletion = connection.onCompletion.mock.calls[0][0]
const result = await onCompletion(
{
textDocument: {
uri: FIXTURE_URI.SOURCING,
},
position: {
// BOL (BOLD is defined in multiple places)
line: 12,
character: 7,
},
},
{} as any,
)
expect(result).toMatchInlineSnapshot(`
Array [
Object {
"data": Object {
"name": "BOLD",
"type": 3,
},
"documentation": undefined,
"kind": 6,
"label": "BOLD",
},
]
`)
})
})

@@ -5,2 +5,3 @@ import * as fs from 'fs'

import * as URI from 'urijs'
import { promisify } from 'util'
import * as LSP from 'vscode-languageserver'

@@ -10,4 +11,2 @@ import * as Parser from 'web-tree-sitter'

import { getGlobPattern } from './config'
import { BashCompletionItem, CompletionItemDataType } from './types'
import { uniqueBasedOnHash } from './util/array'
import { flattenArray, flattenObjectValues } from './util/flatten'

@@ -18,2 +17,4 @@ import { getFilePaths } from './util/fs'

const readFileAsync = promisify(fs.readFile)
type Kinds = { [type: string]: LSP.SymbolKind }

@@ -68,3 +69,3 @@

filePaths.forEach(filePath => {
for (const filePath of filePaths) {
const uri = `file://${filePath}`

@@ -74,7 +75,7 @@ connection.console.log(`Analyzing ${uri}`)

try {
const fileContent = fs.readFileSync(filePath, 'utf8')
const fileContent = await readFileAsync(filePath, 'utf8')
const shebang = getShebang(fileContent)
if (shebang && !isBashShebang(shebang)) {
connection.console.log(`Skipping file ${uri} with shebang "${shebang}"`)
return
continue
}

@@ -86,3 +87,3 @@

}
})
}

@@ -248,3 +249,3 @@ connection.console.log(`Analyzer finished after ${getTimePassed()}`)

*/
public findSymbols(uri: string): LSP.SymbolInformation[] {
public findSymbolsForFile({ uri }: { uri: string }): LSP.SymbolInformation[] {
const declarationsInFile = this.uriToDeclarations[uri] || {}

@@ -255,17 +256,17 @@ return flattenObjectValues(declarationsInFile)

/**
* Find unique symbol completions for the given file.
* Find symbol completions for the given word.
*/
public findSymbolCompletions(uri: string): BashCompletionItem[] {
const hashFunction = ({ name, kind }: LSP.SymbolInformation) => `${name}${kind}`
public findSymbolsMatchingWord({ word }: { word: string }): LSP.SymbolInformation[] {
const symbols: LSP.SymbolInformation[] = []
return uniqueBasedOnHash(this.findSymbols(uri), hashFunction).map(
(symbol: LSP.SymbolInformation) => ({
label: symbol.name,
kind: this.symbolKindToCompletionKind(symbol.kind),
data: {
name: symbol.name,
type: CompletionItemDataType.Symbol,
},
}),
)
Object.keys(this.uriToDeclarations).forEach(uri => {
const declarationsInFile = this.uriToDeclarations[uri] || {}
Object.keys(declarationsInFile).map(name => {
if (name.startsWith(word)) {
declarationsInFile[name].forEach(symbol => symbols.push(symbol))
}
})
})
return symbols
}

@@ -393,54 +394,2 @@

}
private symbolKindToCompletionKind(s: LSP.SymbolKind): LSP.CompletionItemKind {
switch (s) {
case LSP.SymbolKind.File:
return LSP.CompletionItemKind.File
case LSP.SymbolKind.Module:
case LSP.SymbolKind.Namespace:
case LSP.SymbolKind.Package:
return LSP.CompletionItemKind.Module
case LSP.SymbolKind.Class:
return LSP.CompletionItemKind.Class
case LSP.SymbolKind.Method:
return LSP.CompletionItemKind.Method
case LSP.SymbolKind.Property:
return LSP.CompletionItemKind.Property
case LSP.SymbolKind.Field:
return LSP.CompletionItemKind.Field
case LSP.SymbolKind.Constructor:
return LSP.CompletionItemKind.Constructor
case LSP.SymbolKind.Enum:
return LSP.CompletionItemKind.Enum
case LSP.SymbolKind.Interface:
return LSP.CompletionItemKind.Interface
case LSP.SymbolKind.Function:
return LSP.CompletionItemKind.Function
case LSP.SymbolKind.Variable:
return LSP.CompletionItemKind.Variable
case LSP.SymbolKind.Constant:
return LSP.CompletionItemKind.Constant
case LSP.SymbolKind.String:
case LSP.SymbolKind.Number:
case LSP.SymbolKind.Boolean:
case LSP.SymbolKind.Array:
case LSP.SymbolKind.Key:
case LSP.SymbolKind.Null:
return LSP.CompletionItemKind.Text
case LSP.SymbolKind.Object:
return LSP.CompletionItemKind.Module
case LSP.SymbolKind.EnumMember:
return LSP.CompletionItemKind.EnumMember
case LSP.SymbolKind.Struct:
return LSP.CompletionItemKind.Struct
case LSP.SymbolKind.Event:
return LSP.CompletionItemKind.Event
case LSP.SymbolKind.Operator:
return LSP.CompletionItemKind.Operator
case LSP.SymbolKind.TypeParameter:
return LSP.CompletionItemKind.TypeParameter
default:
return LSP.CompletionItemKind.Text
}
}
}

@@ -1,3 +0,4 @@

import * as Fs from 'fs'
import * as Path from 'path'
import * as fs from 'fs'
import { basename, join } from 'path'
import { promisify } from 'util'

@@ -8,2 +9,5 @@ import * as ArrayUtil from './util/array'

const lstatAsync = promisify(fs.lstat)
const readdirAsync = promisify(fs.readdir)
/**

@@ -62,32 +66,38 @@ * Provides information based on the programs on your PATH

*/
function findExecutablesInPath(path: string): Promise<string[]> {
async function findExecutablesInPath(path: string): Promise<string[]> {
path = FsUtil.untildify(path)
return new Promise((resolve, _) => {
Fs.lstat(path, (err, stat) => {
if (err) {
resolve([])
} else {
if (stat.isDirectory()) {
Fs.readdir(path, (readDirErr, paths) => {
if (readDirErr) {
resolve([])
} else {
const files = paths.map(p =>
FsUtil.getStats(Path.join(path, p))
.then(s => (s.isFile() ? [Path.basename(p)] : []))
.catch(() => []),
)
resolve(Promise.all(files).then(ArrayUtil.flatten))
}
})
} else if (stat.isFile()) {
resolve([Path.basename(path)])
} else {
// Something else.
resolve([])
try {
const pathStats = await lstatAsync(path)
if (pathStats.isDirectory()) {
const childrenPaths = await readdirAsync(path)
const files = []
for (const childrenPath of childrenPaths) {
try {
const stats = await lstatAsync(join(path, childrenPath))
if (isExecutableFile(stats)) {
files.push(basename(childrenPath))
}
} catch (error) {
// Ignore error
}
}
})
})
return files
} else if (isExecutableFile(pathStats)) {
return [basename(path)]
}
} catch (error) {
// Ignore error
}
return []
}
function isExecutableFile(stats: fs.Stats): boolean {
const isExecutable = !!(1 & parseInt((stats.mode & parseInt('777', 8)).toString(8)[0]))
return stats.isFile() && isExecutable
}

@@ -0,1 +1,2 @@

import * as path from 'path'
import * as TurndownService from 'turndown'

@@ -11,2 +12,3 @@ import * as LSP from 'vscode-languageserver'

import { BashCompletionItem, CompletionItemDataType } from './types'
import { uniqueBasedOnHash } from './util/array'

@@ -181,2 +183,4 @@ /**

// FIXME: could also be a symbol
return null

@@ -193,3 +197,3 @@ }

this.connection.console.log(`onDocumentSymbol`)
return this.analyzer.findSymbols(params.textDocument.uri)
return this.analyzer.findSymbolsForFile({ uri: params.textDocument.uri })
}

@@ -227,5 +231,10 @@

const symbolCompletions = this.analyzer.findSymbolCompletions(params.textDocument.uri)
const currentUri = params.textDocument.uri
// TODO: we could do some caching here...
const symbolCompletions = getCompletionItemsForSymbols({
symbols: this.analyzer.findSymbolsMatchingWord({
word,
}),
currentUri,
})

@@ -241,12 +250,15 @@ const reservedWordsCompletions = ReservedWords.LIST.map(reservedWord => ({

const programCompletions = this.executables.list().map((s: string) => {
return {
label: s,
kind: LSP.SymbolKind.Function,
data: {
name: s,
type: CompletionItemDataType.Executable,
},
}
})
const programCompletions = this.executables
.list()
.filter(executable => !Builtins.isBuiltin(executable))
.map(executable => {
return {
label: executable,
kind: LSP.SymbolKind.Function,
data: {
name: executable,
type: CompletionItemDataType.Executable,
},
}
})

@@ -262,3 +274,2 @@ const builtinsCompletions = Builtins.LIST.map(builtin => ({

// TODO: we have duplicates here (e.g. echo is both a builtin AND have a man page)
const allCompletions = [

@@ -321,1 +332,109 @@ ...reservedWordsCompletions,

}
function getCompletionItemsForSymbols({
symbols,
currentUri,
}: {
symbols: LSP.SymbolInformation[]
currentUri: string
}): BashCompletionItem[] {
const isCurrentFile = ({ location: { uri } }: LSP.SymbolInformation) =>
uri === currentUri
const getSymbolId = ({ name, kind }: LSP.SymbolInformation) => `${name}${kind}`
const symbolsCurrentFile = symbols.filter(s => isCurrentFile(s))
const symbolsOtherFiles = symbols
.filter(s => !isCurrentFile(s))
// Remove identical symbols
.filter(
symbolOtherFiles =>
!symbolsCurrentFile.some(
symbolCurrentFile =>
getSymbolId(symbolCurrentFile) === getSymbolId(symbolOtherFiles),
),
)
return uniqueBasedOnHash(
[...symbolsCurrentFile, ...symbolsOtherFiles],
getSymbolId,
).map((symbol: LSP.SymbolInformation) => ({
label: symbol.name,
kind: symbolKindToCompletionKind(symbol.kind),
data: {
name: symbol.name,
type: CompletionItemDataType.Symbol,
},
documentation:
symbol.location.uri !== currentUri
? `${symbolKindToDescription(symbol.kind)} defined in ${path.relative(
currentUri,
symbol.location.uri,
)}`
: undefined,
}))
}
function symbolKindToCompletionKind(s: LSP.SymbolKind): LSP.CompletionItemKind {
switch (s) {
case LSP.SymbolKind.File:
return LSP.CompletionItemKind.File
case LSP.SymbolKind.Module:
case LSP.SymbolKind.Namespace:
case LSP.SymbolKind.Package:
return LSP.CompletionItemKind.Module
case LSP.SymbolKind.Class:
return LSP.CompletionItemKind.Class
case LSP.SymbolKind.Method:
return LSP.CompletionItemKind.Method
case LSP.SymbolKind.Property:
return LSP.CompletionItemKind.Property
case LSP.SymbolKind.Field:
return LSP.CompletionItemKind.Field
case LSP.SymbolKind.Constructor:
return LSP.CompletionItemKind.Constructor
case LSP.SymbolKind.Enum:
return LSP.CompletionItemKind.Enum
case LSP.SymbolKind.Interface:
return LSP.CompletionItemKind.Interface
case LSP.SymbolKind.Function:
return LSP.CompletionItemKind.Function
case LSP.SymbolKind.Variable:
return LSP.CompletionItemKind.Variable
case LSP.SymbolKind.Constant:
return LSP.CompletionItemKind.Constant
case LSP.SymbolKind.String:
case LSP.SymbolKind.Number:
case LSP.SymbolKind.Boolean:
case LSP.SymbolKind.Array:
case LSP.SymbolKind.Key:
case LSP.SymbolKind.Null:
return LSP.CompletionItemKind.Text
case LSP.SymbolKind.Object:
return LSP.CompletionItemKind.Module
case LSP.SymbolKind.EnumMember:
return LSP.CompletionItemKind.EnumMember
case LSP.SymbolKind.Struct:
return LSP.CompletionItemKind.Struct
case LSP.SymbolKind.Event:
return LSP.CompletionItemKind.Event
case LSP.SymbolKind.Operator:
return LSP.CompletionItemKind.Operator
case LSP.SymbolKind.TypeParameter:
return LSP.CompletionItemKind.TypeParameter
default:
return LSP.CompletionItemKind.Text
}
}
function symbolKindToDescription(s: LSP.SymbolKind): string {
switch (s) {
case LSP.SymbolKind.Function:
return 'Function'
case LSP.SymbolKind.Variable:
return 'Variable'
default:
return 'Keyword'
}
}

@@ -1,17 +0,4 @@

import * as Fs from 'fs'
import * as glob from 'glob'
import * as Os from 'os'
export function getStats(path: string): Promise<Fs.Stats> {
return new Promise((resolve, reject) => {
Fs.lstat(path, (err, stat) => {
if (err) {
reject(err)
} else {
resolve(stat)
}
})
})
}
// from https://github.com/sindresorhus/untildify/blob/f85a087418aeaa2beb56fe2684fe3b64fc8c588d/index.js#L11

@@ -18,0 +5,0 @@ export function untildify(pathWithTilde: string): string {

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc