Skip to content

Commit df896ce

Browse files
authored
feat(config): load config file on nested packages (#25)
* chore: run build "as a test" * feat(config): load config file on nested packages
1 parent 4243289 commit df896ce

6 files changed

Lines changed: 157 additions & 47 deletions

File tree

.github/workflows/verify.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,8 @@ jobs:
4747
- name: Run type check
4848
run: yarn test:types
4949

50+
- name: Build check
51+
run: yarn build
52+
5053
- name: Run tests
5154
run: yarn test

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ span {
4343

4444
## Configuration
4545

46-
You can either change the settings inside your VSCode (`eufemia.x`) or in a config file (`.vscode-eufemia.json`) you commit to your repository. This way, everyone on the team can use the same settings:
46+
You can either change the settings inside your VSCode (`eufemia.x`) or in a config file (`.vscode-eufemia.json`) you commit to your repository. The config file can be placed on every relative directory level (mono-repo support). This way, everyone on the team can use the same settings. Above the default options:
4747

4848
```json
4949
{
@@ -52,7 +52,7 @@ You can either change the settings inside your VSCode (`eufemia.x`) or in a conf
5252
"autoRemovePrefixZero": true,
5353
"ingoresViaCommand": [],
5454
"addMark": false,
55-
"hover": "onlyMark",
55+
"hover": "onlyMark" /* disabled | always | onlyMark */,
5656
"ingores": [],
5757
"languages": [
5858
"css",
@@ -72,7 +72,7 @@ You can either change the settings inside your VSCode (`eufemia.x`) or in a conf
7272
"right",
7373
"inset"
7474
],
75-
"currentLine": "show",
75+
"currentLine": "show" /* disabled | show */,
7676
"calcMethodName": "calc"
7777
}
7878
```

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"displayName": "DNB Eufemia Tools",
44
"description": "DNB Eufemia Design System Extension",
55
"categories": [],
6-
"version": "1.4.0",
6+
"version": "1.5.0",
77
"publisher": "dnbexperience",
88
"author": "Tobias Høegh <tobias.hoegh@dnb.no>",
99
"license": "SEE LICENSE IN LICENSE",

src/extension.ts

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,17 @@
1-
import { commands, ExtensionContext, languages, workspace } from 'vscode'
1+
import { commands, languages } from 'vscode'
22
import CssRemProvider from './extension/completion'
3-
import {
4-
conf,
5-
eufemiaConfigFileName,
6-
loadConfig,
7-
} from './extension/helpers'
3+
import { conf, initConfig } from './extension/helpers'
84
import { initRules } from './rules'
95
import CssRemHoverProvider from './extension/hover'
106
import { LineAnnotation } from './extension/annotation'
117
import { CSSProcessor } from './extension/convert'
12-
13-
let process: CSSProcessor
8+
import type { ExtensionContext } from 'vscode'
149

1510
export function activate(context: ExtensionContext) {
16-
loadConfig()
11+
initConfig(context)
1712
initRules()
1813

19-
workspace.onDidChangeConfiguration(loadConfig)
20-
21-
process = new CSSProcessor()
14+
let process = new CSSProcessor()
2215

2316
const syntaxLanguages = [...conf.languages]
2417
for (const lang of syntaxLanguages) {
@@ -28,6 +21,7 @@ export function activate(context: ExtensionContext) {
2821
)
2922
context.subscriptions.push(providerDisposable)
3023
}
24+
3125
if (conf.hover !== 'disabled') {
3226
const hoverProvider = new CssRemHoverProvider()
3327
context.subscriptions.push(
@@ -71,16 +65,6 @@ export function activate(context: ExtensionContext) {
7165
if (conf.currentLine !== 'disabled') {
7266
context.subscriptions.push(new LineAnnotation())
7367
}
74-
75-
const eufemiaConfigWatcher = workspace.createFileSystemWatcher(
76-
`**/${eufemiaConfigFileName}`
77-
)
78-
79-
eufemiaConfigWatcher.onDidChange(loadConfig)
80-
eufemiaConfigWatcher.onDidCreate(loadConfig)
81-
eufemiaConfigWatcher.onDidDelete(loadConfig)
82-
83-
context.subscriptions.push(eufemiaConfigWatcher)
8468
}
8569

8670
export function deactivate() {}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { beforeEach, afterEach, describe, expect, it, vi } from 'vitest'
2+
import * as vscode from 'vscode'
3+
import { getConfig, loadConfigFromFile, conf } from '../helpers'
4+
import * as helpers from '../helpers'
5+
import * as nodeFs from 'fs'
6+
import * as nodePath from 'path'
7+
8+
vi.mock('fs', async () => {
9+
const original = (await vi.importActual('fs')) as {
10+
readFileSync: (
11+
file: nodeFs.PathOrFileDescriptor,
12+
encoding: BufferEncoding
13+
) => string
14+
existsSync: (file: nodeFs.PathOrFileDescriptor) => boolean
15+
}
16+
17+
return {
18+
...original,
19+
readFileSync: vi.fn((file, encoding) => {
20+
if (file.includes('package.json')) {
21+
return original.readFileSync(file, encoding)
22+
}
23+
24+
return JSON.stringify({})
25+
}),
26+
}
27+
})
28+
29+
vi.mock('path', async () => {
30+
const original = (await vi.importActual('path')) as {
31+
resolve: (...paths: string[]) => string
32+
}
33+
34+
return {
35+
...original,
36+
}
37+
})
38+
39+
vi.mock('vscode', () => {
40+
const workspace = {
41+
getConfiguration: () => getConfig(),
42+
}
43+
const window = {}
44+
45+
return { workspace, window }
46+
})
47+
48+
describe('loadConfigFromFile', () => {
49+
beforeEach(() => {
50+
const dirPath = '/Users/user/dir'
51+
const fileName = `${dirPath}/file.css`
52+
53+
vscode.window.activeTextEditor = {
54+
document: { fileName },
55+
} as vscode.TextEditor
56+
57+
const readFileSync = vi.fn(() => {
58+
return JSON.stringify({ foo: 'bar' })
59+
})
60+
vi.spyOn(nodeFs, 'readFileSync').mockImplementation(readFileSync)
61+
62+
const existsSync = vi.fn((file) => {
63+
return file.includes('.vscode-eufemia.json')
64+
})
65+
vi.spyOn(nodeFs, 'existsSync').mockImplementation(existsSync)
66+
67+
const resolve = vi.fn(() => {
68+
return dirPath
69+
})
70+
vi.spyOn(nodePath, 'resolve').mockImplementation(resolve)
71+
72+
const info = vi.fn()
73+
vi.spyOn(console, 'info').mockImplementation(info)
74+
})
75+
76+
it('should load and set config', () => {
77+
loadConfigFromFile()
78+
79+
expect(conf).toEqual(
80+
expect.objectContaining({
81+
foo: 'bar',
82+
})
83+
)
84+
expect(console.info).toHaveBeenCalledTimes(1)
85+
expect(console.info).toHaveBeenCalledWith(
86+
'Using config from file: /Users/user/dir/.vscode-eufemia.json'
87+
)
88+
})
89+
})

src/extension/helpers.ts

Lines changed: 55 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,62 @@
11
import * as nls from 'vscode-nls'
2+
import { Uri, workspace, window } from 'vscode'
23
import { existsSync, readFileSync } from 'fs'
34
import { parse } from 'jsonc-parser'
4-
import { join, resolve } from 'path'
5-
import { Uri, workspace } from 'vscode'
5+
import { join, resolve, dirname } from 'path'
6+
import type { ExtensionContext } from 'vscode'
67
import type { Config, Line } from './types'
78

89
export let conf!: Config
9-
export const eufemiaConfigFileName = '.vscode-eufemia.json'
10+
export const configFileName = '.vscode-eufemia.json'
1011

1112
export const localize = nls.config({
1213
messageFormat: nls.MessageFormat.both,
1314
})()
1415

15-
function loadConfigViaFile() {
16-
if (
17-
!workspace.workspaceFolders ||
18-
workspace.workspaceFolders?.length <= 0
19-
) {
20-
return
21-
}
16+
let CACHE_CONFIG_DIR_PATH: Record<string, string | null> = {}
2217

23-
const eufemiaConfigPath = join(
24-
workspace.workspaceFolders[0].uri.fsPath,
25-
eufemiaConfigFileName
18+
export function loadConfigFromFile() {
19+
const activeFilePath = dirname(
20+
window.activeTextEditor?.document.fileName || ''
2621
)
2722

28-
if (!existsSync(eufemiaConfigPath)) {
29-
console.warn(`File not found: ${eufemiaConfigPath}`)
30-
return
23+
let configDirPath = (CACHE_CONFIG_DIR_PATH?.[activeFilePath] ||
24+
null) as string
25+
26+
if (!configDirPath && activeFilePath) {
27+
const paths = activeFilePath.split(/\/+|\\+/g)
28+
29+
for (let i = 0, l = paths.length; i < l; i++) {
30+
const path = resolve(...paths)
31+
32+
if (
33+
existsSync(join(path, configFileName)) ||
34+
// Skip on package.json too, so we not always run this on every file over again (when in same directory)
35+
existsSync(join(path, 'package.json'))
36+
) {
37+
configDirPath = CACHE_CONFIG_DIR_PATH[activeFilePath] = path
38+
break
39+
}
40+
41+
paths.pop()
42+
}
43+
}
44+
45+
const configFilePath = join(configDirPath, configFileName)
46+
47+
if (!existsSync(configFilePath)) {
48+
return // stop here
3149
}
3250

3351
try {
34-
const res = parse(readFileSync(eufemiaConfigPath).toString('utf-8'))
52+
const res = parse(readFileSync(configFilePath, 'utf-8'))
3553
conf = {
3654
...conf,
3755
...res,
3856
}
39-
console.warn(`Use override config via ${eufemiaConfigPath} file`)
40-
} catch (ex) {
41-
console.warn(`Parse error in ${eufemiaConfigPath}`, ex)
57+
console.info(`Using config from file: ${configFilePath}`)
58+
} catch (e) {
59+
console.warn(`Parse error in ${configFilePath}`, e)
4260
}
4361
}
4462

@@ -77,9 +95,25 @@ function setConfig() {
7795
conf = tmp as unknown as Config
7896
}
7997

98+
export function initConfig(context: ExtensionContext) {
99+
loadConfig()
100+
101+
workspace.onDidChangeConfiguration(loadConfig)
102+
workspace.onDidOpenTextDocument(loadConfigFromFile)
103+
104+
const configWatcher = workspace.createFileSystemWatcher(
105+
`**/${configFileName}`
106+
)
107+
108+
configWatcher.onDidChange(loadConfig)
109+
configWatcher.onDidCreate(loadConfig)
110+
configWatcher.onDidDelete(loadConfig)
111+
112+
context.subscriptions.push(configWatcher)
113+
}
114+
80115
export function loadConfig() {
81116
setConfig()
82-
loadConfigViaFile()
83117
initIngores()
84118
initLanguages()
85119
}

0 commit comments

Comments
 (0)