Skip to content

Commit 2575632

Browse files
authored
Merge pull request #25 from moliva/feat/support-dashes
Support LSP actions in classes with dashes
2 parents 77efeca + 52acbcc commit 2575632

File tree

5 files changed

+88
-13
lines changed

5 files changed

+88
-13
lines changed

src/CompletionProvider.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,26 @@ export class CSSModulesCompletionProvider {
8383
const res = classNames.map(_class => {
8484
const name = this._classTransformer(_class);
8585

86-
return CompletionItem.create(name);
86+
let completionItem: CompletionItem;
87+
88+
// in case of items with dashes, we need to replace the `.` and suggest the field using the subscript expression
89+
if (name.includes('-')) {
90+
const arrayAccessor = `['${name}']`;
91+
const range = lsp.Range.create(
92+
lsp.Position.create(position.line, position.character - 1),
93+
position,
94+
);
95+
96+
completionItem = CompletionItem.create(arrayAccessor);
97+
completionItem.textEdit = lsp.InsertReplaceEdit.create(
98+
arrayAccessor,
99+
range,
100+
range,
101+
);
102+
} else {
103+
completionItem = CompletionItem.create(name);
104+
}
105+
return completionItem;
87106
});
88107

89108
return res.map((x, i) => ({

src/DefinitionProvider.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
getTransformer,
2121
getWords,
2222
isImportLineMatch,
23-
stringiyClassname,
23+
stringifyClassname,
2424
} from './utils';
2525

2626
export class CSSModulesDefinitionProvider {
@@ -66,11 +66,12 @@ export class CSSModulesDefinitionProvider {
6666
const currentDir = getCurrentDirFromUri(textdocument.uri);
6767

6868
const words = getWords(currentLine, position);
69-
if (words === '' || words.indexOf('.') === -1) {
69+
if (words === null) {
7070
return null;
7171
}
7272

73-
const [obj, field] = words.split('.');
73+
const [obj, field] = words;
74+
7475
const importPath = findImportPath(fileContent, obj, currentDir);
7576
if (importPath === '') {
7677
return null;
@@ -88,7 +89,7 @@ export class CSSModulesDefinitionProvider {
8889
return {
8990
contents: {
9091
language: 'css',
91-
value: stringiyClassname(
92+
value: stringifyClassname(
9293
field,
9394
node.declarations,
9495
node.comments,
@@ -124,11 +125,12 @@ export class CSSModulesDefinitionProvider {
124125
}
125126

126127
const words = getWords(currentLine, position);
127-
if (words === '' || words.indexOf('.') === -1) {
128+
if (words === null) {
128129
return null;
129130
}
130131

131-
const [obj, field] = words.split('.');
132+
const [obj, field] = words;
133+
132134
const importPath = findImportPath(fileContent, obj, currentDir);
133135
if (importPath === '') {
134136
return null;

src/spec/resolveAliasedFilepath.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ vi.mock('fs', async () => {
2626
};
2727
});
2828

29-
describe('utils: resolveAliaedFilepath', () => {
29+
describe('utils: resolveAliasedFilepath', () => {
3030
it('returns null if config does not exist', () => {
3131
(lilconfigSync as Mock).mockReturnValueOnce({
3232
search: () => null,

src/spec/utils.spec.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import * as path from 'path';
22
import {describe, expect, it} from 'vitest';
3+
import {Position} from 'vscode-languageserver-protocol';
34
import {
45
filePathToClassnameDict,
56
findImportPath,
67
getTransformer,
8+
getWords,
79
} from '../utils';
810

911
describe('filePathToClassnameDict', () => {
@@ -228,3 +230,33 @@ describe('getTransformer', () => {
228230
});
229231
});
230232
});
233+
describe('getWords', () => {
234+
it('returns null for a line with no .', () => {
235+
const line = 'nostyles';
236+
const position = Position.create(0, 1);
237+
const result = getWords(line, position);
238+
239+
expect(result).toEqual(null);
240+
});
241+
it('returns pair of obj and field for line with property accessor expression', () => {
242+
const line = 'styles.myclass';
243+
const position = Position.create(0, 'styles.'.length);
244+
const result = getWords(line, position);
245+
246+
expect(result).toEqual(['styles', 'myclass']);
247+
});
248+
it('returns pair of obj and field for line with subscript accessor expression (single quoted)', () => {
249+
const line = "styles['myclass']";
250+
const position = Position.create(0, "styles['".length);
251+
const result = getWords(line, position);
252+
253+
expect(result).toEqual(['styles', 'myclass']);
254+
});
255+
it('returns pair of obj and field for line with subscript accessor expression (double quoted)', () => {
256+
const line = 'styles["myclass"]';
257+
const position = Position.create(0, 'styles["'.length);
258+
const result = getWords(line, position);
259+
260+
expect(result).toEqual(['styles', 'myclass']);
261+
});
262+
});

src/utils.ts

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,20 +128,42 @@ export async function getPosition(
128128
: null;
129129
}
130130

131-
export function getWords(line: string, position: Position): string {
131+
export function getWords(
132+
line: string,
133+
position: Position,
134+
): [string, string] | null {
132135
const headText = line.slice(0, position.character);
133136
const startIndex = headText.search(/[a-z0-9\._]*$/i);
134137
// not found or not clicking object field
135138
if (startIndex === -1 || headText.slice(startIndex).indexOf('.') === -1) {
136-
return '';
139+
// check if this is a subscript expression instead
140+
const startIndex = headText.search(/[a-z0-9"'_\[\-]*$/i);
141+
if (
142+
startIndex === -1 ||
143+
headText.slice(startIndex).indexOf('[') === -1
144+
) {
145+
return null;
146+
}
147+
148+
const match = /^([a-z0-9_\-\['"]*)/i.exec(line.slice(startIndex));
149+
if (match === null) {
150+
return null;
151+
}
152+
153+
const [styles, className] = match[1].split('[');
154+
155+
// remove wrapping quotes around class name (both `'` or `"`)
156+
const unwrappedName = className.substring(1, className.length - 1);
157+
158+
return [styles, unwrappedName] as [string, string];
137159
}
138160

139161
const match = /^([a-z0-9\._]*)/i.exec(line.slice(startIndex));
140162
if (match === null) {
141-
return '';
163+
return null;
142164
}
143165

144-
return match[1];
166+
return match[1].split('.') as [string, string];
145167
}
146168

147169
type ClassnamePostion = {
@@ -401,7 +423,7 @@ export async function getAllClassNames(
401423
: classList;
402424
}
403425

404-
export function stringiyClassname(
426+
export function stringifyClassname(
405427
classname: string,
406428
declarations: string[],
407429
comments: string[],

0 commit comments

Comments
 (0)