fix(ivy): more descriptive errors for nested i18n sections (#33583)
This commit moves nested i18n section detection to an earlier stage where we convert HTML AST to Ivy AST. This also gives a chance to produce better diagnistic message for nested i18n sections, that also includes a file name and location. PR Close #33583
This commit is contained in:
parent
25aaff2ee1
commit
d9a38928f5
@ -3415,14 +3415,57 @@ describe('i18n support in the template compiler', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('errors', () => {
|
describe('errors', () => {
|
||||||
|
const verifyNestedSectionsError = (errorThrown: any, expectedErrorText: string) => {
|
||||||
|
expect(errorThrown.ngParseErrors.length).toBe(1);
|
||||||
|
const msg = errorThrown.ngParseErrors[0].toString();
|
||||||
|
expect(msg).toContain(
|
||||||
|
'Cannot mark an element as translatable inside of a translatable section. Please remove the nested i18n marker.');
|
||||||
|
expect(msg).toContain(expectedErrorText);
|
||||||
|
expect(msg).toMatch(/app\/spec\.ts\@\d+\:\d+/);
|
||||||
|
};
|
||||||
|
|
||||||
it('should throw on nested i18n sections', () => {
|
it('should throw on nested i18n sections', () => {
|
||||||
const files = getAppFilesWithTemplate(`
|
const files = getAppFilesWithTemplate(`
|
||||||
<div i18n><div i18n>Some content</div></div>
|
<div i18n>
|
||||||
|
<div i18n>Some content</div>
|
||||||
|
</div>
|
||||||
`);
|
`);
|
||||||
expect(() => compile(files, angularFiles))
|
try {
|
||||||
.toThrowError(
|
compile(files, angularFiles);
|
||||||
'Could not mark an element as translatable inside of a translatable section');
|
} catch (error) {
|
||||||
|
verifyNestedSectionsError(error, '[ERROR ->]<div i18n>Some content</div>');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should throw on nested i18n sections with tags in between', () => {
|
||||||
|
const files = getAppFilesWithTemplate(`
|
||||||
|
<div i18n>
|
||||||
|
<div>
|
||||||
|
<div i18n>Some content</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
try {
|
||||||
|
compile(files, angularFiles);
|
||||||
|
} catch (error) {
|
||||||
|
verifyNestedSectionsError(error, '[ERROR ->]<div i18n>Some content</div>');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw on nested i18n sections represented with <ng-container>s', () => {
|
||||||
|
const files = getAppFilesWithTemplate(`
|
||||||
|
<ng-container i18n>
|
||||||
|
<div>
|
||||||
|
<ng-container i18n>Some content</ng-container>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
`);
|
||||||
|
try {
|
||||||
|
compile(files, angularFiles);
|
||||||
|
} catch (error) {
|
||||||
|
verifyNestedSectionsError(
|
||||||
|
error, '[ERROR ->]<ng-container i18n>Some content</ng-container>');
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -80,11 +80,21 @@ class HtmlAstToIvyAst implements html.Visitor {
|
|||||||
errors: ParseError[] = [];
|
errors: ParseError[] = [];
|
||||||
styles: string[] = [];
|
styles: string[] = [];
|
||||||
styleUrls: string[] = [];
|
styleUrls: string[] = [];
|
||||||
|
private inI18nBlock: boolean = false;
|
||||||
|
|
||||||
constructor(private bindingParser: BindingParser) {}
|
constructor(private bindingParser: BindingParser) {}
|
||||||
|
|
||||||
// HTML visitor
|
// HTML visitor
|
||||||
visitElement(element: html.Element): t.Node|null {
|
visitElement(element: html.Element): t.Node|null {
|
||||||
|
const isI18nRootElement = isI18nRootNode(element.i18n);
|
||||||
|
if (isI18nRootElement) {
|
||||||
|
if (this.inI18nBlock) {
|
||||||
|
this.reportError(
|
||||||
|
'Cannot mark an element as translatable inside of a translatable section. Please remove the nested i18n marker.',
|
||||||
|
element.sourceSpan);
|
||||||
|
}
|
||||||
|
this.inI18nBlock = true;
|
||||||
|
}
|
||||||
const preparsedElement = preparseElement(element);
|
const preparsedElement = preparseElement(element);
|
||||||
if (preparsedElement.type === PreparsedElementType.SCRIPT) {
|
if (preparsedElement.type === PreparsedElementType.SCRIPT) {
|
||||||
return null;
|
return null;
|
||||||
@ -209,7 +219,7 @@ class HtmlAstToIvyAst implements html.Visitor {
|
|||||||
// For <ng-template>s with structural directives on them, avoid passing i18n information to
|
// For <ng-template>s with structural directives on them, avoid passing i18n information to
|
||||||
// the wrapping template to prevent unnecessary i18n instructions from being generated. The
|
// the wrapping template to prevent unnecessary i18n instructions from being generated. The
|
||||||
// necessary i18n meta information will be extracted from child elements.
|
// necessary i18n meta information will be extracted from child elements.
|
||||||
const i18n = isTemplateElement && isI18nRootNode(element.i18n) ? undefined : element.i18n;
|
const i18n = isTemplateElement && isI18nRootElement ? undefined : element.i18n;
|
||||||
|
|
||||||
// TODO(pk): test for this case
|
// TODO(pk): test for this case
|
||||||
parsedElement = new t.Template(
|
parsedElement = new t.Template(
|
||||||
@ -218,6 +228,9 @@ class HtmlAstToIvyAst implements html.Visitor {
|
|||||||
templateVariables, element.sourceSpan, element.startSourceSpan, element.endSourceSpan,
|
templateVariables, element.sourceSpan, element.startSourceSpan, element.endSourceSpan,
|
||||||
i18n);
|
i18n);
|
||||||
}
|
}
|
||||||
|
if (isI18nRootElement) {
|
||||||
|
this.inI18nBlock = false;
|
||||||
|
}
|
||||||
return parsedElement;
|
return parsedElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -535,10 +535,6 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||||||
const isI18nRootElement: boolean =
|
const isI18nRootElement: boolean =
|
||||||
isI18nRootNode(element.i18n) && !isSingleI18nIcu(element.i18n);
|
isI18nRootNode(element.i18n) && !isSingleI18nIcu(element.i18n);
|
||||||
|
|
||||||
if (isI18nRootElement && this.i18n) {
|
|
||||||
throw new Error(`Could not mark an element as translatable inside of a translatable section`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const i18nAttrs: (t.TextAttribute | t.BoundAttribute)[] = [];
|
const i18nAttrs: (t.TextAttribute | t.BoundAttribute)[] = [];
|
||||||
const outputAttrs: t.TextAttribute[] = [];
|
const outputAttrs: t.TextAttribute[] = [];
|
||||||
let ngProjectAsAttr: t.TextAttribute|undefined;
|
let ngProjectAsAttr: t.TextAttribute|undefined;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user