Skip to content

Commit 297fe47

Browse files
committed
fix: clear corner cases in i18n rules
1 parent 7d4dced commit 297fe47

File tree

2 files changed

+85
-2
lines changed

2 files changed

+85
-2
lines changed

src/i18nRule.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,11 @@ class I18NAttrVisitor extends BasicTemplateAstVisitor
4545

4646
class I18NTextVisitor extends BasicTemplateAstVisitor
4747
implements ConfigurableVisitor {
48+
static Error = 'Each element containing text node should has an i18n attribute';
49+
4850
private hasI18n = false;
4951
private nestedElements = [];
50-
private visited = new Set<ast.TextAst>();
52+
private visited = new Set<ast.TextAst | ast.BoundTextAst>();
5153

5254
visitText(text: ast.TextAst, context: BasicTemplateAstVisitor) {
5355
if (!this.visited.has(text)) {
@@ -62,14 +64,37 @@ class I18NTextVisitor extends BasicTemplateAstVisitor
6264
context.createFailure(
6365
span.start.offset,
6466
span.end.offset - span.start.offset,
65-
'Each element containing text node should has an i18n attribute'
67+
I18NTextVisitor.Error
6668
)
6769
);
6870
}
6971
}
7072
super.visitText(text, context);
7173
}
7274

75+
visitBoundText(text: ast.BoundTextAst, context: BasicTemplateAstVisitor) {
76+
if (!this.visited.has(text)) {
77+
this.visited.add(text);
78+
const val = text.value;
79+
if (
80+
val instanceof ast.ASTWithSource &&
81+
val.ast instanceof ast.Interpolation
82+
) {
83+
const textNonEmpty = val.ast.strings.some(s => /\w+/.test(s));
84+
if (textNonEmpty) {
85+
const span = text.sourceSpan;
86+
context.addFailure(
87+
context.createFailure(
88+
span.start.offset,
89+
span.end.offset - span.start.offset,
90+
I18NTextVisitor.Error
91+
)
92+
);
93+
}
94+
}
95+
}
96+
}
97+
7398
visitElement(element: ast.ElementAst, context: BasicTemplateAstVisitor) {
7499
this.hasI18n = element.attrs.some(e => e.name === 'i18n');
75100
this.nestedElements.push(element.name);
@@ -132,6 +157,16 @@ class I18NTemplateVisitor extends BasicTemplateAstVisitor {
132157
.forEach(f => this.addFailure(f));
133158
super.visitText(text, context);
134159
}
160+
161+
visitBoundText(text: ast.BoundTextAst, context: any): any {
162+
const options = this.getOptions();
163+
this.visitors
164+
.filter(v => options.indexOf(v.getOption()) >= 0)
165+
.map(v => v.visitBoundText(text, this))
166+
.filter(f => !!f)
167+
.forEach(f => this.addFailure(f));
168+
super.visitBoundText(text, context);
169+
}
135170
}
136171

137172
export class Rule extends Lint.Rules.AbstractRule {

test/i18nRule.spec.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,5 +152,53 @@ describe('i18n', () => {
152152
['check-text']
153153
);
154154
});
155+
156+
it('should fail with missing id string', () => {
157+
let source = `
158+
@Component({
159+
template: \`
160+
<div>Text {{ foo }}</div>
161+
~~~~~~~~~~~~~~
162+
\`
163+
})
164+
class Bar {}
165+
`;
166+
assertAnnotated({
167+
ruleName: 'i18n',
168+
options: ['check-text'],
169+
source,
170+
message:
171+
'Each element containing text node should has an i18n attribute'
172+
});
173+
});
174+
175+
it('should fail with text outside element with i18n attribute', () => {
176+
let source = `
177+
@Component({
178+
template: \`
179+
<div i18n>Text</div>
180+
{{foo}} text
181+
\`
182+
})
183+
class Bar {}
184+
`;
185+
assertFailure(
186+
'i18n',
187+
source,
188+
{
189+
message:
190+
'Each element containing text node should has an i18n attribute',
191+
startPosition: {
192+
line: 3,
193+
character: 30
194+
},
195+
endPosition: {
196+
line: 5,
197+
character: 8
198+
}
199+
},
200+
['check-text']
201+
);
202+
});
155203
});
156204
});

0 commit comments

Comments
 (0)