| Index: lib/filterValidation.js | 
| =================================================================== | 
| --- a/lib/filterValidation.js | 
| +++ b/lib/filterValidation.js | 
| @@ -19,6 +19,63 @@ | 
|  | 
| let {Filter, InvalidFilter, ElemHideBase} = require("filterClasses"); | 
|  | 
| +/** | 
| + * An error returned by | 
| + * {@link module:filterValidation.parseFilter parseFilter()} or | 
| + * {@link module:filterValidation.parseFilters parseFilters()} | 
| + * indicating that a given filter cannot be parsed, | 
| + * contains an invalid CSS selector or is a filter list header. | 
| + * | 
| + * @constructor | 
| + */ | 
| +function FilterParsingError(type, details) | 
| +{ | 
| +  /** | 
| +   * Indicates why the filter is rejected. Possible choices: | 
| +   * "invalid-filter", "invalid-css-selector", "unexpected-filter-list-header" | 
| +   * | 
| +   * @type {string} | 
| +   */ | 
| +  this.type = type; | 
| + | 
| +  if (details) | 
| +  { | 
| +    if ("reason" in details) | 
| +      this.reason = details.reason; | 
| +    if ("selector" in details) | 
| +      this.selector = details.selector; | 
| +  } | 
| +} | 
| +FilterParsingError.prototype = { | 
| +  /** | 
| +   * The line number the error occurred on if | 
| +   * {@link module:filterValidation.parseFilters parseFilters()} | 
| +   * were used. Or null if the error was returned by | 
| +   * {@link module:filterValidation.parseFilter parseFilter()}. | 
| +   * | 
| +   * @type {?number} | 
| +   */ | 
| +  lineno: null, | 
| + | 
| +  /** | 
| +   * Returns a detailed translated error message. | 
| +   * | 
| +   * @return {string} | 
| +   */ | 
| +  toString: function() | 
| +  { | 
| +    let message = this.reason || ext.i18n.getMessage( | 
| +      this.type.replace(/-/g, "_"), | 
| +      "selector" in this ? "'" + this.selector + "'" : null | 
| +    ); | 
| + | 
| +    if (this.lineno) | 
| +      message = ext.i18n.getMessage("line", this.lineno.toLocaleString()) + ": " + message; | 
| + | 
| +    return message; | 
| +  } | 
| +}; | 
| + | 
| function isValidCSSSelector(selector) | 
| { | 
| let style = document.createElement("style"); | 
| @@ -40,10 +97,10 @@ | 
|  | 
| /** | 
| * @typedef ParsedFilter | 
| - * @property {?Filter} [filter]  The parsed filter if it is valid. Or null if | 
| - *                               the given string is empty or a filter list header. | 
| - * @property {string} [error]    An error message indicated that the filter cannot | 
| - *                               be parsed or contains an invalid CSS selector. | 
| + * @property {?Filter} [filter]  The parsed filter if it is valid. | 
| + *                               Or null if the given string is empty. | 
| + * @property {FilterParsingError} [error]  See {@link module:filterValidation~FilterParsingError FilterParsingError} | 
| + * | 
| */ | 
|  | 
| let parseFilter = | 
| @@ -51,32 +108,25 @@ | 
| * Parses and validates a filter given by the user. | 
| * | 
| * @param {string}  text | 
| - * @param {Boolean} [ignoreHeaders=false]  If true, {filter: null} is | 
| -                                           returned instead an error | 
| -                                           for filter list headers. | 
| * @return {ParsedFilter} | 
| */ | 
| -exports.parseFilter = function(text, ignoreHeaders) | 
| +exports.parseFilter = function(text) | 
| { | 
| let filter = null; | 
| text = Filter.normalize(text); | 
|  | 
| if (text) | 
| { | 
| -    if (text[0] != "[") | 
| -    { | 
| -      filter = Filter.fromText(text); | 
| +    if (text[0] == "[") | 
| +      return {error: new FilterParsingError("unexpected-filter-list-header")}; | 
|  | 
| -      if (filter instanceof InvalidFilter) | 
| -        return {error: filter.reason}; | 
| +    filter = Filter.fromText(text); | 
|  | 
| -      if (filter instanceof ElemHideBase && !isValidCSSSelector(filter.selector)) | 
| -        return {error: ext.i18n.getMessage("invalid_css_selector", "'" + filter.selector + "'")}; | 
| -    } | 
| -    else if (!ignoreHeaders) | 
| -    { | 
| -      return {error: ext.i18n.getMessage("unexpected_filter_list_header")}; | 
| -    } | 
| +    if (filter instanceof InvalidFilter) | 
| +      return {error: new FilterParsingError("invalid-filter", {reason: filter.reason})}; | 
| + | 
| +    if (filter instanceof ElemHideBase && !isValidCSSSelector(filter.selector)) | 
| +      return {error: new FilterParsingError("invalid-css-selector", {selector: filter.selector})}; | 
| } | 
|  | 
| return {filter: filter}; | 
| @@ -84,35 +134,35 @@ | 
|  | 
| /** | 
| * @typedef ParsedFilters | 
| - * @property {Filter[]} [filters]  The parsed filters if all of them are valid. | 
| - * @property {string} [error]      An error message indicated that any filter cannot | 
| - *                                 be parsed or contains an invalid CSS selector. | 
| + * @property {Filter[]} filters  The parsed result without invalid filters. | 
| + * @property {FilterParsingError[]} errors  See {@link module:filterValidation~FilterParsingError FilterParsingError} | 
| */ | 
|  | 
| /** | 
| * Parses and validates a newline-separated list of filters given by the user. | 
| * | 
| * @param {string}  text | 
| - * @param {Boolean} [ignoreHeaders=false]  If true, filter list headers | 
| - *                                         will be stripped instead of | 
| - *                                         returning an error. | 
| * @return {ParsedFilters} | 
| */ | 
| -exports.parseFilters = function(text, ignoreHeaders) | 
| +exports.parseFilters = function(text) | 
| { | 
| let lines = text.split("\n"); | 
| let filters = []; | 
| +  let errors = []; | 
|  | 
| for (let i = 0; i < lines.length; i++) | 
| { | 
| -    let {filter, error} = parseFilter(lines[i], ignoreHeaders); | 
| - | 
| -    if (error) | 
| -      return {error: ext.i18n.getMessage("line", (i + 1).toString()) + ": " + error}; | 
| +    let {filter, error} = parseFilter(lines[i]); | 
|  | 
| if (filter) | 
| filters.push(filter); | 
| + | 
| +    if (error) | 
| +    { | 
| +      error.lineno = i + 1; | 
| +      errors.push(error); | 
| +    } | 
| } | 
|  | 
| -  return {filters: filters}; | 
| +  return {filters: filters, errors: errors}; | 
| }; | 
|  |