js-slang
Version:
Javascript-based implementations of Source, written in Typescript
208 lines • 9.73 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.SourceTypedParser = void 0;
const parser_1 = require("@babel/parser");
const constants_1 = require("../../../constants");
const typeErrorChecker_1 = require("../../../typeChecker/typeErrorChecker");
const errors_1 = require("../../errors");
const utils_1 = require("../../utils");
const __1 = require("..");
const typeParser_1 = require("./typeParser");
const utils_2 = require("./utils");
class SourceTypedParser extends __1.SourceParser {
parse(programStr, context, options, throwOnError) {
// Parse with acorn type parser first to catch errors such as
// import/export not at top level, trailing commas, missing semicolons
try {
typeParser_1.default.parse(programStr, (0, utils_1.createAcornParserOptions)(constants_1.DEFAULT_ECMA_VERSION, context.errors, options));
}
catch (error) {
if (error instanceof SyntaxError) {
error = new errors_1.FatalSyntaxError((0, utils_1.positionToSourceLocation)(error.loc, options?.sourceFile), error.toString());
}
if (throwOnError)
throw error;
context.errors.push(error);
return null;
}
// Parse again with babel parser to capture all type syntax
// and catch remaining syntax errors not caught by acorn type parser
const ast = (0, parser_1.parse)(programStr, {
...utils_1.defaultBabelOptions,
sourceFilename: options?.sourceFile,
errorRecovery: throwOnError ?? true
});
if (ast.errors.length) {
ast.errors
.filter(error => error instanceof SyntaxError)
.forEach(error => {
context.errors.push(new errors_1.FatalSyntaxError((0, utils_1.positionToSourceLocation)(error.loc, options?.sourceFile), error.toString()));
});
return null;
}
const typedProgram = ast.program;
if (context.prelude !== programStr) {
// Check for any declaration only if the program is not the prelude
checkForAnyDeclaration(typedProgram, context);
}
const typedCheckedProgram = (0, typeErrorChecker_1.checkForTypeErrors)(typedProgram, context);
(0, utils_2.transformBabelASTToESTreeCompliantAST)(typedCheckedProgram);
return typedCheckedProgram;
}
toString() {
return 'SourceTypedParser';
}
}
exports.SourceTypedParser = SourceTypedParser;
function checkForAnyDeclaration(program, context) {
function parseConfigOption(option) {
return option === 'true' || option === undefined;
}
const config = {
allowAnyInVariables: parseConfigOption(context.languageOptions['typedAllowAnyInVariables']),
allowAnyInParameters: parseConfigOption(context.languageOptions['typedAllowAnyInParameters']),
allowAnyInReturnType: parseConfigOption(context.languageOptions['typedAllowAnyInReturnType']),
allowAnyInTypeAnnotationParameters: parseConfigOption(context.languageOptions['typedAllowAnyInTypeAnnotationParameters']),
allowAnyInTypeAnnotationReturnType: parseConfigOption(context.languageOptions['typedAllowAnyInTypeAnnotationReturnType'])
};
function pushAnyUsageError(message, node) {
if (node.loc) {
context.errors.push(new errors_1.FatalSyntaxError(node.loc, message));
}
}
function isAnyType(node) {
return node?.typeAnnotation?.type === 'TSAnyKeyword' || node?.typeAnnotation === undefined;
}
function checkNode(node) {
switch (node.type) {
case 'VariableDeclaration': {
node.declarations.forEach(decl => {
const tsType = decl.id?.typeAnnotation;
if (!config.allowAnyInVariables && isAnyType(tsType)) {
pushAnyUsageError('Usage of "any" in variable declaration is not allowed.', node);
}
if (decl.init) {
// check for lambdas
checkNode(decl.init);
}
});
break;
}
case 'FunctionDeclaration': {
if (!config.allowAnyInParameters || !config.allowAnyInReturnType) {
const func = node;
// Check parameters
func.params?.forEach((param) => {
if (!config.allowAnyInParameters && isAnyType(param.typeAnnotation)) {
pushAnyUsageError('Usage of "any" in function parameter is not allowed.', param);
}
});
// Check return type
if (!config.allowAnyInReturnType && isAnyType(func.returnType)) {
pushAnyUsageError('Usage of "any" in function return type is not allowed.', node);
}
checkNode(node.body);
}
break;
}
case 'ArrowFunctionExpression': {
if (!config.allowAnyInParameters || !config.allowAnyInReturnType) {
const arrow = node;
// Check parameters
arrow.params?.forEach((param) => {
if (!config.allowAnyInParameters && isAnyType(param.typeAnnotation)) {
pushAnyUsageError('Usage of "any" in arrow function parameter is not allowed.', param);
}
});
// Recursively check return type if present
if (!config.allowAnyInReturnType && isAnyType(arrow.returnType)) {
pushAnyUsageError('Usage of "any" in arrow function return type is not allowed.', arrow);
}
if (!config.allowAnyInReturnType &&
arrow.params?.some((param) => isAnyType(param.typeAnnotation))) {
pushAnyUsageError('Usage of "any" in arrow function return type is not allowed.', arrow);
}
checkNode(node.body);
}
break;
}
case 'ReturnStatement': {
if (node.argument) {
checkNode(node.argument);
}
break;
}
case 'BlockStatement':
node.body.forEach(checkNode);
break;
default:
break;
}
}
function checkTSNode(node) {
if (!node) {
// Happens when there is no type annotation
// This should have been caught by checkNode function
return;
}
switch (node.type) {
case 'VariableDeclaration': {
node.declarations.forEach(decl => {
const tsType = decl.id?.typeAnnotation;
checkTSNode(tsType);
});
break;
}
case 'TSTypeAnnotation': {
const annotation = node;
// If it's a function type annotation, check params and return
if (annotation.typeAnnotation?.type === 'TSFunctionType') {
annotation.typeAnnotation.parameters?.forEach(param => {
// Recursively check nested TSTypeAnnotations in parameters
if (!config.allowAnyInTypeAnnotationParameters && isAnyType(param.typeAnnotation)) {
pushAnyUsageError('Usage of "any" in type annotation\'s function parameter is not allowed.', param);
}
if (param.typeAnnotation) {
checkTSNode(param.typeAnnotation);
}
});
const returnAnno = annotation.typeAnnotation.typeAnnotation;
if (!config.allowAnyInTypeAnnotationReturnType && isAnyType(returnAnno)) {
pushAnyUsageError('Usage of "any" in type annotation\'s function return type is not allowed.', annotation);
}
// Recursively check nested TSTypeAnnotations in return type
checkTSNode(returnAnno);
}
break;
}
case 'FunctionDeclaration': {
// Here we also check param type annotations + return type via config
if (!config.allowAnyInTypeAnnotationParameters ||
!config.allowAnyInTypeAnnotationReturnType) {
const func = node;
// Check parameters
if (!config.allowAnyInTypeAnnotationParameters) {
func.params?.forEach((param) => {
checkTSNode(param.typeAnnotation);
});
}
// Recursively check the function return type annotation
checkTSNode(func.returnType);
}
break;
}
case 'BlockStatement':
node.body.forEach(checkTSNode);
break;
default:
break;
}
}
if (!config.allowAnyInVariables || !config.allowAnyInParameters || !config.allowAnyInReturnType) {
program.body.forEach(checkNode);
}
if (!config.allowAnyInTypeAnnotationParameters || !config.allowAnyInTypeAnnotationReturnType) {
program.body.forEach(checkTSNode);
}
}
//# sourceMappingURL=index.js.map
;