Skip to content

Commit

Permalink
Fix: Unstable rule scope variable groups
Browse files Browse the repository at this point in the history
  • Loading branch information
1aron committed Mar 28, 2024
1 parent 4697add commit 6129e5a
Show file tree
Hide file tree
Showing 11 changed files with 272 additions and 164 deletions.
4 changes: 2 additions & 2 deletions packages/css/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@ export {
}

export type VariableValue = number | string | Array<number | string>
export type VariableDefinition = { [key in '' | `@${string}` | string]?: VariableValue | VariableDefinition }
export type VariableDefinition = { [key in '' | `@${string}` | string]?: VariableValue | VariableDefinition } | VariableValue
export type CSSKeyframes = { [key in 'from' | 'to' | string]: PropertiesHyphen }
export type AnimationDefinitions = { [key: string]: CSSKeyframes }
export type SelectorDefinitions = { [key: string]: string | string[] | SelectorDefinitions }
export type MediaQueryDefinitions = { [key: string]: number | string | MediaQueryDefinitions }
export type StyleDefinitions = { [key: string]: string | StyleDefinitions }
export type RuleDefinitions = { [key in keyof typeof rules | string]?: RuleDefinition }
export type VariableDefinitions = { [key in keyof typeof rules]?: VariableDefinition | VariableValue } & { [key: string]: VariableDefinition | VariableValue }
export type VariableDefinitions = { [key in keyof typeof rules]?: VariableDefinition } & { [key: string]: VariableDefinition }
export type UtilityDefinitions = { [key in keyof typeof utilities]?: PropertiesHyphen } & { [key: string]: PropertiesHyphen }
export interface FunctionDefinition {
unit?: string
Expand Down
2 changes: 1 addition & 1 deletion packages/css/src/config/rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,7 @@ const rules = {
} as RuleDefinition,
'text-rendering': {
ambiguousKeys: ['text', 't'],
ambiguousValues: ['optimizespeed', 'optimizelegibility', 'geometricprecision'],
ambiguousValues: ['optimizeSpeed', 'optimizeLegibility', 'geometricPrecision'],
layer: Layer.Native,
} as RuleDefinition,
'text-indent': {
Expand Down
208 changes: 103 additions & 105 deletions packages/css/src/core.ts

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions packages/css/src/rule.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */
import MasterCSS, { type Variable } from './core'
import MasterCSS, { ColorVariable, type Variable } from './core'
import cssEscape from 'css-shared/utils/css-escape'
import Layer from './layer'
import { type PropertiesHyphen } from 'csstype'
Expand Down Expand Up @@ -569,7 +569,7 @@ export class Rule {
handleVariable(
(variable) => {
if (bypassParsing) {
currentValue += eachValueComponent.text = variable.value
currentValue += eachValueComponent.text = String(variable.value)
} else {
const valueComponent = this.parseValue(variable.value, unit) as NumericValueComponent
currentValue += eachValueComponent.text = valueComponent.value + (valueComponent.unit ?? '')
Expand All @@ -587,7 +587,7 @@ export class Rule {
const alpha = eachValueComponent.alpha ? '/' + eachValueComponent.alpha : ''
handleVariable(
(variable) => {
currentValue += eachValueComponent.text = `${variable['space']}(${variable.value}${alpha})`
currentValue += eachValueComponent.text = `${(variable as ColorVariable)['space']}(${variable.value}${alpha})`
},
() => {
currentValue += eachValueComponent.text = `${variable.space}(var(--${eachValueComponent.name})${alpha})`
Expand Down Expand Up @@ -856,13 +856,13 @@ export interface RegisteredRule {
value?: RegExp
arbitrary?: RegExp
}
variables?: any
variables: Record<string, Variable>
order: number
definition: RuleDefinition
}

export interface RuleDefinition {
layer: Layer
layer?: Layer
matcher?: RegExp
key?: string
subkey?: string
Expand Down
18 changes: 18 additions & 0 deletions packages/css/tests/config/variables/font.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
it('should be able to access related font variables using inherited rules', () => {
expect(Object.keys(new MasterCSS().Rules.find(({ id }) => id === 'font')?.variables || {})).toEqual([
// font-family
'mono',
'sans',
'serif',
// font-weight
'thin',
'extralight',
'light',
'regular',
'medium',
'semibold',
'bold',
'extrabold',
'heavy'
])
})
16 changes: 8 additions & 8 deletions packages/css/tests/rule.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,26 +28,26 @@ test('registered Rule', () => {
test('variables', () => {
const css = new MasterCSS({
variables: {
lv1: {
a: {
1: 'test',
lv2: {
b: {
2: 'test'
}
}
},
rules: {
content: {
variables: ['lv1.lv2']
variables: ['a.b']
}
}
})
expect(css.variables['lv1-1']).toMatchObject({ type: 'string', value: 'test', group: 'lv1' })
expect(css.variables['lv1-lv2-2']).toMatchObject({ type: 'string', value: 'test', group: 'lv1.lv2' })
expect(css.variables['a-1']).toMatchObject({ type: 'string', value: 'test', group: 'a' })
expect(css.variables['a-b-2']).toMatchObject({ type: 'string', value: 'test', group: 'a.b' })
expect(css.Rules.find(({ id }) => id === 'content')).toEqual({
definition: {
key: 'content',
layer: -1,
variables: ['lv1.lv2']
variables: ['a.b']
},
id: 'content',
matchers: {
Expand All @@ -57,10 +57,10 @@ test('variables', () => {
variables: {
2: {
key: '2',
name: 'lv1-lv2-2',
name: 'a-b-2',
type: 'string',
value: 'test',
group: 'lv1.lv2'
group: 'a.b'
}
}
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<div class=>hello world</div>
<div class="font-family:sans">hello world</div>
7 changes: 6 additions & 1 deletion packages/language-service/playground/master.css.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { Config } from '@master/css'
import { Config, variables } from '@master/css'

export default {
styles: {
btn: 'inline-flex'
},
variables: {
'font-family': {
sans: ['Inter', ...variables['font-family'].sans],
}
}
} as Config
51 changes: 43 additions & 8 deletions packages/language-service/src/utils/get-main-completion-items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,57 @@ import type { IPropertyData, IValueData } from 'vscode-css-languageservice'
export default function getMainCompletionItems(css: MasterCSS = new MasterCSS()): CompletionItem[] {
const nativeProperties = cssDataProvider.provideProperties()
const completionItems: CompletionItem[] = []
const addedKeys = new Set<string>()
process.env.VSCODE_IPC_HOOK && console.time('getMainCompletionItems')
for (const ruleId in css.config.rules) {
const eachRule = css.config.rules[ruleId]
const nativeCSSPropertyData = nativeProperties.find(({ name }) => name === ruleId)
// todo: key alias
completionItems.push({
label: ruleId + ':',
sortText: ruleId,
for (const id in css.config.rules) {
const eachRule = css.config.rules[id]
const nativeCSSPropertyData = nativeProperties.find(({ name }) => name === id)
const eachCompletionItem = {
kind: CompletionItemKind.Property,
documentation: getCSSDataDocumentation(nativeCSSPropertyData),
detail: nativeCSSPropertyData?.syntax,
command: {
title: 'triggerSuggest',
command: 'editor.action.triggerSuggest'
}
})
}
if (eachRule?.key) {
addedKeys.add(eachRule.key)
completionItems.push({
...eachCompletionItem,
label: eachRule.key + ':',
sortText: eachRule.key
})
}
if (eachRule?.subkey) {
addedKeys.add(eachRule.subkey)
completionItems.push({
...eachCompletionItem,
label: eachRule.subkey + ':',
sortText: eachRule.subkey
})
}
if (eachRule?.ambiguousKeys?.length) {
for (const ambiguousKey of eachRule.ambiguousKeys) {
if (addedKeys.has(ambiguousKey)) {
continue
}
/**
* Ambiguous keys are added to the completion list
* @example text: t:
*/
completionItems.push({
kind: CompletionItemKind.Property,
detail: 'ambiguous key',
label: ambiguousKey + ':',
sortText: ambiguousKey,
command: {
title: 'triggerSuggest',
command: 'editor.action.triggerSuggest'
}
})
}
}
}

// todo: test remap utility to native css property
Expand Down
46 changes: 43 additions & 3 deletions packages/language-service/src/utils/get-value-completion-items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,48 @@ import { getCSSDataDocumentation } from './get-css-data-documentation'
export default function getValueCompletionItems(key: string, css: MasterCSS = new MasterCSS()): CompletionItem[] {
const nativeProperties = cssDataProvider.provideProperties()
const completionItems: CompletionItem[] = []
const nativeCSSPropertyData = nativeProperties.find((x: { name: string }) => x.name === key)
if (!nativeCSSPropertyData) return completionItems
nativeCSSPropertyData.values?.forEach(value => {
for (const EachRule of css.Rules) {
const nativePropertyData = nativeProperties.find((x: { name: string }) => x.name === EachRule.id)
/**
* Scoped variables
*/
if (EachRule.definition.key === key || EachRule.definition.subkey === key) {
for (const variableName in EachRule.variables) {
if (variableName.startsWith('-')) continue
const variable = EachRule.variables[variableName]
completionItems.push({
label: variableName,
kind: CompletionItemKind.Variable,
detail: '(variable) ' + variable.value
})
}
}
/**
* Ambiguous values
* @example text: -> center, left, right, justify
* @example t: -> center, left, right, justify
*/
if (EachRule.definition.ambiguousKeys?.includes(key) && EachRule.definition.ambiguousValues?.length) {
for (const ambiguousValue of EachRule.definition.ambiguousValues) {
if (typeof ambiguousValue !== 'string') continue
const nativeValueData = nativePropertyData?.values?.find((x: { name: string }) => x.name === ambiguousValue)
completionItems.push({
label: ambiguousValue,
kind: CompletionItemKind.Value,
sortText: ambiguousValue,
documentation: getCSSDataDocumentation(nativeValueData, {
generatedCSS: generateCSS([key + ':' + ambiguousValue], css)
}),
detail: EachRule.id + ': ' + ambiguousValue
})
}
}
}
/**
* Native values
*/
nativeProperties.find((x: { name: string }) => x.name === key)?.values?.forEach(value => {
if (completionItems.find(x => x.label === value.name)) return
completionItems.push({
label: value.name,
kind: CompletionItemKind.Value,
Expand All @@ -21,5 +60,6 @@ export default function getValueCompletionItems(key: string, css: MasterCSS = ne
detail: key + ': ' + value.name
})
})

return completionItems
}
72 changes: 42 additions & 30 deletions packages/language-service/tests/hint-syntax-completions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,42 +23,54 @@ const simulateHintingCompletions = (target: string, { quotes = true, settings }:
// it('types a', () => expect(simulateHintingCompletions('a')?.length).toBeDefined())

test.todo('following test require e2e -> packages/language-server')
// it('types " should hint completions', () => expect(simulateHintingCompletions('"', { quotes: false })?.length).toBeGreaterThan(0))
it('types " should hint completions', () => expect(simulateHintingCompletions('""', { quotes: false })?.length).toBeGreaterThan(0))
it('types should hint completions', () => expect(simulateHintingCompletions('text:center ')?.length).toBeGreaterThan(0))
it('types "text:center" should not hint completions', () => expect(simulateHintingCompletions('text:center')?.length).toBe(0))

test.todo('types any trigger character in "" should not hint')
test.todo(`types any trigger character in '' should not hint`)

test.todo('keys')
// describe('keys', () => {
// // it('should not hint selectors', () => expect(simulateHintingCompletions('text:')?.[0]).not.toMatchObject({ insertText: 'active' }))
// test('@delay on invoked', () => expect(simulateHintingCompletions('"', { quotes: false })?.find(({ label }) => label === '@delay:')).toMatchObject({ label: '@delay:' }))
// test('~delay on invoked', () => expect(simulateHintingCompletions('"', { quotes: false })?.find(({ label }) => label === '~delay:')).toMatchObject({ label: '~delay:' }))
// it('starts with @', () => expect(simulateHintingCompletions('@')?.[0]).toMatchObject({ label: 'delay:' }))
// it('starts with @d and list related', () => expect(simulateHintingCompletions('@d')?.map(({ label }) => label)).toEqual([
// 'delay:',
// 'direction:',
// 'duration:'
// ]))
// it('starts with @ and list related', () => expect(simulateHintingCompletions('@')?.map(({ label }) => label)).toEqual([
// 'delay:',
// 'direction:',
// 'duration:',
// 'easing:',
// 'fill-mode:',
// 'iteration-count:',
// 'name:',
// 'play-state:',
// ]))
// it('starts with ~', () => expect(simulateHintingCompletions('~')?.[0]).toMatchObject({ label: 'delay:' }))
// it('starts with ~ and list related', () => expect(simulateHintingCompletions('~')?.map(({ label }) => label)).toEqual([
// 'delay:',
// 'duration:',
// 'easing:',
// 'property:'
// ]))
// })
describe('keys', () => {
it('should not hint selectors', () => expect(simulateHintingCompletions('text:')?.[0]).not.toMatchObject({ insertText: 'active' }))
test('@delay on invoked', () => expect(simulateHintingCompletions('""', { quotes: false })?.find(({ label }) => label === '@delay:')).toMatchObject({ label: '@delay:' }))
test('~delay on invoked', () => expect(simulateHintingCompletions('""', { quotes: false })?.find(({ label }) => label === '~delay:')).toMatchObject({ label: '~delay:' }))
it('starts with @', () => expect(simulateHintingCompletions('@')?.[0]).toMatchObject({ label: 'delay:' }))
it('starts with @d and list related', () => expect(simulateHintingCompletions('@d')?.map(({ label }) => label)).toEqual([
'delay:',
'direction:',
'duration:'
]))
it('starts with @ and list related', () => expect(simulateHintingCompletions('@')?.map(({ label }) => label)).toEqual([
'delay:',
'direction:',
'duration:',
'easing:',
'fill:',
'iteration:',
'name:',
'play:',
]))
it('starts with ~', () => expect(simulateHintingCompletions('~')?.[0]).toMatchObject({ label: 'delay:' }))
it('starts with ~ and list related', () => expect(simulateHintingCompletions('~')?.map(({ label }) => label)).toEqual([
'delay:',
'duration:',
'easing:',
'property:'
]))
test('native property', () => expect(simulateHintingCompletions('f')?.map(({ label }) => label)).toContain('font-size:'))
describe('ambiguous', () => {
test('t', () => expect(simulateHintingCompletions('t')?.map(({ label }) => label)).toContain('t:'))
test('t', () => expect(simulateHintingCompletions('t')?.map(({ label }) => label)).toContain('text:'))
})
})

describe('values', () => {
test('scoped variables', () => expect(simulateHintingCompletions('font:')?.map(({ label }) => label)).toContain('semibold'))
describe('ambiguous', () => {
test('text:capitalize', () => expect(simulateHintingCompletions('text:')?.map(({ label }) => label)).toContain('capitalize'))
test('text:center', () => expect(simulateHintingCompletions('text:')?.map(({ label }) => label)).toContain('center'))
})
})

describe('utilities', () => {
it('types a', () => expect(simulateHintingCompletions('a')?.find(({ label }) => label === 'abs')).toMatchObject({ label: 'abs' }))
Expand Down

0 comments on commit 6129e5a

Please sign in to comment.