mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-15 12:51:30 -05:00
Put error spans deep on nested object literals (#25140)
* Add ncie deep elaborations * Nice stuff * Modify tuple error to use length error mroe often * Accept good baselines * Accept meh baselines * Fix literal types * Calculate elaborations like it was the very first time again~ * Use tristate for enum relationship to ensure elaborations are printed at least once * Update message text, nits * move some functions back to where they were * Add test of deep JSX elaboration * Add elaboration test with parenthesized expressions, comma expressions, and assignments * Move check to allow elaborations on more anonymous types * Fix nits * Add specialized error to elaborations of nonliteral computed named-members * Update error message
This commit is contained in:
@@ -598,7 +598,7 @@ namespace ts {
|
||||
const definitelyAssignableRelation = createMap<RelationComparisonResult>();
|
||||
const comparableRelation = createMap<RelationComparisonResult>();
|
||||
const identityRelation = createMap<RelationComparisonResult>();
|
||||
const enumRelation = createMap<boolean>();
|
||||
const enumRelation = createMap<RelationComparisonResult>();
|
||||
|
||||
type TypeSystemEntity = Symbol | Type | Signature;
|
||||
|
||||
@@ -10135,8 +10135,163 @@ namespace ts {
|
||||
return isTypeComparableTo(type1, type2) || isTypeComparableTo(type2, type1);
|
||||
}
|
||||
|
||||
function checkTypeAssignableTo(source: Type, target: Type, errorNode: Node | undefined, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined): boolean {
|
||||
return checkTypeRelatedTo(source, target, assignableRelation, errorNode, headMessage, containingMessageChain);
|
||||
function checkTypeAssignableTo(source: Type, target: Type, errorNode: Node | undefined, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined, errorOutputObject?: { error?: Diagnostic }): boolean {
|
||||
return checkTypeRelatedTo(source, target, assignableRelation, errorNode, headMessage, containingMessageChain, errorOutputObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Like `checkTypeAssignableTo`, but if it would issue an error, instead performs structural comparisons of the types using the given expression node to
|
||||
* attempt to issue more specific errors on, for example, specific object literal properties or tuple members.
|
||||
*/
|
||||
function checkTypeAssignableToAndOptionallyElaborate(source: Type, target: Type, errorNode: Node | undefined, expr: Expression | undefined, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined): boolean {
|
||||
if (isTypeAssignableTo(source, target)) return true;
|
||||
if (!elaborateError(expr, source, target)) {
|
||||
return checkTypeRelatedTo(source, target, assignableRelation, errorNode, headMessage, containingMessageChain);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function elaborateError(node: Expression | undefined, source: Type, target: Type): boolean {
|
||||
if (!node) return false;
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.JsxExpression:
|
||||
case SyntaxKind.ParenthesizedExpression:
|
||||
return elaborateError((node as ParenthesizedExpression | JsxExpression).expression, source, target);
|
||||
case SyntaxKind.BinaryExpression:
|
||||
switch ((node as BinaryExpression).operatorToken.kind) {
|
||||
case SyntaxKind.EqualsToken:
|
||||
case SyntaxKind.CommaToken:
|
||||
return elaborateError((node as BinaryExpression).right, source, target);
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.ObjectLiteralExpression:
|
||||
return elaborateObjectLiteral(node as ObjectLiteralExpression, source, target);
|
||||
case SyntaxKind.ArrayLiteralExpression:
|
||||
return elaborateArrayLiteral(node as ArrayLiteralExpression, source, target);
|
||||
case SyntaxKind.JsxAttributes:
|
||||
return elaborateJsxAttributes(node as JsxAttributes, source, target);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
type ElaborationIterator = IterableIterator<{ errorNode: Node, innerExpression: Expression | undefined, nameType: Type, errorMessage?: DiagnosticMessage | undefined }>;
|
||||
/**
|
||||
* For every element returned from the iterator, checks that element to issue an error on a property of that element's type
|
||||
* If that element would issue an error, we first attempt to dive into that element's inner expression and issue a more specific error by recuring into `elaborateError`
|
||||
* Otherwise, we issue an error on _every_ element which fail the assignability check
|
||||
*/
|
||||
function elaborateElementwise(iterator: ElaborationIterator, source: Type, target: Type) {
|
||||
// Assignability failure - check each prop individually, and if that fails, fall back on the bad error span
|
||||
let reportedError = false;
|
||||
for (let status = iterator.next(); !status.done; status = iterator.next()) {
|
||||
const { errorNode: prop, innerExpression: next, nameType, errorMessage } = status.value;
|
||||
const sourcePropType = getIndexedAccessType(source, nameType);
|
||||
const targetPropType = getIndexedAccessType(target, nameType);
|
||||
if (!isTypeAssignableTo(sourcePropType, targetPropType)) {
|
||||
const elaborated = next && elaborateError(next, sourcePropType, targetPropType);
|
||||
if (elaborated) {
|
||||
reportedError = true;
|
||||
}
|
||||
else {
|
||||
// Issue error on the prop itself, since the prop couldn't elaborate the error
|
||||
const resultObj: { error?: Diagnostic } = {};
|
||||
// Use the expression type, if available
|
||||
const specificSource = next ? checkExpressionForMutableLocation(next, CheckMode.Normal, sourcePropType) : sourcePropType;
|
||||
const result = checkTypeAssignableTo(specificSource, targetPropType, prop, errorMessage, /*containingChain*/ undefined, resultObj);
|
||||
if (result && specificSource !== sourcePropType) {
|
||||
// If for whatever reason the expression type doesn't yield an error, make sure we still issue an error on the sourcePropType
|
||||
checkTypeAssignableTo(sourcePropType, targetPropType, prop, errorMessage, /*containingChain*/ undefined, resultObj);
|
||||
}
|
||||
if (resultObj.error) {
|
||||
const reportedDiag = resultObj.error;
|
||||
const propertyName = isTypeUsableAsLateBoundName(nameType) ? getLateBoundNameFromType(nameType) : undefined;
|
||||
const targetProp = propertyName !== undefined ? getPropertyOfType(target, propertyName) : undefined;
|
||||
|
||||
let issuedElaboration = false;
|
||||
if (!targetProp) {
|
||||
const indexInfo = isTypeAssignableToKind(nameType, TypeFlags.NumberLike) && getIndexInfoOfType(target, IndexKind.Number) ||
|
||||
getIndexInfoOfType(target, IndexKind.String) ||
|
||||
undefined;
|
||||
if (indexInfo && indexInfo.declaration) {
|
||||
issuedElaboration = true;
|
||||
addRelatedInfo(reportedDiag, createDiagnosticForNode(indexInfo.declaration, Diagnostics.The_expected_type_comes_from_this_index_signature));
|
||||
}
|
||||
}
|
||||
|
||||
if (!issuedElaboration && (length(targetProp && targetProp.declarations) || length(target.symbol && target.symbol.declarations))) {
|
||||
addRelatedInfo(reportedDiag, createDiagnosticForNode(
|
||||
targetProp ? targetProp.declarations[0] : target.symbol.declarations[0],
|
||||
Diagnostics.The_expected_type_comes_from_property_0_which_is_declared_here_on_type_1,
|
||||
propertyName && !(nameType.flags & TypeFlags.UniqueESSymbol) ? unescapeLeadingUnderscores(propertyName) : typeToString(nameType),
|
||||
typeToString(target)
|
||||
));
|
||||
}
|
||||
}
|
||||
reportedError = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return reportedError;
|
||||
}
|
||||
|
||||
function *generateJsxAttributes(node: JsxAttributes): ElaborationIterator {
|
||||
if (!length(node.properties)) return;
|
||||
for (const prop of node.properties) {
|
||||
if (isJsxSpreadAttribute(prop)) continue;
|
||||
yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: getLiteralType(idText(prop.name)) };
|
||||
}
|
||||
}
|
||||
|
||||
function elaborateJsxAttributes(node: JsxAttributes, source: Type, target: Type) {
|
||||
return elaborateElementwise(generateJsxAttributes(node), source, target);
|
||||
}
|
||||
|
||||
function *generateLimitedTupleElements(node: ArrayLiteralExpression, target: Type): ElaborationIterator {
|
||||
const len = length(node.elements);
|
||||
if (!len) return;
|
||||
for (let i = 0; i < len; i++) {
|
||||
// Skip elements which do not exist in the target - a length error on the tuple overall is likely better than an error on a mismatched index signature
|
||||
if (isTupleLikeType(target) && !getPropertyOfType(target, ("" + i) as __String)) continue;
|
||||
const elem = node.elements[i];
|
||||
if (isOmittedExpression(elem)) continue;
|
||||
const nameType = getLiteralType(i);
|
||||
yield { errorNode: elem, innerExpression: elem, nameType };
|
||||
}
|
||||
}
|
||||
|
||||
function elaborateArrayLiteral(node: ArrayLiteralExpression, source: Type, target: Type) {
|
||||
if (isTupleLikeType(source)) {
|
||||
return elaborateElementwise(generateLimitedTupleElements(node, target), source, target);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function *generateObjectLiteralElements(node: ObjectLiteralExpression): ElaborationIterator {
|
||||
if (!length(node.properties)) return;
|
||||
for (const prop of node.properties) {
|
||||
if (isSpreadAssignment(prop)) continue;
|
||||
const type = getLiteralTypeFromPropertyName(getSymbolOfNode(prop), TypeFlags.StringOrNumberLiteralOrUnique);
|
||||
if (!type || (type.flags & TypeFlags.Never)) {
|
||||
continue;
|
||||
}
|
||||
switch (prop.kind) {
|
||||
case SyntaxKind.SetAccessor:
|
||||
case SyntaxKind.GetAccessor:
|
||||
case SyntaxKind.MethodDeclaration:
|
||||
case SyntaxKind.ShorthandPropertyAssignment:
|
||||
yield { errorNode: prop.name, innerExpression: undefined, nameType: type };
|
||||
break;
|
||||
case SyntaxKind.PropertyAssignment:
|
||||
yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: type, errorMessage: isComputedNonLiteralName(prop.name) ? Diagnostics.Type_of_computed_property_s_value_is_0_which_is_not_assignable_to_type_1 : undefined };
|
||||
break;
|
||||
default:
|
||||
Debug.assertNever(prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function elaborateObjectLiteral(node: ObjectLiteralExpression, source: Type, target: Type) {
|
||||
return elaborateElementwise(generateObjectLiteralElements(node), source, target);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -10351,11 +10506,11 @@ namespace ts {
|
||||
}
|
||||
const id = getSymbolId(sourceSymbol) + "," + getSymbolId(targetSymbol);
|
||||
const relation = enumRelation.get(id);
|
||||
if (relation !== undefined) {
|
||||
return relation;
|
||||
if (relation !== undefined && !(relation === RelationComparisonResult.Failed && errorReporter)) {
|
||||
return relation === RelationComparisonResult.Succeeded;
|
||||
}
|
||||
if (sourceSymbol.escapedName !== targetSymbol.escapedName || !(sourceSymbol.flags & SymbolFlags.RegularEnum) || !(targetSymbol.flags & SymbolFlags.RegularEnum)) {
|
||||
enumRelation.set(id, false);
|
||||
enumRelation.set(id, RelationComparisonResult.FailedAndReported);
|
||||
return false;
|
||||
}
|
||||
const targetEnumType = getTypeOfSymbol(targetSymbol);
|
||||
@@ -10366,13 +10521,16 @@ namespace ts {
|
||||
if (errorReporter) {
|
||||
errorReporter(Diagnostics.Property_0_is_missing_in_type_1, symbolName(property),
|
||||
typeToString(getDeclaredTypeOfSymbol(targetSymbol), /*enclosingDeclaration*/ undefined, TypeFormatFlags.UseFullyQualifiedType));
|
||||
enumRelation.set(id, RelationComparisonResult.FailedAndReported);
|
||||
}
|
||||
else {
|
||||
enumRelation.set(id, RelationComparisonResult.Failed);
|
||||
}
|
||||
enumRelation.set(id, false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
enumRelation.set(id, true);
|
||||
enumRelation.set(id, RelationComparisonResult.Succeeded);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -10457,7 +10615,9 @@ namespace ts {
|
||||
relation: Map<RelationComparisonResult>,
|
||||
errorNode: Node | undefined,
|
||||
headMessage?: DiagnosticMessage,
|
||||
containingMessageChain?: () => DiagnosticMessageChain | undefined): boolean {
|
||||
containingMessageChain?: () => DiagnosticMessageChain | undefined,
|
||||
errorOutputContainer?: { error?: Diagnostic }
|
||||
): boolean {
|
||||
|
||||
let errorInfo: DiagnosticMessageChain | undefined;
|
||||
let maybeKeys: string[];
|
||||
@@ -10497,7 +10657,11 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
diagnostics.add(createDiagnosticForNodeFromMessageChain(errorNode!, errorInfo, relatedInformation)); // TODO: GH#18217
|
||||
const diag = createDiagnosticForNodeFromMessageChain(errorNode!, errorInfo, relatedInformation);
|
||||
if (errorOutputContainer) {
|
||||
errorOutputContainer.error = diag;
|
||||
}
|
||||
diagnostics.add(diag); // TODO: GH#18217
|
||||
}
|
||||
return result !== Ternary.False;
|
||||
|
||||
@@ -11019,9 +11183,8 @@ namespace ts {
|
||||
const related = relation.get(id);
|
||||
if (related !== undefined) {
|
||||
if (reportErrors && related === RelationComparisonResult.Failed) {
|
||||
// We are elaborating errors and the cached result is an unreported failure. Record the result as a reported
|
||||
// failure and continue computing the relation such that errors get reported.
|
||||
relation.set(id, RelationComparisonResult.FailedAndReported);
|
||||
// We are elaborating errors and the cached result is an unreported failure. The result will be reported
|
||||
// as a failure, and should be updated as a reported failure by the bottom of this function.
|
||||
}
|
||||
else {
|
||||
return related === RelationComparisonResult.Succeeded ? Ternary.True : Ternary.False;
|
||||
@@ -15636,7 +15799,7 @@ namespace ts {
|
||||
const discriminatingType = checkExpression(prop.initializer);
|
||||
for (const type of (contextualType as UnionType).types) {
|
||||
const targetType = getTypeOfPropertyOfType(type, prop.symbol.escapedName);
|
||||
if (targetType && checkTypeAssignableTo(discriminatingType, targetType, /*errorNode*/ undefined)) {
|
||||
if (targetType && isTypeAssignableTo(discriminatingType, targetType)) {
|
||||
if (match) {
|
||||
if (type === match) continue; // Finding multiple fields which discriminate to the same type is fine
|
||||
match = undefined;
|
||||
@@ -17153,30 +17316,7 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
else if (!isSourceAttributeTypeAssignableToTarget) {
|
||||
// Assignability failure - check each prop individually, and if that fails, fall back on the bad error span
|
||||
if (length(openingLikeElement.attributes.properties)) {
|
||||
let reportedError = false;
|
||||
for (const prop of openingLikeElement.attributes.properties) {
|
||||
if (isJsxSpreadAttribute(prop)) continue;
|
||||
const name = idText(prop.name);
|
||||
const sourcePropType = getIndexedAccessType(sourceAttributesType, getLiteralType(name));
|
||||
const targetPropType = getIndexedAccessType(targetAttributesType, getLiteralType(name));
|
||||
const rootChain = () => chainDiagnosticMessages(
|
||||
/*details*/ undefined,
|
||||
Diagnostics.Types_of_property_0_are_incompatible,
|
||||
name,
|
||||
);
|
||||
if (!checkTypeAssignableTo(sourcePropType, targetPropType, prop, /*headMessage*/ undefined, rootChain)) {
|
||||
reportedError = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (reportedError) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Report fallback error on just the component name
|
||||
checkTypeAssignableTo(sourceAttributesType, targetAttributesType, openingLikeElement.tagName);
|
||||
checkTypeAssignableToAndOptionallyElaborate(sourceAttributesType, targetAttributesType, openingLikeElement.tagName, openingLikeElement.attributes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20160,10 +20300,10 @@ namespace ts {
|
||||
if (returnOrPromisedType) {
|
||||
if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async) { // Async function
|
||||
const awaitedType = checkAwaitedType(exprType, node.body, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member);
|
||||
checkTypeAssignableTo(awaitedType, returnOrPromisedType, node.body);
|
||||
checkTypeAssignableToAndOptionallyElaborate(awaitedType, returnOrPromisedType, node.body, node.body);
|
||||
}
|
||||
else { // Normal function
|
||||
checkTypeAssignableTo(exprType, returnOrPromisedType, node.body);
|
||||
checkTypeAssignableToAndOptionallyElaborate(exprType, returnOrPromisedType, node.body, node.body);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20581,7 +20721,7 @@ namespace ts {
|
||||
Diagnostics.The_target_of_an_object_rest_assignment_must_be_a_variable_or_a_property_access :
|
||||
Diagnostics.The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access;
|
||||
if (checkReferenceExpression(target, error)) {
|
||||
checkTypeAssignableTo(sourceType, targetType, target, /*headMessage*/ undefined);
|
||||
checkTypeAssignableToAndOptionallyElaborate(sourceType, targetType, target, target);
|
||||
}
|
||||
return sourceType;
|
||||
}
|
||||
@@ -20880,7 +21020,7 @@ namespace ts {
|
||||
if (checkReferenceExpression(left, Diagnostics.The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access)
|
||||
&& (!isIdentifier(left) || unescapeLeadingUnderscores(left.escapedText) !== "exports")) {
|
||||
// to avoid cascading errors check assignability only if 'isReference' check succeeded and no errors were reported
|
||||
checkTypeAssignableTo(valueType, leftType, left, /*headMessage*/ undefined);
|
||||
checkTypeAssignableToAndOptionallyElaborate(valueType, leftType, left, right);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20992,7 +21132,7 @@ namespace ts {
|
||||
const returnType = getEffectiveReturnTypeNode(func);
|
||||
if (returnType) {
|
||||
const signatureElementType = getIteratedTypeOfGenerator(getTypeFromTypeNode(returnType), isAsync) || anyType;
|
||||
checkTypeAssignableTo(yieldedType, signatureElementType, node.expression || node, /*headMessage*/ undefined);
|
||||
checkTypeAssignableToAndOptionallyElaborate(yieldedType, signatureElementType, node.expression || node, node.expression);
|
||||
}
|
||||
|
||||
// Both yield and yield* expressions have type 'any'
|
||||
@@ -23713,7 +23853,7 @@ namespace ts {
|
||||
checkNonNullType(initializerType, node);
|
||||
}
|
||||
else {
|
||||
checkTypeAssignableTo(initializerType, getWidenedTypeForVariableLikeDeclaration(node), node, /*headMessage*/ undefined);
|
||||
checkTypeAssignableToAndOptionallyElaborate(initializerType, getWidenedTypeForVariableLikeDeclaration(node), node, node.initializer);
|
||||
}
|
||||
checkParameterInitializer(node);
|
||||
}
|
||||
@@ -23731,7 +23871,7 @@ namespace ts {
|
||||
(initializer.properties.length === 0 || isPrototypeAccess(node.name)) &&
|
||||
hasEntries(symbol.exports);
|
||||
if (!isJSObjectLiteralInitializer && node.parent.parent.kind !== SyntaxKind.ForInStatement) {
|
||||
checkTypeAssignableTo(checkExpressionCached(initializer), type, node, /*headMessage*/ undefined);
|
||||
checkTypeAssignableToAndOptionallyElaborate(checkExpressionCached(initializer), type, node, initializer, /*headMessage*/ undefined);
|
||||
checkParameterInitializer(node);
|
||||
}
|
||||
}
|
||||
@@ -23747,7 +23887,7 @@ namespace ts {
|
||||
errorNextVariableOrPropertyDeclarationMustHaveSameType(type, node, declarationType);
|
||||
}
|
||||
if (node.initializer) {
|
||||
checkTypeAssignableTo(checkExpressionCached(node.initializer), declarationType, node, /*headMessage*/ undefined);
|
||||
checkTypeAssignableToAndOptionallyElaborate(checkExpressionCached(node.initializer), declarationType, node, node.initializer, /*headMessage*/ undefined);
|
||||
}
|
||||
if (!areDeclarationFlagsIdentical(node, symbol.valueDeclaration)) {
|
||||
error(getNameOfDeclaration(symbol.valueDeclaration), Diagnostics.All_declarations_of_0_must_have_identical_modifiers, declarationNameToString(node.name));
|
||||
@@ -23920,7 +24060,7 @@ namespace ts {
|
||||
// because we accessed properties from anyType, or it may have led to an error inside
|
||||
// getElementTypeOfIterable.
|
||||
if (iteratedType) {
|
||||
checkTypeAssignableTo(iteratedType, leftType, varExpr, /*headMessage*/ undefined);
|
||||
checkTypeAssignableToAndOptionallyElaborate(iteratedType, leftType, varExpr, node.expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24364,7 +24504,7 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
else if (func.kind === SyntaxKind.Constructor) {
|
||||
if (node.expression && !checkTypeAssignableTo(exprType, returnType, node)) {
|
||||
if (node.expression && !checkTypeAssignableToAndOptionallyElaborate(exprType, returnType, node, node.expression)) {
|
||||
error(node, Diagnostics.Return_type_of_constructor_signature_must_be_assignable_to_the_instance_type_of_the_class);
|
||||
}
|
||||
}
|
||||
@@ -24380,7 +24520,7 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
else {
|
||||
checkTypeAssignableTo(exprType, returnType, node);
|
||||
checkTypeAssignableToAndOptionallyElaborate(exprType, returnType, node, node.expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1452,6 +1452,10 @@
|
||||
"category": "Error",
|
||||
"code": 2417
|
||||
},
|
||||
"Type of computed property's value is '{0}', which is not assignable to type '{1}'.": {
|
||||
"category": "Error",
|
||||
"code": 2418
|
||||
},
|
||||
"Class '{0}' incorrectly implements interface '{1}'.": {
|
||||
"category": "Error",
|
||||
"code": 2420
|
||||
@@ -3754,6 +3758,15 @@
|
||||
"code": 6371
|
||||
},
|
||||
|
||||
"The expected type comes from property '{0}' which is declared here on type '{1}'": {
|
||||
"category": "Message",
|
||||
"code": 6500
|
||||
},
|
||||
"The expected type comes from this index signature.": {
|
||||
"category": "Message",
|
||||
"code": 6501
|
||||
},
|
||||
|
||||
"Variable '{0}' implicitly has an '{1}' type.": {
|
||||
"category": "Error",
|
||||
"code": 7005
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"pretty": true,
|
||||
"lib": ["es5"],
|
||||
"lib": ["es2015"],
|
||||
"target": "es5",
|
||||
|
||||
"declaration": true,
|
||||
|
||||
Reference in New Issue
Block a user