public final void testBadCall() throws Exception { runTest( "" + "<ihtml:template formals=\"\" name=\"hi\">\n" + " \n" + "</ihtml:template>", "" + "<ihtml:template name='hi' formals=''>\n" + " <ihtml:call foo='bar' baz:boo='far'/>\n" + "</ihtml:template>", new Message( IhtmlMessageType.MISSING_ATTRIB, FilePosition.instance(is, 2, 41, 3, 37), elKey("ihtml:call"), attrKey("ihtml:call", "ihtml:template")), new Message( IhtmlMessageType.BAD_ATTRIB, FilePosition.instance(is, 2, 63, 25, 13), elKey("ihtml:call"), AttribKey.forAttribute( new Namespaces(Namespaces.XML_SPECIAL, "baz", "unknown:///baz"), elKey("ihtml:call"), "baz:boo"), MessagePart.Factory.valueOf("far")), new Message( MessageType.NO_SUCH_NAMESPACE, FilePosition.fromLinePositions(is, 2, 25, 2, 32), MessagePart.Factory.valueOf("baz"), MessagePart.Factory.valueOf("baz:boo"))); }
public final void testMisplacedPlaceholderContent() throws Exception { runTest( "" + "<ihtml:template formals=\"\" name=\"t\">\n" + " <ihtml:message name=\"m\">\n" + " \n" + " \n" + " </ihtml:message>\n" + "</ihtml:template>", "" + "<ihtml:template formals=\"\" name=\"t\">\n" + " <ihtml:message name=\"m\">\n" + " <ihtml:ph name=\"p\">Hi</ihtml:ph>\n" + " <ihtml:eph>There</ihtml:eph>\n" + " </ihtml:message>\n" + "</ihtml:template>", new Message( IhtmlMessageType.INAPPROPRIATE_CONTENT, FilePosition.instance(is, 3, 88, 24, 2), MessagePart.Factory.valueOf("ph")), new Message( IhtmlMessageType.INAPPROPRIATE_CONTENT, FilePosition.instance(is, 4, 117, 16, 5), MessagePart.Factory.valueOf("eph"))); }
public final void testNestedMessage() throws Exception { runTest( "" + "<ihtml:template formals=\"\" name=\"t\">" + "<ihtml:message name=\"hi\">" + "Hello " + "<ihtml:ph name=\"planet\"></ihtml:ph>" + "<ihtml:dynamic expr=\"planet\"></ihtml:dynamic>" + "<ihtml:eph></ihtml:eph>!" + "</ihtml:message>" + "</ihtml:template>", "" + "<ihtml:template formals=\"\" name=\"t\">" + "<ihtml:message name=\"hi\">" + "<ihtml:message name=\"there\" />" + "Hello " + "<ihtml:ph name=\"planet\"/>" + "<ihtml:dynamic expr=\"planet\"/>" + "<ihtml:eph/>!" + "</ihtml:message>" + "</ihtml:template>", new Message( IhtmlMessageType.NESTED_MESSAGE, FilePosition.instance(is, 1, 62, 62, 30), FilePosition.instance(is, 1, 37, 37, 145))); }
public final void testTemplateNames() throws Exception { runTest( "<ihtml:template formals=\"x\" name=\"hi\"></ihtml:template>", "" + "<ihtml:template formals='x' name='hi'>" + "<ihtml:template name='3nested' formals='a,,x,if,3' zoinks='ahoy'/>" + "</ihtml:template>", new Message( IhtmlMessageType.BAD_ATTRIB, FilePosition.instance(is, 1, 55, 55, 14), elKey("ihtml:template"), attrKey("ihtml:template", "name"), MessagePart.Factory.valueOf("3nested")), new Message( IhtmlMessageType.BAD_ATTRIB, FilePosition.instance(is, 1, 70, 70, 19), elKey("ihtml:template"), attrKey("ihtml:template", "formals"), MessagePart.Factory.valueOf("a,,x,if,3")), new Message( IhtmlMessageType.BAD_ATTRIB, FilePosition.instance(is, 1, 90, 90, 13), elKey("ihtml:template"), attrKey("ihtml:template", "zoinks"), MessagePart.Factory.valueOf("ahoy"))); }
public final void testUnnamedPlaceholder() throws Exception { runTest( "" + "<ihtml:template formals=\"\" name=\"t\">" + "<ihtml:message name=\"hi\">" + "Hello " + "<ihtml:dynamic expr=\"planet\"></ihtml:dynamic>" + "!" + "</ihtml:message>" + "</ihtml:template>", "" + "<ihtml:template formals=\"\" name=\"t\">" + "<ihtml:message name=\"hi\">" + "Hello " + "<ihtml:ph/>" + "<ihtml:dynamic expr=\"planet\"/>" + "<ihtml:eph/>!" + "</ihtml:message>" + "</ihtml:template>", new Message( IhtmlMessageType.MISSING_ATTRIB, elKey("ihtml:ph"), attrKey("ihtml:ph", "name"), FilePosition.instance(is, 1, 68, 68, 11)), new Message( IhtmlMessageType.ORPHANED_PLACEHOLDER_END, FilePosition.instance(is, 1, 109, 109, 12))); }
public final void testBadPlaceholderName() throws Exception { runTest( "" + "<ihtml:template formals=\"\" name=\"t\">" + "<ihtml:message name=\"hi\">" + "Hello " + "<ihtml:dynamic expr=\"planet\"></ihtml:dynamic>" + "!" + "</ihtml:message>" + "</ihtml:template>", "" + "<ihtml:template formals=\"\" name=\"t\">" + "<ihtml:message name=\"hi\">" + "Hello " + "<ihtml:ph name=\"if\"/>" + "<ihtml:dynamic expr=\"planet\"/>" + "<ihtml:eph/>!" + "</ihtml:message>" + "</ihtml:template>", new Message( IhtmlMessageType.BAD_ATTRIB, FilePosition.instance(is, 1, 78, 78, 9), elKey("ihtml:ph"), attrKey("ihtml:ph", "name"), MessagePart.Factory.valueOf("if")), new Message( IhtmlMessageType.ORPHANED_PLACEHOLDER_END, FilePosition.instance(is, 1, 119, 119, 12))); }
public final void testDynamicAttr() throws Exception { runTest( "" + "<ihtml:template formals=\"\" name=\"t\">\n" + " <a>\n" + " <ihtml:attribute name=\"href\">" + "<ihtml:dynamic expr=\"url\"></ihtml:dynamic>" + "</ihtml:attribute>\n" + " <ihtml:attribute name=\"title\">\n" + " <ihtml:message name=\"linkHover\">Howdy</ihtml:message>\n" + " </ihtml:attribute>\n" + " Link Text\n" + " \n" + " </a>\n" + "</ihtml:template>", "" + "<ihtml:template formals=\"\" name=\"t\">\n" + " <a>\n" + " <ihtml:attribute name=\"href\"><ihtml:dynamic expr=\"url\"" + " /></ihtml:attribute>\n" + " <ihtml:attribute name=\"title\">\n" + " <ihtml:message name=\"linkHover\">Howdy</ihtml:message>\n" + " </ihtml:attribute>\n" + " Link Text\n" + " <ihtml:attribute>onclick=\"badness()\"</ihtml:attribute>\n" + " </a>\n" + "</ihtml:template>", new Message( IhtmlMessageType.MISSING_ATTRIB, FilePosition.instance(is, 8, 264, 5, 54), elKey("ihtml:attribute"), attrKey("ihtml:attribute", "name"))); }
public final void testUnclosedPlaceholder2() throws Exception { runTest( "" + "<ihtml:template formals=\"\" name=\"t\">" + "<ihtml:message name=\"hi\">" + "Hello " + "<ihtml:dynamic expr=\"planet\"></ihtml:dynamic>" + "<ihtml:ph name=\"punc\"></ihtml:ph>" + "!" + "<ihtml:eph></ihtml:eph>" + "</ihtml:message>" + "</ihtml:template>", "" + "<ihtml:template formals=\"\" name=\"t\">" + "<ihtml:message name=\"hi\">" + "Hello " + "<ihtml:ph name=\"planet\"/>" + "<ihtml:dynamic expr=\"planet\"/>" + "<ihtml:ph name=\"punc\"/>" + "!" + "<ihtml:eph/>" + "</ihtml:message>" + "</ihtml:template>", new Message( IhtmlMessageType.UNCLOSED_PLACEHOLDER, // Ends before placeholder punc. FilePosition.instance(is, 1, 68, 68, 55))); }
/** * Reduces the pending tokens to a single token with the given type. For example, if the pending * list contains an identifier followed by an open parenthesis, then it can be reduced to a single * function token. This is necessitated by CSS2's odd lexical convention which classifies as * single tokens things that most other languages treat as sequences of primitive tokens. * * <p>Modifies the pending list in place. */ private void reduce(CssTokenType type) { StringBuilder sb = new StringBuilder(); for (Token<CssTokenType> t : pending) { sb.append(t.text); } FilePosition fp = FilePosition.span(pending.getFirst().pos, pending.getLast().pos); pending.clear(); pending.add(Token.instance(sb.toString(), type, fp)); }
public final void testMisplacedElementAndAttribute() throws Exception { runTest( "" + "<ihtml:template formals=\"\" name=\"t\">\n" + " <ihtml:element></ihtml:element>\n" + " <ihtml:message name=\"m\">\n" + " \n" + " <ihtml:ph name=\"ph\"></ihtml:ph>\n" + " \n" + " <ihtml:eph></ihtml:eph>\n" + " </ihtml:message>\n" + " <ihtml:do init=\"maybe\">\n" + " <ihtml:element></ihtml:element>\n" + " <ihtml:else></ihtml:else>\n" + " <ihtml:element></ihtml:element>\n" + " </ihtml:do>\n" + "</ihtml:template>", "" + "<ihtml:template formals=\"\" name=\"t\">\n" + " <ihtml:element/>\n" // OK inside templates + " <ihtml:message name=\"m\">\n" + " <ihtml:element/>\n" // But not inside messages + " <ihtml:ph name=\"ph\"/>\n" + " <ihtml:element/>\n" // Not even inside messages + " <ihtml:eph/>\n" + " </ihtml:message>\n" + " <ihtml:do init=\"maybe\">\n" + " <ihtml:element/>\n" // OK inside a conditional + " <ihtml:else/>\n" + " <ihtml:element/>\n" // OK inside a conditional's alternate + " </ihtml:do>\n" + "</ihtml:template>", new Message( IhtmlMessageType.MISPLACED_ELEMENT, FilePosition.instance(is, 4, 88, 5, 16), MessagePart.Factory.valueOf("ihtml:element"), MessagePart.Factory.valueOf("ihtml:message")), new Message( IhtmlMessageType.MISPLACED_ELEMENT, FilePosition.instance(is, 6, 137, 7, 16), MessagePart.Factory.valueOf("ihtml:element"), MessagePart.Factory.valueOf("ihtml:message"))); }
public static FilePosition span(FilePosition start, FilePosition end) { if (start == end) { return start; } if (!start.source.equals(end.source)) { return FilePosition.UNKNOWN; } if (start.startCharInFile > end.endCharInFile) { throw new IllegalArgumentException(start + ", " + end); } return instance( start.source, start.startLineNo, start.startCharInFile, start.startCharInLine, end.endLineNo(), end.endCharInFile(), end.endCharInLine()); }
public final void testOrphanedPlaceholder() throws Exception { runTest( "" + "<ihtml:template formals=\"\" name=\"t\">" + "Hello " + "<ihtml:dynamic expr=\"planet\"></ihtml:dynamic>!" + "</ihtml:template>", "" + "<ihtml:template formals=\"\" name=\"t\">" + "Hello " + "<ihtml:ph name=\"planet\"/>" + "<ihtml:dynamic expr=\"planet\"/>!" + "<ihtml:eph/>" + "</ihtml:template>", new Message( IhtmlMessageType.ORPHANED_PLACEHOLDER, FilePosition.instance(is, 1, 43, 43, 25)), new Message( IhtmlMessageType.ORPHANED_PLACEHOLDER, FilePosition.instance(is, 1, 99, 99, 12))); }
public static FilePosition between(FilePosition a, FilePosition b) { return instance( a.source(), a.endLineNo(), a.endCharInFile(), a.endCharInLine(), b.startLineNo(), b.startCharInFile(), b.startCharInLine()); }
public final void testUnnamedMessage() throws Exception { runTest( "<ihtml:template formals=\"a b\" name=\"t\"></ihtml:template>", "" + "<ihtml:template formals='a b' name='t'>" + "<ihtml:message>Hello</ihtml:message>" + "</ihtml:template>", new Message( IhtmlMessageType.MISSING_ATTRIB, elKey("ihtml:message"), attrKey("ihtml:message", "name"), FilePosition.instance(is, 1, 40, 40, 36))); }
public final void testIhtmlElementInMessageOutsidePlaceholder() throws Exception { runTest( "" + "<ihtml:template formals=\"\" name=\"t\"></ihtml:template>", "" + "<ihtml:template formals=\"\" name=\"t\">" + "<ihtml:message name='SayHowdy'>" + "Hello " + "<ihtml:dynamic expr=\"planet\"/>!" + "</ihtml:message>" + "</ihtml:template>", new Message( IhtmlMessageType.IHTML_IN_MESSAGE_OUTSIDE_PLACEHOLDER, FilePosition.instance(is, 1, 74, 74, 30), MessagePart.Factory.valueOf("dynamic"))); }
public final void testMisnamedMessage() throws Exception { runTest( "<ihtml:template formals=\"a b\" name=\"t\"></ihtml:template>", "" + "<ihtml:template formals=\"a b\" name=\"t\">" + "<ihtml:message name=\"x__\">" + "Hello" + "</ihtml:message>" + "</ihtml:template>", new Message( IhtmlMessageType.BAD_ATTRIB, FilePosition.instance(is, 1, 55, 55, 10), elKey("ihtml:message"), attrKey("ihtml:message", "name"), MessagePart.Factory.valueOf("x__"))); }
public final void testBadAttr() throws Exception { runTest( "" + "<ihtml:template formals=\"\" name=\"t\">\n" + " \n" + " <div><ihtml:element>p</ihtml:element></div>\n" + "</ihtml:template>", "" + "<ihtml:template formals=\"\" name=\"t\">\n" + " <ihtml:element bogus=\"\">p</ihtml:element>\n" + " <div><ihtml:element>p</ihtml:element></div>\n" + "</ihtml:template>", new Message( IhtmlMessageType.BAD_ATTRIB, FilePosition.instance(is, 2, 55, 18, 8), elKey("ihtml:element"), attrKey("ihtml:element", "bogus"), MessagePart.Factory.valueOf(""))); }
public final void testContentType() throws Exception { // The callingContext attribute is set by a later pass, so make sure it // can't be passed in. runTest( "" + "<ihtml:template formals=\"\" name=\"main\">\n" + " \n" + "</ihtml:template>", "" + "<ihtml:template formals=\"\" name=\"main\">\n" + " <ihtml:template formals=\"\" name=\"sub\"\n" + " " + IHTML.CALLING_CONTEXT_ATTR + "=\"div\">\n" + " </ihtml:template>\n" + "</ihtml:template>", new Message( IhtmlMessageType.BAD_ATTRIB, FilePosition.instance(is, 3, 4, 4, 20), elKey("ihtml:template"), attrKey("ihtml:template", "callingContext"), MessagePart.Factory.valueOf("div"))); }
private void runTest(String goldenIhtml, String inputIhtml, Message... expectedMessages) throws Exception { Element ihtmlRoot = new DomParser( DomParser.makeTokenQueue( FilePosition.startOfFile(is), new StringReader(inputIhtml), true, false), true, mq) .parseDocument(); new IhtmlSanityChecker(mq).check(ihtmlRoot); for (Message msg : expectedMessages) { assertMessage( true, msg.getMessageType(), msg.getMessageLevel(), msg.getMessageParts().toArray(new MessagePart[0])); } assertMessagesLessSevereThan(MessageLevel.WARNING); String checkedIhtml = Nodes.render(ihtmlRoot, MarkupRenderMode.XML); assertEquals(goldenIhtml, checkedIhtml); }
void processToken(String text) { TokenClassification tClass = TokenClassification.classify(text); if (tClass == null) { return; } switch (tClass) { case LINEBREAK: // Allow external code to force line-breaks. // This allows us to create a composite-renderer that renders // original source code next to translated source code. emit("\n"); return; case SPACE: pendingSpace = true; return; case COMMENT: if (mark != null && lastLine != mark.startLineNo()) { newline(); lastLine = mark.startLineNo(); } else if ("/".equals(lastToken) || pendingSpace) { space(); } pendingSpace = false; emit(text); if (text.startsWith("//")) { newline(); pendingSpace = false; } else { pendingSpace = true; } return; default: break; } boolean spaceBefore = pendingSpace; pendingSpace = false; boolean spaceAfter = false; // Determine which pairs of tokens cannot be adjacent and put a space // between them. if (tClass == lastClass) { // Adjacent punctuation, strings, and words require space. // Numbers and words are both of type OTHER. // This decision may be revisited in the following to prevent // excessive space inside parentheses. spaceBefore = !"(".equals(lastToken); } else if (lastClass == TokenClassification.REGEX) { if (tClass == TokenClassification.OTHER || "/".equals(text)) { // Make sure words don't run into regex flags, and that / operator // does not combine with end of regex to make a line comment. spaceBefore = true; } } else if (tClass == TokenClassification.REGEX && "/".equals(lastToken)) { // Allowing these two tokens to run together could introduce a line // comment. spaceBefore = true; } else if (tClass == TokenClassification.OTHER && Character.isDigit(text.charAt(0)) && ".".equals(lastToken)) { // Following a dot operator with a number is illegal syntactically, but // this renderer should not allow any lexical confusion. spaceBefore = true; } if (tClass == TokenClassification.OTHER) { if ("}".equals(lastToken)) { spaceBefore = true; } if (isKeyword(text.toString())) { // Put a space between if and other keywords and the parenthesis. spaceAfter = true; } } // If this token is an open bracket, we want to indent, but not before // writing the token to avoid over-indenting the open bracket. if (text.length() == 1) { char ch0 = text.charAt(0); switch (ch0) { case '{': if (lastClass == TokenClassification.PUNCTUATION) { if (":".equals(lastToken)) { // See JSON test. spaceBefore = true; } else if (!(")".equals(lastToken) || "=".equals(lastToken))) { // If starting a block following a parenthesized condition, or // an object literal assigned. spaceBefore = !("(".equals(lastToken) || "[".equals(lastToken)); } } spaceAfter = true; break; case '[': if (")".equals(lastToken)) { spaceBefore = false; } spaceAfter = true; break; case '(': if (")".equals(lastToken)) { // Calling a parenthesized value. spaceBefore = false; } break; case '}': spaceBefore = !"{".equals(lastToken); spaceAfter = true; break; case ')': spaceBefore = false; spaceAfter = true; break; case ']': spaceBefore = !"}".equals(lastToken); spaceAfter = true; break; case ',': spaceBefore = false; spaceAfter = true; break; case ';': spaceBefore = false; spaceAfter = true; break; case ':': spaceBefore = ":".equals(lastToken); // Since :: is a token in ES4 spaceAfter = true; break; case '=': spaceBefore = true; spaceAfter = true; break; case '.': spaceBefore = lastToken != null && (TokenClassification.isNumber(lastToken) || ".".equals(lastToken)); spaceAfter = false; break; } } // Write any whitespace before the token. if (spaceBefore) { space(); } // Actually write the token. emit(text); pendingSpace = spaceAfter; lastClass = tClass; lastToken = text; if (mark != null) { lastLine = mark.startLineNo(); } }
public static FilePosition endOf(FilePosition fp) { return FilePosition.instance(fp.source, fp.endLineNo, fp.endCharInFile, fp.endCharInLine); }
public static FilePosition startOf(FilePosition fp) { return FilePosition.instance(fp.source, fp.startLineNo, fp.startCharInFile, fp.startCharInLine); }
public final void mark(@Nullable FilePosition mark) { if (mark != null && !InputSource.UNKNOWN.equals(mark.source())) { pending.add(mark); } }