Skip to content

Commit

Permalink
fix: recursive find nested deps
Browse files Browse the repository at this point in the history
Example:

```proto
message A {
  message B {}
}
message B {
  message A {
    message C {}
  }
  A.B item = 1;
  A.C local = 2;
}
```
  • Loading branch information
askuzminov committed Mar 22, 2021
1 parent 1207457 commit a04d052
Show file tree
Hide file tree
Showing 17 changed files with 1,077 additions and 99 deletions.
25 changes: 23 additions & 2 deletions proto/whisk/api/user/v2/user.proto
Original file line number Diff line number Diff line change
Expand Up @@ -235,8 +235,14 @@ message TestEmpty {
}
}

// Test on dublicate
message MatchedIngredient {
string name = 1;
message Calories { int32 daily_calories = 1; }
}

message SearchRecipesResponse {
message MatchedIngredient {
message MatchedIngredient {
string name = 1;
message Recommendations { int32 daily_calories = 1; }
}
Expand All @@ -247,17 +253,32 @@ message SearchRecipesResponse {
MatchedIngredient content = 1 [ deprecated = true ];
repeated MatchedIngredient ingredients = 2 [ required = true ];
map<string, MatchedIngredient.Recommendations> map_ingredients = 3;
map<string, TestItem> map_external = 4;
map<string, MatchedIngredient.Calories> map_calories = 4;
map<string, TestItem> map_external = 5;
}

message Height {
enum Unit2 {
UNIT_INVALID = 0;
UNIT_CM = 1;
}
}

repeated Hit hit = 1;
whisk.api.shared.v1.Date date = 2;
repeated whisk.api.shared.v1.Date dates = 3;
Empty empty = 4;
repeated MatchedIngredient.Recommendations items = 5;
MatchedIngredient.Calories calories = 6;
Height.Unit size = 7;
Height.Unit2 size2 = 8;
}

message DeepCheck {
repeated SearchRecipesResponse.Hit items = 1;
repeated MatchedIngredient.Calories recommendations = 2;
SearchRecipesResponse.MatchedIngredient.Recommendations ingredients = 3;
Height.Unit size = 7;
}

message ApiUpdateBusinessApp {
Expand Down
52 changes: 52 additions & 0 deletions src/generator/collect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { isString } from '@whisklabs/typeguards';

import { Parser } from '../parser';
import { MakeOuts } from './generator';
import { checkDublicate, safeString } from './utils';

export function collectEmuns(pack: string, out: MakeOuts, items: Parser.Enum[]) {
for (const msg of items) {
enu(`${pack}_${msg.name}`, out, msg);
}
}

function enu(pack: string, out: MakeOuts, item: Parser.Enum) {
const eName = safeString(pack);
out.enumsList.add(eName);
checkDublicate(eName, out, `${pack}.${item.name}`);
}

export function collectMessages(pack: string, out: MakeOuts, items: Parser.Message[], parent?: string) {
for (const msg of items) {
const newPack = `${parent ?? pack}_${msg.name}`;
message(pack, out, msg, parent);
if (msg.messages.length > 0) {
collectMessages(pack, out, msg.messages, newPack);
}
}
}

function message(pack: string, out: MakeOuts, item: Parser.Message, parent?: string) {
const base = `${safeString(parent ?? pack)}_${safeString(item.name)}`;
collectEmuns(base, out, item.enums);
const baseName = safeString(base);
const packName = `${pack}.${item.name}`;
checkDublicate(baseName, out, `${isString(parent) ? `${parent}.` : ''}${packName}`);
}

export function collectServices(pack: string, out: MakeOuts, items: Parser.Service[] = []) {
for (const msg of items) {
service(pack, out, msg);
}
}

function service(pack: string, out: MakeOuts, item: Parser.Service) {
for (const msg of item.methods) {
method(pack, out, msg, item);
}
}

export function method(pack: string, out: MakeOuts, item: Parser.Method, serv: Parser.Service) {
const sName = `${safeString(pack)}_${serv.name}_${item.name}`;
checkDublicate(sName, out, `${pack}.${serv.name}.${item.name}`);
}
12 changes: 5 additions & 7 deletions src/generator/enum.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
import { isNumber } from '@whisklabs/typeguards';

import { Parser } from '../parser';
import { EnumsList, MakeOuts } from './generator';
import { checkDublicate, checkSame, safeString } from './utils';
import { MakeOuts } from './generator';
import { checkSame, safeString } from './utils';

export function enums(pack: string, out: MakeOuts, items: Parser.Enum[], enumsList: EnumsList) {
export function enums(pack: string, out: MakeOuts, items: Parser.Enum[]) {
for (const msg of items) {
enu(`${pack}_${msg.name}`, out, msg, enumsList);
enu(`${pack}_${msg.name}`, out, msg);
}
}

function enu(pack: string, out: MakeOuts, item: Parser.Enum, enumsList: EnumsList) {
function enu(pack: string, out: MakeOuts, item: Parser.Enum) {
const eName = safeString(pack);
enumsList.add(eName);

checkDublicate(eName, out, `${pack}.${item.name}`);
const cID = checkSame(out, 'id');
const cName = checkSame(out, 'name');

Expand Down
63 changes: 40 additions & 23 deletions src/generator/field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,59 +2,76 @@ import { isPresent, isString } from '@whisklabs/typeguards';

import { Parser } from '../parser';
import { GOOGLE_WRAPPERS, TYPES } from './constants';
import { EnumsList, MakeOuts } from './generator';
import { MakeOuts } from './generator';
import { safeString } from './utils';

export const getField = (
field: Parser.Field,
base: string,
enumsList: EnumsList,
name: string,
out: MakeOuts
): string => {
export const getField = (field: Parser.Field, base: string, name: string, out: MakeOuts, baseName: string): string => {
const type = field.type;

if (type === 'map') {
const to = getField({ type: field.map?.to } as Parser.Field, base, enumsList, name, out);
const from = getField({ type: field.map?.from } as Parser.Field, base, enumsList, name, out);
const to = getField({ type: field.map?.to } as Parser.Field, base, name, out, baseName);
const from = getField({ type: field.map?.from } as Parser.Field, base, name, out, baseName);
return `Record<${from}, ${to}>`;
} else if (isString(TYPES[type])) {
return TYPES[type];
} else if (isString(TYPES[GOOGLE_WRAPPERS[type]])) {
return TYPES[GOOGLE_WRAPPERS[type]];
} else {
const sName = pathField(type, base, out);
const sName = pathField(type, base, out, baseName);
out.fields.push([sName, name]);
return enumsList.has(sName) ? `${sName}` : sName;
return out.enumsList.has(sName) ? `${sName}` : sName;
}
};

export const getStruct = (field: Parser.Field, base: string, enumsList: EnumsList, out: MakeOuts): string => {
export const getStruct = (field: Parser.Field, base: string, out: MakeOuts, baseName: string): string => {
const type = field.type;

if (type === 'map') {
const from = getStruct({ type: field.map?.from } as Parser.Field, base, enumsList, out);
const to = getStruct({ type: field.map?.to } as Parser.Field, base, enumsList, out);
const from = getStruct({ type: field.map?.from } as Parser.Field, base, out, baseName);
const to = getStruct({ type: field.map?.to } as Parser.Field, base, out, baseName);
return `["map", ${from}, ${to}]`;
} else if (field.repeated) {
return `["repeated", ${getStruct({ type } as Parser.Field, base, enumsList, out)}]`;
return `["repeated", ${getStruct({ type } as Parser.Field, base, out, baseName)}]`;
} else if (isString(TYPES[type])) {
return `"${type}"`;
} else if (isString(GOOGLE_WRAPPERS[type])) {
return `["wrapper", "${GOOGLE_WRAPPERS[type]}"]`;
} else {
const sName = pathField(field.type, base, out);
return enumsList.has(sName) ? '"enum"' : sName;
const sName = pathField(field.type, base, out, baseName);
return out.enumsList.has(sName) ? '"enum"' : sName;
}
};

export const pathField = (field: string, base: string, out: MakeOuts) => {
export const pathField = (field: string, base: string, out: MakeOuts, parent?: string) => {
let inRoot = false;
out.roots.forEach(root => {
out.packagesList.forEach(root => {
inRoot = inRoot || field.startsWith(root);
});

return inRoot ? safeString(field) : safeString(`${base}_${field}`);
// Absolute path
if (inRoot) {
return safeString(field);
}

// Relative path with recursive check
if (isString(parent) && field.indexOf('.') > -1) {
const safeBase = safeString(base);
const par = (parent.startsWith(safeBase) ? parent.slice(safeBase.length + 1) : parent).split('_');

while (par.length) {
const current = par.join('_');
const tryName = safeString(`${base}_${current}_${field}`);

if (out.names.has(tryName)) {
return tryName;
}

par.pop();
}
}

// Common path
return safeString(`${base}_${field}`);
};

export const isRequiredField = (field: Parser.Field) =>
Expand All @@ -64,5 +81,5 @@ export const isRequiredField = (field: Parser.Field) =>
? false
: field.required || field.repeated || field.type === 'map' || isString(TYPES[field.type]);

export const isEnumField = (field: Parser.Field, base: string, enumsList: EnumsList, out: MakeOuts) =>
enumsList.has(pathField(field.type, base, out));
export const isEnumField = (field: Parser.Field, base: string, out: MakeOuts, parent?: string) =>
out.enumsList.has(pathField(field.type, base, out, parent));
5 changes: 2 additions & 3 deletions src/generator/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,8 @@ export type MakeOuts = {
names: Set<string>;
fields: [string, string][];
errors: string[];
roots: Set<string>;
packagesList: Set<string>;
enumsList: Set<string>;
};

export type List = { name: string; pack: string };

export type EnumsList = Set<string>;
19 changes: 11 additions & 8 deletions src/generator/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import { extname, isAbsolute, join, parse as pathParse, relative } from 'path';
import { CompilerOptions, ModuleKind, ModuleResolutionKind, ScriptTarget, transpileModule } from 'typescript';

import { Parser, parser } from '../parser';
import { collectEmuns, collectMessages, collectServices } from './collect';
import { enums } from './enum';
import { Config, EnumsList, MakeOuts, Out } from './generator';
import { Config, MakeOuts, Out } from './generator';
import { messages } from './message';
import { services } from './service';
import { walk } from './utils';
Expand Down Expand Up @@ -127,31 +128,33 @@ export function checkTypes(file: string, errors: string[]) {
}

export function make(schemas: Parser.Schema[]): MakeOuts {
const enumsList: EnumsList = new Set();

const out: MakeOuts = {
js: [],
dts: [],
names: new Set(),
errors: [],
fields: [],
roots: new Set(),
packagesList: new Set(),
enumsList: new Set(),
};

for (const schema of schemas) {
const path = schema.package ?? '';
out.roots.add(path);
out.packagesList.add(path);

collectEmuns(path, out, schema.enums);
collectMessages(path, out, schema.messages);
collectServices(path, out, schema.services);
}

for (const schema of schemas) {
const path = schema.package ?? '';
enums(path, out, schema.enums, enumsList);
enums(path, out, schema.enums);
}

for (const schema of schemas) {
const path = schema.package ?? '';
messages(path, out, schema.messages, [], enumsList);

messages(path, out, schema.messages, []);
services(path, out, schema.services);
}

Expand Down
46 changes: 19 additions & 27 deletions src/generator/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,50 +4,36 @@ import { Parser } from '../parser';
import { GOOGLE_WRAPPERS } from './constants';
import { enums } from './enum';
import { getField, getStruct, isRequiredField } from './field';
import { EnumsList, List, MakeOut, MakeOuts } from './generator';
import { camelCase, checkDublicate, checkSame, errorColor, safeString } from './utils';

export function messages(
pack: string,
out: MakeOuts,
items: Parser.Message[],
list: List[] = [],
enumsList: EnumsList,
parent?: string
) {
import { List, MakeOut, MakeOuts } from './generator';
import { camelCase, checkSame, errorColor, safeString } from './utils';

export function messages(pack: string, out: MakeOuts, items: Parser.Message[], list: List[] = [], parent?: string) {
for (const msg of items) {
const newPack = `${pack}_${msg.name}`;
const newPack = `${parent ?? pack}_${msg.name}`;
list = list.concat(
msg.messages.map(i => ({ name: i.name, pack: newPack })),
msg.enums.map(i => ({ name: i.name, pack: newPack }))
);

message(pack, out, msg, list, enumsList, parent);
message(pack, out, msg, list, parent);

if (msg.messages.length > 0) {
messages(pack, out, msg.messages, list, enumsList, newPack);
messages(pack, out, msg.messages, list, newPack);
}
}
}

// eslint-disable-next-line complexity
function message(
pack: string,
out: MakeOuts,
item: Parser.Message,
list: List[],
enumsList: EnumsList,
parent?: string
) {
function message(pack: string, out: MakeOuts, item: Parser.Message, list: List[], parent?: string) {
const base = `${safeString(parent ?? pack)}_${safeString(item.name)}`;
enums(base, out, item.enums, enumsList);
enums(base, out, item.enums);

const runtime: MakeOut[] = [];
const baseName = safeString(base);
const oneof = {} as Record<string, Parser.Field[]>;
// const massagesNames = item.messages.map(m => m.name);

const packName = `${pack}.${item.name}`;
checkDublicate(baseName, out, `${isString(parent) ? `${parent}.` : ''}${packName}`);
const cID = checkSame(out, 'id');
const cName = checkSame(out, 'name');

Expand Down Expand Up @@ -78,12 +64,18 @@ function message(
out.dts.push(' /** @deprecated */');
}

const fieldName = getField(field, fieldPack, enumsList, `in "${baseName}" field "${naming}"`, out);
const fieldName = getField(
field,
fieldPack,
`in "${baseName}" field "${field.name} = ${field.tag}"`,
out,
baseName
);

out.dts.push(` ${naming}${required ? '' : '?'}: ${fieldName}${field.repeated ? '[]' : ''};`);
}

const type = getStruct(field, fieldPack, enumsList, out);
const type = getStruct(field, fieldPack, out, baseName);
runtime.push(
` [${field.tag}, "${naming}", ${type}, ${required ? '1' : '0'}${
isText(field.oneof) ? `, "${field.oneof}"` : ''
Expand All @@ -109,7 +101,7 @@ function message(

const naming = camelCase(field.name);

const fieldName = getField(field, fieldPack, enumsList, `in "${baseName}" field "${naming}"`, out);
const fieldName = getField(field, fieldPack, `in "${baseName}" field "${naming}"`, out, baseName);

out.dts.push(
` | { oneof: '${naming}'; value${isRequiredField(field) ? '' : '?'}: ${fieldName}${
Expand Down

0 comments on commit a04d052

Please sign in to comment.