Skip to content

Commit

Permalink
feat(baseUrl): add extended support
Browse files Browse the repository at this point in the history
- reads subfolder of baseurl and adds them as mappings

Fixes #196
  • Loading branch information
ChristianKohler authored Feb 11, 2022
1 parent a8af32b commit a2307ad
Show file tree
Hide file tree
Showing 27 changed files with 342 additions and 73 deletions.
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,18 @@ Pathintellisense uses the ts.config.compilerOptions.baseUrl as a mapping. So no

For example:

```markdown
# Folderstructure

src/
module-a
foo.ts
module-b
```

```javascript
// tsconfig

{
"baseUrl": "src",
}
Expand All @@ -71,7 +82,7 @@ would allow to type:

```javascript
{
import {} from "src/mymodule";
import {} from "module-a/foo.ts";
}
```

Expand Down
6 changes: 5 additions & 1 deletion src/configuration/configuration.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ export interface Config {
absolutePathToWorkspace: boolean;
absolutePathTo: string | null;
showOnAbsoluteSlash: boolean;
filesExclude: { [key: string]: string };
filesExclude: FilesExclude;
}

export interface FilesExclude {
[key: string]: string;
}

export interface Mapping {
Expand Down
8 changes: 7 additions & 1 deletion src/configuration/configuration.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,15 @@ async function getMappings(
): Promise<Mapping[]> {
const mappings = parseMappings(configuration["mappings"]);
const ignoreTsConfigBaseUrl = configuration["ignoreTsConfigBaseUrl"];
const showHiddenFiles = configuration["showHiddenFiles"];
const filesExclude = configuration["exclude"];
const tsConfigMappings = await (ignoreTsConfigBaseUrl
? []
: getWorkfolderTsConfigConfiguration(workfolder));
: getWorkfolderTsConfigConfiguration(
workfolder,
showHiddenFiles,
filesExclude
));
const allMappings = [...mappings, ...tsConfigMappings];
return replaceWorkspaceFolder(allMappings, workfolder);
}
Expand Down
188 changes: 160 additions & 28 deletions src/configuration/tsconfig.service.ts
Original file line number Diff line number Diff line change
@@ -1,68 +1,196 @@
import * as vscode from "vscode";
import * as JSON5 from "json5";
import { Mapping } from "./configuration.interface";
import { FilesExclude, Mapping } from "./configuration.interface";
import { join } from "path";
import { getChildrenOfPath } from "../utils/file-utills";

export const getWorkfolderTsConfigConfiguration = memoize(async function (
workfolder: vscode.WorkspaceFolder
workfolder: vscode.WorkspaceFolder,
showHiddenFiles: boolean,
filesExclude: FilesExclude
): Promise<Mapping[]> {
const include = new vscode.RelativePattern(workfolder, "[tj]sconfig.json");
const exclude = new vscode.RelativePattern(workfolder, "**/node_modules/**");
const files = await vscode.workspace.findFiles(include, exclude);
const parsedFiles = await findTsConfigFiles(workfolder);

let mappings: Mapping[] = [];

for (const file of files) {
for (const parsedFile of parsedFiles) {
try {
const fileUri = vscode.Uri.file(file.fsPath);
const fileContents = await vscode.workspace.fs.readFile(fileUri);
const parsedFile = JSON5.parse(fileContents.toString());
const newMappings = createMappingsFromWorkspaceConfig(parsedFile);
const newMappings = await createMappingsFromWorkspaceConfig(
parsedFile,
workfolder,
showHiddenFiles,
filesExclude
);
mappings.push(...newMappings);
} catch {}
}

return mappings;
});

export function subscribeToTsConfigChanges(): vscode.Disposable[] {
const disposables: vscode.Disposable[] = [];
export async function subscribeToTsConfigChanges(
context: vscode.ExtensionContext
) {
for (const workfolder of vscode.workspace.workspaceFolders || []) {
/**
* Invalidate Cache when tsconfig changes
*/
let baseUrlWatchers = await subscribeToBaseUrlFolderChanges(workfolder);
context.subscriptions.push(...baseUrlWatchers);

/**
* Invalidate Cache when tsconfig changes
*/
const pattern = new vscode.RelativePattern(workfolder, "[tj]sconfig.json");
const fileWatcher = vscode.workspace.createFileSystemWatcher(pattern);
fileWatcher.onDidChange(() => invalidateCache(workfolder));
disposables.push(fileWatcher);
fileWatcher.onDidChange(async () => {
invalidateCache(workfolder);

// Throw away old base url subscriptions...
for (const disposable of baseUrlWatchers) {
disposable.dispose();
}

// .. and create new subscriptions
baseUrlWatchers = await subscribeToBaseUrlFolderChanges(workfolder);
context.subscriptions.push(...baseUrlWatchers);
});

context.subscriptions.push(fileWatcher);
}
}

/**
* Invalidate Cache when tsconfig changes
*/
async function subscribeToBaseUrlFolderChanges(
workfolder: vscode.WorkspaceFolder
): Promise<vscode.Disposable[]> {
const disposables = [];

const tsConfigs = await findTsConfigFiles(workfolder);

for (const tsconfig of tsConfigs) {
const baseUrl = tsconfig?.compilerOptions?.baseUrl;

if (!baseUrl) {
continue;
}

const patternBaseUrl = new vscode.RelativePattern(
join(workfolder.uri.fsPath, baseUrl),
"*"
);
const fileWatcherBaseUrl = vscode.workspace.createFileSystemWatcher(
patternBaseUrl,
false,
true,
false
);

fileWatcherBaseUrl.onDidCreate((file) =>
invalidateCacheIfDirectory(file, workfolder)
);
fileWatcherBaseUrl.onDidDelete((file) =>
invalidateCacheIfDirectory(file, workfolder)
);
disposables.push(fileWatcherBaseUrl);
}

return disposables;
}

function createMappingsFromWorkspaceConfig(tsconfig: {
compilerOptions: { baseUrl: string };
}): Mapping[] {
async function invalidateCacheIfDirectory(
file: vscode.Uri,
workdfolder: vscode.WorkspaceFolder
) {
const fileStat = await vscode.workspace.fs.stat(file);
if (fileStat.type === vscode.FileType.Directory) {
invalidateCache(workdfolder);
}
}

async function findTsConfigFiles(workfolder: vscode.WorkspaceFolder) {
const include = new vscode.RelativePattern(workfolder, "[tj]sconfig.json");
const exclude = new vscode.RelativePattern(workfolder, "**/node_modules/**");
const files = await vscode.workspace.findFiles(include, exclude);

const parsedFiles = [];

for (const file of files) {
try {
const fileUri = vscode.Uri.file(file.fsPath);
const fileContents = await vscode.workspace.fs.readFile(fileUri);
const parsedFile = JSON5.parse(fileContents.toString());
parsedFiles.push(parsedFile);
} catch {}
}

return parsedFiles;
}

async function createMappingsFromWorkspaceConfig(
tsconfig: {
compilerOptions: { baseUrl: string };
},
workfolder: vscode.WorkspaceFolder,
showHiddenFiles: boolean,
filesExclude: FilesExclude
): Promise<Mapping[]> {
const mappings: Mapping[] = [];
const baseUrl = tsconfig?.compilerOptions?.baseUrl;

if (baseUrl) {
mappings.push({
key: baseUrl,
// value: `${workfolder.uri.path}/${baseUrl}`
value: "${workspaceFolder}/" + baseUrl,
});
if (baseUrl && workfolder) {
const foldersInBaseUrl = await getFoldersInBaseUrl(
workfolder,
baseUrl,
showHiddenFiles,
filesExclude
);

for (const folderInBaseUrl of foldersInBaseUrl) {
mappings.push({
key: folderInBaseUrl,
// value: `${workfolder.uri.path}/${baseUrl}`
value: "${workspaceFolder}/" + baseUrl + "/" + folderInBaseUrl,
});
}
}

// Todo: paths property

return mappings;
}

async function getFoldersInBaseUrl(
workfolder: vscode.WorkspaceFolder,
baseUrl: string,
showHiddenFiles: boolean,
filesExclude: FilesExclude
) {
const allFiles = await getChildrenOfPath(
join(workfolder.uri.fsPath, baseUrl),
showHiddenFiles,
filesExclude
);

const folders = allFiles.filter((file) => !file.isFile);
return folders.map((folder) => folder.file);
}

/** Caching */

let cachedMappings = new Map<string, Mapping[]>();

function memoize(
fn: (workfolder: vscode.WorkspaceFolder) => Promise<Mapping[]>
fn: (
workfolder: vscode.WorkspaceFolder,
showHiddenFiles: boolean,
filesExclude: FilesExclude
) => Promise<Mapping[]>
) {
async function cachedFunction(
workfolder?: vscode.WorkspaceFolder
workfolder?: vscode.WorkspaceFolder,
showHiddenFiles?: boolean,
filesExclude?: FilesExclude
): Promise<Mapping[]> {
if (!workfolder) {
return Promise.resolve([]);
Expand All @@ -74,7 +202,11 @@ function memoize(
if (cachedMapping) {
return cachedMapping;
} else {
let result = await fn(workfolder);
let result = await fn(
workfolder,
showHiddenFiles || false,
filesExclude || {}
);
cachedMappings.set(key, result);
return result;
}
Expand Down
2 changes: 1 addition & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export function activate(context: ExtensionContext) {
/**
* Subscribe to the ts config changes
*/
context.subscriptions.push(...subscribeToTsConfigChanges());
subscribeToTsConfigChanges(context);

/**
* Register Providers
Expand Down
22 changes: 13 additions & 9 deletions src/providers/javascript/javascript.provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,11 @@ function shouldProvide(context: Context, config: Config): boolean {
fromString.startsWith(key)
);

return isImport && (
startsWithDot
|| startsWithMapping
|| (startsWithSlash && config.showOnAbsoluteSlash)
return (
isImport &&
(startsWithDot ||
startsWithMapping ||
(startsWithSlash && config.showOnAbsoluteSlash))
);
}

Expand All @@ -71,10 +72,9 @@ async function provide(
): Promise<vscode.CompletionItem[]> {
const workspace = vscode.workspace.getWorkspaceFolder(context.document.uri);


const rootPath = config.absolutePathTo||(config.absolutePathToWorkspace
? workspace?.uri.fsPath
: undefined);
const rootPath =
config.absolutePathTo ||
(config.absolutePathToWorkspace ? workspace?.uri.fsPath : undefined);

const path = getPathOfFolderToLookupFiles(
context.document.uri.fsPath,
Expand All @@ -83,7 +83,11 @@ async function provide(
config.mappings
);

const childrenOfPath = await getChildrenOfPath(path, config);
const childrenOfPath = await getChildrenOfPath(
path,
config.showHiddenFiles,
config.filesExclude
);

return [
...childrenOfPath.map((child) =>
Expand Down
8 changes: 7 additions & 1 deletion src/test/demo-workspace/demo.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,15 @@
{
"path": "project-three-absolute-changes"
},
{
"path": "project-withBaseUrlRoot"
},
{
"path": "project-withBaseUrlRoot2"
}
],
"settings": {
"typescript.suggest.paths": false,
"javascript.suggest.paths": false,
"javascript.suggest.paths": false
}
}
2 changes: 1 addition & 1 deletion src/test/demo-workspace/project-one/index.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
import {} from "./";
import {} from "./";
Empty file.
Empty file.
4 changes: 2 additions & 2 deletions src/test/demo-workspace/project-one/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"compilerOptions": {
"baseUrl": "baseurl-one"
"baseUrl": "myfolder"
}
}
}
Empty file.
2 changes: 1 addition & 1 deletion src/test/demo-workspace/project-two/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"compilerOptions": {
"baseUrl": "baseurl-two"
"baseUrl": "./"
}
}
1 change: 1 addition & 0 deletions src/test/demo-workspace/project-withBaseUrlRoot/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import {} from "./";
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import { } from "./";
import { } from "otherfolder/";
Empty file.
Empty file.
Empty file.
5 changes: 5 additions & 0 deletions src/test/demo-workspace/project-withBaseUrlRoot/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"compilerOptions": {
"baseUrl": "./"
}
}
Loading

0 comments on commit a2307ad

Please sign in to comment.