Skip to content

Commit 882e4a9

Browse files
committed
feat: get extractor by file extension
1 parent 53036de commit 882e4a9

11 files changed

Lines changed: 161 additions & 181 deletions

File tree

src/composables/workspace-context.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { Uri } from 'vscode'
22
import { SUPPORTED_DOCUMENT_PATTERN } from '#constants'
3-
import { isSupportedDependencyDocument } from '#extractors'
43
import { logger } from '#state'
4+
import { isSupportedDependencyDocument } from '#utils/file'
55
import { getWorkspaceContext } from '#utils/workspace'
66
import { useActiveTextEditor, useDocumentText, useFileSystemWatcher, watch } from 'reactive-vscode'
77
import { workspace } from 'vscode'

src/extractors/index.ts

Lines changed: 20 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,21 @@
1-
import type { PackageManager } from '#types/context'
2-
import type { Extractor, PackageManifestExtractor, WorkspaceCatalogExtractor } from '#types/extractor'
3-
import type { TextDocument, Uri } from 'vscode'
4-
import { PACKAGE_JSON_BASENAME, PNPM_WORKSPACE_BASENAME, YARN_WORKSPACE_BASENAME } from '#constants'
5-
import { basename } from 'pathe'
6-
import { PackageJsonDocumentExtractor } from './package-json'
7-
import { WorkspaceCatalogDocumentExtractor } from './workspace-catalog'
8-
9-
interface BaseExtractorEntry<TExtractor extends Extractor = Extractor> {
10-
basename: string
11-
extractor: TExtractor
12-
}
13-
14-
interface PackageManifestExtractorEntry extends BaseExtractorEntry<PackageManifestExtractor> {}
15-
16-
interface WorkspaceCatalogExtractorEntry extends BaseExtractorEntry<WorkspaceCatalogExtractor> {
17-
packageManager: Exclude<PackageManager, 'npm'>
18-
}
19-
20-
const packageJsonExtractor = new PackageJsonDocumentExtractor()
21-
const workspaceCatalogExtractor = new WorkspaceCatalogDocumentExtractor()
22-
23-
export const packageManifestExtractorEntry: PackageManifestExtractorEntry = {
24-
basename: PACKAGE_JSON_BASENAME,
25-
extractor: packageJsonExtractor,
26-
}
27-
28-
export const workspaceCatalogExtractorEntries: WorkspaceCatalogExtractorEntry[] = [
29-
{
30-
basename: PNPM_WORKSPACE_BASENAME,
31-
extractor: workspaceCatalogExtractor,
32-
packageManager: 'pnpm',
33-
},
34-
{
35-
basename: YARN_WORKSPACE_BASENAME,
36-
extractor: workspaceCatalogExtractor,
37-
packageManager: 'yarn',
38-
},
39-
]
40-
41-
const SUPPORTED_BASENAMES = new Set([
42-
PACKAGE_JSON_BASENAME,
43-
PNPM_WORKSPACE_BASENAME,
44-
YARN_WORKSPACE_BASENAME,
45-
])
46-
47-
export function isSupportedDependencyDocument(documentOrUri: TextDocument | Uri): boolean {
48-
const path = 'uri' in documentOrUri ? documentOrUri.uri.path : documentOrUri.path
49-
return SUPPORTED_BASENAMES.has(basename(path))
1+
import { extname } from 'pathe'
2+
import { JsonExtractor } from './json'
3+
import { YamlExtractor } from './yaml'
4+
5+
const jsonExtractor = new JsonExtractor()
6+
const yamlExtractor = new YamlExtractor()
7+
8+
const extractorsByExtension = {
9+
'.json': jsonExtractor,
10+
'.yaml': yamlExtractor,
11+
'.yml': yamlExtractor,
12+
} as const satisfies Record<string, JsonExtractor | YamlExtractor>
13+
14+
type ExtractorByExt<T extends string>
15+
= T extends `${string}.json` ? JsonExtractor
16+
: T extends `${string}.yaml` | `${string}.yml` ? YamlExtractor
17+
: JsonExtractor | YamlExtractor | undefined
18+
19+
export function getExtractor<T extends string>(filename: T): ExtractorByExt<T> {
20+
return extractorsByExtension[extname(filename) as keyof typeof extractorsByExtension] as ExtractorByExt<T>
5021
}
Lines changed: 31 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { DependencyCategory, DependencyInfo, JsonNode, OffsetRange, PackageManifestExtractor } from '#types/extractor'
1+
import type { BaseExtractor, DependencyCategory, DependencyInfo, JsonNode, OffsetRange, PackageManifestExtractor } from '#types/extractor'
22
import type { Engines } from 'fast-npm-meta'
33
import { findNodeAtLocation, parseTree } from 'jsonc-parser'
44

@@ -9,31 +9,19 @@ const DEPENDENCY_SECTIONS: DependencyCategory[] = [
99
'optionalDependencies',
1010
]
1111

12-
export class PackageJsonDocumentExtractor implements PackageManifestExtractor<JsonNode> {
12+
export class JsonExtractor implements PackageManifestExtractor, BaseExtractor<JsonNode> {
1313
parse = (text: string) => parseTree(text) ?? null
1414

15-
private getStringValue(root: JsonNode, key: string): string | undefined {
15+
#getStringValue(root: JsonNode, key: string): string | undefined {
1616
const node = findNodeAtLocation(root, [key])
1717
return typeof node?.value === 'string' ? node.value : undefined
1818
}
1919

20-
getPackageName(root: JsonNode): string {
21-
return this.getStringValue(root, 'name')!
22-
}
23-
24-
getPackageVersion(root: JsonNode): string {
25-
return this.getStringValue(root, 'version')!
26-
}
27-
28-
getPackageManager(root: JsonNode): string | undefined {
29-
return this.getStringValue(root, 'packageManager')
30-
}
31-
32-
private getStringNodeRange(node: JsonNode): OffsetRange {
20+
#getStringNodeRange(node: JsonNode): OffsetRange {
3321
return [node.offset + 1, node.offset + node.length - 1]
3422
}
3523

36-
private parseDependencyNode(node: JsonNode, category: DependencyCategory): DependencyInfo | undefined {
24+
#parseDependencyNode(node: JsonNode, category: DependencyCategory): DependencyInfo | undefined {
3725
if (!node.children?.length)
3826
return
3927

@@ -50,31 +38,12 @@ export class PackageJsonDocumentExtractor implements PackageManifestExtractor<Js
5038
category,
5139
rawName: nameNode.value,
5240
rawSpec: specNode.value,
53-
nameRange: this.getStringNodeRange(nameNode),
54-
specRange: this.getStringNodeRange(specNode),
41+
nameRange: this.#getStringNodeRange(nameNode),
42+
specRange: this.#getStringNodeRange(specNode),
5543
}
5644
}
5745

58-
getDependenciesInfo(root: JsonNode) {
59-
const result: DependencyInfo[] = []
60-
61-
DEPENDENCY_SECTIONS.forEach((section) => {
62-
const node = findNodeAtLocation(root, [section])
63-
if (!node || !node.children)
64-
return
65-
66-
for (const dep of node.children) {
67-
const info = this.parseDependencyNode(dep, section)
68-
69-
if (info)
70-
result.push(info)
71-
}
72-
})
73-
74-
return result
75-
}
76-
77-
getEngines(root: JsonNode): Engines | undefined {
46+
#getEngines(root: JsonNode): Engines | undefined {
7847
const enginesNode = findNodeAtLocation(root, ['engines'])
7948
if (enginesNode?.type !== 'object' || !enginesNode.children?.length)
8049
return
@@ -93,16 +62,35 @@ export class PackageJsonDocumentExtractor implements PackageManifestExtractor<Js
9362
return engines
9463
}
9564

65+
getDependenciesInfo(root: JsonNode) {
66+
const result: DependencyInfo[] = []
67+
68+
DEPENDENCY_SECTIONS.forEach((section) => {
69+
const node = findNodeAtLocation(root, [section])
70+
if (!node || !node.children)
71+
return
72+
73+
for (const dep of node.children) {
74+
const info = this.#parseDependencyNode(dep, section)
75+
76+
if (info)
77+
result.push(info)
78+
}
79+
})
80+
81+
return result
82+
}
83+
9684
getPackageManifestInfo(text: string) {
9785
const root = this.parse(text)
9886
if (!root)
9987
return
10088

10189
return {
102-
name: this.getPackageName(root),
103-
version: this.getPackageVersion(root),
104-
packageManager: this.getPackageManager(root),
105-
engines: this.getEngines(root),
90+
name: this.#getStringValue(root, 'name')!,
91+
version: this.#getStringValue(root, 'version')!,
92+
packageManager: this.#getStringValue(root, 'packageManager'),
93+
engines: this.#getEngines(root),
10694
dependencies: this.getDependenciesInfo(root),
10795
}
10896
}
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { DependencyInfo, OffsetRange, WorkspaceCatalogExtractor, YamlNode } from '#types/extractor'
1+
import type { BaseExtractor, DependencyInfo, OffsetRange, WorkspaceCatalogExtractor, YamlNode } from '#types/extractor'
22
import type { Pair, Scalar, YAMLMap } from 'yaml'
33
import { isMap, isPair, isScalar, parseDocument } from 'yaml'
44

@@ -15,27 +15,67 @@ type CatalogEntryVisitor = (
1515
},
1616
) => boolean | void
1717

18-
export class WorkspaceCatalogDocumentExtractor implements WorkspaceCatalogExtractor<YamlNode> {
18+
export class YamlExtractor implements WorkspaceCatalogExtractor, BaseExtractor<YamlNode> {
1919
parse = (text: string) => parseDocument(text).contents
2020

21-
private getScalarRange(node: YamlNode): OffsetRange {
21+
#getScalarRange(node: YamlNode): OffsetRange {
2222
const [start, end] = node.range!
2323
return [start, end]
2424
}
2525

26+
#traverseCatalog(
27+
catalog: unknown,
28+
meta: {
29+
category: 'catalog' | 'catalogs'
30+
categoryName?: string
31+
},
32+
callback: CatalogEntryVisitor,
33+
): boolean {
34+
if (!isPair(catalog))
35+
return false
36+
if (!isMap(catalog.value))
37+
return false
38+
39+
for (const item of catalog.value.items) {
40+
if (isScalar(item.key) && isScalar(item.value)) {
41+
if (callback(item as CatalogEntry, meta))
42+
return true
43+
}
44+
}
45+
46+
return false
47+
}
48+
49+
#traverseCatalogs(root: YAMLMap, callback: CatalogEntryVisitor): boolean {
50+
const catalog = root.items.find((i) => isScalar(i.key) && i.key.value === CATALOG_SECTION)
51+
if (this.#traverseCatalog(catalog, { category: 'catalog' }, callback))
52+
return true
53+
54+
const catalogs = root.items.find((i) => isScalar(i.key) && i.key.value === CATALOGS_SECTION)
55+
if (isMap(catalogs?.value)) {
56+
for (const c of catalogs.value.items) {
57+
const categoryName = isScalar(c.key) ? String(c.key.value) : undefined
58+
if (this.#traverseCatalog(c, { category: 'catalogs', categoryName }, callback))
59+
return true
60+
}
61+
}
62+
63+
return false
64+
}
65+
2666
getDependenciesInfo(root: YamlNode): DependencyInfo[] {
2767
if (!isMap(root))
2868
return []
2969

3070
const result: DependencyInfo[] = []
3171

32-
this.traverseCatalogs(root, (item, meta) => {
72+
this.#traverseCatalogs(root, (item, meta) => {
3373
result.push({
3474
category: meta.category,
3575
rawName: String(item.key.value),
3676
rawSpec: String(item.value!.value),
37-
nameRange: this.getScalarRange(item.key),
38-
specRange: this.getScalarRange(item.value!),
77+
nameRange: this.#getScalarRange(item.key),
78+
specRange: this.#getScalarRange(item.value!),
3979
categoryName: meta.categoryName,
4080
})
4181
})
@@ -62,44 +102,4 @@ export class WorkspaceCatalogDocumentExtractor implements WorkspaceCatalogExtrac
62102
catalogs: Object.keys(catalogs).length > 0 ? catalogs : undefined,
63103
}
64104
}
65-
66-
private traverseCatalogs(root: YAMLMap, callback: CatalogEntryVisitor): boolean {
67-
const catalog = root.items.find((i) => isScalar(i.key) && i.key.value === CATALOG_SECTION)
68-
if (this.traverseCatalog(catalog, { category: 'catalog' }, callback))
69-
return true
70-
71-
const catalogs = root.items.find((i) => isScalar(i.key) && i.key.value === CATALOGS_SECTION)
72-
if (isMap(catalogs?.value)) {
73-
for (const c of catalogs.value.items) {
74-
const categoryName = isScalar(c.key) ? String(c.key.value) : undefined
75-
if (this.traverseCatalog(c, { category: 'catalogs', categoryName }, callback))
76-
return true
77-
}
78-
}
79-
80-
return false
81-
}
82-
83-
private traverseCatalog(
84-
catalog: unknown,
85-
meta: {
86-
category: 'catalog' | 'catalogs'
87-
categoryName?: string
88-
},
89-
callback: CatalogEntryVisitor,
90-
): boolean {
91-
if (!isPair(catalog))
92-
return false
93-
if (!isMap(catalog.value))
94-
return false
95-
96-
for (const item of catalog.value.items) {
97-
if (isScalar(item.key) && isScalar(item.value)) {
98-
if (callback(item as CatalogEntry, meta))
99-
return true
100-
}
101-
}
102-
103-
return false
104-
}
105105
}

src/providers/completion-item/index.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ import { watchEffect } from 'reactive-vscode'
44
import { languages } from 'vscode'
55
import { VersionCompletionItemProvider } from './version'
66

7-
export const VERSION_TRIGGER_CHARACTERS = [':', '^', '~', '.', ...Array.from({ length: 10 }).map((_, i) => `${i}`)]
8-
97
export function useCompletionItem() {
108
watchEffect((onCleanup) => {
119
if (config.completion.version === 'off')

src/providers/diagnostics/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import type { OffsetRange } from '#types/extractor'
33
import type { Awaitable } from 'reactive-vscode'
44
import type { Diagnostic, TextDocument, Uri } from 'vscode'
55
import { SUPPORTED_DOCUMENT_PATTERN } from '#constants'
6-
import { isSupportedDependencyDocument } from '#extractors'
76
import { config, logger } from '#state'
87
import { offsetRangeToRange } from '#utils/ast'
8+
import { isSupportedDependencyDocument } from '#utils/file'
99
import { getResolvedDependencies } from '#utils/workspace'
1010
import { debounce } from 'perfect-debounce'
1111
import { computed, nextTick, useActiveTextEditor, useDisposable, useDocumentText, useFileSystemWatcher, watch } from 'reactive-vscode'

src/providers/diagnostics/rules/engine-mismatch.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export function resolveEngineMismatches(
4949
}
5050

5151
export const checkEngineMismatch: DiagnosticRule = async ({ uri, dep, pkg }) => {
52-
if (!isPackageManifestPath(uri))
52+
if (!isPackageManifestPath(uri.path))
5353
return
5454

5555
const resolvedVersion = await dep.resolvedVersion()

src/types/extractor.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,15 @@ export interface WorkspaceCatalogInfo extends DependenciesInfo {
4242
catalogs?: Record<string, Record<string, string>>
4343
}
4444

45-
export interface Extractor<T = any> {
45+
export interface BaseExtractor<T = any> {
4646
parse: (text: string) => T | null | undefined
4747
getDependenciesInfo: (root: T) => DependencyInfo[]
48-
getEngines?: (root: T) => Engines | undefined
4948
}
5049

51-
export interface PackageManifestExtractor<T = any> extends Extractor<T> {
50+
export interface PackageManifestExtractor {
5251
getPackageManifestInfo: (text: string) => PackageManifestInfo | undefined
5352
}
5453

55-
export interface WorkspaceCatalogExtractor<T = any> extends Extractor<T> {
54+
export interface WorkspaceCatalogExtractor {
5655
getWorkspaceCatalogInfo: (text: string) => WorkspaceCatalogInfo | undefined
5756
}

0 commit comments

Comments
 (0)