-
Notifications
You must be signed in to change notification settings - Fork 914
Add HTML rule and hint+fix for missing alt attribute for tag img, applet and area #4485
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
a84cc67
c7fb071
07405c3
ff1fd4e
b2055c9
09d31d9
d54d461
f5ebc51
ee1a6d9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| /* | ||
| * Licensed to the Apache Software Foundation (ASF) under one | ||
| * or more contributor license agreements. See the NOTICE file | ||
| * distributed with this work for additional information | ||
| * regarding copyright ownership. The ASF licenses this file | ||
| * to you under the Apache License, Version 2.0 (the | ||
| * "License"); you may not use this file except in compliance | ||
| * with the License. You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, | ||
| * software distributed under the License is distributed on an | ||
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
| * KIND, either express or implied. See the License for the | ||
| * specific language governing permissions and limitations | ||
| * under the License. | ||
| */ | ||
| package org.netbeans.modules.html.editor.hints.other; | ||
|
|
||
| import java.awt.EventQueue; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unnecessary import |
||
| import java.util.Collections; | ||
| import java.util.logging.Level; | ||
| import java.util.logging.Logger; | ||
| import java.util.regex.Matcher; | ||
| import java.util.regex.Pattern; | ||
| import javax.swing.text.BadLocationException; | ||
| import javax.swing.text.Document; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unnecessary import
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You mean, they are unnecessary, when I change the logik, right? Because atm I need them.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, those three are correct marked as not used, but your both other comments have different lines. That's why I'm asking. It was about the matcher, pattern, the exception, etc. That's why I was confused about it. |
||
| import org.netbeans.editor.BaseDocument; | ||
| import org.netbeans.modules.csl.api.Hint; | ||
| import org.netbeans.modules.csl.api.HintFix; | ||
| import org.netbeans.modules.csl.api.OffsetRange; | ||
| import org.netbeans.modules.html.editor.hints.HtmlRuleContext; | ||
| import org.netbeans.modules.html.editor.utils.HtmlTagContextUtils; | ||
| import org.netbeans.modules.parsing.api.Source; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unnecessary import
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above |
||
|
|
||
| /** | ||
| * | ||
| * @author Christian Lenz | ||
| */ | ||
| public class AddMissingAltAttributeHint extends Hint { | ||
|
|
||
| public AddMissingAltAttributeHint(HtmlRuleContext context, OffsetRange range) { | ||
| super(AddMissingAltAttributeRule.getInstance(), | ||
| AddMissingAltAttributeRule.getInstance().getDescription(), | ||
| context.getFile(), | ||
| range, | ||
| Collections.<HintFix>singletonList(new AddMissingAltAttributeHintFix(context, range)), | ||
| 10); | ||
| } | ||
|
|
||
| private static class AddMissingAltAttributeHintFix implements HintFix { | ||
|
|
||
| private static final Logger LOGGER = Logger.getLogger(AddMissingAltAttributeHintFix.class.getSimpleName()); | ||
|
|
||
| HtmlRuleContext context; | ||
| OffsetRange range; | ||
|
Comment on lines
+56
to
+57
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These can be |
||
|
|
||
| public AddMissingAltAttributeHintFix(HtmlRuleContext context, OffsetRange range) { | ||
| this.context = context; | ||
| this.range = range; | ||
| } | ||
|
|
||
| @Override | ||
| public String getDescription() { | ||
| return AddMissingAltAttributeRule.getInstance().getDisplayName(); | ||
| } | ||
|
|
||
| @Override | ||
| public void implement() throws Exception { | ||
| BaseDocument document = (BaseDocument) context.getSnapshot().getSource().getDocument(true); | ||
| document.runAtomic(() -> { | ||
| try { | ||
| OffsetRange adjustedRange = HtmlTagContextUtils.adjustContextRange(document, range.getStart(), range.getEnd(), true); | ||
| String tagContent = document.getText(adjustedRange.getStart(), adjustedRange.getLength()); | ||
|
|
||
| // Find last self-closing or non self-closing tag | ||
| Pattern closingTagPattern = Pattern.compile("(/?>)"); | ||
| Matcher closingTagMatcher = closingTagPattern.matcher(tagContent); | ||
|
|
||
| if (closingTagMatcher.find()) { | ||
| int altInsertPosition = adjustedRange.getStart() + closingTagMatcher.start(1); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This might lead to wrong results. Consider PHP with embedded HTML. The document could look like this: <img src='demo.png' alt='<?php echo htmlspecialchars('x > y');?>' />I would use the HTML token stream and either use the start of the closing token or, if that does not exist, the start of the last HTML token + length.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TBH, I didn't try PHP yet, will do it too. Thx for the hint. If I guess it correct, your example will not trigger the hint, because you already have the alt attribute. But yes It would be better to take the HTML token instead of just looking for the closing tag symbol with an regex. |
||
|
|
||
| // Check whether a space before alt is needed or not | ||
| boolean needsSpaceBefore = altInsertPosition == 0 || tagContent.charAt(closingTagMatcher.start(1) - 1) != ' '; | ||
| boolean isSelfClosing = closingTagMatcher.group(0).endsWith("/>"); | ||
| String altAttribute = (needsSpaceBefore ? " " : "") + "alt=\"\"" + (isSelfClosing ? " " : ""); // NOI18N | ||
|
|
||
| document.insertString(altInsertPosition, altAttribute, null); | ||
| } | ||
| } catch (BadLocationException ex) { | ||
| LOGGER.log(Level.WARNING, "Invalid offset: {0}", ex.offsetRequested()); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean isSafe() { | ||
| return true; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean isInteractive() { | ||
| return false; | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| /* | ||
| * Licensed to the Apache Software Foundation (ASF) under one | ||
| * or more contributor license agreements. See the NOTICE file | ||
| * distributed with this work for additional information | ||
| * regarding copyright ownership. The ASF licenses this file | ||
| * to you under the Apache License, Version 2.0 (the | ||
| * "License"); you may not use this file except in compliance | ||
| * with the License. You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, | ||
| * software distributed under the License is distributed on an | ||
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
| * KIND, either express or implied. See the License for the | ||
| * specific language governing permissions and limitations | ||
| * under the License. | ||
| */ | ||
| package org.netbeans.modules.html.editor.hints.other; | ||
|
|
||
| import java.io.IOException; | ||
| import java.util.List; | ||
| import org.netbeans.modules.csl.api.Hint; | ||
| import org.netbeans.modules.csl.api.HintSeverity; | ||
| import org.netbeans.modules.csl.api.RuleContext; | ||
| import org.netbeans.modules.html.editor.api.gsf.HtmlParserResult; | ||
| import org.netbeans.modules.html.editor.hints.HtmlRule; | ||
| import org.netbeans.modules.html.editor.hints.HtmlRuleContext; | ||
| import org.netbeans.modules.html.editor.lib.api.elements.ElementType; | ||
| import org.netbeans.modules.html.editor.lib.api.elements.ElementUtils; | ||
| import org.openide.util.Exceptions; | ||
|
|
||
| /** | ||
| * | ||
| * @author Christian Lenz | ||
| */ | ||
| public class AddMissingAltAttributeRule extends HtmlRule { | ||
|
|
||
| private final static AddMissingAltAttributeRule INSTANCE = new AddMissingAltAttributeRule(); | ||
|
|
||
| private AddMissingAltAttributeRule() { | ||
| } | ||
|
|
||
| public static AddMissingAltAttributeRule getInstance() { | ||
| return INSTANCE; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean appliesTo(RuleContext context) { | ||
| return true; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean showInTasklist() { | ||
| return true; | ||
| } | ||
|
|
||
| @Override | ||
| public HintSeverity getDefaultSeverity() { | ||
| return HintSeverity.CURRENT_LINE_WARNING; | ||
| } | ||
|
|
||
| @Override | ||
| protected void run(HtmlRuleContext context, List<Hint> result) { | ||
| try { | ||
| HtmlParserResult parserResult = context.getHtmlParserResult(); | ||
| AltAttributeVisitor visitor = new AltAttributeVisitor(this, context, result); | ||
|
|
||
| ElementUtils.visitChildren(parserResult.root(), visitor, ElementType.OPEN_TAG); | ||
| } catch (IOException ioe) { | ||
| Exceptions.printStackTrace(ioe); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| /* | ||
| * Licensed to the Apache Software Foundation (ASF) under one | ||
| * or more contributor license agreements. See the NOTICE file | ||
| * distributed with this work for additional information | ||
| * regarding copyright ownership. The ASF licenses this file | ||
| * to you under the Apache License, Version 2.0 (the | ||
| * "License"); you may not use this file except in compliance | ||
| * with the License. You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, | ||
| * software distributed under the License is distributed on an | ||
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
| * KIND, either express or implied. See the License for the | ||
| * specific language governing permissions and limitations | ||
| * under the License. | ||
| */ | ||
| package org.netbeans.modules.html.editor.hints.other; | ||
|
|
||
| import java.io.IOException; | ||
| import java.util.*; | ||
| import org.netbeans.modules.csl.api.Hint; | ||
| import org.netbeans.modules.csl.api.OffsetRange; | ||
| import org.netbeans.modules.csl.api.Rule; | ||
| import org.netbeans.modules.html.editor.hints.HtmlRuleContext; | ||
| import org.netbeans.modules.html.editor.lib.api.elements.*; | ||
|
|
||
| /** | ||
| * | ||
| * @author Christian Lenz | ||
| */ | ||
| public class AltAttributeVisitor implements ElementVisitor { | ||
|
|
||
| private static final String ALT_ATTR = "alt"; // NOI18N | ||
|
|
||
| private final HtmlRuleContext context; | ||
| private final List<Hint> hints; | ||
|
|
||
| public AltAttributeVisitor(Rule rule, HtmlRuleContext context, List<Hint> hints) throws IOException { | ||
| this.context = context; | ||
| this.hints = hints; | ||
| } | ||
|
|
||
| @Override | ||
| public void visit(Element node) { | ||
| // We should only be invoked for opening tags | ||
| if (!(node instanceof OpenTag)) { | ||
| return; | ||
| } | ||
|
|
||
| // We are only interested in img, area, applet elements | ||
| String lowerCaseTag = node.id().toString().toLowerCase(); | ||
| if (!(lowerCaseTag.equals("img") || lowerCaseTag.equals("area") || lowerCaseTag.equals("applet"))) { // NOI18N | ||
| return; | ||
| } | ||
|
|
||
| OpenTag ot = (OpenTag) node; | ||
| if (ot.getAttribute(ALT_ATTR) == null) { | ||
| hints.add(new AddMissingAltAttributeHint(context, new OffsetRange(node.from(), node.to()))); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| /* | ||
| * Licensed to the Apache Software Foundation (ASF) under one | ||
| * or more contributor license agreements. See the NOTICE file | ||
| * distributed with this work for additional information | ||
| * regarding copyright ownership. The ASF licenses this file | ||
| * to you under the Apache License, Version 2.0 (the | ||
| * "License"); you may not use this file except in compliance | ||
| * with the License. You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, | ||
| * software distributed under the License is distributed on an | ||
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
| * KIND, either express or implied. See the License for the | ||
| * specific language governing permissions and limitations | ||
| * under the License. | ||
| */ | ||
| package org.netbeans.modules.html.editor.utils; | ||
|
|
||
| import java.util.concurrent.atomic.AtomicReference; | ||
| import javax.swing.text.Document; | ||
| import org.netbeans.api.html.lexer.HTMLTokenId; | ||
| import org.netbeans.api.lexer.Token; | ||
| import org.netbeans.api.lexer.TokenSequence; | ||
| import org.netbeans.modules.csl.api.OffsetRange; | ||
| import org.netbeans.modules.html.editor.api.Utils; | ||
|
|
||
| /** | ||
| * | ||
| * @author Christian Lenz | ||
| */ | ||
| public final class HtmlTagContextUtils { | ||
|
|
||
| private HtmlTagContextUtils() { | ||
| } | ||
|
|
||
| public static OffsetRange adjustContextRange(final Document doc, final int from, final int to, final boolean beforeClosingToken) { | ||
| final AtomicReference<OffsetRange> ret = new AtomicReference<>(); | ||
| ret.set(new OffsetRange(from, to)); //return the same pair by default | ||
| doc.render(() -> { | ||
| TokenSequence<HTMLTokenId> ts = Utils.getJoinedHtmlSequence(doc, from); | ||
|
|
||
| if (ts == null) { | ||
| //no html token sequence at the offset, try to | ||
| //TODO possibly try to travese the top level sequence backward | ||
| //and try to find an html embedding. | ||
| return; | ||
| } | ||
|
|
||
| Token<HTMLTokenId> openTag = Utils.findTagOpenToken(ts); | ||
|
|
||
| if (openTag == null) { | ||
| return; | ||
| } | ||
|
|
||
| int adjustedFrom = ts.offset(); | ||
|
|
||
| //now try to find the tag's end | ||
| ts.move(to); | ||
| int adjustedTo = -1; | ||
| while (ts.moveNext()) { | ||
| Token<HTMLTokenId> t = ts.token(); | ||
Chris2011 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| if (t == null) { | ||
| return; | ||
| } | ||
|
|
||
| if (t.id() == HTMLTokenId.TAG_CLOSE_SYMBOL) { | ||
| adjustedTo = beforeClosingToken ? ts.offset() : ts.offset() + t.length(); | ||
|
|
||
| break; | ||
| } else if (t.id() == HTMLTokenId.TEXT) { | ||
| //do not go too far - out of the tag | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| if (adjustedTo == -1) { | ||
| return; | ||
| } | ||
|
|
||
| //we found the adjusted range | ||
| ret.set(new OffsetRange(adjustedFrom, adjustedTo)); | ||
| }); | ||
| return ret.get(); | ||
| } | ||
| } | ||

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you test, that all current tests invoking
computeHints, potentially transitively, still work?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, not yet. I just thought that the pipes will show me the result and I just saw some failing tests of Latte. PHP where not triggert here at github inside my branch. I will just revert this change as you said and will try to implement a better logic into the HtmlHintsProvider as similiar as for PHP and JS.