Skip to content

Commit

Permalink
refactor: encapsulation parseJsonc
Browse files Browse the repository at this point in the history
  • Loading branch information
tjx666 committed Apr 2, 2024
1 parent 9088f07 commit 7fcf17e
Show file tree
Hide file tree
Showing 12 changed files with 65 additions and 83 deletions.
10 changes: 2 additions & 8 deletions src/codeLens/packageJsonDependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { CodeLens, workspace } from 'vscode';

import { configuration, configurationKeys } from '../configuration';
import { commands } from '../utils/constants';
import { jsoncStringNodeToRange } from '../utils/editor';
import { jsoncStringNodeToRange, parseJsonc } from '../utils/jsonc';
import type { SearchImportsMatch } from '../utils/searchImports';
import { BaseCodeLensProvider } from './BaseCodeLensProvider';

Expand Down Expand Up @@ -95,13 +95,7 @@ export class PackageJsonDependenciesCodeLensProvider extends BaseCodeLensProvide
_token: CancellationToken,
): Promise<CodeLens[] | undefined> {
const packageJson = document.getText();
const { parseTree } = await import('jsonc-parser');
let root: Node | undefined;
try {
root = parseTree(packageJson);
} catch {
return;
}
const root = await parseJsonc(packageJson);
if (!root) return;

const dependencies: Dependency[] = (
Expand Down
18 changes: 5 additions & 13 deletions src/codeLens/packageJsonFiles.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { dirname, resolve } from 'node:path';

import type { Node } from 'jsonc-parser';
import type { CancellationToken, ExtensionContext, TextDocument } from 'vscode';
import { CodeLens, Range } from 'vscode';

import { configuration, configurationKeys } from '../configuration';
import { jsoncStringNodeToRange } from '../utils/editor';
import { pathExists } from '../utils/fs';
import { jsoncStringNodeToRange, parseJsonc } from '../utils/jsonc';
import { GlobCodeLensProvider } from './GlobCodeLensProvider';

const filesLiteral = 'files';
Expand Down Expand Up @@ -53,19 +52,12 @@ export class PackageJsonFilesCodeLensProvider extends GlobCodeLensProvider {
super.getCodeLenses(document, _token);

const { globby } = await import('globby');
const { parseTree, findNodeAtLocation } = await import('jsonc-parser');
const { findNodeAtLocation } = await import('jsonc-parser');

const filePath = document.uri.fsPath;
const packageJson = document.getText();
let root: Node | undefined;
try {
// jsonc has builtin cache
root = parseTree(packageJson);
} catch {
return;
}

const root = await parseJsonc(packageJson);
if (!root) return;

const filesPropertyNode = findNodeAtLocation(root, ['files']);
if (
!filesPropertyNode ||
Expand Down Expand Up @@ -98,7 +90,7 @@ export class PackageJsonFilesCodeLensProvider extends GlobCodeLensProvider {
const totalFiles: Set<string> = new Set();
const codeLensList: CodeLens[] = [];
const promises: Array<Promise<string[]>> = [];
const cwd = dirname(filePath);
const cwd = dirname(document.uri.fsPath);
for (const item of patternList) {
const codeLens = new CodeLens(item.range);
codeLensList.push(codeLens);
Expand Down
12 changes: 3 additions & 9 deletions src/codeLens/packageJsonVersion.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import ExpiryMap from 'expiry-map';
import type { Node } from 'jsonc-parser';
import pMemoize from 'p-memoize';
import fetchPackageJson from 'package-json';
import type { CancellationToken, ExtensionContext, Range, TextDocument } from 'vscode';
import { CodeLens } from 'vscode';

import { configuration, configurationKeys } from '../configuration';
import { commands } from '../utils/constants';
import { jsoncStringNodeToRange } from '../utils/editor';
import { jsoncStringNodeToRange, parseJsonc } from '../utils/jsonc';
import { BaseCodeLensProvider } from './BaseCodeLensProvider';

interface CodeLensData {
Expand Down Expand Up @@ -57,13 +56,8 @@ export class PackageJsonVersionCodeLensProvider extends BaseCodeLensProvider {
_token: CancellationToken,
): Promise<CodeLens[] | undefined> {
const packageJson = document.getText();
const { parseTree, findNodeAtLocation } = await import('jsonc-parser');
let root: Node | undefined;
try {
root = parseTree(packageJson);
} catch {
return;
}
const { findNodeAtLocation } = await import('jsonc-parser');
const root = await parseJsonc(packageJson);
if (!root) return;

const codeLensList: CodeLens[] = [];
Expand Down
2 changes: 1 addition & 1 deletion src/codeLens/pnpmWorkspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { CancellationToken, ExtensionContext, TextDocument } from 'vscode';
import { CodeLens, Range } from 'vscode';

import { configuration, configurationKeys } from '../configuration';
import { jsoncStringNodeToRange } from '../utils/editor';
import { jsoncStringNodeToRange } from '../utils/jsonc';
import { GlobCodeLensProvider } from './GlobCodeLensProvider';

const packagesLiteral = 'packages';
Expand Down
13 changes: 8 additions & 5 deletions src/commands/addMissingDeps.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
import { dirname } from 'node:path';

import * as jsonc from 'jsonc-parser';
import type { TextEditor } from 'vscode';
import { Range } from 'vscode';

import { parseJsonc } from '../utils/jsonc';
import { getPackageInfoFromNpmView } from '../utils/pkg';
import { searchUsedDeps } from '../utils/searchUsedDeps';

export async function addMissingDeps(editor: TextEditor) {
const { findNodeAtLocation, getNodeValue, modify } = await import('jsonc-parser');
const { document } = editor;

const pkgJsonPath = document.fileName;
const cwd = dirname(pkgJsonPath);
const missingDeps = await searchUsedDeps(cwd);

const pkgJson = document.getText();
const root = jsonc.parseTree(pkgJson)!;
const root = await parseJsonc(pkgJson)!;
if (!root) return;

const dependenciesNodePath = ['dependencies'];
const dependenciesNode = jsonc.findNodeAtLocation(root, dependenciesNodePath);
const dependencies = dependenciesNode ? jsonc.getNodeValue(dependenciesNode) : {};
const dependenciesNode = findNodeAtLocation(root, dependenciesNodePath);
const dependencies = dependenciesNode ? getNodeValue(dependenciesNode) : {};
const missingDepsObj = missingDeps.reduce(
(obj, packageName) => {
if (packageName in dependencies) return obj;
Expand Down Expand Up @@ -50,7 +53,7 @@ export async function addMissingDeps(editor: TextEditor) {
await Promise.all(getVersionPromises);

// keep origin json format
const edits = jsonc.modify(pkgJson, dependenciesNodePath, newDependencies, {
const edits = modify(pkgJson, dependenciesNodePath, newDependencies, {
formattingOptions: {
tabSize: editor.options.tabSize as number,
},
Expand Down
6 changes: 3 additions & 3 deletions src/definitions/dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
Uri,
} from 'vscode';

import { getFileRange, jsoncStringNodeToRange } from '../utils/editor';
import { getFileRange } from '../utils/editor';
import { jsoncStringNodeToRange } from '../utils/jsonc';
import { findPackagePath, getPkgNameAndVersionFromDocPosition } from '../utils/pkg';

export class DependenciesDefinitionProvider implements DefinitionProvider {
Expand All @@ -22,8 +23,7 @@ export class DependenciesDefinitionProvider implements DefinitionProvider {
const pkgInfo = await getPkgNameAndVersionFromDocPosition(document, position);
if (!pkgInfo) return;

const pkgJsonPath = await fs.realpath(document.uri.fsPath);
const pkgPath = await findPackagePath(pkgInfo.name, pkgJsonPath);
const pkgPath = await findPackagePath(pkgInfo.name, document.uri.fsPath);
if (!pkgPath) return;

const [targetUri, targetRange] = await Promise.all([
Expand Down
8 changes: 2 additions & 6 deletions src/hoverTooltips/dependencies.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import fs from 'node:fs/promises';

import type { CancellationToken, HoverProvider, Position, TextDocument } from 'vscode';
import { Hover } from 'vscode';

Expand All @@ -14,17 +12,15 @@ export class DependenciesHoverProvider implements HoverProvider {
token: CancellationToken,
): Promise<Hover | undefined> {
const pkgNameAndVersion = await getPkgNameAndVersionFromDocPosition(document, position);

if (!pkgNameAndVersion) return;

const { name, version } = pkgNameAndVersion;
const pkgJsonPath = await fs.realpath(document.uri.fsPath);
const info = await getPackageInfo(name, {
packageInstalledPath: (await findPackagePath(name, pkgJsonPath))?.pkgDir,
packageInstalledPath: (await findPackagePath(name, document.uri.fsPath))?.pkgDir,
searchVersionRange: version,
fetchBundleSize: true,
remoteFetch: true,
// Note: 当 package.json 中定义了依赖类似"path"这样与 node 内置模块相同名称的包时,永远认为他不是使用 node 内置模块
// Note: 当 package.json 中定义了依赖类似 "path" 这样与 node 内置模块相同名称的包时,永远认为他不是使用 node 内置模块
skipBuiltinModuleCheck: true,
token,
});
Expand Down
11 changes: 3 additions & 8 deletions src/hoverTooltips/npmScripts.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { dirname } from 'node:path';

import type { Node } from 'jsonc-parser';
import type { CancellationToken, HoverProvider, Position, TextDocument } from 'vscode';
import { Hover, MarkdownString, Range } from 'vscode';

import { commands } from '../utils/constants';
import { parseJsonc } from '../utils/jsonc';

export class NpmScriptsHoverProvider implements HoverProvider {
async provideHover(
Expand All @@ -15,13 +15,8 @@ export class NpmScriptsHoverProvider implements HoverProvider {
const filePath = document.uri.fsPath;
const packageJson = document.getText();

const { parseTree, findNodeAtOffset, findNodeAtLocation } = await import('jsonc-parser');
let root: Node | undefined;
try {
root = parseTree(packageJson);
} catch {
return;
}
const { findNodeAtOffset, findNodeAtLocation } = await import('jsonc-parser');
const root = await parseJsonc(packageJson);
if (!root) return;

const scriptNameNode = findNodeAtOffset(root, document.offsetAt(position));
Expand Down
10 changes: 1 addition & 9 deletions src/utils/editor.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { readFile } from 'node:fs/promises';

import type { Node } from 'jsonc-parser';
import type { Location, TextDocument, Uri } from 'vscode';
import type { Location, Uri } from 'vscode';
import vscode, { Position, Range } from 'vscode';

export function goToLocation(uri: Uri, position: Position, location: Location) {
Expand All @@ -16,13 +15,6 @@ export function goToLocation(uri: Uri, position: Position, location: Location) {
);
}

export function jsoncStringNodeToRange(document: TextDocument, node: Node): Range {
return new Range(
document.positionAt(node.offset + 1),
document.positionAt(node.offset + node.length - 1),
);
}

export async function getFileRange(filePath: string) {
const textContent = await readFile(filePath, 'utf8');
const lines = textContent.split(/\r?\n/);
Expand Down
21 changes: 21 additions & 0 deletions src/utils/jsonc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { Node } from 'jsonc-parser';
import type { TextDocument } from 'vscode';
import { Range } from 'vscode';

export async function parseJsonc(json: string) {
const jsoncParser = await import('jsonc-parser');
let root: Node | undefined;
try {
root = jsoncParser.parseTree(json);
} catch {
return;
}
return root;
}

export function jsoncStringNodeToRange(document: TextDocument, node: Node): Range {
return new Range(
document.positionAt(node.offset + 1),
document.positionAt(node.offset + node.length - 1),
);
}
1 change: 1 addition & 0 deletions src/utils/pkg-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const fetchPackageJson = promiseDebounce(_fetchPackageJson, (pkgNameAndRangeVers
});
const remotePkgMetadataCache = new LRUCache<string, PackageJson>({
max: 100,
// 10 mins
ttl: 1000 * 60 * 10,
});
async function getRemotePackageJsonData(pkgName: string, pkgVersion?: string) {
Expand Down
36 changes: 15 additions & 21 deletions src/utils/pkg.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
import fs from 'node:fs/promises';
import { dirname, resolve } from 'node:path';

import { execa } from 'execa';
import type { Node } from 'jsonc-parser';
import type { JsonValue } from 'type-fest';
import validatePkgName from 'validate-npm-package-name';
import type { Position, TextDocument } from 'vscode';

import { NODE_MODULES } from './constants';
import { isFile, pathExists } from './fs';
import { NODE_MODULES, PACKAGE_JSON } from './constants';
import { pathExists } from './fs';
import { parseJsonc } from './jsonc';
import { getRootOfPath, trimRightSlash } from './path';

export async function findPackagePath(packageName: string, startPath: string, endPath?: string) {
export async function findPackagePath(packageName: string, baseFilePath: string, endPath?: string) {
if (endPath === undefined) {
endPath = await getRootOfPath(startPath);
endPath = await getRootOfPath(baseFilePath);
}

startPath = trimRightSlash(startPath);
// maybe soft symbolic link
baseFilePath = await fs.realpath(baseFilePath);

endPath = trimRightSlash(endPath);
if (await isFile(startPath)) {
startPath = dirname(startPath);
}
let currentDirPath = startPath;
let currentDirPath = dirname(baseFilePath);
let end = false;
let pkgDir = '';

do {
pkgDir = resolve(currentDirPath, NODE_MODULES, packageName);
const pkgJsonPath = resolve(pkgDir, 'package.json');
const pkgJsonPath = resolve(pkgDir, PACKAGE_JSON);
// eslint-disable-next-line no-await-in-loop
if ((await Promise.all([pathExists(pkgDir), pathExists(pkgJsonPath)])).every(Boolean)) {
return {
Expand All @@ -45,15 +45,8 @@ export async function getPkgNameAndVersionFromDocPosition(
document: TextDocument,
position: Position,
) {
const jsoncParser = await import('jsonc-parser');

const pkgJson = document.getText();
let root: Node | undefined;
try {
root = jsoncParser.parseTree(pkgJson);
} catch {
return;
}
const root = await parseJsonc(pkgJson);
if (!root) return;

const dependenciesNodePath = [
Expand All @@ -63,11 +56,12 @@ export async function getPkgNameAndVersionFromDocPosition(
'optionalDependencies',
];

const nameNode = jsoncParser.findNodeAtOffset(root, document.offsetAt(position));
const { findNodeAtOffset, findNodeAtLocation } = await import('jsonc-parser');
const nameNode = findNodeAtOffset(root, document.offsetAt(position));
if (!nameNode || !nameNode.parent) return;

const dependenciesNodes = dependenciesNodePath.map((path) =>
jsoncParser.findNodeAtLocation(root, path.split('.')),
findNodeAtLocation(root, path.split('.')),
);

const versionNode = nameNode.parent.children![1];
Expand Down

0 comments on commit 7fcf17e

Please sign in to comment.