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

JS-160 Create Java based AST #4731

Merged
merged 47 commits into from
Jun 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
2d49506
JS-160 Create Java based AST
saberduck Jun 7, 2024
fd18ddf
Reformat code
quentin-jaquier-sonarsource Jun 10, 2024
bb0f369
Extract redundant method call in variable
quentin-jaquier-sonarsource Jun 10, 2024
5660b55
Add Support for directive
quentin-jaquier-sonarsource Jun 10, 2024
694c18b
Change type for Function/Class declaration
quentin-jaquier-sonarsource Jun 10, 2024
03ca5f8
Handle optional elements
quentin-jaquier-sonarsource Jun 10, 2024
0fa4ef7
Handle directive
quentin-jaquier-sonarsource Jun 10, 2024
8af8b95
implement literal mapping
quentin-jaquier-sonarsource Jun 10, 2024
eaab7ed
remove irrelevant comment
quentin-jaquier-sonarsource Jun 10, 2024
79bfe23
add more tests
quentin-jaquier-sonarsource Jun 10, 2024
4a41813
Changing handling of AssignmentProperty
quentin-jaquier-sonarsource Jun 10, 2024
eaa1cb7
Use correctly operators enums
quentin-jaquier-sonarsource Jun 10, 2024
0ae217b
Make ExportAllDeclaration exported optional
quentin-jaquier-sonarsource Jun 11, 2024
c844912
Add remaining optional types
quentin-jaquier-sonarsource Jun 11, 2024
f07aa0c
return undefined instead of empty object
quentin-jaquier-sonarsource Jun 11, 2024
7c94ada
fix type missmatch
quentin-jaquier-sonarsource Jun 11, 2024
64be171
JS-160: parse serialized AST in Java plugin (#4732)
ilia-kebets-sonarsource Jun 11, 2024
55e2152
fix tests
quentin-jaquier-sonarsource Jun 11, 2024
608e622
Only try to build Java AST when response is not null
quentin-jaquier-sonarsource Jun 11, 2024
84e3dfa
test handling of null literal
quentin-jaquier-sonarsource Jun 11, 2024
f218d70
Increase plugin expected size
quentin-jaquier-sonarsource Jun 11, 2024
05929e9
revert erroneous change
quentin-jaquier-sonarsource Jun 11, 2024
04122fd
Add test on serialized file
quentin-jaquier-sonarsource Jun 11, 2024
e40c685
Ensure type safety for null elements in arrays
quentin-jaquier-sonarsource Jun 11, 2024
bb3e3c4
Add more tests
quentin-jaquier-sonarsource Jun 11, 2024
bf4f3f3
Fix ast.ts code
quentin-jaquier-sonarsource Jun 11, 2024
a65b5b7
return a JSON response as usual when we don't support serializing the…
ilia-kebets-sonarsource Jun 12, 2024
fafec8e
remove remaining usage of format in request test helper
ilia-kebets-sonarsource Jun 12, 2024
fd6e671
don't crash if you cannot serialize JS
ilia-kebets-sonarsource Jun 12, 2024
2ccec24
format
ilia-kebets-sonarsource Jun 12, 2024
a63fcab
remove ast from JSON response
ilia-kebets-sonarsource Jun 12, 2024
e9e462b
more formatting
ilia-kebets-sonarsource Jun 12, 2024
99f50e0
push CSS fix
ilia-kebets-sonarsource Jun 12, 2024
acdc224
Change number of processed file in IT
quentin-jaquier-sonarsource Jun 12, 2024
58018d7
add debug line
ilia-kebets-sonarsource Jun 12, 2024
dd707b6
Merge branch 'JS-160' of github.com:SonarSource/SonarJS into JS-160
ilia-kebets-sonarsource Jun 12, 2024
7b0796e
Improve test coverage
quentin-jaquier-sonarsource Jun 12, 2024
b10bd5f
fix issue
quentin-jaquier-sonarsource Jun 12, 2024
e592e29
CAYC
quentin-jaquier-sonarsource Jun 12, 2024
d390109
fix tests
quentin-jaquier-sonarsource Jun 12, 2024
c4b735f
put this back to private
ilia-kebets-sonarsource Jun 12, 2024
2a84b0a
Merge branch 'JS-160' of github.com:SonarSource/SonarJS into JS-160
ilia-kebets-sonarsource Jun 12, 2024
8a29871
improve coverage
ilia-kebets-sonarsource Jun 12, 2024
23bdaf9
Add comment for nul filtering corner case
quentin-jaquier-sonarsource Jun 12, 2024
50af5ad
Update sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/p…
ilia-kebets-sonarsource Jun 12, 2024
d7b6026
cleanup after review
ilia-kebets-sonarsource Jun 12, 2024
b237f2b
Merge branch 'JS-160' of github.com:SonarSource/SonarJS into JS-160
ilia-kebets-sonarsource Jun 12, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@
*/
package com.sonar.javascript.it.plugin;

import static com.sonar.javascript.it.plugin.OrchestratorStarter.JAVASCRIPT_PLUGIN_LOCATION;
import static org.assertj.core.api.Assertions.assertThat;

import com.sonar.orchestrator.Orchestrator;
import com.sonar.orchestrator.build.BuildResult;
import com.sonar.orchestrator.build.SonarScanner;
Expand All @@ -32,6 +29,9 @@
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import static com.sonar.javascript.it.plugin.OrchestratorStarter.JAVASCRIPT_PLUGIN_LOCATION;
import static org.assertj.core.api.Assertions.assertThat;

class ConsumerPluginTest {

private static final String PLUGIN_ARTIFACT_ID = "consumer-plugin";
Expand Down Expand Up @@ -97,6 +97,7 @@ void test() {
var logMatch = ".*DEBUG: Registered JsAnalysisConsumers \\[org.sonar.samples.javascript.consumer.Consumer.*]";
assertThat(buildResult.getLogsLines(l -> l.matches(logMatch))).hasSize(1);

assertThat(buildResult.getLogsLines(l -> l.matches(".*Processing file src/dir.*"))).hasSize(2);
// TS file is not processed yet.
assertThat(buildResult.getLogsLines(l -> l.matches(".*Processing file src/dir.*"))).hasSize(1);
}
}
12 changes: 8 additions & 4 deletions packages/bridge/src/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,15 @@ exports.delegate = function (worker, type) {
case 'success':
if (message.format === 'multipart') {
const fd = new formData();
const buf = Buffer.from(message.result.ast);
fd.append('ast', buf);
fd.append('ast', Buffer.from(message.result.ast));
delete message.result.ast;
fd.append('json', JSON.stringify(message.result));
// this adds the boundary string that will be used to separate the parts
response.set('Content-Type', fd.getHeaders()['content-type']);
response.set('Content-Length', fd.getLengthSync());
fd.pipe(response);
} else {
delete message.result.ast;
response.send(message.result);
}
break;
Expand Down Expand Up @@ -109,7 +109,7 @@ if (parentPort) {
parentThread.postMessage({
type: 'success',
result: output,
format: 'multipart',
format: isSupported(output.ast) ? 'multipart' : 'json',
});
break;
}
Expand All @@ -128,7 +128,7 @@ if (parentPort) {
parentThread.postMessage({
type: 'success',
result: output,
format: 'multipart',
format: isSupported(output.ast) ? 'multipart' : 'json',
});
break;
}
Expand Down Expand Up @@ -231,3 +231,7 @@ if (parentPort) {
}
}
}

function isSupported(ast) {
return ast !== 'not-supported';
}
22 changes: 7 additions & 15 deletions packages/bridge/tests/router.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,11 @@ describe('router', () => {
const filePath = path.join(fixtures, 'file.js');
const fileType = 'MAIN';
const data = { filePath, fileType, tsConfigs: [] };
const response = (await request(server, '/analyze-js', 'POST', data, 'formdata')) as FormData;
const response = (await request(server, '/analyze-js', 'POST', data)) as FormData;
const responseData = JSON.parse(response.get('json') as string);
const {
issues: [issue],
} = JSON.parse(response.get('json') as string);
} = responseData;
expect(issue).toEqual(
expect.objectContaining({
ruleId: 'prefer-regex-literals',
Expand All @@ -149,10 +150,10 @@ describe('router', () => {
const fileType = 'MAIN';
const tsConfig = path.join(fixtures, 'tsconfig.json');
const data = { filePath, fileType, tsConfigs: [tsConfig] };
const response = (await request(server, '/analyze-ts', 'POST', data, 'formdata')) as FormData;
const response = (await request(server, '/analyze-ts', 'POST', data)) as string;
const {
issues: [issue],
} = JSON.parse(response.get('json') as string);
} = JSON.parse(response);
expect(issue).toEqual(
expect.objectContaining({
ruleId: 'no-duplicate-in-composite',
Expand All @@ -163,9 +164,6 @@ describe('router', () => {
message: `Remove this duplicated type or replace with another one.`,
}),
);
const ast = response.get('ast') as File;
const buffer = Buffer.from(await ast.arrayBuffer());
expect(buffer.toString()).toEqual('plop');
});

it('should route /analyze-with-program requests', async () => {
Expand All @@ -179,16 +177,10 @@ describe('router', () => {
(await request(server, '/create-program', 'POST', { tsConfig })) as string,
);
const data = { filePath, fileType, programId };
const response = (await request(
server,
'/analyze-with-program',
'POST',
data,
'formdata',
)) as FormData;
const response = (await request(server, '/analyze-with-program', 'POST', data)) as string;
const {
issues: [issue],
} = JSON.parse(response.get('json') as string);
} = JSON.parse(response);
expect(issue).toEqual(
expect.objectContaining({
ruleId: 'no-duplicate-in-composite',
Expand Down
14 changes: 5 additions & 9 deletions packages/bridge/tests/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { start } from '../src/server';
import path from 'path';
import { setContext } from '@sonar/shared';
import { AddressInfo } from 'net';
import { BridgeResponseType, request } from './tools';
import { request } from './tools';
import http from 'http';

describe('server', () => {
Expand Down Expand Up @@ -75,7 +75,7 @@ describe('server', () => {
});

expect(await requestInitLinter(server, fileType, ruleId)).toBe('OK!');
const response = await requestAnalyzeJs(server, fileType, 'formdata');
const response = await requestAnalyzeJs(server, fileType);
const {
issues: [issue],
} = JSON.parse(response.get('json'));
Expand All @@ -99,7 +99,7 @@ describe('server', () => {
const fileType = 'MAIN';

await requestInitLinter(server, fileType, ruleId);
const response = await requestAnalyzeJs(server, fileType, 'formdata');
const response = await requestAnalyzeJs(server, fileType);

const {
issues: [issue],
Expand Down Expand Up @@ -162,15 +162,11 @@ describe('server', () => {
});
});

async function requestAnalyzeJs(
server: http.Server,
fileType: string,
format: BridgeResponseType = 'text',
): Promise<any> {
async function requestAnalyzeJs(server: http.Server, fileType: string): Promise<any> {
const filePath = path.join(__dirname, 'fixtures', 'routing.js');
const analysisInput = { filePath, fileType };

return await request(server, '/analyze-js', 'POST', analysisInput, format);
return await request(server, '/analyze-js', 'POST', analysisInput);
}

function requestInitLinter(server: http.Server, fileType: string, ruleId: string) {
Expand Down
21 changes: 7 additions & 14 deletions packages/bridge/tests/tools/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,7 @@ export type BridgeResponseType = 'text' | 'formdata';
/**
* Sends an HTTP request to a server's endpoint running on localhost.
*/
export async function request(
server: http.Server,
path: string,
method: string,
body: any = {},
format: BridgeResponseType = 'text',
) {
export async function request(server: http.Server, path: string, method: string, body: any = {}) {
const res = await fetch(`http://127.0.0.1:${(server.address() as AddressInfo).port}${path}`, {
headers: {
'Content-Type': 'application/json',
Expand All @@ -40,12 +34,11 @@ export async function request(
body: method !== 'GET' ? JSON.stringify(body) : undefined,
});

switch (format) {
case 'text':
return res.text();
case 'formdata':
return res.formData();
default:
throw new Error(`Unsupported format: ${format}`);
const contentTypeHeader = res.headers.get('Content-Type');

if (contentTypeHeader?.includes('multipart/form-data')) {
return res.formData();
} else {
return res.text();
}
}
14 changes: 10 additions & 4 deletions packages/jsts/src/analysis/analyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { APIError, debug, getContext, JsTsLanguage } from '@sonar/shared';
import { APIError, debug, info, getContext, JsTsLanguage } from '@sonar/shared';
import { SourceCode } from 'eslint';
import {
computeMetrics,
Expand Down Expand Up @@ -85,6 +85,7 @@ function analyzeFile(
highlightedSymbols,
cognitiveComplexity,
);

return {
issues,
ucfgPaths,
Expand All @@ -105,10 +106,15 @@ function analyzeFile(
* Remove this when we figure out how to serialize the TypeScript AST
*/
function serializeAst(sourceCode: SourceCode, filePath: string) {
if (isSupported(filePath)) {
if (!isSupported(filePath)) {
return 'not-supported';
}

try {
return serializeInProtobuf(sourceCode.ast);
} else {
return 'plop';
} catch (e) {
info(`Failed to serialize AST for file "${filePath}"`);
return 'not-supported';
}

function isSupported(filePath: string) {
Expand Down
11 changes: 8 additions & 3 deletions packages/jsts/src/parsers/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ export function deserializeProtobuf(serialized: Uint8Array): any {

export function visitNode(node: estree.BaseNodeWithoutComments | undefined | null): any {
if (!node) {
return {};
// Null and undefined will be both serialized as "not set" in protobuf when the field is optional.
return undefined;
}

return {
Expand Down Expand Up @@ -362,8 +363,10 @@ export function visitNode(node: estree.BaseNodeWithoutComments | undefined | nul
}

function visitArrayPattern(node: estree.ArrayPattern) {
// If the elements ever contain null together with other nodes, protobuf will fail to serialize the array.
// It is unclear from the estree documentation if this is possible, but as it is from a type perspective, we prefer to be safe.
return {
elements: node.elements.map(visitNode),
elements: node.elements.filter(e => e !== null).map(visitNode),
};
}

Expand Down Expand Up @@ -518,8 +521,10 @@ export function visitNode(node: estree.BaseNodeWithoutComments | undefined | nul
}

function visitArrayExpression(node: estree.ArrayExpression) {
// If the elements ever contain null together with other nodes, protobuf will fail to serialize the array.
// It is unclear from the estree documentation if this is possible, but as it is from a type perspective, we prefer to be safe.
return {
elements: node.elements.map(visitNode),
elements: node.elements.filter(e => e !== null).map(visitNode),
};
}

Expand Down
Loading
Loading