Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/legacy json support #108

Merged
merged 1 commit into from
Sep 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions development.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ To use and debug package locally you don't need publish it to NPM registry:

```shell script
$ cd <package-location>
$ npm install && npm run build && npx yalc publish
$ npm install && npm run build && npm link
```

After that you have to create symlink to your package in your project folder:

```shell script
$ cd <project-location>
$ npx yalc add @snyk/code-client
$ npm link @snyk/code-client
```

## Publishing
Expand Down
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,7 @@
"prettier": "^2.1.1",
"ts-jest": "^26.3.0",
"typescript": "^4.0.2",
"write": "^2.0.0",
"yalc": "^1.0.0-pre.53"
"write": "^2.0.0"
},
"dependencies": {
"@deepcode/dcignore": "^1.0.4",
Expand Down
117 changes: 93 additions & 24 deletions src/analysis.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/* eslint-disable no-await-in-loop */
import omit from 'lodash.omit';

import { AnalyzeFoldersOptions, prepareExtendingBundle } from './files';
import { AnalyzeFoldersOptions, prepareExtendingBundle, resolveBundleFilePath } from './files';
import { POLLING_INTERVAL } from './constants';
import {
GetAnalysisErrorCodes,
getAnalysis,
Expand All @@ -12,9 +14,10 @@ import {
ConnectionOptions,
GetAnalysisOptions,
} from './http';
import { fromEntries } from './lib/utils';
import { createBundleFromFolders, FileBundle, remoteBundleFactory } from './bundles';
import emitter from './emitter';
import { AnalysisResult } from './interfaces/analysis-result.interface';
import { emitter } from './emitter';
import { AnalysisResult, AnalysisResultLegacy, AnalysisResultSarif, AnalysisFiles, Suggestion } from './interfaces/analysis-result.interface';

const sleep = (duration: number) => new Promise(resolve => setTimeout(resolve, duration));

Expand Down Expand Up @@ -56,7 +59,7 @@ async function pollAnalysis(
return analysisResponse as Result<AnalysisFailedResponse, GetAnalysisErrorCodes>;
}

await sleep(500);
await sleep(POLLING_INTERVAL);
}
}

Expand All @@ -73,25 +76,25 @@ export async function analyzeBundle(options: GetAnalysisOptions): Promise<Analys
return analysisData.value;
}

// function normalizeResultFiles(files: AnalysisFiles, baseDir: string): AnalysisFiles {
// if (baseDir) {
// return fromEntries(
// Object.entries(files).map(([path, positions]) => {
// const filePath = resolveBundleFilePath(baseDir, path);
// return [filePath, positions];
// }),
// );
// }
// return files;
// }
function normalizeResultFiles(files: AnalysisFiles, baseDir: string): AnalysisFiles {
if (baseDir) {
return fromEntries(
Object.entries(files).map(([path, positions]) => {
const filePath = resolveBundleFilePath(baseDir, path);
return [filePath, positions];
}),
);
}
return files;
}

interface FileAnalysisOptions {
connection: ConnectionOptions;
analysisOptions: AnalysisOptions;
fileOptions: AnalyzeFoldersOptions;
}

interface FileAnalysis extends FileAnalysisOptions {
export interface FileAnalysis extends FileAnalysisOptions {
fileBundle: FileBundle;
analysisResults: AnalysisResult;
}
Expand All @@ -109,18 +112,36 @@ export async function analyzeFolders(options: FileAnalysisOptions): Promise<File
...options.connection,
...options.analysisOptions,
});
// TODO: expand relative file names to absolute ones
// analysisResults.files = normalizeResultFiles(analysisData.analysisResults.files, baseDir);

if (analysisResults.type === 'legacy') {
// expand relative file names to absolute ones only for legacy results
analysisResults.files = normalizeResultFiles(analysisResults.files, fileBundle.baseDir);
}

return { fileBundle, analysisResults, ...options };
}

function mergeBundleResults(
oldAnalysisResults: AnalysisResult,
function mergeBundleResults(oldAnalysisResults: AnalysisResult,
newAnalysisResults: AnalysisResult,
limitToFiles: string[],
removedFiles: string[] = [],
baseDir: string,
): AnalysisResult {

if (newAnalysisResults.type == 'sarif') {
return mergeSarifResults(oldAnalysisResults as AnalysisResultSarif, newAnalysisResults, limitToFiles, removedFiles);
}

return mergeLegacyResults(oldAnalysisResults as AnalysisResultLegacy, newAnalysisResults, limitToFiles, removedFiles, baseDir);

}

function mergeSarifResults(
oldAnalysisResults: AnalysisResultSarif,
newAnalysisResults: AnalysisResultSarif,
limitToFiles: string[],
removedFiles: string[] = [],
): AnalysisResultSarif {
// Start from the new analysis results
// For each finding of the old analysis,
// if it's location is not part of the limitToFiles or removedFiles (removedFiles should also be checked against condeFlow),
Expand Down Expand Up @@ -189,6 +210,57 @@ function mergeBundleResults(
return newAnalysisResults;
}

const moveSuggestionIndexes = <T>(
suggestionIndex: number,
suggestions: { [index: string]: T },
): { [index: string]: T } => {
const entries = Object.entries(suggestions);
return fromEntries(
entries.map(([i, s]) => {
return [`${parseInt(i, 10) + suggestionIndex + 1}`, s];
}),
);
};

function mergeLegacyResults(
oldAnalysisResults: AnalysisResultLegacy,
newAnalysisResults: AnalysisResultLegacy,
limitToFiles: string[],
removedFiles: string[] = [],
baseDir: string,
): AnalysisResultLegacy {

// expand relative file names to absolute ones only for legacy results
newAnalysisResults.files = normalizeResultFiles(newAnalysisResults.files, baseDir);

// Determine max suggestion index in our data
const suggestionIndex = Math.max(...Object.keys(oldAnalysisResults.suggestions).map(i => parseInt(i, 10))) || -1;

// Addup all new suggestions' indexes
const newSuggestions = moveSuggestionIndexes<Suggestion>(suggestionIndex, newAnalysisResults.suggestions);
const suggestions = { ...oldAnalysisResults.suggestions, ...newSuggestions };

const newFiles = fromEntries(
Object.entries(newAnalysisResults.files).map(([fn, s]) => {
return [fn, moveSuggestionIndexes(suggestionIndex, s)];
}),
);

// expand relative file names to absolute ones only for legacy results
const changedFiles = [...limitToFiles, ...removedFiles].map(path => resolveBundleFilePath(baseDir, path));

const files = {
...omit(oldAnalysisResults.files, changedFiles),
...newFiles,
};

return {
...newAnalysisResults,
files,
suggestions,
};
}

interface ExtendAnalysisOptions extends FileAnalysis {
files: string[];
}
Expand Down Expand Up @@ -234,10 +306,7 @@ export async function extendAnalysis(options: ExtendAnalysisOptions): Promise<Fi
limitToFiles,
});

// TODO: Transform relative paths into absolute
// analysisData.analysisResults.files = normalizeResultFiles(analysisData.analysisResults.files, bundle.baseDir);

analysisResults = mergeBundleResults(options.analysisResults, analysisResults, limitToFiles, removedFiles);
analysisResults = mergeBundleResults(options.analysisResults, analysisResults, limitToFiles, removedFiles, options.fileBundle.baseDir);

return { ...options, fileBundle, analysisResults };
}
2 changes: 1 addition & 1 deletion src/bundles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
} from './http';

import { MAX_PAYLOAD, MAX_UPLOAD_ATTEMPTS, UPLOAD_CONCURRENCY } from './constants';
import emitter from './emitter';
import { emitter } from './emitter';

type BundleErrorCodes = CreateBundleErrorCodes | CheckBundleErrorCodes | ExtendBundleErrorCodes;

Expand Down
1 change: 1 addition & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const EXCLUDED_NAMES = [GIT_FILENAME, GITIGNORE_FILENAME, DCIGNORE_FILENA
export const CACHE_KEY = '.dccache';
export const MAX_UPLOAD_ATTEMPTS = 5;
export const UPLOAD_CONCURRENCY = 5;
export const POLLING_INTERVAL = 500;
export const MAX_RETRY_ATTEMPTS = 5; // Request retries on network errors
export const REQUEST_RETRY_DELAY = 30 * 1000; // 30 seconds delay between retries

Expand Down
3 changes: 1 addition & 2 deletions src/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,4 @@ export class EmitterDC extends EventEmitter {
}
}

const emitter = new EmitterDC();
export default emitter;
export const emitter = new EmitterDC();
5 changes: 2 additions & 3 deletions src/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import pick from 'lodash.pick';
import { ErrorCodes, GenericErrorTypes, DEFAULT_ERROR_MESSAGES } from './constants';

import { BundleFiles, SupportedFiles } from './interfaces/files.interface';
// import { AnalysisSeverity } from './interfaces/analysis-options.interface';
import { AnalysisResult } from './interfaces/analysis-result.interface';
import { makeRequest, Payload } from './needle';

Expand Down Expand Up @@ -386,6 +385,7 @@ export interface AnalysisOptions {
readonly severity?: number;
readonly limitToFiles?: string[];
readonly prioritized?: boolean;
readonly legacy?: boolean;
}

export interface GetAnalysisOptions extends ConnectionOptions, AnalysisOptions {
Expand All @@ -408,8 +408,7 @@ export async function getAnalysis(
hash: options.bundleHash,
limitToFiles: options.limitToFiles || [],
},
...pick(options, ['severity', 'prioritized']),
// severity: options.severity || AnalysisSeverity.info,
...pick(options, ['severity', 'prioritized', 'legacy']),
},
};

Expand Down
24 changes: 16 additions & 8 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
import { analyzeFolders } from './analysis';
import { analyzeFolders, extendAnalysis, FileAnalysis } from './analysis';
import { createBundleFromFolders } from './bundles';
import emitter from './emitter';
import { startSession, checkSession, getAnalysis } from './http';
import { emitter } from './emitter';
import { startSession, checkSession, getAnalysis, getIpFamily, IpFamily } from './http';
import * as constants from './constants';
import { getGlobPatterns } from './files';

import { SupportedFiles } from './interfaces/files.interface';
import { AnalysisSeverity } from './interfaces/analysis-options.interface';
import { AnalysisResult } from './interfaces/analysis-result.interface';
import { AnalysisResult, AnalysisResultLegacy, FilePath, FileSuggestion, Suggestion, Marker } from './interfaces/analysis-result.interface';

export {
getGlobPatterns,
analyzeFolders,
createBundleFromFolders,
// extendAnalysis,
getAnalysis,
startSession,
checkSession,
extendAnalysis,
emitter,
constants,
AnalysisSeverity,
AnalysisResult,
AnalysisResultLegacy,
SupportedFiles,
FileAnalysis,
FilePath,
FileSuggestion,
Suggestion,
Marker,
getAnalysis,
startSession,
checkSession,
getIpFamily,
IpFamily,
};
75 changes: 73 additions & 2 deletions src/interfaces/analysis-result.interface.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Log } from 'sarif';
import { AnalysisSeverity } from './analysis-options.interface';

interface Coverage {
files: number;
isSupported: boolean;
lang: string;
}

export interface AnalysisResult {
sarif: Log;
interface AnalysisResultBase {
timing: {
fetchingCode: number;
analysis: number;
Expand All @@ -16,3 +16,74 @@ export interface AnalysisResult {
coverage: Coverage[];
status: 'COMPLETE';
}

export interface AnalysisResultSarif extends AnalysisResultBase {
type: 'sarif';
sarif: Log;
}

export interface Position {
cols: Point;
rows: Point;
}

export interface MarkerPosition extends Position {
file: string;
}

export type Point = [number, number];

export interface Marker {
msg: Point;
pos: MarkerPosition[];
}

export interface FileSuggestion extends Position {
markers?: Marker[];
}

export interface FilePath {
[suggestionIndex: string]: FileSuggestion[];
}

export interface AnalysisFiles {
[filePath: string]: FilePath;
}

interface CommitChangeLine {
line: string;
lineNumber: number;
lineChange: 'removed' | 'added' | 'none';
}

interface ExampleCommitFix {
commitURL: string;
lines: CommitChangeLine[];
}

export interface Suggestion {
id: string;
message: string;
severity: AnalysisSeverity;
leadURL?: string;
rule: string;
tags: string[];
categories: string[];
repoDatasetSize: number;
exampleCommitDescriptions: string[];
exampleCommitFixes: ExampleCommitFix[];
cwe: string[];
title: string;
text: string;
}

export interface Suggestions {
[suggestionIndex: string]: Suggestion;
}
export interface AnalysisResultLegacy extends AnalysisResultBase {
type: 'legacy';
suggestions: Suggestions;
files: AnalysisFiles;
}

export type AnalysisResult = AnalysisResultSarif | AnalysisResultLegacy;
Loading