* Setup prettier with config borrowed from azure-sdk-for-js
* Run prettier to format existing code
* Turn format on save on so VS Code with prettier will auto-format
* Add rush check-format / rush format to check formatting / run prettier on whole repo
* Add check-format to PR validation
This commit is contained in:
Nick Guerrera 2021-03-01 12:55:14 -08:00 коммит произвёл GitHub
Родитель 9094a8a7c8
Коммит dbe9ffd544
17 изменённых файлов: 900 добавлений и 775 удалений

Просмотреть файл

@ -1,35 +1,42 @@
import { DiagnosticError, formatDiagnostic } from './diagnostics.js';
import { visitChildren } from './parser.js';
import { ADLSourceFile, Program } from './program.js';
import { createSourceFile } from './scanner.js';
import { NamespaceStatementNode, ModelStatementNode, Node, SyntaxKind, TemplateParameterDeclarationNode, SourceLocation, Sym } from './types.js';
import { DiagnosticError, formatDiagnostic } from "./diagnostics.js";
import { visitChildren } from "./parser.js";
import { ADLSourceFile, Program } from "./program.js";
import { createSourceFile } from "./scanner.js";
import {
NamespaceStatementNode,
ModelStatementNode,
Node,
SyntaxKind,
TemplateParameterDeclarationNode,
SourceLocation,
Sym,
} from "./types.js";
export class SymbolTable extends Map<string, Sym> {
duplicates = new Set<Sym>();
// First set for a given key wins, but record all duplicates for diagnostics.
set(key: string, value: Sym) {
const existing = this.get(key);
if (existing === undefined) {
super.set(key, value);
} else {
this.duplicates.add(existing);
this.duplicates.add(value);
}
return this;
// First set for a given key wins, but record all duplicates for diagnostics.
set(key: string, value: Sym) {
const existing = this.get(key);
if (existing === undefined) {
super.set(key, value);
} else {
this.duplicates.add(existing);
this.duplicates.add(value);
}
return this;
}
}
export interface DecoratorSymbol {
kind: 'decorator';
kind: "decorator";
path: string;
name: string;
value: (...args: Array<any>) => any;
}
export interface TypeSymbol {
kind: 'type';
kind: "type";
node: Node;
name: string;
}
@ -49,7 +56,11 @@ export function createBinder(): Binder {
bindSourceFile,
};
function bindSourceFile(program: Program, sourceFile: ADLSourceFile, globalScope: boolean = false) {
function bindSourceFile(
program: Program,
sourceFile: ADLSourceFile,
globalScope: boolean = false
) {
currentFile = sourceFile;
bindNode(sourceFile.ast);
@ -78,7 +89,6 @@ export function createBinder(): Binder {
bindTemplateParameterDeclaration(<any>node);
}
const prevParent = parentNode;
// set parent node when we walk into children
parentNode = node;
@ -98,7 +108,7 @@ export function createBinder(): Binder {
function bindTemplateParameterDeclaration(node: TemplateParameterDeclarationNode) {
(<ModelStatementNode>scope).locals!.set(node.sv, {
kind: 'type',
kind: "type",
node: node,
name: node.sv,
});
@ -106,7 +116,7 @@ export function createBinder(): Binder {
function bindModelStatement(node: ModelStatementNode) {
currentFile.symbols.set(node.id.sv, {
kind: 'type',
kind: "type",
node: node,
name: node.id.sv,
});
@ -115,11 +125,9 @@ export function createBinder(): Binder {
node.locals = new SymbolTable();
}
function bindInterfaceStatement(
statement: NamespaceStatementNode
) {
function bindInterfaceStatement(statement: NamespaceStatementNode) {
currentFile.symbols.set(statement.id.sv, {
kind: 'type',
kind: "type",
node: statement,
name: statement.id.sv,
});
@ -146,13 +154,13 @@ export function createBinder(): Binder {
// That said, decorators are entered into the global symbol table before
// any source file is bound and therefore this will include all duplicate
// decorator implementations.
throw new DiagnosticError(messages.join('\n'));
throw new DiagnosticError(messages.join("\n"));
}
function report(symbol: Sym) {
if (!reported.has(symbol)) {
reported.add(symbol);
const message = formatDiagnostic('Duplicate name: ' + symbol.name, symbol);
const message = formatDiagnostic("Duplicate name: " + symbol.name, symbol);
messages.push(message);
}
}

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -1,5 +1,5 @@
import { throwDiagnostic } from './diagnostics.js';
import { ADLSourceFile, Program } from './program.js';
import { throwDiagnostic } from "./diagnostics.js";
import { ADLSourceFile, Program } from "./program.js";
import {
ArrayExpressionNode,
BooleanLiteralNode,
@ -30,8 +30,8 @@ import {
Type,
UnionExpressionNode,
UnionType,
ReferenceExpression
} from './types.js';
ReferenceExpression,
} from "./types.js";
/**
* A map keyed by a set of objects. Used as a type cache where the base type
@ -55,7 +55,7 @@ export class MultiKeyMap<T> {
}
compositeKeyFor(items: Array<object>) {
return items.map(i => this.keyFor(i)).join(',');
return items.map((i) => this.keyFor(i)).join(",");
}
keyFor(item: object) {
@ -80,7 +80,7 @@ export function createChecker(program: Program) {
checkProgram,
getLiteralType,
getTypeName,
checkNamespaceProperty
checkNamespaceProperty,
};
function getTypeForNode(node: Node): Type {
@ -121,24 +121,24 @@ export function createChecker(program: Program) {
return checkTemplateParameterDeclaration(node);
}
throwDiagnostic('Cannot evaluate ' + SyntaxKind[node.kind], node);
throwDiagnostic("Cannot evaluate " + SyntaxKind[node.kind], node);
}
function getTypeName(type: Type): string {
switch (type.kind) {
case 'Model':
case "Model":
return getModelName(type);
case 'Union':
return type.options.map(getTypeName).join(' | ');
case 'Array':
return getTypeName(type.elementType) + '[]';
case 'String':
case 'Number':
case 'Boolean':
case "Union":
return type.options.map(getTypeName).join(" | ");
case "Array":
return getTypeName(type.elementType) + "[]";
case "String":
case "Number":
case "Boolean":
return type.value.toString();
}
return '(unnamed type)';
return "(unnamed type)";
}
function getModelName(model: ModelType) {
@ -147,14 +147,14 @@ export function createChecker(program: Program) {
} else if (model.templateArguments && model.templateArguments.length > 0) {
// template instantiation
const args = model.templateArguments.map(getTypeName);
return `${model.name}<${args.join(', ')}>`;
return `${model.name}<${args.join(", ")}>`;
} else if ((<ModelStatementNode>model.node).templateParameters?.length > 0) {
// template
const params = (<ModelStatementNode>model.node).templateParameters.map(t => t.sv);
return `${model.name}<${params.join(', ')}>`;
const params = (<ModelStatementNode>model.node).templateParameters.map((t) => t.sv);
return `${model.name}<${params.join(", ")}>`;
} else {
// regular old model.
return model.name || '(anonymous model)';
return model.name || "(anonymous model)";
}
}
@ -162,13 +162,13 @@ export function createChecker(program: Program) {
const parentNode = <ModelStatementNode>node.parent!;
if (instantiatingTemplate === parentNode) {
const index = parentNode.templateParameters.findIndex(v => v === node);
const index = parentNode.templateParameters.findIndex((v) => v === node);
return templateInstantiation[index];
}
return createType({
kind: 'TemplateParameter',
node: node
kind: "TemplateParameter",
node: node,
});
}
@ -179,7 +179,6 @@ export function createChecker(program: Program) {
return instantiateTemplate(<ModelStatementNode>targetType.node, args);
}
/**
* Builds a model type from a template and its template arguments.
* Adds the template node to a set we can check when we bind template
@ -190,11 +189,11 @@ export function createChecker(program: Program) {
*/
function instantiateTemplate(templateNode: ModelStatementNode, args: Array<Type>): ModelType {
if (templateNode.templateParameters!.length < args.length) {
throwDiagnostic('Too few template arguments provided.', templateNode);
throwDiagnostic("Too few template arguments provided.", templateNode);
}
if (templateNode.templateParameters!.length > args.length) {
throwDiagnostic('Too many template arguments provided.', templateNode);
throwDiagnostic("Too many template arguments provided.", templateNode);
}
const oldTis = templateInstantiation;
@ -211,14 +210,14 @@ export function createChecker(program: Program) {
function checkUnionExpression(node: UnionExpressionNode): UnionType {
return createType({
kind: 'Union',
kind: "Union",
node,
options: node.options.map(getTypeForNode),
});
}
function allModelTypes(types: Array<Type>): types is Array<ModelType> {
return types.every(t => t.kind === 'Model');
return types.every((t) => t.kind === "Model");
}
/**
@ -229,7 +228,7 @@ export function createChecker(program: Program) {
function checkIntersectionExpression(node: IntersectionExpressionNode) {
const optionTypes = node.options.map(getTypeForNode);
if (!allModelTypes(optionTypes)) {
throwDiagnostic('Cannot intersect non-model types (including union types).', node);
throwDiagnostic("Cannot intersect non-model types (including union types).", node);
}
const properties = new Map<string, ModelTypeProperty>();
@ -237,25 +236,30 @@ export function createChecker(program: Program) {
const allProps = walkPropertiesInherited(option);
for (const prop of allProps) {
if (properties.has(prop.name)) {
throwDiagnostic(`Intersection contains duplicate property definitions for ${prop.name}`, node);
throwDiagnostic(
`Intersection contains duplicate property definitions for ${prop.name}`,
node
);
}
const newPropType = createType({
... prop,
sourceProperty: prop
}, true);
const newPropType = createType(
{
...prop,
sourceProperty: prop,
},
true
);
properties.set(prop.name, newPropType);
}
}
const intersection = createType({
kind: 'Model',
kind: "Model",
node,
name: '',
name: "",
baseModels: [],
properties: properties
properties: properties,
});
return intersection;
@ -263,7 +267,7 @@ export function createChecker(program: Program) {
function checkArrayExpression(node: ArrayExpressionNode) {
return createType({
kind: 'Array',
kind: "Array",
node,
elementType: getTypeForNode(node.elementType),
});
@ -271,11 +275,11 @@ export function createChecker(program: Program) {
function checkNamespace(node: NamespaceStatementNode) {
const type: Namespace = createType({
kind: 'Namespace',
kind: "Namespace",
name: node.id.sv,
node: node,
properties: new Map(),
parameters: node.parameters ? <ModelType>getTypeForNode(node.parameters) : undefined
parameters: node.parameters ? <ModelType>getTypeForNode(node.parameters) : undefined,
});
for (const prop of node.properties) {
@ -287,7 +291,7 @@ export function createChecker(program: Program) {
function checkNamespaceProperty(prop: NamespacePropertyNode): NamespaceProperty {
return createType({
kind: 'NamespaceProperty',
kind: "NamespaceProperty",
name: prop.id.sv,
node: prop,
parameters: <ModelType>getTypeForNode(prop.parameters),
@ -295,10 +299,9 @@ export function createChecker(program: Program) {
});
}
function checkTupleExpression(node: TupleExpressionNode): TupleType {
return createType({
kind: 'Tuple',
kind: "Tuple",
node: node,
values: node.values.map((v) => getTypeForNode(v)),
});
@ -306,7 +309,7 @@ export function createChecker(program: Program) {
function checkIdentifier(node: IdentifierNode) {
const binding = resolveIdentifier(node);
if (binding.kind === 'decorator') {
if (binding.kind === "decorator") {
return {};
} else {
return getTypeForNode(binding.node);
@ -318,7 +321,7 @@ export function createChecker(program: Program) {
let binding;
while (scope) {
if ('locals' in scope) {
if ("locals" in scope) {
binding = (<any>scope).locals.get(node.sv);
if (binding) break;
}
@ -330,7 +333,7 @@ export function createChecker(program: Program) {
}
if (!binding) {
throwDiagnostic('Unknown identifier ' + node.sv, node);
throwDiagnostic("Unknown identifier " + node.sv, node);
}
return binding;
@ -363,12 +366,11 @@ export function createChecker(program: Program) {
function checkModel(node: ModelExpressionNode | ModelStatementNode) {
if (node.properties) {
const properties = new Map();
const baseModels = node.kind === SyntaxKind.ModelExpression
? []
: checkClassHeritage(node.heritage);
const baseModels =
node.kind === SyntaxKind.ModelExpression ? [] : checkClassHeritage(node.heritage);
for (const prop of node.properties) {
if ('id' in prop) {
if ("id" in prop) {
const propType = <ModelTypeProperty>getTypeForNode(prop);
properties.set(propType.name, propType);
} else {
@ -386,25 +388,24 @@ export function createChecker(program: Program) {
}
return createType({
kind: 'Model',
name: node.kind === SyntaxKind.ModelStatement ? node.id.sv : '',
kind: "Model",
name: node.kind === SyntaxKind.ModelStatement ? node.id.sv : "",
node: node,
properties,
baseModels: baseModels
baseModels: baseModels,
});
} else {
// model =
// this will likely have to change, as right now `model =` is really just
// alias and so disappears. That means you can't easily rename symbols.
const assignmentType = getTypeForNode((<ModelStatementNode>node).assignment!);
if (assignmentType.kind === 'Model') {
if (assignmentType.kind === "Model") {
const type: ModelType = createType({
... <ModelType>assignmentType,
...(<ModelType>assignmentType),
node: node,
name: (<ModelStatementNode>node).id.sv,
assignmentType
assignmentType,
});
return type;
@ -415,7 +416,7 @@ export function createChecker(program: Program) {
}
function checkClassHeritage(heritage: ReferenceExpression[]): ModelType[] {
return heritage.flatMap(heritageRef => {
return heritage.flatMap((heritageRef) => {
const heritageType = getTypeForNode(heritageRef);
if (heritageType.kind === "TemplateParameter") {
@ -435,18 +436,20 @@ export function createChecker(program: Program) {
const props: ModelTypeProperty[] = [];
const targetType = getTypeForNode(targetNode);
if (targetType.kind != 'TemplateParameter') {
if (targetType.kind !== 'Model') {
throwDiagnostic('Cannot spread properties of non-model type.', targetNode);
if (targetType.kind != "TemplateParameter") {
if (targetType.kind !== "Model") {
throwDiagnostic("Cannot spread properties of non-model type.", targetNode);
}
// copy each property
for (const prop of walkPropertiesInherited(targetType)) {
const newProp = createType({
... prop,
sourceProperty: prop
}, true)
const newProp = createType(
{
...prop,
sourceProperty: prop,
},
true
);
props.push(newProp);
}
}
@ -467,11 +470,10 @@ export function createChecker(program: Program) {
return props;
}
function checkModelProperty(prop: ModelPropertyNode): ModelTypeProperty {
if (prop.id.kind === SyntaxKind.Identifier) {
return createType({
kind: 'ModelProperty',
kind: "ModelProperty",
name: prop.id.sv,
node: prop,
optional: prop.optional,
@ -480,7 +482,7 @@ export function createChecker(program: Program) {
} else {
const name = prop.id.value;
return createType({
kind: 'ModelProperty',
kind: "ModelProperty",
name,
node: prop,
optional: prop.optional,
@ -496,7 +498,7 @@ export function createChecker(program: Program) {
(<any>typeDef).templateArguments = templateInstantiation;
// only run decorators on fully instantiated types.
if (templateInstantiation.every(i => i.kind !== 'TemplateParameter')) {
if (templateInstantiation.every((i) => i.kind !== "TemplateParameter")) {
program.executeDecorators(typeDef);
}
@ -514,13 +516,13 @@ export function createChecker(program: Program) {
switch (node.kind) {
case SyntaxKind.StringLiteral:
type = { kind: 'String', node, value: node.value };
type = { kind: "String", node, value: node.value };
break;
case SyntaxKind.NumericLiteral:
type = { kind: 'Number', node, value: node.value };
type = { kind: "Number", node, value: node.value };
break;
case SyntaxKind.BooleanLiteral:
type = { kind: 'Boolean', node, value: node.value };
type = { kind: "Boolean", node, value: node.value };
break;
}

Просмотреть файл

@ -15,66 +15,61 @@ const adlVersion = getVersion();
const args = yargs(process.argv.slice(2))
.help()
.strict()
.command(
"compile <path>",
"Compile a directory of ADL files.",
cmd => {
return cmd
.positional("path", {
description:
"The path to folder containing .adl files",
type: "string"
})
.option("output-path", {
type: "string",
default: "./adl-output",
describe: "The output path for generated artifacts. If it does not exist, it will be created."
})
.option("nostdlib", {
type: "boolean",
default: false,
describe: "Don't load the ADL standard library."
});
}
)
.command("compile <path>", "Compile a directory of ADL files.", (cmd) => {
return cmd
.positional("path", {
description: "The path to folder containing .adl files",
type: "string",
})
.option("output-path", {
type: "string",
default: "./adl-output",
describe:
"The output path for generated artifacts. If it does not exist, it will be created.",
})
.option("nostdlib", {
type: "boolean",
default: false,
describe: "Don't load the ADL standard library.",
});
})
.command(
"generate <path>",
"Generate client and server code from a directory of ADL files.",
cmd => {
(cmd) => {
return cmd
.positional("path", {
description:
"The path to folder containing .adl files",
type: "string"
description: "The path to folder containing .adl files",
type: "string",
})
.option("client", {
type: "boolean",
describe: "Generate a client library for the ADL definition"
describe: "Generate a client library for the ADL definition",
})
.option("language", {
type: "string",
choices: ["typescript", "csharp", "python"],
describe: "The language to use for code generation"
describe: "The language to use for code generation",
})
.option("output-path", {
type: "string",
default: "./adl-output",
describe: "The output path for generated artifacts. If it does not exist, it will be created."
describe:
"The output path for generated artifacts. If it does not exist, it will be created.",
});
}
)
.option("debug", {
type: "boolean",
description: "Output debug log messages."
description: "Output debug log messages.",
})
.option("verbose", {
alias: "v",
type: "boolean",
description: "Output verbose log messages."
description: "Output verbose log messages.",
})
.version(adlVersion)
.demandCommand(1, "You must use one of the supported commands.")
.argv;
.demandCommand(1, "You must use one of the supported commands.").argv;
async function compileInput(compilerOptions: CompilerOptions): Promise<boolean> {
try {
@ -101,17 +96,13 @@ async function getCompilerOptions(): Promise<CompilerOptions> {
return {
outputPath,
swaggerOutputFile: path.resolve(args["output-path"], "openapi.json"),
nostdlib: args["nostdlib"]
nostdlib: args["nostdlib"],
};
}
function getVersion(): string {
const packageJsonPath = new url.URL(`../../package.json`, import.meta.url);
const packageJson = JSON.parse(
readFileSync(
url.fileURLToPath(packageJsonPath),
"utf-8")
);
const packageJson = JSON.parse(readFileSync(url.fileURLToPath(packageJsonPath), "utf-8"));
return packageJson.version;
}
@ -123,7 +114,7 @@ async function main() {
if (!(await compileInput(options))) {
process.exit(1);
}
console.log(`Compilation completed successfully, output files are in ${options.outputPath}.`)
console.log(`Compilation completed successfully, output files are in ${options.outputPath}.`);
} else if (args._[0] === "generate") {
const options = await getCompilerOptions();
if (!(await compileInput(options))) {
@ -132,28 +123,31 @@ async function main() {
if (args.client) {
const clientPath = path.resolve(args["output-path"], "client");
const autoRestBin =
process.platform === "win32"
? "autorest.cmd"
: "autorest"
const autoRestBin = process.platform === "win32" ? "autorest.cmd" : "autorest";
const autoRestPath = new url.URL(`../../node_modules/.bin/${autoRestBin}`, import.meta.url);
// Execute AutoRest on the output file
const result = spawnSync(url.fileURLToPath(autoRestPath), [
`--${args.language}`,
`--clear-output-folder=true`,
`--output-folder=${clientPath}`,
`--title=AdlClient`,
`--input-file=${options.swaggerOutputFile}`
], {
stdio: 'inherit',
shell: true
});
const result = spawnSync(
url.fileURLToPath(autoRestPath),
[
`--${args.language}`,
`--clear-output-folder=true`,
`--output-folder=${clientPath}`,
`--title=AdlClient`,
`--input-file=${options.swaggerOutputFile}`,
],
{
stdio: "inherit",
shell: true,
}
);
if (result.status === 0) {
console.log(`Generation completed successfully, output files are in ${options.outputPath}.`)
console.log(
`Generation completed successfully, output files are in ${options.outputPath}.`
);
} else {
console.error("\nAn error occurred during compilation or client generation.")
console.error("\nAn error occurred during compilation or client generation.");
process.exit(result.status || 1);
}
}

Просмотреть файл

@ -3,9 +3,9 @@ import { Message, Node, SourceLocation, SyntaxKind, Type, Sym } from "./types.js
/**
* Represents an error in the code input that is fatal and bails the compilation.
*
*
* This isn't meant to be kept long term, but we currently do this on all errors.
*/
*/
export class DiagnosticError extends Error {
constructor(message: string) {
super(message);
@ -26,19 +26,31 @@ export class ChainedError extends Error {
export type DiagnosticTarget = Node | Type | Sym | SourceLocation;
export type ErrorHandler = (message: Message | string, target: DiagnosticTarget, ...args: Array<string | number>) => void;
export type ErrorHandler = (
message: Message | string,
target: DiagnosticTarget,
...args: Array<string | number>
) => void;
export const throwOnError: ErrorHandler = throwDiagnostic;
export function throwDiagnostic(message: Message | string, target: DiagnosticTarget, ...args: Array<string | number>): never {
export function throwDiagnostic(
message: Message | string,
target: DiagnosticTarget,
...args: Array<string | number>
): never {
throw new DiagnosticError(formatDiagnostic(message, target, ...args));
}
/**
/**
* Format a diagnostic into <file>:<line> - ADL<code> <category>: <text>.
* Take extra care to preserve all info in thrown Error if this fails.
*/
export function formatDiagnostic(message: Message | string, target: DiagnosticTarget, ...args: Array<string | number>) {
export function formatDiagnostic(
message: Message | string,
target: DiagnosticTarget,
...args: Array<string | number>
) {
let location: SourceLocation;
let locationError: Error | undefined;
@ -46,22 +58,24 @@ export function formatDiagnostic(message: Message | string, target: DiagnosticTa
location = getSourceLocation(target);
} catch (err) {
locationError = err;
location = {
location = {
file: createSourceFile("", "<unknown location>"),
pos: 0,
end: 0
}
end: 0,
};
}
if (typeof message === 'string') {
if (typeof message === "string") {
// Temporarily allow ad-hoc strings as error messages.
message = { code: -1, text: message, category: 'error' }
message = { code: -1, text: message, category: "error" };
}
const [msg, formatError] = format(message.text, ...args);
const code = message.code < 0 ? "" : ` ADL${message.code}`;
const pos = location.file.getLineAndCharacterOfPosition(location.pos);
const diagnostic = `${location.file.path}:${pos.line + 1}:${pos.character + 1} - ${message.category}${code}: ${msg}`;
const diagnostic = `${location.file.path}:${pos.line + 1}:${pos.character + 1} - ${
message.category
}${code}: ${msg}`;
if (locationError || formatError) {
throw new ChainedError(diagnostic, locationError, formatError);
@ -101,8 +115,8 @@ function getSourceLocationOfNode(node: Node): SourceLocation {
return {
file: root.file,
pos: node.pos,
end: node.end
}
end: node.end,
};
}
export function dumpError(error: Error, writeLine: (s?: string) => void) {
@ -133,4 +147,4 @@ function format(text: string, ...args: Array<string | number>): [string, Error?]
function isNotUndefined<T>(value: T | undefined): value is T {
return value !== undefined;
}
}

Просмотреть файл

@ -2,13 +2,30 @@ import { Message } from "./types";
export const messages: { [key: string]: Message } = {
// Scanner errors
DigitExpected: { code: 1100, category: 'error', text: 'Digit expected (0-9)' },
HexDigitExpected: { code: 1101, category: 'error', text: 'Hex Digit expected (0-F,0-f)' },
BinaryDigitExpected: { code: 1102, category: 'error', text: 'Binary Digit expected (0,1)' },
UnexpectedEndOfFile: { code: 1103, category: 'error', text: 'Unexpected end of file while searching for \'{0}\'' },
InvalidEscapeSequence: { code: 1104, category: 'error', text: 'Invalid escape sequence' },
NoNewLineAtStartOfTripleQuotedString: { code: 1105, category: 'error', text: 'String content in triple quotes must begin on a new line' },
NoNewLineAtEndOfTripleQuotedString: { code: 1106, category: 'error', text: 'Closing triple quotes must begin on a new line' },
InconsistentTripleQuoteIndentation: { code: 1107, category: 'error', text: 'All lines in triple-quoted string lines must have the same indentation as closing triple quotes' },
UnexpectedToken: { code: 1108, category: 'error', text: 'Unexpected token: \'{0}\'' },
DigitExpected: { code: 1100, category: "error", text: "Digit expected (0-9)" },
HexDigitExpected: { code: 1101, category: "error", text: "Hex Digit expected (0-F,0-f)" },
BinaryDigitExpected: { code: 1102, category: "error", text: "Binary Digit expected (0,1)" },
UnexpectedEndOfFile: {
code: 1103,
category: "error",
text: "Unexpected end of file while searching for '{0}'",
},
InvalidEscapeSequence: { code: 1104, category: "error", text: "Invalid escape sequence" },
NoNewLineAtStartOfTripleQuotedString: {
code: 1105,
category: "error",
text: "String content in triple quotes must begin on a new line",
},
NoNewLineAtEndOfTripleQuotedString: {
code: 1106,
category: "error",
text: "Closing triple quotes must begin on a new line",
},
InconsistentTripleQuoteIndentation: {
code: 1107,
category: "error",
text:
"All lines in triple-quoted string lines must have the same indentation as closing triple quotes",
},
UnexpectedToken: { code: 1108, category: "error", text: "Unexpected token: '{0}'" },
};

Просмотреть файл

@ -1,7 +1,6 @@
import { DiagnosticError, throwDiagnostic } from './diagnostics.js';
import { createScanner, Token } from './scanner.js';
import * as Types from './types.js';
import { DiagnosticError, throwDiagnostic } from "./diagnostics.js";
import { createScanner, Token } from "./scanner.js";
import * as Types from "./types.js";
export function parse(code: string | Types.SourceFile) {
const scanner = createScanner(code);
@ -14,7 +13,7 @@ export function parse(code: string | Types.SourceFile) {
file: scanner.file,
statements: [],
pos: 0,
end: 0
end: 0,
};
while (!scanner.eof()) {
@ -26,7 +25,6 @@ export function parse(code: string | Types.SourceFile) {
}
function parseStatement(): Types.Statement {
// eslint-disable-next-line no-constant-condition
while (true) {
const decorators = parseDecoratorList();
const tok = token();
@ -34,7 +32,7 @@ export function parse(code: string | Types.SourceFile) {
switch (tok) {
case Token.ImportKeyword:
if (decorators.length > 0) {
error('Cannot decorate an import statement');
error("Cannot decorate an import statement");
}
return parseImportStatement();
case Token.ModelKeyword:
@ -43,7 +41,7 @@ export function parse(code: string | Types.SourceFile) {
return parseInterfaceStatement(decorators);
case Token.Semicolon:
if (decorators.length > 0) {
error('Cannot decorate an empty statement');
error("Cannot decorate an empty statement");
}
// no need to put empty statement nodes in the tree for now
// since we aren't trying to emit ADL
@ -78,14 +76,16 @@ export function parse(code: string | Types.SourceFile) {
parseExpected(Token.OpenParen);
const modelProps = parseModelPropertyList();
parseExpected(Token.CloseParen);
parameters = finishNode({
kind: Types.SyntaxKind.ModelExpression,
properties: modelProps,
decorators: []
}, modelPos);
parameters = finishNode(
{
kind: Types.SyntaxKind.ModelExpression,
properties: modelProps,
decorators: [],
},
modelPos
);
}
parseExpected(Token.OpenBrace);
const properties: Array<Types.NamespacePropertyNode> = [];
@ -99,43 +99,54 @@ export function parse(code: string | Types.SourceFile) {
parseExpected(Token.CloseBrace);
return finishNode({
kind: Types.SyntaxKind.NamespaceStatement,
decorators,
id,
parameters,
properties
}, pos);
return finishNode(
{
kind: Types.SyntaxKind.NamespaceStatement,
decorators,
id,
parameters,
properties,
},
pos
);
}
function parseNamespaceProperty(decorators: Array<Types.DecoratorExpressionNode>): Types.NamespacePropertyNode {
function parseNamespaceProperty(
decorators: Array<Types.DecoratorExpressionNode>
): Types.NamespacePropertyNode {
const pos = tokenPos();
parseExpected(Token.OpKeyword);
const id = parseIdentifier();
parseExpected(Token.OpenParen);
const modelPos = tokenPos();
let modelProps: Array<Types.ModelPropertyNode | Types.ModelSpreadPropertyNode>= [];
let modelProps: Array<Types.ModelPropertyNode | Types.ModelSpreadPropertyNode> = [];
if (!parseOptional(Token.CloseParen)) {
modelProps = parseModelPropertyList();
parseExpected(Token.CloseParen);
}
const parameters: Types.ModelExpressionNode = finishNode({
kind: Types.SyntaxKind.ModelExpression,
properties: modelProps,
decorators: []
}, modelPos);
const parameters: Types.ModelExpressionNode = finishNode(
{
kind: Types.SyntaxKind.ModelExpression,
properties: modelProps,
decorators: [],
},
modelPos
);
parseExpected(Token.Colon);
const returnType = parseExpression();
return finishNode({
kind: Types.SyntaxKind.NamespaceProperty,
id,
parameters,
returnType,
decorators
}, pos);
return finishNode(
{
kind: Types.SyntaxKind.NamespaceProperty,
id,
parameters,
returnType,
decorators,
},
pos
);
}
function parseModelStatement(
@ -152,40 +163,50 @@ export function parse(code: string | Types.SourceFile) {
parseExpected(Token.GreaterThan);
}
if (token() !== Token.Equals && token() !== Token.OpenBrace && token() !== Token.ExtendsKeyword) {
throw error('Expected equals, open curly, or extends after model statement');
if (
token() !== Token.Equals &&
token() !== Token.OpenBrace &&
token() !== Token.ExtendsKeyword
) {
throw error("Expected equals, open curly, or extends after model statement");
}
if (parseOptional(Token.Equals)) {
const assignment = parseExpression();
parseExpected(Token.Semicolon);
return finishNode({
kind: Types.SyntaxKind.ModelStatement,
id,
heritage: [],
templateParameters,
assignment,
decorators,
}, pos);
return finishNode(
{
kind: Types.SyntaxKind.ModelStatement,
id,
heritage: [],
templateParameters,
assignment,
decorators,
},
pos
);
} else {
let heritage: Types.ReferenceExpression[] = [];
if (parseOptional(Token.ExtendsKeyword)) {
heritage = parseReferenceExpressionList();
}
parseExpected(Token.OpenBrace);
const properties = parseModelPropertyList();
parseExpected(Token.CloseBrace);
return finishNode({
kind: Types.SyntaxKind.ModelStatement,
id,
heritage,
templateParameters,
decorators,
properties
}, pos);
return finishNode(
{
kind: Types.SyntaxKind.ModelStatement,
id,
heritage,
templateParameters,
decorators,
properties,
},
pos
);
}
}
@ -194,10 +215,13 @@ export function parse(code: string | Types.SourceFile) {
do {
const pos = tokenPos();
const id = parseIdentifier();
const param = finishNode({
kind: Types.SyntaxKind.TemplateParameterDeclaration,
sv: id.sv
} as const, pos);
const param = finishNode(
{
kind: Types.SyntaxKind.TemplateParameterDeclaration,
sv: id.sv,
} as const,
pos
);
params.push(param);
} while (parseOptional(Token.Comma));
@ -205,7 +229,9 @@ export function parse(code: string | Types.SourceFile) {
return params;
}
function parseModelPropertyList(): Array<Types.ModelPropertyNode | Types.ModelSpreadPropertyNode> {
function parseModelPropertyList(): Array<
Types.ModelPropertyNode | Types.ModelSpreadPropertyNode
> {
const properties: Array<Types.ModelPropertyNode | Types.ModelSpreadPropertyNode> = [];
do {
@ -217,7 +243,7 @@ export function parse(code: string | Types.SourceFile) {
if (token() === Token.Elipsis) {
if (memberDecorators.length > 0) {
error('Cannot decorate a spread property');
error("Cannot decorate a spread property");
}
properties.push(parseModelSpreadProperty());
} else {
@ -225,7 +251,6 @@ export function parse(code: string | Types.SourceFile) {
}
} while (parseOptional(Token.Comma) || parseOptional(Token.Semicolon));
return properties;
}
@ -236,13 +261,18 @@ export function parse(code: string | Types.SourceFile) {
// This could be broadened to allow any type expression
const target = parseReferenceExpression();
return finishNode({
kind: Types.SyntaxKind.ModelSpreadProperty,
target
}, pos);
return finishNode(
{
kind: Types.SyntaxKind.ModelSpreadProperty,
target,
},
pos
);
}
function parseModelProperty(decorators: Array<Types.DecoratorExpressionNode>): Types.ModelPropertyNode {
function parseModelProperty(
decorators: Array<Types.DecoratorExpressionNode>
): Types.ModelPropertyNode {
const pos = tokenPos();
let id: Types.IdentifierNode | Types.StringLiteralNode;
switch (token()) {
@ -260,13 +290,16 @@ export function parse(code: string | Types.SourceFile) {
parseExpected(Token.Colon);
const value = parseExpression();
return finishNode({
kind: Types.SyntaxKind.ModelProperty,
id,
decorators,
value,
optional
}, pos);
return finishNode(
{
kind: Types.SyntaxKind.ModelProperty,
id,
decorators,
value,
optional,
},
pos
);
}
function parseExpression(): Types.Expression {
@ -281,10 +314,13 @@ export function parse(code: string | Types.SourceFile) {
return node;
}
node = finishNode({
kind: Types.SyntaxKind.UnionExpression,
options: [node]
}, pos);
node = finishNode(
{
kind: Types.SyntaxKind.UnionExpression,
options: [node],
},
pos
);
while (parseOptional(Token.Bar)) {
const expr = parseIntersectionExpressionOrHigher();
@ -304,10 +340,13 @@ export function parse(code: string | Types.SourceFile) {
return node;
}
node = finishNode({
kind: Types.SyntaxKind.IntersectionExpression,
options: [node]
}, pos);
node = finishNode(
{
kind: Types.SyntaxKind.IntersectionExpression,
options: [node],
},
pos
);
while (parseOptional(Token.Ampersand)) {
const expr = parseArrayExpressionOrHigher();
@ -326,10 +365,13 @@ export function parse(code: string | Types.SourceFile) {
while (parseOptional(Token.OpenBracket)) {
parseExpected(Token.CloseBracket);
expr = finishNode({
kind: Types.SyntaxKind.ArrayExpression,
elementType: expr
}, pos);
expr = finishNode(
{
kind: Types.SyntaxKind.ArrayExpression,
elementType: expr,
},
pos
);
}
return expr;
@ -347,11 +389,14 @@ export function parse(code: string | Types.SourceFile) {
const args = parseExpressionList();
parseExpected(Token.GreaterThan);
return finishNode({
kind: Types.SyntaxKind.TemplateApplication,
target: expr,
arguments: args,
}, pos);
return finishNode(
{
kind: Types.SyntaxKind.TemplateApplication,
target: expr,
arguments: args,
},
pos
);
}
function parseReferenceExpressionList(): Types.ReferenceExpression[] {
@ -370,7 +415,7 @@ export function parse(code: string | Types.SourceFile) {
const id = parseIdentifier();
let as: Array<Types.NamedImportNode> = [];
if (token() === Token.Identifier && tokenValue() === 'as') {
if (token() === Token.Identifier && tokenValue() === "as") {
parseExpected(Token.Identifier);
parseExpected(Token.OpenBrace);
@ -382,20 +427,29 @@ export function parse(code: string | Types.SourceFile) {
}
parseExpected(Token.Semicolon);
return finishNode({
kind: Types.SyntaxKind.ImportStatement,
as, id
}, pos);
return finishNode(
{
kind: Types.SyntaxKind.ImportStatement,
as,
id,
},
pos
);
}
function parseNamedImports(): Array<Types.NamedImportNode> {
const names: Array<Types.NamedImportNode> = [];
do {
const pos = tokenPos();
names.push(finishNode({
kind: Types.SyntaxKind.NamedImport,
id: parseIdentifier()
}, pos));
names.push(
finishNode(
{
kind: Types.SyntaxKind.NamedImport,
id: parseIdentifier(),
},
pos
)
);
} while (parseOptional(Token.Comma));
return names;
}
@ -416,11 +470,14 @@ export function parse(code: string | Types.SourceFile) {
args = [parsePrimaryExpression()];
}
return finishNode({
kind: Types.SyntaxKind.DecoratorExpression,
arguments: args,
target
}, pos);
return finishNode(
{
kind: Types.SyntaxKind.DecoratorExpression,
arguments: args,
target,
},
pos
);
}
function parseExpressionList(): Array<Types.Expression> {
@ -434,16 +491,18 @@ export function parse(code: string | Types.SourceFile) {
}
function parseIdentifierOrMemberExpression(): Types.IdentifierNode | Types.MemberExpressionNode {
let base: Types.IdentifierNode | Types.MemberExpressionNode = parseIdentifier();
while (parseOptional(Token.Dot)) {
const pos = tokenPos();
base = finishNode({
kind: Types.SyntaxKind.MemberExpression,
base,
id: parseIdentifier()
}, pos);
base = finishNode(
{
kind: Types.SyntaxKind.MemberExpression,
base,
id: parseIdentifier(),
},
pos
);
}
return base;
@ -484,32 +543,43 @@ export function parse(code: string | Types.SourceFile) {
parseExpected(Token.OpenBracket);
const values = parseExpressionList();
parseExpected(Token.CloseBracket);
return finishNode({
kind: Types.SyntaxKind.TupleExpression,
values
}, pos);
return finishNode(
{
kind: Types.SyntaxKind.TupleExpression,
values,
},
pos
);
}
function parseModelExpression(decorators: Array<Types.DecoratorExpressionNode>): Types.ModelExpressionNode {
function parseModelExpression(
decorators: Array<Types.DecoratorExpressionNode>
): Types.ModelExpressionNode {
const pos = tokenPos();
parseExpected(Token.OpenBrace);
const properties = parseModelPropertyList();
parseExpected(Token.CloseBrace);
return finishNode({
kind: Types.SyntaxKind.ModelExpression,
decorators,
properties
}, pos);
return finishNode(
{
kind: Types.SyntaxKind.ModelExpression,
decorators,
properties,
},
pos
);
}
function parseStringLiteral(): Types.StringLiteralNode {
const pos = tokenPos();
const value = tokenValue();
parseExpected(Token.StringLiteral);
return finishNode({
kind: Types.SyntaxKind.StringLiteral,
value
}, pos);
return finishNode(
{
kind: Types.SyntaxKind.StringLiteral,
value,
},
pos
);
}
function parseNumericLiteral(): Types.NumericLiteralNode {
@ -518,21 +588,27 @@ export function parse(code: string | Types.SourceFile) {
const value = Number(text);
parseExpected(Token.NumericLiteral);
return finishNode({
kind: Types.SyntaxKind.NumericLiteral,
text,
value
}, pos);
return finishNode(
{
kind: Types.SyntaxKind.NumericLiteral,
text,
value,
},
pos
);
}
function parseBooleanLiteral(): Types.BooleanLiteralNode {
const pos = tokenPos();
const token = parseExpectedOneOf(Token.TrueKeyword, Token.FalseKeyword);
const value = token == Token.TrueKeyword;
return finishNode({
kind: Types.SyntaxKind.BooleanLiteral,
value
}, pos);
return finishNode(
{
kind: Types.SyntaxKind.BooleanLiteral,
value,
},
pos
);
}
function parseIdentifier(): Types.IdentifierNode {
@ -546,10 +622,13 @@ export function parse(code: string | Types.SourceFile) {
nextToken();
return finishNode({
kind: Types.SyntaxKind.Identifier,
sv
}, pos);
return finishNode(
{
kind: Types.SyntaxKind.Identifier,
sv,
},
pos
);
}
// utility functions
@ -602,15 +681,15 @@ export function parse(code: string | Types.SourceFile) {
return {
...o,
pos,
end: tokenPos()
end: tokenPos(),
};
}
function error(message: string) {
throwDiagnostic(message, {
throwDiagnostic(message, {
file: scanner.file,
pos: scanner.tokenPosition,
end: scanner.position
end: scanner.position,
});
}
@ -622,18 +701,18 @@ export function parse(code: string | Types.SourceFile) {
}
}
function parseExpectedOneOf<T extends Token[]>(... options: T): T[number] {
function parseExpectedOneOf<T extends Token[]>(...options: T): T[number] {
for (const tok of options) {
if (token() === tok) {
nextToken();
return tok
return tok;
}
}
// Intl isn't in standard library as it is stage 3, however it is supported in node >= 12
const listfmt = new (Intl as any).ListFormat('en', { style: 'long', type: 'disjunction' });
const textOptions = options.map(o => Token[o]);
const listfmt = new (Intl as any).ListFormat("en", { style: "long", type: "disjunction" });
const textOptions = options.map((o) => Token[o]);
throw error(`expected ${listfmt.format(textOptions)}, got ${Token[token()]}`);
}
@ -647,7 +726,7 @@ export function parse(code: string | Types.SourceFile) {
}
}
type NodeCb<T> = (c: Types.Node) => T;
type NodeCb<T> = (c: Types.Node) => T;
export function visitChildren<T>(node: Types.Node, cb: NodeCb<T>): T | undefined {
switch (node.kind) {
@ -656,47 +735,46 @@ export function visitChildren<T>(node: Types.Node, cb: NodeCb<T>): T | undefined
case Types.SyntaxKind.ArrayExpression:
return visitNode(cb, node.elementType);
case Types.SyntaxKind.DecoratorExpression:
return visitNode(cb, node.target) ||
visitEach(cb, node.arguments);
return visitNode(cb, node.target) || visitEach(cb, node.arguments);
case Types.SyntaxKind.ImportStatement:
return visitNode(cb, node.id) ||
visitEach(cb, node.as);
return visitNode(cb, node.id) || visitEach(cb, node.as);
case Types.SyntaxKind.NamespaceProperty:
return visitEach(cb, node.decorators) ||
return (
visitEach(cb, node.decorators) ||
visitNode(cb, node.id) ||
visitNode(cb, node.parameters) ||
visitNode(cb, node.returnType);
visitNode(cb, node.returnType)
);
case Types.SyntaxKind.NamespaceStatement:
return visitEach(cb, node.decorators) ||
return (
visitEach(cb, node.decorators) ||
visitNode(cb, node.id) ||
visitNode(cb, node.parameters) ||
visitEach(cb, node.properties);
visitEach(cb, node.properties)
);
case Types.SyntaxKind.IntersectionExpression:
return visitEach(cb, node.options);
case Types.SyntaxKind.MemberExpression:
return visitNode(cb, node.base) ||
visitNode(cb, node.id);
return visitNode(cb, node.base) || visitNode(cb, node.id);
case Types.SyntaxKind.ModelExpression:
return visitEach(cb, node.decorators) ||
visitEach(cb, node.properties);
return visitEach(cb, node.decorators) || visitEach(cb, node.properties);
case Types.SyntaxKind.ModelProperty:
return visitEach(cb, node.decorators) ||
visitNode(cb, node.id) ||
visitNode(cb, node.value);
return visitEach(cb, node.decorators) || visitNode(cb, node.id) || visitNode(cb, node.value);
case Types.SyntaxKind.ModelSpreadProperty:
return visitNode(cb, node.target);
case Types.SyntaxKind.ModelStatement:
return visitEach(cb, node.decorators) ||
return (
visitEach(cb, node.decorators) ||
visitNode(cb, node.id) ||
visitEach(cb, node.templateParameters) ||
visitEach(cb, node.heritage) ||
visitNode(cb, node.assignment) ||
visitEach(cb, node.properties);
visitEach(cb, node.properties)
);
case Types.SyntaxKind.NamedImport:
return visitNode(cb, node.id);
case Types.SyntaxKind.TemplateApplication:
return visitNode(cb, node.target) ||
visitEach(cb, node.arguments);
return visitNode(cb, node.target) || visitEach(cb, node.arguments);
case Types.SyntaxKind.TupleExpression:
return visitEach(cb, node.values);
case Types.SyntaxKind.UnionExpression:

Просмотреть файл

@ -1,15 +1,16 @@
import url from "url";
import path from "path";
import { readdir, readFile } from 'fs/promises';
import { join } from 'path';
import { createBinder, SymbolTable } from './binder.js';
import { createChecker, MultiKeyMap } from './checker.js';
import { CompilerOptions } from './options.js';
import { parse } from './parser.js';
import { resolvePath } from './util.js';
import { readdir, readFile } from "fs/promises";
import { join } from "path";
import { createBinder, SymbolTable } from "./binder.js";
import { createChecker, MultiKeyMap } from "./checker.js";
import { CompilerOptions } from "./options.js";
import { parse } from "./parser.js";
import { resolvePath } from "./util.js";
import {
ADLScriptNode,
DecoratorExpressionNode, IdentifierNode,
DecoratorExpressionNode,
IdentifierNode,
Namespace,
LiteralType,
ModelStatementNode,
@ -18,8 +19,7 @@ import {
Type,
SourceFile,
DecoratorSymbol,
} from './types.js';
} from "./types.js";
import { createSourceFile } from "./scanner.js";
import { throwDiagnostic } from "./diagnostics.js";
@ -68,9 +68,9 @@ export async function compile(rootDir: string, options?: CompilerOptions) {
if (!options?.nostdlib) {
await loadStandardLibrary(program);
}
await loadDirectory(program, rootDir);
const checker = program.checker = createChecker(program);
const checker = (program.checker = createChecker(program));
program.checker.checkProgram(program);
buildCbs.forEach((cb: any) => cb(program));
@ -104,7 +104,7 @@ export async function compile(rootDir: string, options?: CompilerOptions) {
for (const [name, propType] of type.properties) {
const propNode = propType.node;
if ('decorators' in propNode) {
if ("decorators" in propNode) {
for (const dec of propNode.decorators) {
executeDecorator(dec, program, propType);
}
@ -123,13 +123,11 @@ export async function compile(rootDir: string, options?: CompilerOptions) {
function executeDecorator(dec: DecoratorExpressionNode, program: Program, type: Type) {
if (dec.target.kind !== SyntaxKind.Identifier) {
throwDiagnostic('Decorator must be identifier', dec);
throwDiagnostic("Decorator must be identifier", dec);
}
const decName = dec.target.sv;
const args = dec.arguments.map((a) =>
toJSON(checker.getTypeForNode(a))
);
const args = dec.arguments.map((a) => toJSON(checker.getTypeForNode(a)));
const decBinding = <DecoratorSymbol>program.globalSymbols.get(decName);
if (!decBinding) {
throwDiagnostic(`Can't find decorator ${decName}`, dec);
@ -139,10 +137,9 @@ export async function compile(rootDir: string, options?: CompilerOptions) {
}
async function importDecorator(modulePath: string, name: string) {
const resolvedPath =
path.isAbsolute(modulePath)
? modulePath
: path.resolve(process.cwd(), modulePath);
const resolvedPath = path.isAbsolute(modulePath)
? modulePath
: path.resolve(process.cwd(), modulePath);
const moduleUrl = url.pathToFileURL(resolvedPath);
const module = await import(moduleUrl.href);
@ -155,7 +152,7 @@ export async function compile(rootDir: string, options?: CompilerOptions) {
* treated specially.
*/
function toJSON(type: Type): Type | string | number {
if ('value' in type) {
if ("value" in type) {
return (<any>type).value;
}
@ -184,9 +181,9 @@ export async function compile(rootDir: string, options?: CompilerOptions) {
for (const entry of dir) {
if (entry.isFile()) {
const path = join(rootDir, entry.name);
if (entry.name.endsWith('.js')) {
if (entry.name.endsWith(".js")) {
await loadJsFile(program, path);
} else if (entry.name.endsWith('.adl')) {
} else if (entry.name.endsWith(".adl")) {
await loadAdlFile(program, path);
}
}
@ -194,12 +191,12 @@ export async function compile(rootDir: string, options?: CompilerOptions) {
}
async function loadAdlFile(program: Program, path: string) {
const contents = await readFile(path, 'utf-8');
const contents = await readFile(path, "utf-8");
program.evalAdlScript(contents, path);
}
async function loadJsFile(program: Program, path: string) {
const contents = await readFile(path, 'utf-8');
const contents = await readFile(path, "utf-8");
const exports = contents.match(/export function \w+/g);
if (!exports) return;
@ -209,14 +206,14 @@ export async function compile(rootDir: string, options?: CompilerOptions) {
const name = match.match(/function (\w+)/)![1];
const value = await importDecorator(path, name);
if (name === 'onBuild') {
if (name === "onBuild") {
program.onBuild(value);
} else {
program.globalSymbols.set(name, {
kind: 'decorator',
kind: "decorator",
path,
name,
value
value,
});
}
}

Просмотреть файл

@ -1,7 +1,16 @@
import { CharacterCodes, isBinaryDigit, isDigit, isHexDigit, isIdentifierPart, isIdentifierStart, isLineBreak, isWhiteSpaceSingleLine } from './character-codes.js';
import { throwOnError } from './diagnostics.js';
import { messages } from './messages.js';
import { Message, SourceFile } from './types.js';
import {
CharacterCodes,
isBinaryDigit,
isDigit,
isHexDigit,
isIdentifierPart,
isIdentifierStart,
isLineBreak,
isWhiteSpaceSingleLine,
} from "./character-codes.js";
import { throwOnError } from "./diagnostics.js";
import { messages } from "./messages.js";
import { Message, SourceFile } from "./types.js";
// All conflict markers consist of the same character repeated seven times. If it is
// a <<<<<<< or >>>>>>> marker then it is also followed by a space.
@ -54,17 +63,17 @@ export enum Token {
OpKeyword,
ExtendsKeyword,
TrueKeyword,
FalseKeyword
FalseKeyword,
}
const keywords = new Map([
['import', Token.ImportKeyword],
['model', Token.ModelKeyword],
['namespace', Token.NamespaceKeyword],
['op', Token.OpKeyword],
['extends', Token.ExtendsKeyword],
['true', Token.TrueKeyword],
['false', Token.FalseKeyword],
["import", Token.ImportKeyword],
["model", Token.ModelKeyword],
["namespace", Token.NamespaceKeyword],
["op", Token.OpKeyword],
["extends", Token.ExtendsKeyword],
["true", Token.TrueKeyword],
["false", Token.FalseKeyword],
]);
export interface Scanner {
@ -105,7 +114,7 @@ const enum TokenFlags {
}
export function createScanner(source: string | SourceFile, onError = throwOnError): Scanner {
const file = typeof source === 'string' ? createSourceFile(source, '<anonymous file>') : source;
const file = typeof source === "string" ? createSourceFile(source, "<anonymous file>") : source;
const input = file.text;
let position = 0;
let token = Token.Unknown;
@ -114,9 +123,15 @@ export function createScanner(source: string | SourceFile, onError = throwOnErro
let tokenFlags = 0;
return {
get position() { return position; },
get token() { return token; },
get tokenPosition() { return tokenPosition; },
get position() {
return position;
},
get token() {
return token;
},
get tokenPosition() {
return tokenPosition;
},
file,
scan,
eof,
@ -130,7 +145,7 @@ export function createScanner(source: string | SourceFile, onError = throwOnErro
function next(t: Token, count = 1) {
position += count;
return token = t;
return (token = t);
}
function getTokenText() {
@ -153,7 +168,7 @@ export function createScanner(source: string | SourceFile, onError = throwOnErro
if (lookAhead(1) === CharacterCodes.lineFeed) {
position++;
}
// fallthrough
// fallthrough
case CharacterCodes.lineFeed:
case CharacterCodes.lineSeparator:
case CharacterCodes.paragraphSeparator:
@ -220,10 +235,9 @@ export function createScanner(source: string | SourceFile, onError = throwOnErro
return next(Token.Ampersand);
case CharacterCodes.dot:
return lookAhead(1) === CharacterCodes.dot &&
lookAhead(2) === CharacterCodes.dot ?
next(Token.Elipsis, 3) :
next(Token.Dot);
return lookAhead(1) === CharacterCodes.dot && lookAhead(2) === CharacterCodes.dot
? next(Token.Elipsis, 3)
: next(Token.Dot);
case CharacterCodes.slash:
switch (lookAhead(1)) {
@ -241,7 +255,7 @@ export function createScanner(source: string | SourceFile, onError = throwOnErro
case CharacterCodes.b:
return scanBinaryNumber();
}
// fallthrough
// fallthrough
case CharacterCodes._1:
case CharacterCodes._2:
case CharacterCodes._3:
@ -254,35 +268,34 @@ export function createScanner(source: string | SourceFile, onError = throwOnErro
return scanNumber();
case CharacterCodes.lessThan:
return isConflictMarker() ?
next(Token.ConflictMarker, mergeConflictMarkerLength) :
next(Token.LessThan);
return isConflictMarker()
? next(Token.ConflictMarker, mergeConflictMarkerLength)
: next(Token.LessThan);
case CharacterCodes.greaterThan:
return isConflictMarker() ?
next(Token.ConflictMarker, mergeConflictMarkerLength) :
next(Token.GreaterThan);
return isConflictMarker()
? next(Token.ConflictMarker, mergeConflictMarkerLength)
: next(Token.GreaterThan);
case CharacterCodes.equals:
return isConflictMarker() ?
next(Token.ConflictMarker, mergeConflictMarkerLength) :
next(Token.Equals);
return isConflictMarker()
? next(Token.ConflictMarker, mergeConflictMarkerLength)
: next(Token.Equals);
case CharacterCodes.bar:
return isConflictMarker() ?
next(Token.ConflictMarker, mergeConflictMarkerLength) :
next(Token.Bar);
return isConflictMarker()
? next(Token.ConflictMarker, mergeConflictMarkerLength)
: next(Token.Bar);
case CharacterCodes.doubleQuote:
return scanString();
default:
return isIdentifierStart(ch) ? scanIdentifier() : unknownToken();
}
}
return token = Token.EndOfFile;
return (token = Token.EndOfFile);
}
function unknownToken() {
@ -295,13 +308,16 @@ export function createScanner(source: string | SourceFile, onError = throwOnErro
// Conflict markers must be at the start of a line.
const ch = input.charCodeAt(position);
if (position === 0 || isLineBreak(input.charCodeAt(position - 1))) {
if ((position + mergeConflictMarkerLength) < input.length) {
if (position + mergeConflictMarkerLength < input.length) {
for (let i = 0; i < mergeConflictMarkerLength; i++) {
if (lookAhead(i) !== ch) {
return false;
}
}
return ch === CharacterCodes.equals || lookAhead(mergeConflictMarkerLength) === CharacterCodes.space;
return (
ch === CharacterCodes.equals ||
lookAhead(mergeConflictMarkerLength) === CharacterCodes.space
);
}
}
@ -317,7 +333,7 @@ export function createScanner(source: string | SourceFile, onError = throwOnErro
position++;
} while (isWhiteSpaceSingleLine(input.charCodeAt(position)));
return token = Token.Whitespace;
return (token = Token.Whitespace);
}
function scanDigits() {
@ -353,7 +369,7 @@ export function createScanner(source: string | SourceFile, onError = throwOnErro
}
}
return token = Token.NumericLiteral;
return (token = Token.NumericLiteral);
}
function scanHexNumber() {
@ -363,8 +379,8 @@ export function createScanner(source: string | SourceFile, onError = throwOnErro
}
position += 2;
scanUntil(ch => !isHexDigit(ch), 'Hex Digit');
return token = Token.NumericLiteral;
scanUntil((ch) => !isHexDigit(ch), "Hex Digit");
return (token = Token.NumericLiteral);
}
function scanBinaryNumber() {
@ -374,12 +390,15 @@ export function createScanner(source: string | SourceFile, onError = throwOnErro
}
position += 2;
scanUntil(ch => !isBinaryDigit(ch), 'Binary Digit');
return token = Token.NumericLiteral;
scanUntil((ch) => !isBinaryDigit(ch), "Binary Digit");
return (token = Token.NumericLiteral);
}
function scanUntil(predicate: (char: number) => boolean, expectedClose?: string, consumeClose?: number) {
function scanUntil(
predicate: (char: number) => boolean,
expectedClose?: string,
consumeClose?: number
) {
let ch: number;
do {
@ -402,12 +421,16 @@ export function createScanner(source: string | SourceFile, onError = throwOnErro
function scanSingleLineComment() {
scanUntil(isLineBreak);
return token = Token.SingleLineComment;
return (token = Token.SingleLineComment);
}
function scanMultiLineComment() {
scanUntil(ch => ch === CharacterCodes.asterisk && lookAhead(1) === CharacterCodes.slash, '*/', 2);
return token = Token.MultiLineComment;
scanUntil(
(ch) => ch === CharacterCodes.asterisk && lookAhead(1) === CharacterCodes.slash,
"*/",
2
);
return (token = Token.MultiLineComment);
}
function scanString() {
@ -415,8 +438,8 @@ export function createScanner(source: string | SourceFile, onError = throwOnErro
let closing = '"';
let isEscaping = false;
const tripleQuoted = lookAhead(1) === CharacterCodes.doubleQuote &&
lookAhead(2) === CharacterCodes.doubleQuote;
const tripleQuoted =
lookAhead(1) === CharacterCodes.doubleQuote && lookAhead(2) === CharacterCodes.doubleQuote;
if (tripleQuoted) {
tokenFlags |= TokenFlags.TripleQuoted;
@ -426,36 +449,43 @@ export function createScanner(source: string | SourceFile, onError = throwOnErro
position += quoteLength;
scanUntil(ch => {
if (isEscaping) {
isEscaping = false;
return false;
}
switch (ch) {
case CharacterCodes.carriageReturn:
if (lookAhead(1) === CharacterCodes.lineFeed) {
tokenFlags |= TokenFlags.HasCrlf;
}
scanUntil(
(ch) => {
if (isEscaping) {
isEscaping = false;
return false;
}
case CharacterCodes.backslash:
isEscaping = true;
tokenFlags |= TokenFlags.Escaped;
return false;
switch (ch) {
case CharacterCodes.carriageReturn:
if (lookAhead(1) === CharacterCodes.lineFeed) {
tokenFlags |= TokenFlags.HasCrlf;
}
return false;
case CharacterCodes.doubleQuote:
if (tripleQuoted) {
return lookAhead(1) === CharacterCodes.doubleQuote && lookAhead(2) === CharacterCodes.doubleQuote;
}
return true;
case CharacterCodes.backslash:
isEscaping = true;
tokenFlags |= TokenFlags.Escaped;
return false;
default:
return false;
}
}, closing, quoteLength);
case CharacterCodes.doubleQuote:
if (tripleQuoted) {
return (
lookAhead(1) === CharacterCodes.doubleQuote &&
lookAhead(2) === CharacterCodes.doubleQuote
);
}
return true;
return token = Token.StringLiteral;
default:
return false;
}
},
closing,
quoteLength
);
return (token = Token.StringLiteral);
}
function getTokenValue() {
@ -464,18 +494,18 @@ export function createScanner(source: string | SourceFile, onError = throwOnErro
}
if (token !== Token.StringLiteral) {
return tokenValue = getTokenText();
return (tokenValue = getTokenText());
}
// strip quotes
const quoteLength = (tokenFlags & TokenFlags.TripleQuoted) ? 3 : 1;
const quoteLength = tokenFlags & TokenFlags.TripleQuoted ? 3 : 1;
let value = input.substring(tokenPosition + quoteLength, position - quoteLength);
// Normalize CRLF to LF when interpreting value of multi-line string
// literals. Matches JavaScript behavior and ensures program behavior does
// not change due to line-ending conversion.
if (tokenFlags & TokenFlags.HasCrlf) {
value = value.replace(/\r\n/g, '\n');
value = value.replace(/\r\n/g, "\n");
}
if (tokenFlags & TokenFlags.TripleQuoted) {
@ -486,7 +516,7 @@ export function createScanner(source: string | SourceFile, onError = throwOnErro
value = unescapeString(value);
}
return tokenValue = value;
return (tokenValue = value);
}
function unindentTripleQuoteString(text: string) {
@ -523,8 +553,13 @@ export function createScanner(source: string | SourceFile, onError = throwOnErro
return removeMatchingIndentation(text, start, end, indentation);
}
function removeMatchingIndentation(text: string, start: number, end: number, indentation: string) {
let result = '';
function removeMatchingIndentation(
text: string,
start: number,
end: number,
indentation: string
) {
let result = "";
let pos = start;
while (pos < end) {
@ -563,7 +598,7 @@ export function createScanner(source: string | SourceFile, onError = throwOnErro
}
function unescapeString(text: string) {
let result = '';
let result = "";
let start = 0;
let pos = 0;
const end = text.length;
@ -581,19 +616,19 @@ export function createScanner(source: string | SourceFile, onError = throwOnErro
switch (ch) {
case CharacterCodes.r:
result += '\r';
result += "\r";
break;
case CharacterCodes.n:
result += '\n';
result += "\n";
break;
case CharacterCodes.t:
result += '\t';
result += "\t";
break;
case CharacterCodes.doubleQuote:
result += '"';
break;
case CharacterCodes.backslash:
result += '\\';
result += "\\";
break;
default:
error(messages.InvalidEscapeSequence);
@ -610,8 +645,8 @@ export function createScanner(source: string | SourceFile, onError = throwOnErro
}
function scanIdentifier() {
scanUntil(ch => !isIdentifierPart(ch));
return token = keywords.get(getTokenValue()) ?? Token.Identifier;
scanUntil((ch) => !isIdentifierPart(ch));
return (token = keywords.get(getTokenValue()) ?? Token.Identifier);
}
}
@ -626,7 +661,7 @@ export function createSourceFile(text: string, path: string): SourceFile {
};
function getLineStarts() {
return lineStarts = (lineStarts ?? scanLineStarts());
return (lineStarts = lineStarts ?? scanLineStarts());
}
function getLineAndCharacterOfPosition(position: number) {
@ -663,7 +698,7 @@ export function createSourceFile(text: string, path: string): SourceFile {
if (text.charCodeAt(pos) === CharacterCodes.lineFeed) {
pos++;
}
// fallthrough
// fallthrough
case CharacterCodes.lineFeed:
case CharacterCodes.lineSeparator:
case CharacterCodes.paragraphSeparator:
@ -701,4 +736,3 @@ export function createSourceFile(text: string, path: string): SourceFile {
return ~low;
}
}

Просмотреть файл

@ -23,7 +23,7 @@ export type Type =
| UnionType;
export interface ModelType extends BaseType {
kind: 'Model';
kind: "Model";
name: string;
properties: Map<string, ModelTypeProperty>;
baseModels: Array<ModelType>;
@ -33,7 +33,7 @@ export interface ModelType extends BaseType {
}
export interface ModelTypeProperty {
kind: 'ModelProperty';
kind: "ModelProperty";
node: ModelPropertyNode | ModelSpreadPropertyNode;
name: string;
type: Type;
@ -44,7 +44,7 @@ export interface ModelTypeProperty {
}
export interface NamespaceProperty {
kind: 'NamespaceProperty';
kind: "NamespaceProperty";
node: NamespacePropertyNode;
name: string;
parameters?: ModelType;
@ -52,7 +52,7 @@ export interface NamespaceProperty {
}
export interface Namespace extends BaseType {
kind: 'Namespace';
kind: "Namespace";
name: string;
node: NamespaceStatementNode;
properties: Map<string, NamespaceProperty>;
@ -62,56 +62,56 @@ export interface Namespace extends BaseType {
export type LiteralType = StringLiteralType | NumericLiteralType | BooleanLiteralType;
export interface StringLiteralType extends BaseType {
kind: 'String';
kind: "String";
node: StringLiteralNode;
value: string;
}
export interface NumericLiteralType extends BaseType {
kind: 'Number';
kind: "Number";
node: NumericLiteralNode;
value: number;
}
export interface BooleanLiteralType extends BaseType {
kind: 'Boolean';
kind: "Boolean";
node: BooleanLiteralNode;
value: boolean;
}
export interface ArrayType extends BaseType {
kind: 'Array';
kind: "Array";
node: ArrayExpressionNode;
elementType: Type;
}
export interface TupleType extends BaseType {
kind: 'Tuple';
kind: "Tuple";
node: TupleExpressionNode;
values: Array<Type>;
}
export interface UnionType extends BaseType {
kind: 'Union';
kind: "Union";
options: Array<Type>;
}
export interface TemplateParameterType extends BaseType {
kind: 'TemplateParameter';
kind: "TemplateParameter";
}
// trying to avoid masking built-in Symbol
export type Sym = DecoratorSymbol | TypeSymbol;
export interface DecoratorSymbol {
kind: 'decorator';
kind: "decorator";
path: string;
name: string;
value: (...args: Array<any>) => any;
}
export interface TypeSymbol {
kind: 'type';
kind: "type";
node: Node;
name: string;
}
@ -140,7 +140,7 @@ export enum SyntaxKind {
NumericLiteral,
BooleanLiteral,
TemplateApplication,
TemplateParameterDeclaration
TemplateParameterDeclaration,
}
export interface BaseNode extends TextRange {
@ -148,8 +148,8 @@ export interface BaseNode extends TextRange {
parent?: Node;
}
export type Node =
| ADLScriptNode
export type Node =
| ADLScriptNode
| TemplateParameterDeclarationNode
| ModelPropertyNode
| NamespacePropertyNode
@ -157,7 +157,7 @@ export type Node =
| ModelPropertyNode
| ModelSpreadPropertyNode
| DecoratorExpressionNode
| Statement
| Statement
| Expression;
export interface ADLScriptNode extends BaseNode {
@ -166,10 +166,7 @@ export interface ADLScriptNode extends BaseNode {
file: SourceFile;
}
export type Statement =
| ImportStatementNode
| ModelStatementNode
| NamespaceStatementNode;
export type Statement = ImportStatementNode | ModelStatementNode | NamespaceStatementNode;
export interface ImportStatementNode extends BaseNode {
kind: SyntaxKind.ImportStatement;
@ -206,10 +203,7 @@ export type Expression =
| NumericLiteralNode
| BooleanLiteralNode;
export type ReferenceExpression =
| TemplateApplicationNode
| MemberExpressionNode
| IdentifierNode;
export type ReferenceExpression = TemplateApplicationNode | MemberExpressionNode | IdentifierNode;
export interface MemberExpressionNode extends BaseNode {
kind: SyntaxKind.MemberExpression;
@ -233,7 +227,6 @@ export interface NamespacePropertyNode extends BaseNode {
decorators: Array<DecoratorExpressionNode>;
}
export interface ModelStatementNode extends BaseNode {
kind: SyntaxKind.ModelStatement;
id: IdentifierNode;
@ -360,13 +353,13 @@ export interface SourceFile {
}
export interface TextRange {
/**
/**
* The starting position of the ranger measured in UTF-16 code units from the
* start of the full string. Inclusive.
*/
pos: number;
/**
/**
* The ending position measured in UTF-16 code units from the start of the
* full string. Exclusive.
*/
@ -380,5 +373,5 @@ export interface SourceLocation extends TextRange {
export interface Message {
code: number;
text: string;
category: 'error' | 'warning';
category: "error" | "warning";
}

Просмотреть файл

@ -1,6 +1,6 @@
import url from "url";
export function resolvePath(basePath: string, ...parts: string[]): string {
const resolvedPath = new url.URL(parts.join(''), basePath);
const resolvedPath = new url.URL(parts.join(""), basePath);
return url.fileURLToPath(resolvedPath);
}

Просмотреть файл

@ -38,9 +38,7 @@ export function getIntrinsicType(target: Type | undefined): string | undefined {
return target.name;
}
target =
(target.assignmentType?.kind === "Model" && target.assignmentType)
|| undefined;
target = (target.assignmentType?.kind === "Model" && target.assignmentType) || undefined;
} else if (target.kind === "ModelProperty") {
return getIntrinsicType(target.type);
} else {
@ -151,11 +149,7 @@ export function getVisibility(target: Type): string | undefined {
return visibilitySettings.get(target);
}
export function withVisibility(
program: Program,
target: Type,
...visibilities: string[]
) {
export function withVisibility(program: Program, target: Type, ...visibilities: string[]) {
if (target.kind !== "Model") {
throw new Error("The @withVisibility decorator can only be applied to models.");
}

Просмотреть файл

@ -3,8 +3,8 @@ import { Type } from "../compiler/types";
const basePaths = new Map<Type, string>();
export function resource(program: Program, entity: Type, basePath = '') {
if (entity.kind !== 'Namespace') return;
export function resource(program: Program, entity: Type, basePath = "") {
if (entity.kind !== "Namespace") return;
basePaths.set(entity, basePath);
}
@ -23,7 +23,7 @@ export function basePathForResource(resource: Type) {
const headerFields = new Map<Type, string>();
export function header(program: Program, entity: Type, headerName: string) {
if (!headerName && entity.kind === "ModelProperty") {
headerName = entity.name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
headerName = entity.name.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
}
headerFields.set(entity, headerName);
}
@ -93,28 +93,28 @@ export function getOperationRoute(entity: Type): OperationRoute | undefined {
export function get(program: Program, entity: Type, subPath?: string) {
setOperationRoute(entity, {
verb: "get",
subPath
subPath,
});
}
export function put(program: Program, entity: Type, subPath?: string) {
setOperationRoute(entity, {
verb: "put",
subPath
subPath,
});
}
export function post(program: Program, entity: Type, subPath?: string) {
setOperationRoute(entity, {
verb: "post",
subPath
subPath,
});
}
export function patch(program: Program, entity: Type, subPath?: string) {
setOperationRoute(entity, {
verb: "patch",
subPath
subPath,
});
}
@ -122,6 +122,6 @@ export function patch(program: Program, entity: Type, subPath?: string) {
export function _delete(program: Program, entity: Type, subPath?: string) {
setOperationRoute(entity, {
verb: "delete",
subPath
subPath,
});
}

Просмотреть файл

@ -15,7 +15,9 @@
"prepare": "npm run build",
"test": "mocha 'dist/test/**/*.js'",
"grammargen": "grammarkdown --newLine LF language.grammar",
"regen-samples": "node scripts/regen-samples.js"
"regen-samples": "node scripts/regen-samples.js",
"check-format": "prettier --list-different --config ../../.prettierrc.json --ignore-path ../../.prettierignore \"**/*.ts\" \"*.{js,json}\"",
"format": "prettier --write --config ../../.prettierrc.json --ignore-path ../../.prettierignore \"**/*.ts\" \"*.{js,json}\""
},
"repository": {
"type": "git",
@ -46,7 +48,8 @@
"mocha": "7.1.2",
"ts-node": "^8.10.2",
"typescript": "~3.9.5",
"@types/yargs": "~15.0.12"
"@types/yargs": "~15.0.12",
"prettier": "~2.2.1"
},
"dependencies": {
"yargs": "~16.2.0",
@ -55,4 +58,4 @@
"autorest": "~3.0.6335"
},
"type": "module"
}
}

Просмотреть файл

@ -1,20 +1,20 @@
import * as assert from 'assert';
import { parse } from '../compiler/parser.js';
import { SyntaxKind } from '../compiler/types.js';
import * as assert from "assert";
import { parse } from "../compiler/parser.js";
import { SyntaxKind } from "../compiler/types.js";
describe('syntax', () => {
describe('import statements', () => {
describe("syntax", () => {
describe("import statements", () => {
parseEach([
'import x;',
'import x as { one };',
'import x as {};',
'import x as { one, two };'
"import x;",
"import x as { one };",
"import x as {};",
"import x as { one, two };",
]);
});
describe('model statements', () => {
describe("model statements", () => {
parseEach([
'model Car { };',
"model Car { };",
`@foo()
model Car { };`,
@ -75,101 +75,80 @@ describe('syntax', () => {
'model Foo { "strKey": number, "😂😂😂": string }',
'model Foo<A, B> { }',
"model Foo<A, B> { }",
'model Car { @foo @bar x: number }',
"model Car { @foo @bar x: number }",
'model Car { ... A, ... B, c: number, ... D, e: string }',
"model Car { ... A, ... B, c: number, ... D, e: string }",
'model Car { ... A.B, ... C<D> }'
"model Car { ... A.B, ... C<D> }",
]);
});
describe('model extends statements', () => {
describe("model extends statements", () => {
parseEach([
'model foo extends bar { }',
'model foo extends bar, baz { }',
'model foo extends bar.baz { }',
'model foo extends bar<T> { }',
'model foo<T> extends bar<T> { }',
'model foo<T> extends bar.baz<T> { }'
"model foo extends bar { }",
"model foo extends bar, baz { }",
"model foo extends bar.baz { }",
"model foo extends bar<T> { }",
"model foo<T> extends bar<T> { }",
"model foo<T> extends bar.baz<T> { }",
]);
parseErrorEach([
'model foo extends { }',
'model foo extends = { }',
'model foo extends bar = { }'
"model foo extends { }",
"model foo extends = { }",
"model foo extends bar = { }",
]);
});
describe('model = statements', () => {
describe("model = statements", () => {
parseEach(["model x = y;", "model foo = bar | baz;", "model bar<a, b> = a | b;"]);
});
describe("model expressions", () => {
parseEach(['model Car { engine: { type: "v8" } }']);
});
describe("tuple model expressions", () => {
parseEach(['namespace A { op b(param: [number, string]): [1, "hi"] }']);
});
describe("array expressions", () => {
parseEach(["model A { foo: B[] }", "model A { foo: B[][] }"]);
});
describe("union expressions", () => {
parseEach(["model A { foo: B | C }", "model A { foo: B | C & D }"]);
});
describe("template instantiations", () => {
parseEach(["model A = Foo<number, string>;", "model B = Foo<number, string>[];"]);
});
describe("intersection expressions", () => {
parseEach(["model A { foo: B & C }"]);
});
describe("parenthesized expressions", () => {
parseEach(["model A = ((B | C) & D)[];"]);
});
describe("namespace statements", () => {
parseEach([
'model x = y;',
'model foo = bar | baz;',
'model bar<a, b> = a | b;'
"namespace Store {}",
"namespace Store { op read(): int32 }",
"namespace Store { op read(): int32, op write(v: int32): {} }",
"namespace Store { op read(): int32; op write(v: int32): {} }",
"@foo namespace Store { @dec op read():number, @dec op write(n: number): {} }",
"@foo @bar namespace Store { @foo @bar op read(): number; }",
"namespace Store(apiKey: string, otherArg: number) { }",
"namespace Store(... apiKeys, x: string) { op foo(... A, b: string, ...C, d: number): void }",
]);
});
describe('model expressions', () => {
describe("multiple statements", () => {
parseEach([
'model Car { engine: { type: "v8" } }'
]);
});
describe('tuple model expressions', () => {
parseEach([
'namespace A { op b(param: [number, string]): [1, "hi"] }'
]);
});
describe('array expressions', () => {
parseEach([
'model A { foo: B[] }',
'model A { foo: B[][] }',
]);
});
describe('union expressions', () => {
parseEach([
'model A { foo: B | C }',
'model A { foo: B | C & D }'
]);
});
describe('template instantiations', () => {
parseEach([
'model A = Foo<number, string>;',
'model B = Foo<number, string>[];'
]);
});
describe('intersection expressions', () => {
parseEach([
'model A { foo: B & C }'
]);
});
describe('parenthesized expressions', () => {
parseEach([
'model A = ((B | C) & D)[];'
]);
});
describe('namespace statements', () => {
parseEach([
'namespace Store {}',
'namespace Store { op read(): int32 }',
'namespace Store { op read(): int32, op write(v: int32): {} }',
'namespace Store { op read(): int32; op write(v: int32): {} }',
'@foo namespace Store { @dec op read():number, @dec op write(n: number): {} }',
'@foo @bar namespace Store { @foo @bar op read(): number; }',
'namespace Store(apiKey: string, otherArg: number) { }',
'namespace Store(... apiKeys, x: string) { op foo(... A, b: string, ...C, d: number): void }'
]);
});
describe('multiple statements', () => {
parseEach([`
`
model A { };
model B { }
model C = A;
@ -182,11 +161,13 @@ describe('syntax', () => {
}
`]);
`,
]);
});
describe('comments', () => {
parseEach([`
describe("comments", () => {
parseEach([
`
// Comment
model A { /* Another comment */
/*
@ -195,13 +176,14 @@ describe('syntax', () => {
*/
property /* 👀 */ : /* 👍 */ int32; // one more
}
`]);
`,
]);
});
});
function parseEach(cases: string[]) {
for (const code of cases) {
it('parses `' + shorten(code) + '`', () => {
it("parses `" + shorten(code) + "`", () => {
dumpAST(parse(code));
});
}
@ -212,18 +194,18 @@ function parseErrorEach(cases: string[]) {
it(`doesn't parse ${shorten(code)}`, () => {
assert.throws(() => {
parse(code);
})
});
});
}
}
function dumpAST(astNode: any) {
const replacer = function(this: any, key: string, value: any) {
return key == 'kind' ? SyntaxKind[value] : value;
const replacer = function (this: any, key: string, value: any) {
return key == "kind" ? SyntaxKind[value] : value;
};
console.log(JSON.stringify(astNode, replacer, 4));
}
function shorten(code: string) {
return code.replace(/\s+/g, ' ');
return code.replace(/\s+/g, " ");
}

Просмотреть файл

@ -1,9 +1,9 @@
import { deepStrictEqual, strictEqual } from 'assert';
import { readFile } from 'fs/promises';
import { URL } from 'url';
import { throwOnError } from '../compiler/diagnostics.js';
import { createScanner, Token } from '../compiler/scanner.js';
import { LineAndCharacter } from '../compiler/types.js';
import { deepStrictEqual, strictEqual } from "assert";
import { readFile } from "fs/promises";
import { URL } from "url";
import { throwOnError } from "../compiler/diagnostics.js";
import { createScanner, Token } from "../compiler/scanner.js";
import { LineAndCharacter } from "../compiler/types.js";
type TokenEntry = [Token, string?, number?, LineAndCharacter?];
@ -22,14 +22,17 @@ function tokens(text: string, onError = throwOnError): Array<TokenEntry> {
} while (!scanner.eof());
// verify that the input matches the output
const out = result.map(each => each[1]).join('');
strictEqual(out, text, 'Input text should match parsed token values');
const out = result.map((each) => each[1]).join("");
strictEqual(out, text, "Input text should match parsed token values");
return result;
}
function verify(tokens: Array<TokenEntry>, expecting: Array<TokenEntry>) {
for (const [index, [expectedToken, expectedText, expectedPosition, expectedLineAndCharacter]] of expecting.entries()) {
for (const [
index,
[expectedToken, expectedText, expectedPosition, expectedLineAndCharacter],
] of expecting.entries()) {
const [token, text, position, lineAndCharacter] = tokens[index];
strictEqual(Token[token], Token[expectedToken], `Token ${index} must match`);
@ -42,96 +45,95 @@ function verify(tokens: Array<TokenEntry>, expecting: Array<TokenEntry>) {
}
if (expectedLineAndCharacter) {
deepStrictEqual(lineAndCharacter, expectedLineAndCharacter, `Token ${index} line and character must match`);
deepStrictEqual(
lineAndCharacter,
expectedLineAndCharacter,
`Token ${index} line and character must match`
);
}
}
}
describe('scanner', () => {
describe("scanner", () => {
/** verifies that we can scan tokens and get back some output. */
it('smoketest', () => {
const all = tokens('\tthis is a test');
it("smoketest", () => {
const all = tokens("\tthis is a test");
verify(all, [
[Token.Whitespace],
[Token.Identifier, 'this'],
[Token.Identifier, "this"],
[Token.Whitespace],
[Token.Identifier, 'is'],
[Token.Identifier, "is"],
[Token.Whitespace],
[Token.Identifier, 'a'],
[Token.Identifier, "a"],
[Token.Whitespace],
[Token.Identifier, 'test'],
[Token.Identifier, "test"],
]);
});
it('scans objects', () => {
const all = tokens('model Foo{x:y}');
it("scans objects", () => {
const all = tokens("model Foo{x:y}");
verify(all, [
[Token.ModelKeyword],
[Token.Whitespace],
[Token.Identifier, 'Foo'],
[Token.Identifier, "Foo"],
[Token.OpenBrace],
[Token.Identifier, 'x'],
[Token.Identifier, "x"],
[Token.Colon],
[Token.Identifier, 'y'],
[Token.CloseBrace]
[Token.Identifier, "y"],
[Token.CloseBrace],
]);
});
it('scans decorator expressions', () => {
it("scans decorator expressions", () => {
const all = tokens('@foo(1,"hello",foo)');
verify(all, [
[Token.At],
[Token.Identifier, 'foo'],
[Token.Identifier, "foo"],
[Token.OpenParen],
[Token.NumericLiteral, '1'],
[Token.NumericLiteral, "1"],
[Token.Comma],
[Token.StringLiteral, '"hello"'],
[Token.Comma],
[Token.Identifier],
[Token.CloseParen]
[Token.CloseParen],
]);
});
it('scans extends keyword', () => {
const all = tokens('model foo extends bar{}');
it("scans extends keyword", () => {
const all = tokens("model foo extends bar{}");
verify(all, [
[Token.ModelKeyword],
[Token.Whitespace],
[Token.Identifier, 'foo'],
[Token.Identifier, "foo"],
[Token.Whitespace],
[Token.ExtendsKeyword],
[Token.Whitespace],
[Token.Identifier, 'bar'],
[Token.Identifier, "bar"],
[Token.OpenBrace],
[Token.CloseBrace]
])
});
it('does not scan greater-than-equals as one operator', () => {
const all = tokens('x>=y');
verify(all, [
[Token.Identifier],
[Token.GreaterThan],
[Token.Equals],
[Token.Identifier]
[Token.CloseBrace],
]);
});
it("does not scan greater-than-equals as one operator", () => {
const all = tokens("x>=y");
verify(all, [[Token.Identifier], [Token.GreaterThan], [Token.Equals], [Token.Identifier]]);
});
it('scans numeric literals', () => {
const all = tokens('42 0xBEEF 0b1010 1.5e4 314.0e-2 1e+1000');
it("scans numeric literals", () => {
const all = tokens("42 0xBEEF 0b1010 1.5e4 314.0e-2 1e+1000");
verify(all, [
[Token.NumericLiteral, '42'],
[Token.NumericLiteral, "42"],
[Token.Whitespace],
[Token.NumericLiteral, '0xBEEF'],
[Token.NumericLiteral, "0xBEEF"],
[Token.Whitespace],
[Token.NumericLiteral, '0b1010'],
[Token.NumericLiteral, "0b1010"],
[Token.Whitespace],
[Token.NumericLiteral, '1.5e4'],
[Token.NumericLiteral, "1.5e4"],
[Token.Whitespace],
[Token.NumericLiteral, '314.0e-2'],
[Token.NumericLiteral, "314.0e-2"],
[Token.Whitespace],
[Token.NumericLiteral, '1e+1000'],
[Token.NumericLiteral, "1e+1000"],
]);
});
@ -143,19 +145,15 @@ describe('scanner', () => {
strictEqual(scanner.getTokenValue(), expectedValue);
}
it('scans strings single-line strings with escape sequences', () => {
scanString(
'"Hello world \\r\\n \\t \\" \\\\ !"',
'Hello world \r\n \t " \\ !');
it("scans strings single-line strings with escape sequences", () => {
scanString('"Hello world \\r\\n \\t \\" \\\\ !"', 'Hello world \r\n \t " \\ !');
});
it('scans multi-line strings', () => {
scanString(
'"More\r\nthan\r\none\r\nline"',
'More\nthan\none\nline');
it("scans multi-line strings", () => {
scanString('"More\r\nthan\r\none\r\nline"', "More\nthan\none\nline");
});
it('scans triple-quoted strings', () => {
it("scans triple-quoted strings", () => {
scanString(
`"""
This is a triple-quoted string
@ -166,45 +164,48 @@ describe('scanner', () => {
"""`,
// NOTE: sloppy blank line formatting and trailing whitespace after open
// quotes above is deliberately tolerated.
'This is a triple-quoted string\n\n\n\nAnd this is another line');
"This is a triple-quoted string\n\n\n\nAnd this is another line"
);
});
it('provides token position', () => {
const all = tokens('a x\raa x\r\naaa x\naaaa x\u{2028}aaaaa x\u{2029}aaaaaa x');
it("provides token position", () => {
const all = tokens("a x\raa x\r\naaa x\naaaa x\u{2028}aaaaa x\u{2029}aaaaaa x");
verify(all, [
[Token.Identifier, 'a', 0, { line: 0, character: 0}],
[Token.Whitespace, ' ', 1, { line: 0, character: 1}],
[Token.Identifier, 'x', 2, { line: 0, character: 2}],
[Token.NewLine, '\r', 3, { line: 0, character: 3}],
[Token.Identifier, "a", 0, { line: 0, character: 0 }],
[Token.Whitespace, " ", 1, { line: 0, character: 1 }],
[Token.Identifier, "x", 2, { line: 0, character: 2 }],
[Token.NewLine, "\r", 3, { line: 0, character: 3 }],
[Token.Identifier, 'aa', 4, { line: 1, character: 0 }],
[Token.Whitespace, ' ', 6, { line: 1, character: 2 }],
[Token.Identifier, 'x', 7, { line: 1, character: 3 }],
[Token.NewLine, '\r\n', 8, { line: 1, character: 4 }],
[Token.Identifier, "aa", 4, { line: 1, character: 0 }],
[Token.Whitespace, " ", 6, { line: 1, character: 2 }],
[Token.Identifier, "x", 7, { line: 1, character: 3 }],
[Token.NewLine, "\r\n", 8, { line: 1, character: 4 }],
[Token.Identifier, 'aaa', 10, { line: 2, character: 0 }],
[Token.Whitespace, ' ', 13, { line: 2, character: 3 }],
[Token.Identifier, 'x', 14, { line: 2, character: 4 }],
[Token.NewLine, '\n', 15, { line: 2, character: 5 }],
[Token.Identifier, "aaa", 10, { line: 2, character: 0 }],
[Token.Whitespace, " ", 13, { line: 2, character: 3 }],
[Token.Identifier, "x", 14, { line: 2, character: 4 }],
[Token.NewLine, "\n", 15, { line: 2, character: 5 }],
[Token.Identifier, 'aaaa', 16, { line: 3, character: 0 }],
[Token.Whitespace, ' ', 20, { line: 3, character: 4 }],
[Token.Identifier, 'x', 21, { line: 3, character: 5 }],
[Token.NewLine, '\u{2028}', 22, { line: 3, character: 6 }],
[Token.Identifier, "aaaa", 16, { line: 3, character: 0 }],
[Token.Whitespace, " ", 20, { line: 3, character: 4 }],
[Token.Identifier, "x", 21, { line: 3, character: 5 }],
[Token.NewLine, "\u{2028}", 22, { line: 3, character: 6 }],
[Token.Identifier, 'aaaaa', 23, { line: 4, character: 0 }],
[Token.Whitespace, ' ', 28, { line: 4, character: 5 }],
[Token.Identifier, 'x', 29, { line: 4, character: 6 }],
[Token.NewLine, '\u{2029}', 30, { line: 4, character: 7 }],
[Token.Identifier, "aaaaa", 23, { line: 4, character: 0 }],
[Token.Whitespace, " ", 28, { line: 4, character: 5 }],
[Token.Identifier, "x", 29, { line: 4, character: 6 }],
[Token.NewLine, "\u{2029}", 30, { line: 4, character: 7 }],
[Token.Identifier, 'aaaaaa', 31, { line: 5, character: 0 }],
[Token.Whitespace, ' ', 37, { line: 5, character: 6 }],
[Token.Identifier, 'x', 38, { line: 5, character: 7 }],
[Token.Identifier, "aaaaaa", 31, { line: 5, character: 0 }],
[Token.Whitespace, " ", 37, { line: 5, character: 6 }],
[Token.Identifier, "x", 38, { line: 5, character: 7 }],
]);
});
it('scans this file', async () => {
const text = await readFile(new URL(import.meta.url), 'utf-8');
tokens(text, function() { /* ignore errors */});
it("scans this file", async () => {
const text = await readFile(new URL(import.meta.url), "utf-8");
tokens(text, function () {
/* ignore errors */
});
});
});

Просмотреть файл

@ -7,12 +7,5 @@
"types": ["node", "mocha"]
},
"include": ["./**/*.ts"],
"exclude": [
"dist",
"test/scenarios/**",
"resources",
"node_modules",
"**/*.d.ts",
"**/*.adl.ts"
]
"exclude": ["dist", "test/scenarios/**", "resources", "node_modules", "**/*.d.ts", "**/*.adl.ts"]
}