Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/test/simulation/diagnosticProviders/roslyn.ts
13394 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import * as cp from 'child_process';
7
import * as fs from 'fs';
8
import * as path from 'path';
9
import { TestingCacheSalts } from '../../base/salts';
10
import { CacheScope } from '../../base/simulationContext';
11
import { cleanTempDirWithRetry, createTempDir } from '../stestUtil';
12
import { IFile, ITestDiagnostic } from './diagnosticsProvider';
13
import { CachingDiagnosticsProvider, findIfInstalled, setupTemporaryWorkspace } from './utils';
14
15
/**
16
* Class which finds roslyn diagnostics after compilation of C# files
17
*/
18
export class RoslynDiagnosticsProvider extends CachingDiagnosticsProvider {
19
20
override readonly id = 'roslyn';
21
override readonly cacheSalt = TestingCacheSalts.roslynCacheSalt;
22
override readonly cacheScope = CacheScope.Roslyn;
23
24
private _isInstalled: 'local' | 'docker' | false | undefined;
25
26
private get csprojFile(): string {
27
return [
28
'<Project Sdk="Microsoft.NET.Sdk">',
29
' <PropertyGroup>',
30
' <OutputType>Library</OutputType>',
31
' <TargetFramework>net7.0</TargetFramework>',
32
' <ImplicitUsings>enable</ImplicitUsings>',
33
' <Nullable>enable</Nullable>',
34
' <AllowUnsafeBlocks>true</AllowUnsafeBlocks>',
35
' <ErrorLog>error_list.sarif,version=2.1</ErrorLog>',
36
' <CodeAnalysisRuleSet>CSharp.ruleset</CodeAnalysisRuleSet>',
37
' </PropertyGroup>',
38
' <ItemGroup>',
39
' <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.2.1"/>',
40
' <PackageReference Include="System.Runtime.Loader" Version="4.0.0-*"/>',
41
' </ItemGroup>',
42
'</Project>',
43
].join('\n');
44
}
45
46
private get rulesetFile(): string {
47
return [
48
'<?xml version="1.0" encoding="utf-8" ?>',
49
'<RuleSet Name="CSharp Ruleset" Description="Code analysis rules for CSharp project" ToolsVersion="14.0">',
50
' <Rules AnalyzerId="Microsoft.CodeAnalysis.CSharp" RuleNamespace="Microsoft.CodeAnalysis.CSharp">',
51
' <Rule Id="CS8981" Action="None"/>',
52
' </Rules>',
53
'</RuleSet>',
54
].join('\n');
55
}
56
57
override isInstalled(): boolean {
58
if (this._isInstalled === undefined) {
59
if (findIfInstalled({ command: 'dotnet', arguments: ['--version'] }, /^(\d+[\.\d+]*)/g)) {
60
this._isInstalled = 'local';
61
} else if (findIfInstalled({ command: 'docker', arguments: ['--version'] }, /\d+\.\d+\.\d+/)) {
62
this._isInstalled = 'docker';
63
} else {
64
this._isInstalled = false;
65
}
66
}
67
return this._isInstalled !== false;
68
}
69
70
protected override async computeDiagnostics(_files: IFile[]): Promise<ITestDiagnostic[]> {
71
if (!this.isInstalled()) {
72
throw new Error('clang or dotnet must be available in this environment for csharp diagnostics.');
73
}
74
const temporaryDirectory = await createTempDir();
75
try {
76
return await this.runDotnetCompiler(temporaryDirectory, _files);
77
} finally {
78
await cleanTempDirWithRetry(temporaryDirectory);
79
}
80
}
81
82
private runInDocker(temporaryDirectory: string, basename: string, command: string[]): string {
83
const args = ['run', '--rm', '-v', `${temporaryDirectory}:/${basename}`, 'mcr.microsoft.com/dotnet/sdk:8.0', ...command];
84
//console.log('docker ' + args.map(arg => `'${arg}'`).join(' '));
85
const spawnResult = cp.spawnSync('docker', args, { shell: true, encoding: 'utf-8' });
86
if (spawnResult.status !== 0) {
87
throw new Error(`Error while running '${command.join(' ')}' in docker : ${spawnResult.stdout} , ${spawnResult.stderr}`);
88
}
89
return spawnResult.stdout;
90
}
91
92
private runInLocal(command: string[]): string {
93
const spawnResult = cp.spawnSync(command[0], command.slice(1), { shell: true, encoding: 'utf-8' });
94
if (spawnResult.status !== 0) {
95
throw new Error(`Error while running '${command.join(' ')}' in local OS : ${spawnResult.stderr}`);
96
}
97
return spawnResult.stdout;
98
}
99
100
private async runDotnetCompiler(temporaryDirectory: string, files: IFile[]): Promise<ITestDiagnostic[]> {
101
102
const projectName = 'project123';
103
const projectLocation = path.join(temporaryDirectory, projectName);
104
await setupTemporaryWorkspace(projectLocation, files);
105
let outputLocation;
106
if (this._isInstalled === 'docker') {
107
const script = [
108
`set -x`,
109
`cd "$(dirname "$0")"`, // cd to the directory of the script
110
`dotnet new classlib --force -o ${projectName}`,
111
`cp CSharp.ruleset ${projectName}/CSharp.ruleset`,
112
`cp proj.csproj ${projectName}/${projectName}.csproj`, // replace the csproj file
113
`dotnet build ${projectName} --no-incremental`,
114
`cp ${projectName}/error_list.sarif error_list.sarif`, // the error_list.sarif file is generated by dotnet build and has the output
115
`rm -rfd ${projectName}`, // Docker might run as root, so clean up all generated files right away, we won't be able to do it from the outside
116
].join('\n');
117
await fs.promises.writeFile(path.join(temporaryDirectory, `validate.sh`), script);
118
await fs.promises.writeFile(path.join(temporaryDirectory, `error_list.sarif`), ''); // pre-create the file so it gets the permissions of the current user and we can delete it after
119
await fs.promises.writeFile(path.join(temporaryDirectory, `CSharp.ruleset`), this.rulesetFile);
120
await fs.promises.writeFile(path.join(temporaryDirectory, `proj.csproj`), this.csprojFile);
121
122
const basename = path.basename(temporaryDirectory);
123
this.runInDocker(temporaryDirectory, basename, ['/bin/sh', `/${basename}/validate.sh`]);
124
outputLocation = temporaryDirectory;
125
} else {
126
this.runInLocal(['dotnet', 'new', 'classlib', '--force', '-o', projectLocation]);
127
await fs.promises.writeFile(path.join(projectLocation, `${projectName}.csproj`), this.csprojFile);
128
await fs.promises.writeFile(path.join(projectLocation, `CSharp.ruleset`), this.rulesetFile);
129
this.runInLocal(['dotnet', 'build', projectLocation, '--no-incremental']);
130
outputLocation = projectLocation;
131
}
132
const fileContents = await fs.promises.readFile(path.join(outputLocation, `error_list.sarif`), 'utf8');
133
const parsedErrors = JSON.parse(fileContents).runs[0].results;
134
const diagnostics: ITestDiagnostic[] = [];
135
for (const error of parsedErrors) {
136
const uri = error.locations[0].physicalLocation.artifactLocation.uri;
137
const diagnostic = {
138
file: path.basename(uri),
139
message: error.message.text,
140
code: error.ruleId,
141
startLine: error.locations[0].physicalLocation.region.startLine - 1,
142
startCharacter: error.locations[0].physicalLocation.region.startColumn - 1,
143
endLine: error.locations[0].physicalLocation.region.endLine - 1,
144
endCharacter: error.locations[0].physicalLocation.region.endColumn - 1,
145
relatedInformation: undefined,
146
source: 'roslyn'
147
};
148
diagnostics.push(diagnostic);
149
}
150
return diagnostics;
151
}
152
}
153
154