Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4813,4 +4813,4 @@ public CaretLineOffset(int offset, String caretLine) {
}

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,7 @@ MSG_SET_DEFAULT_HTML_VERSION=Change project default to {0}
MSG_FatalHtmlRuleName=Fatal Errors
MSG_FatalHtmlErrorAddendum=Further processing of the file may be significantly affected by this fatal error. Please fix the problem before continuing editing of this file!

AddMissingAltAttributeRule=Provide Text Alternatives
AddMissingAltAttributeRule_Desc=Find img/applet/area elements where no alt attribute is provided.

#MSG_HINT_GENERATE_REQUIRED_ATTRIBUTES=Generate required attributes

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
Expand All @@ -29,6 +30,7 @@
import org.netbeans.api.project.Project;
import org.netbeans.modules.csl.api.Error;
import org.netbeans.modules.csl.api.HintFix;
import org.netbeans.modules.csl.api.RuleContext;
import org.netbeans.modules.csl.api.Severity;
import org.netbeans.modules.css.indexing.api.CssIndex;
import org.netbeans.modules.html.editor.api.gsf.HtmlParserResult;
Expand All @@ -43,44 +45,56 @@
*
* @author marekfukala
*/
public class HtmlRuleContext {
public class HtmlRuleContext extends RuleContext {

private final SyntaxAnalyzerResult syntaxAnalyzerResult;
private final HtmlParserResult parserResult;
private final List<HintFix> defaultFixes;
private final List<? extends Error> leftDiagnostics;
private SyntaxAnalyzerResult syntaxAnalyzerResult;
private List<HintFix> defaultFixes;
private List<? extends Error> leftDiagnostics;
private CssIndex cssIndex;
private DependenciesGraph cssDependencies;
private final Lines lines;
private final Collection<Integer> linesWithHints;
private Lines lines;
private Collection<Integer> linesWithHints;

public HtmlRuleContext(HtmlParserResult parserResult, SyntaxAnalyzerResult syntaxAnalyzerResult, List<HintFix> defaultFixes) {
this.parserResult = parserResult;
public void initialize(SyntaxAnalyzerResult syntaxAnalyzerResult, List<HintFix> defaultFixes) {
this.syntaxAnalyzerResult = syntaxAnalyzerResult;
this.defaultFixes = defaultFixes;
this.leftDiagnostics = new ArrayList<>(parserResult.getDiagnostics(EnumSet.allOf(Severity.class)));
this.lines = new Lines(parserResult.getSnapshot().getText());

HtmlParserResult htmlResult = getHtmlParserResult();
if (htmlResult != null) {
this.leftDiagnostics = new ArrayList<>(htmlResult.getDiagnostics(EnumSet.allOf(Severity.class)));
this.lines = new Lines(htmlResult.getSnapshot().getText());
} else {
this.leftDiagnostics = Collections.emptyList();
this.lines = null;
}

this.linesWithHints = new HashSet<>();
}

public boolean isFirstHintForPosition(int offset) {
if (lines == null) {
return true;
}

try {
int lineIndex = lines.getLineIndex(offset);
if(linesWithHints.contains(lineIndex)) {
if (linesWithHints.contains(lineIndex)) {
return false;
} else {
linesWithHints.add(lineIndex);
return true;
}

} catch (BadLocationException ex) {
Exceptions.printStackTrace(ex);
}
return true;
}

public HtmlParserResult getHtmlParserResult() {
return parserResult;
if (parserResult instanceof HtmlParserResult) {
return (HtmlParserResult) parserResult;
}
return null;
}

public SyntaxAnalyzerResult getSyntaxAnalyzerResult() {
Expand Down Expand Up @@ -112,15 +126,15 @@ public synchronized CssIndex getCssIndex() throws IOException {
}
return cssIndex;
}

public synchronized DependenciesGraph getCssDependenciesGraph() throws IOException {
if(cssDependencies == null) {
if (cssDependencies == null) {
CssIndex index = getCssIndex();
if(index != null) {
if (index != null) {
cssDependencies = index.getDependencies(getFile());
}
}
return cssDependencies;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* 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.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 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;

/**
*
* @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());

private final HtmlRuleContext context;
private final OffsetRange range;

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);
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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
Expand Up @@ -534,6 +534,9 @@
<file name="org-netbeans-modules-html-editor-hints-NotValidatedContent.instance">
<attr name="position" intvalue="190"/>
</file>
<file name="org-netbeans-modules-html-editor-hints-other-AddMissingAltAttributeRule.instance">
<attr name="position" intvalue="210"/>
</file>
<!-- the following hint 'Other' must be last in the hints list!!! -->
<file name="org-netbeans-modules-html-editor-hints-Other.instance">
<attr name="position" intvalue="99999"/>
Expand Down
Loading
Loading