refactor(bazel): Simplify parser output structure (#18270)

This commit is contained in:
Sergei Zharinov 2022-10-12 07:55:13 +03:00 committed by GitHub
parent 8b9259b0e9
commit f2d85c16cd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 135 additions and 161 deletions

View file

@ -1,8 +1,7 @@
import { logger } from '../../../logger';
import type { PackageDependency, PackageFile } from '../types'; import type { PackageDependency, PackageFile } from '../types';
import { parse } from './parser'; import { parse } from './parser';
import { extractDepFromFragment } from './rules'; import { extractDepFromFragment } from './rules';
import type { ArrayFragment } from './types'; import type { RecordFragment } from './types';
export function extractPackageFile( export function extractPackageFile(
content: string, content: string,
@ -10,19 +9,13 @@ export function extractPackageFile(
): PackageFile | null { ): PackageFile | null {
const deps: PackageDependency[] = []; const deps: PackageDependency[] = [];
let parsed: ArrayFragment | null = null; const fragments: RecordFragment[] | null = parse(content, packageFile);
try { if (!fragments) {
parsed = parse(content);
} catch (err) /* istanbul ignore next */ {
logger.debug({ err, packageFile }, 'Bazel parsing error');
}
if (!parsed) {
return null; return null;
} }
for (let idx = 0; idx < parsed.children.length; idx += 1) { for (let idx = 0; idx < fragments.length; idx += 1) {
const fragment = parsed.children[idx]; const fragment = fragments[idx];
const dep = extractDepFromFragment(fragment); const dep = extractDepFromFragment(fragment);
if (!dep) { if (!dep) {

View file

@ -9,42 +9,36 @@ describe('modules/manager/bazel/parser', () => {
const res = parse(input); const res = parse(input);
expect(res).toEqual({ expect(res).toEqual([
type: 'array', {
value: type: 'record',
'go_repository(name = "foo")\nmaybe(go_repository, name = "bar", deps = ["baz", "qux"])', value: 'go_repository(name = "foo")',
offset: 0, offset: 0,
children: [ children: {
{ rule: { type: 'string', value: 'go_repository', offset: 0 },
type: 'record', name: { type: 'string', value: 'foo', offset: 22 },
value: 'go_repository(name = "foo")', },
offset: 0, },
children: { {
rule: { type: 'string', value: 'go_repository', offset: 0 }, type: 'record',
name: { type: 'string', value: 'foo', offset: 22 }, value: 'maybe(go_repository, name = "bar", deps = ["baz", "qux"])',
offset: 28,
children: {
rule: { type: 'string', value: 'go_repository', offset: 34 },
name: { type: 'string', value: 'bar', offset: 57 },
deps: {
type: 'array',
value: '["baz", "qux"]',
offset: 70,
children: [
{ type: 'string', value: 'baz', offset: 72 },
{ type: 'string', value: 'qux', offset: 79 },
],
}, },
}, },
{ },
type: 'record', ]);
value: 'maybe(go_repository, name = "bar", deps = ["baz", "qux"])', expect(res?.map(extract)).toMatchObject([
offset: 28,
children: {
rule: { type: 'string', value: 'go_repository', offset: 34 },
name: { type: 'string', value: 'bar', offset: 57 },
deps: {
type: 'array',
value: '["baz", "qux"]',
offset: 70,
children: [
{ type: 'string', value: 'baz', offset: 72 },
{ type: 'string', value: 'qux', offset: 79 },
],
},
},
},
],
});
expect(extract(res!)).toMatchObject([
{ rule: 'go_repository', name: 'foo' }, { rule: 'go_repository', name: 'foo' },
{ rule: 'go_repository', name: 'bar', deps: ['baz', 'qux'] }, { rule: 'go_repository', name: 'bar', deps: ['baz', 'qux'] },
]); ]);
@ -66,45 +60,44 @@ describe('modules/manager/bazel/parser', () => {
const res = parse(input); const res = parse(input);
expect(res).toMatchObject({ expect(res).toMatchObject([
children: [ {
{ children: {
children: { rule: { value: 'http_archive' },
rule: { value: 'http_archive' }, name: { value: 'aspect_rules_js' },
name: { value: 'aspect_rules_js' }, sha256: {
sha256: { value:
value: 'db9f446752fe4100320cf8487e8fd476b9af0adf6b99b601bcfd70b289bb0598',
'db9f446752fe4100320cf8487e8fd476b9af0adf6b99b601bcfd70b289bb0598', },
}, strip_prefix: { value: 'rules_js-1.1.2' },
strip_prefix: { value: 'rules_js-1.1.2' }, url: {
url: { value:
value: 'https://github.com/aspect-build/rules_js/archive/refs/tags/v1.1.2.tar.gz',
'https://github.com/aspect-build/rules_js/archive/refs/tags/v1.1.2.tar.gz',
},
}, },
}, },
{ },
children: { {
rule: { value: 'http_archive' }, children: {
name: { value: 'rules_nodejs' }, rule: { value: 'http_archive' },
sha256: { name: { value: 'rules_nodejs' },
value: sha256: {
'5aef09ed3279aa01d5c928e3beb248f9ad32dde6aafe6373a8c994c3ce643064', value:
}, '5aef09ed3279aa01d5c928e3beb248f9ad32dde6aafe6373a8c994c3ce643064',
urls: { },
type: 'array', urls: {
children: [ type: 'array',
{ children: [
value: {
'https://github.com/bazelbuild/rules_nodejs/releases/download/5.5.3/rules_nodejs-core-5.5.3.tar.gz', value:
}, 'https://github.com/bazelbuild/rules_nodejs/releases/download/5.5.3/rules_nodejs-core-5.5.3.tar.gz',
], },
}, ],
}, },
}, },
], },
}); ]);
expect(extract(res!)).toMatchObject([
expect(res?.map(extract)).toMatchObject([
{ {
rule: 'http_archive', rule: 'http_archive',
name: 'aspect_rules_js', name: 'aspect_rules_js',
@ -134,25 +127,24 @@ describe('modules/manager/bazel/parser', () => {
const res = parse(input); const res = parse(input);
expect(res).toMatchObject({ expect(res).toMatchObject([
children: [ {
{ children: {
children: { name: { value: 'rules_nodejs' },
name: { value: 'rules_nodejs' }, rule: { value: 'http_archive' },
rule: { value: 'http_archive' }, sha256: {
sha256: { value:
value: '5aef09ed3279aa01d5c928e3beb248f9ad32dde6aafe6373a8c994c3ce643064',
'5aef09ed3279aa01d5c928e3beb248f9ad32dde6aafe6373a8c994c3ce643064', },
}, url: {
url: { value:
value: 'https://github.com/bazelbuild/rules_nodejs/releases/download/5.5.3/rules_nodejs-core-5.5.3.tar.gz',
'https://github.com/bazelbuild/rules_nodejs/releases/download/5.5.3/rules_nodejs-core-5.5.3.tar.gz',
},
}, },
}, },
], },
}); ]);
expect(extract(res!)).toMatchObject([
expect(res?.map(extract)).toMatchObject([
{ {
rule: 'http_archive', rule: 'http_archive',
name: 'rules_nodejs', name: 'rules_nodejs',
@ -176,37 +168,35 @@ describe('modules/manager/bazel/parser', () => {
const res = parse(input); const res = parse(input);
expect(res).toMatchObject({ expect(res).toMatchObject([
children: [ {
{ children: {
children: { name: { value: 'bazel_toolchains' },
name: { value: 'bazel_toolchains' }, rule: { value: 'http_archive' },
rule: { value: 'http_archive' }, sha256: {
sha256: { value:
value: '4b1468b254a572dbe134cc1fd7c6eab1618a72acd339749ea343bd8f55c3b7eb',
'4b1468b254a572dbe134cc1fd7c6eab1618a72acd339749ea343bd8f55c3b7eb', },
}, strip_prefix: {
strip_prefix: { value: 'bazel-toolchains-d665ccfa3e9c90fa789671bf4ef5f7c19c5715c4',
value: },
'bazel-toolchains-d665ccfa3e9c90fa789671bf4ef5f7c19c5715c4', urls: {
}, children: [
urls: { {
children: [ value:
{ 'https://mirror.bazel.build/github.com/bazelbuild/bazel-toolchains/archive/d665ccfa3e9c90fa789671bf4ef5f7c19c5715c4.tar.gz',
value: },
'https://mirror.bazel.build/github.com/bazelbuild/bazel-toolchains/archive/d665ccfa3e9c90fa789671bf4ef5f7c19c5715c4.tar.gz', {
}, value:
{ 'https://github.com/bazelbuild/bazel-toolchains/archive/d665ccfa3e9c90fa789671bf4ef5f7c19c5715c4.tar.gz',
value: },
'https://github.com/bazelbuild/bazel-toolchains/archive/d665ccfa3e9c90fa789671bf4ef5f7c19c5715c4.tar.gz', ],
},
],
},
}, },
}, },
], },
}); ]);
expect(extract(res!)).toMatchObject([
expect(res?.map(extract)).toMatchObject([
{ {
name: 'bazel_toolchains', name: 'bazel_toolchains',
rule: 'http_archive', rule: 'http_archive',

View file

@ -1,9 +1,9 @@
import { lang, lexer, parser, query as q } from 'good-enough-parser'; import { lang, lexer, parser, query as q } from 'good-enough-parser';
import hasha from 'hasha'; import hasha from 'hasha';
import { logger } from '../../../logger';
import * as memCache from '../../../util/cache/memory'; import * as memCache from '../../../util/cache/memory';
import { supportedRulesRegex } from './rules/index'; import { supportedRulesRegex } from './rules/index';
import type { import type {
ArrayFragment,
Fragment, Fragment,
FragmentData, FragmentData,
NestedFragment, NestedFragment,
@ -12,6 +12,7 @@ import type {
interface Ctx { interface Ctx {
readonly source: string; readonly source: string;
results: RecordFragment[];
stack: NestedFragment[]; stack: NestedFragment[];
recordKey?: string; recordKey?: string;
} }
@ -19,14 +20,8 @@ interface Ctx {
function emptyCtx(source: string): Ctx { function emptyCtx(source: string): Ctx {
return { return {
source, source,
stack: [ results: [],
{ stack: [],
type: 'array',
value: source,
offset: 0,
children: [],
},
],
}; };
} }
@ -139,12 +134,8 @@ function ruleCall(search: q.QueryBuilder<Ctx>): q.QueryBuilder<Ctx> {
const frag = currentFragment(ctx); const frag = currentFragment(ctx);
if (frag.type === 'record' && tree.type === 'wrapped-tree') { if (frag.type === 'record' && tree.type === 'wrapped-tree') {
frag.value = extractTreeValue(ctx.source, tree, frag.offset); frag.value = extractTreeValue(ctx.source, tree, frag.offset);
} ctx.stack.pop();
ctx.results.push(frag);
ctx.stack.pop();
const parentFrag = currentFragment(ctx);
if (parentFrag.type === 'array') {
parentFrag.children.push(frag);
} }
return ctx; return ctx;
@ -152,17 +143,13 @@ function ruleCall(search: q.QueryBuilder<Ctx>): q.QueryBuilder<Ctx> {
}); });
} }
function ruleStartHandler(ctx: Ctx, { value, offset }: lexer.Token): Ctx { function ruleStartHandler(ctx: Ctx, { offset }: lexer.Token): Ctx {
const parentFragment = currentFragment(ctx); ctx.stack.push({
if (parentFragment.type === 'array') { type: 'record',
ctx.stack.push({ value: '',
type: 'record', offset,
value: '', children: {},
offset, });
children: {},
});
}
return ctx; return ctx;
} }
@ -217,22 +204,26 @@ function getCacheKey(input: string): string {
const starlark = lang.createLang('starlark'); const starlark = lang.createLang('starlark');
export function parse(input: string): ArrayFragment | null { export function parse(
input: string,
packageFile?: string
): RecordFragment[] | null {
const cacheKey = getCacheKey(input); const cacheKey = getCacheKey(input);
const cachedResult = memCache.get<ArrayFragment | null>(cacheKey); const cachedResult = memCache.get<RecordFragment[] | null>(cacheKey);
// istanbul ignore if // istanbul ignore if
if (cachedResult === null || cachedResult) { if (cachedResult === null || cachedResult) {
return cachedResult; return cachedResult;
} }
let result: ArrayFragment | null = null; let result: RecordFragment[] | null = null;
const parsedResult = starlark.query(input, query, emptyCtx(input)); try {
if (parsedResult) { const parsedResult = starlark.query(input, query, emptyCtx(input));
const rootFragment = parsedResult.stack[0]; if (parsedResult) {
if (rootFragment.type === 'array') { result = parsedResult.results;
result = rootFragment;
} }
} catch (err) /* istanbul ignore next */ {
logger.debug({ err, packageFile }, 'Bazel parsing error');
} }
memCache.set(cacheKey, result); memCache.set(cacheKey, result);