Skip to content
This repository was archived by the owner on May 4, 2023. It is now read-only.

Commit 56076d7

Browse files
authored
Merge pull request #53 from codiga/feat/suggest-codiga-file
feat: suggest codiga.yml file
2 parents b92d30f + bb92424 commit 56076d7

6 files changed

Lines changed: 172 additions & 0 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ node_modules
55
*.vsix
66
.DS_Store
77
*~
8+
webview

src/constants.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { Language } from "./graphql-api/types";
2+
13
export const GRAPHQL_ENDPOINT_PROD = "https://api.codiga.io/graphql";
24
export const GRAPHQL_ENDPOINT_STAGING = "https://api-staging.codiga.io/graphql";
35

@@ -7,6 +9,9 @@ export const MESSAGE_STARTUP_SHOW_SHORTCUTS = "View Shortcuts";
79
export const MESSAGE_STARTUP_SHOW_SNIPPETS = "Search Snippets";
810
export const MESSAGE_STARTUP_DO_NOT_SHOW_AGAIN = "Do not show again";
911

12+
export const VALUE_STRING_TRUE = "true";
13+
export const VALUE_STRING_FALSE = "false";
14+
1015
export const DIAGNOSTIC_CODE = "codiga";
1116
export const DIAGNOSTIC_SOURCE = "Codiga";
1217
export const LEARN_MORE_COMMAND = "CODIGA_LEARN_MORE";
@@ -68,3 +73,21 @@ export const ELEMENT_CHECKED_TO_ENTITY_CHECKED: Map<string, string> = new Map([
6873
[ELEMENT_CHECKED_IMPORT, ROSIE_ENTITY_IMPORT],
6974
[ELEMENT_CHECKED_ASSIGNMENT, ROSIE_ENTITY_ASSIGNMENT],
7075
]);
76+
77+
export const INFO_MESSAGE_CODIGA_FILE_KEY = "ignoreCodigaFile";
78+
export const INFO_MESSAGE_CODIGA_FILE =
79+
"Check for security, code style in your Python code with Codiga";
80+
export const INFO_MESSAGE_CODIGA_FILE_ACTION_CREATE =
81+
"Create a codiga.yml file to check code";
82+
export const INFO_MESSAGE_CODIGA_FILE_ACTION_IGNORE = "Never remind me";
83+
84+
export const DEFAULT_PYTHON_RULESET_CONFIG = `
85+
rulesets:
86+
- python-security
87+
- python-best-practices
88+
- python-code-style
89+
`.trim();
90+
91+
export const ROSIE_SUPPORTED_LANGUAGES = [Language.Python];
92+
93+
export const ROSIE_LANGUAGE_DETECT_MAX_RESULTS = 1;

src/extension.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import {
4646
IgnoreViolation,
4747
ignoreViolation,
4848
} from "./diagnostics/ignore-violation";
49+
import { checkCodigaFileSuggestion } from "./utils/startupUtils";
4950

5051
// this method is called when your extension is activated
5152
// your extension is activated the very first time the command is executed
@@ -226,6 +227,8 @@ export async function activate(context: vscode.ExtensionContext) {
226227
context.subscriptions.push(codeCompletionProvider);
227228
});
228229

230+
await checkCodigaFileSuggestion();
231+
229232
/**
230233
* Finally, attempt to get the current user. If the current user
231234
* does not show, we propose to configure the API keys.

src/rosie/rosieUtils.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import * as vscode from "vscode";
2+
import { Extension } from "vscode";
3+
4+
import {
5+
ROSIE_LANGUAGE_DETECT_MAX_RESULTS,
6+
ROSIE_SUPPORTED_LANGUAGES,
7+
} from "../constants";
8+
import { LANGUAGE_TO_EXTENSION } from "../utils/fileUtils";
9+
10+
/**
11+
* find out if the workspace contains files of
12+
* languages that rosie supports
13+
* @returns boolean
14+
*/
15+
export async function isRosieLanguageDetected(): Promise<boolean> {
16+
/**
17+
* get an array of extensions (with the . removed)
18+
* for all supported rosie languages
19+
*/
20+
const rosieSupportedExtensions = ROSIE_SUPPORTED_LANGUAGES.reduce(
21+
(acc, cVal) => {
22+
return [...acc, ...LANGUAGE_TO_EXTENSION[cVal]];
23+
},
24+
[] as string[]
25+
).map((extension) => extension.substring(1));
26+
27+
/**
28+
* look through the workspace/files to see if
29+
* it's using a language that codiga/rosie supports
30+
* TODO - detect which languages are used to
31+
* improve ruleset suggestion
32+
*/
33+
const shouldSuggestCodiga = await vscode.workspace
34+
.findFiles(
35+
`**/*.{${rosieSupportedExtensions.join(",")}}`,
36+
"node_modules",
37+
ROSIE_LANGUAGE_DETECT_MAX_RESULTS
38+
)
39+
.then(
40+
(files) => files && files.length === ROSIE_LANGUAGE_DETECT_MAX_RESULTS
41+
);
42+
43+
return shouldSuggestCodiga;
44+
}

src/utils/fileUtils.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import * as fs from "fs";
12
import * as vscode from "vscode";
23
import { Language } from "../graphql-api/types";
34
const pathModule = require("path");
@@ -29,6 +30,7 @@ const EXTENSION_TO_LANGUAGE: Record<string, Language> = {
2930
".php4": Language.Php,
3031
".php5": Language.Php,
3132
".php": Language.Php,
33+
".ipynb": Language.Python,
3234
".py": Language.Python,
3335
".py3": Language.Python,
3436
".pm": Language.Perl,
@@ -51,6 +53,22 @@ const EXTENSION_TO_LANGUAGE: Record<string, Language> = {
5153
".yaml": Language.Yaml,
5254
};
5355

56+
/**
57+
* converts the EXTENSION_TO_LANGUAGE object into:
58+
* { [Language]: extension-strings[] }
59+
* used to get all extensions for a language
60+
*/
61+
export const LANGUAGE_TO_EXTENSION = Object.entries(
62+
EXTENSION_TO_LANGUAGE
63+
).reduce((acc, [extension, language]) => {
64+
if (acc[language]) {
65+
acc[language].push(extension);
66+
} else {
67+
acc[language] = [extension];
68+
}
69+
return acc;
70+
}, {} as { [key in Language]: string[] });
71+
5472
export function getBasename(filename: string): string | undefined {
5573
const parsedFilename: any = pathModule.parse(filename);
5674
const basename: string | undefined = parsedFilename.base;
@@ -190,3 +208,33 @@ export function insertIndentSize(size: number, tabSize: number) {
190208
.fill("\t")
191209
.join("");
192210
}
211+
212+
/**
213+
* creates a Uri for the given file, if workspaces are present
214+
* @param fileLocation string - from root
215+
* @returns vscode.Uri | null
216+
*/
217+
export default function getFileUri(fileLocation: string): vscode.Uri | null {
218+
const workspaceFolder = vscode.workspace.workspaceFolders;
219+
/**
220+
* if no workspaces are open or the length is zero,
221+
* then the file cannot exist, so we return null
222+
*/
223+
if (!workspaceFolder || workspaceFolder.length === 0) {
224+
return null;
225+
}
226+
return vscode.Uri.joinPath(workspaceFolder[0].uri, fileLocation);
227+
}
228+
229+
/**
230+
* checks if a file exists in the location given
231+
* @param fileLocation string - from root
232+
* @returns boolean
233+
*/
234+
export function doesFileExist(fileLocation: string): boolean {
235+
const fileUri = getFileUri(fileLocation);
236+
if (!fileUri) {
237+
return false;
238+
}
239+
return fs.existsSync(fileUri.fsPath);
240+
}

src/utils/startupUtils.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import * as vscode from "vscode";
2+
import {
3+
CODIGA_RULES_FILE,
4+
DEFAULT_PYTHON_RULESET_CONFIG,
5+
INFO_MESSAGE_CODIGA_FILE,
6+
INFO_MESSAGE_CODIGA_FILE_ACTION_CREATE,
7+
INFO_MESSAGE_CODIGA_FILE_ACTION_IGNORE,
8+
INFO_MESSAGE_CODIGA_FILE_KEY,
9+
VALUE_STRING_TRUE,
10+
} from "../constants";
11+
import { isRosieLanguageDetected } from "../rosie/rosieUtils";
12+
import getFileUri, { doesFileExist } from "./fileUtils";
13+
import { getFromLocalStorage, setToLocalStorage } from "./localStorage";
14+
15+
/**
16+
* checks whether we should suggest a codiga.yml file
17+
* for this user's workspace
18+
*/
19+
export async function checkCodigaFileSuggestion() {
20+
/**
21+
* check if
22+
* - the user has ignore this warning before
23+
* - if there's a codiga.yml file present
24+
* - if the workspace contains languages that rosie supports
25+
*/
26+
if (
27+
getFromLocalStorage(INFO_MESSAGE_CODIGA_FILE_KEY) !== VALUE_STRING_TRUE &&
28+
!doesFileExist(CODIGA_RULES_FILE) &&
29+
(await isRosieLanguageDetected())
30+
) {
31+
vscode.window
32+
.showInformationMessage(
33+
INFO_MESSAGE_CODIGA_FILE,
34+
INFO_MESSAGE_CODIGA_FILE_ACTION_CREATE,
35+
INFO_MESSAGE_CODIGA_FILE_ACTION_IGNORE
36+
)
37+
.then(async (selectedItem) => {
38+
if (selectedItem === INFO_MESSAGE_CODIGA_FILE_ACTION_CREATE) {
39+
const codigaUri = getFileUri(CODIGA_RULES_FILE);
40+
if (codigaUri) {
41+
const codigaFileContent = Buffer.from(
42+
DEFAULT_PYTHON_RULESET_CONFIG,
43+
"utf-8"
44+
);
45+
await vscode.workspace.fs.writeFile(codigaUri, codigaFileContent);
46+
}
47+
}
48+
if (selectedItem === INFO_MESSAGE_CODIGA_FILE_ACTION_IGNORE) {
49+
setToLocalStorage(INFO_MESSAGE_CODIGA_FILE_KEY, VALUE_STRING_TRUE);
50+
}
51+
});
52+
}
53+
}

0 commit comments

Comments
 (0)