Skip to content

Commit 019efd0

Browse files
committed
Resolve file links in cbuild*.yml files
1 parent 29f4f93 commit 019efd0

5 files changed

Lines changed: 104 additions & 21 deletions

File tree

src/solutions/language-features/reference-link-provider.test.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { URI as Uri, Utils as UriUtils } from 'vscode-uri';
2121
import * as path from 'path';
2222
import { solutionManagerFactory } from '../solution-manager.factories';
2323
import { SolutionRpcDataMock } from '../solution-rpc-data.factory';
24+
import * as pathUtils from '../../utils/path-utils';
2425

2526
describe('ReferenceLinkProvider', () => {
2627
it('returns no links if the document cannot be parsed', () => {
@@ -63,7 +64,7 @@ describe('ReferenceLinkProvider', () => {
6364
const rpcData = solutionManager.getRpcData() as SolutionRpcDataMock;
6465
rpcData.seedVariables('my.Build+Target', { 'Board-layer': 'mylayer.clayer.yml' });
6566

66-
const provider = new ReferenceLinkProvider(solutionManager);
67+
const provider = new ReferenceLinkProvider(solutionManager, false);
6768
const output = provider.provideDocumentLinks(textDocument, { isCancellationRequested: false, onCancellationRequested: jest.fn() });
6869

6970
expect(output).toEqual([
@@ -72,4 +73,47 @@ describe('ReferenceLinkProvider', () => {
7273
expect.objectContaining({ target: expect.objectContaining({ fsPath: UriUtils.joinPath(documentUri, '../mylayer.clayer.yml').fsPath }) }),
7374
]);
7475
});
76+
77+
it('returns links for cbuild files without expanding RPC variables', () => {
78+
jest.spyOn(pathUtils, 'getCmsisPackRoot').mockReturnValue('TEST_CMSIS_PACK_ROOT');
79+
80+
const cbuildDoc = `
81+
build-idx:
82+
csolution: ./my.csolution.yml
83+
cbuilds:
84+
- cbuild: ./my/debug/my.cbuild.yml
85+
clayers:
86+
- clayer: my.clayer.yml
87+
cprojects:
88+
- cproject: my/my.cproject.yml
89+
clayers:
90+
- clayer: my/$Board-layer$
91+
files:
92+
- file: \${CMSIS_PACK_ROOT}/myPack/0.0.1/myfile.yml
93+
`;
94+
95+
const documentFileName = path.join(__dirname, 'my.cbuild-idx.yml');
96+
const documentUri = Uri.file(documentFileName);
97+
const textDocument = textDocumentFactory({ uri: documentUri, fileName: documentFileName });
98+
textDocument.getText.mockReturnValue(cbuildDoc);
99+
100+
const solutionManager = solutionManagerFactory();
101+
const provider = new ReferenceLinkProvider(solutionManager, true);
102+
const output = provider.provideDocumentLinks(textDocument, { isCancellationRequested: false, onCancellationRequested: jest.fn() });
103+
const outputPaths = output
104+
.map(link => link.target?.fsPath)
105+
.filter((target): target is string => !!target);
106+
107+
const expectedPaths = [
108+
UriUtils.joinPath(documentUri, '../my.csolution.yml').fsPath,
109+
UriUtils.joinPath(documentUri, '../my/debug/my.cbuild.yml').fsPath,
110+
UriUtils.joinPath(documentUri, '../my.clayer.yml').fsPath,
111+
UriUtils.joinPath(documentUri, '../my/my.cproject.yml').fsPath,
112+
path.join(path.sep, 'TEST_CMSIS_PACK_ROOT', 'myPack', '0.0.1', 'myfile.yml'),
113+
];
114+
115+
expectedPaths.forEach(expectedPath => {
116+
expect(outputPaths.some(outputPath => pathUtils.pathsEqual(outputPath, expectedPath))).toBe(true);
117+
});
118+
});
75119
});

src/solutions/language-features/reference-link-provider.ts

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,25 @@ import { CancellationToken, DocumentLink, DocumentLinkProvider, Range, TextDocum
1818
import { parseYamlToCTreeItem } from '../../generic/tree-item-yaml-parser';
1919
import { CTreeItem, ETreeItemKind, ITreeItem } from '../../generic/tree-item';
2020
import type { SolutionManager } from '../solution-manager';
21+
import { getCmsisPackRoot } from '../../utils/path-utils';
2122

2223
/**
23-
* Provide links for file references in solution and project files.
24+
* Provide links for file references in solution, project, and layer files as well as for *.cbuild*.yml files.
25+
*
2426
*/
2527
export class ReferenceLinkProvider implements DocumentLinkProvider<DocumentLink> {
28+
private static readonly REFERENCE_ITEM_TAGS = ['file', 'layer', 'project', 'script', 'regions',
29+
'solution', 'csolution', 'cbuild', 'clayer', 'cproject'];
30+
2631
constructor(
2732
private readonly solutionManager: SolutionManager,
33+
private readonly cbuildFile?: boolean,
2834
) {
2935
}
3036

3137
public provideDocumentLinks(textDocument: TextDocument, _token?: CancellationToken): DocumentLink[] {
3238
try {
3339
const topItem = parseYamlToCTreeItem(textDocument.getText(), textDocument.fileName);
34-
3540
return (topItem?.filterItems(item => this.isReferenceFileItem(item)) ?? [])?.flatMap((item): DocumentLink[] => {
3641
const documentLink = this.treeItemToDocumentLink(item, textDocument);
3742
return documentLink ? [documentLink] : [];
@@ -47,15 +52,28 @@ export class ReferenceLinkProvider implements DocumentLinkProvider<DocumentLink>
4752
}
4853

4954
protected isReferenceFileItem(item: ITreeItem<CTreeItem>): item is CTreeItem {
55+
if (!item || item.getKind() !== ETreeItemKind.Scalar || !item.getText())
56+
return false;
5057
const tag = item.getTag();
51-
return !!tag && this.getReferenceItemTags().includes(tag);
58+
if (!tag || !this.getReferenceItemTags().includes(tag)) {
59+
return false;
60+
}
61+
// in case of cbuild-ids.yml we can only expand links under 'cbuilds' section
62+
if (tag === 'clayer' && !item.getParent('cbuilds')) {
63+
return false;
64+
}
65+
// in case of cbuild-ids.yml 'project' under 'cbuilds' is just a name, not path
66+
if (tag === 'project' && !!item.getParent('cbuilds')) {
67+
return false;
68+
}
69+
return true;
5270
}
5371

5472
protected getReferenceItemTags(): string[] {
55-
return ['file', 'layer', 'project', 'script', 'regions'];
73+
return ReferenceLinkProvider.REFERENCE_ITEM_TAGS;
5674
}
5775

58-
private treeItemToDocumentLink(item: ITreeItem<CTreeItem> | undefined, textDocument: TextDocument) : DocumentLink | undefined {
76+
private treeItemToDocumentLink(item: ITreeItem<CTreeItem>, textDocument: TextDocument): DocumentLink | undefined {
5977
const uri = this.getUriFromItem(item);
6078
if (!uri) {
6179
return undefined;
@@ -68,20 +86,23 @@ export class ReferenceLinkProvider implements DocumentLinkProvider<DocumentLink>
6886
}
6987

7088

71-
private getUriFromItem(item?: ITreeItem<CTreeItem>): Uri | undefined {
72-
if (!item || item.getKind() !== ETreeItemKind.Scalar) {
73-
return undefined;
74-
}
89+
private getUriFromItem(item: ITreeItem<CTreeItem>): Uri | undefined {
7590
let text = item.getText();
7691
if (!text) {
7792
return undefined;
7893
}
79-
const rpcData = this.solutionManager.getRpcData();
80-
const context = this.getItemContext(item);
81-
if (rpcData && context) {
82-
text = rpcData.expandString(text, context);
83-
}
8494

95+
// generated files can contain references to files in packs directory
96+
if (text.startsWith('${CMSIS_PACK_ROOT}')) {
97+
return Uri.file(text.replace('${CMSIS_PACK_ROOT}', getCmsisPackRoot()));
98+
}
99+
if (!this.cbuildFile) { // generated files have all sequences expanded
100+
const rpcData = this.solutionManager.getRpcData();
101+
const context = this.getItemContext(item);
102+
if (rpcData && context) {
103+
text = rpcData.expandString(text, context);
104+
}
105+
}
85106
const resolvedPath = item.resolvePath(text);
86107
return resolvedPath ? Uri.file(resolvedPath) : undefined;
87108
}

src/solutions/language-features/solution-language-features-provider.test.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,26 @@
1515
*/
1616

1717
import 'jest';
18-
import { SolutionLanguageFeaturesProvider, solutionFilesSelectors } from './solution-language-features-provider';
18+
import { SolutionLanguageFeaturesProvider, solutionBuildFilesSelectors, solutionFilesSelectors } from './solution-language-features-provider';
1919
import { ReferenceLinkProvider } from './reference-link-provider';
2020
import type { SolutionManager } from '../solution-manager';
2121

2222
describe('SolutionLanguageFeaturesProvider', () => {
23-
it('registers a document link provider for solution, project, and layer files on activation', async () => {
23+
it('registers document link providers for solution and build files on activation', async () => {
2424
const registerDocumentLinkProvider = jest.fn();
2525
const solutionManager = {} as SolutionManager;
2626
const provider = new SolutionLanguageFeaturesProvider(solutionManager, { registerDocumentLinkProvider });
2727

2828
await provider.activate({ subscriptions: [] });
2929

30-
expect(registerDocumentLinkProvider).toHaveBeenCalledWith(solutionFilesSelectors, expect.any(ReferenceLinkProvider));
31-
expect(registerDocumentLinkProvider).toHaveBeenCalledTimes(1);
30+
expect(registerDocumentLinkProvider).toHaveBeenNthCalledWith(1,
31+
solutionFilesSelectors,
32+
expect.any(ReferenceLinkProvider),
33+
);
34+
expect(registerDocumentLinkProvider).toHaveBeenNthCalledWith(2,
35+
solutionBuildFilesSelectors,
36+
expect.any(ReferenceLinkProvider),
37+
);
38+
expect(registerDocumentLinkProvider).toHaveBeenCalledTimes(2);
3239
});
3340
});

src/solutions/language-features/solution-language-features-provider.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@ export const solutionFilesSelectors: Readonly<DocumentSelector> = [
2626
{ pattern: '**/*.cproject.yaml' },
2727
{ pattern: '**/*.clayer.yml' },
2828
{ pattern: '**/*.clayer.yaml' },
29+
{ pattern: '**/*.cgen.yml' },
30+
{ pattern: '**/*.cgen.yaml' },
31+
];
32+
33+
export const solutionBuildFilesSelectors: Readonly<DocumentSelector> = [
34+
{ pattern: '**/*.cbuild.yml' },
35+
{ pattern: '**/*.cbuild-idx.yml' },
36+
{ pattern: '**/*.cbuild-run.yml' },
2937
];
3038

3139
export class SolutionLanguageFeaturesProvider {
@@ -36,7 +44,10 @@ export class SolutionLanguageFeaturesProvider {
3644

3745
public async activate(context: Pick<ExtensionContext, 'subscriptions'>) {
3846
context.subscriptions.push(
39-
this.languages.registerDocumentLinkProvider(solutionFilesSelectors, new ReferenceLinkProvider(this.solutionManager)),
47+
this.languages.registerDocumentLinkProvider(solutionFilesSelectors,
48+
new ReferenceLinkProvider(this.solutionManager)),
49+
this.languages.registerDocumentLinkProvider(solutionBuildFilesSelectors,
50+
new ReferenceLinkProvider(this.solutionManager, true)),
4051
);
4152
}
4253
}

src/views/solution-outline/tree-structure/solution-outline-file-item.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export class FileItemBuilder extends SolutionOutlineItemBuilder {
4242
return;
4343
}
4444

45-
const hasCmsisPackRoot = fileValue.indexOf('${CMSIS_PACK_ROOT}') !== -1;
45+
const hasCmsisPackRoot = fileValue.startsWith('${CMSIS_PACK_ROOT}');
4646
const resolvedFilePath = this.resolveFilePath(hasCmsisPackRoot, fileValue);
4747
const fileBaseName = path.basename(resolvedFilePath);
4848
const resourcePath = hasCmsisPackRoot ? resolvedFilePath : f.resolvePath(resolvedFilePath);

0 commit comments

Comments
 (0)