Skip to content

Commit

Permalink
Detect duplicate declaration
Browse files Browse the repository at this point in the history
  • Loading branch information
hackerwins committed Jan 17, 2025
1 parent 0a9905d commit a2b3dfb
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 18 deletions.
74 changes: 56 additions & 18 deletions src/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,43 +8,90 @@ import {
RecognitionException,
} from 'antlr4ts';
import { YorkieSchemaLexer } from '../antlr/YorkieSchemaLexer';
import { YorkieSchemaParser } from '../antlr/YorkieSchemaParser';
import {
PropertyNameContext,
YorkieSchemaParser,
} from '../antlr/YorkieSchemaParser';
import { YorkieSchemaListener } from '../antlr/YorkieSchemaListener';
import {
TypeAliasDeclarationContext,
TypeReferenceContext,
} from '../antlr/YorkieSchemaParser';
import { ParseTreeWalker } from 'antlr4ts/tree';

/**
* `TypeSymbol` represents a type alias declaration.
*/
type TypeSymbol = {
name: string;
line: number;
column: number;
};

/**
* `TypeReference` represents a type reference in a type alias declaration.
*/
type TypeReference = {
name: string;
parent: string;
line: number;
column: number;
};

/**
* `Diagnostic` represents a diagnostic message.
*/
export type Diagnostic = {
severity: 'error' | 'warning' | 'info';
message: string;
range: {
start: { column: number; line: number };
end: { column: number; line: number };
};
};

export class TypeCollectorListener implements YorkieSchemaListener {
public symbolTable: Map<string, TypeSymbol> = new Map();
public errors: Array<{ message: string; line: number; column: number }> = [];
private symbol: string | null = null;
private properties: Set<string> = new Set();

public parent: string | null = null;
public symbolMap: Map<string, TypeSymbol> = new Map();
public referenceMap: Map<string, TypeReference> = new Map();
public errors: Array<{ message: string; line: number; column: number }> = [];

enterTypeAliasDeclaration(ctx: TypeAliasDeclarationContext) {
const typeName = ctx.Identifier().text;
const { line, charPositionInLine } = ctx.Identifier().symbol;
this.symbolTable.set(typeName, {

if (this.symbolMap.has(typeName)) {
this.errors.push({
message: `Duplicate type declaration: ${typeName}`,
line: line,
column: charPositionInLine,
});
}

this.symbolMap.set(typeName, {
name: typeName,
line: line,
column: charPositionInLine,
});
this.parent = typeName;

this.symbol = typeName;
}

enterPropertyName(ctx: PropertyNameContext) {
const typeName = ctx.Identifier()!.text;
const { line, charPositionInLine } = ctx.Identifier()!.symbol;

if (this.properties.has(typeName)) {
this.errors.push({
message: `Duplicate property name: ${typeName}`,
line: line,
column: charPositionInLine,
});
}

this.properties.add(typeName);
}

enterTypeReference(ctx: TypeReferenceContext) {
Expand All @@ -53,22 +100,13 @@ export class TypeCollectorListener implements YorkieSchemaListener {

this.referenceMap.set(typeName, {
name: typeName,
parent: this.parent!,
parent: this.symbol!,
line: line,
column: charPositionInLine,
});
}
}

export type Diagnostic = {
severity: 'error' | 'warning' | 'info';
message: string;
range: {
start: { column: number; line: number };
end: { column: number; line: number };
};
};

class LexerErrorListener implements ANTLRErrorListener<number> {
constructor(private errorList: Diagnostic[]) {}

Expand Down Expand Up @@ -144,7 +182,7 @@ export function validate(data: string): { errors: Array<Diagnostic> } {

// TODO(hackerwins): This is a naive implementation and performance can be improved.
for (const [, ref] of listener.referenceMap) {
if (!listener.symbolTable.has(ref.name)) {
if (!listener.symbolMap.has(ref.name)) {
listener.errors.push({
message: `Type '${ref.name}' is not defined.`,
line: ref.line,
Expand All @@ -153,7 +191,7 @@ export function validate(data: string): { errors: Array<Diagnostic> } {
}
}

for (const [, symbol] of listener.symbolTable) {
for (const [, symbol] of listener.symbolMap) {
const visited = new Set();
let current: string | undefined = symbol.name;
while (current) {
Expand Down
20 changes: 20 additions & 0 deletions test/schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,26 @@ describe('Schema:TypeScript', () => {
`;
expect(validate(schema).errors.length).toBe(0);
});

it('should detect duplicate type alias declarations', () => {
const schema = `
type Document = {
};
type Document = {
};
`;
expect(validate(schema).errors.length).toBeGreaterThan(0);
});

it('should detect duplicate keys in object', () => {
const schema = `
type Document = {
field: string;
field: number;
};
`;
expect(validate(schema).errors.length).toBeGreaterThan(0);
});
});

describe('Schema:Yorkie', () => {
Expand Down

0 comments on commit a2b3dfb

Please sign in to comment.