Skip to content

Commit

Permalink
New: Add API to get the location of content in an element
Browse files Browse the repository at this point in the history
Also update docs to explain how `report` can be used with both
`element` and `location` to specify an offset into an element's
content.
  • Loading branch information
antross committed Mar 15, 2019
1 parent 62e6fa9 commit b9708ae
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 13 deletions.
3 changes: 2 additions & 1 deletion packages/hint/docs/contributor-guide/how-to/hint.md
Expand Up @@ -108,7 +108,8 @@ context.report(resource, message, { element: element });
* `content` is a string of text within `element` where the issue was found
(used to refine a `ProblemLocation`).;
* `location` is an explicit `ProblemLocation` (`{col: number, line: number}`)
where the issue was found.
where the issue was found. If used with `element`, it represents an offset
from the start of that element's content (e.g. for inline CSS in HTML).
* `severity` overrides the default `Severity` for the hint to determine how
the issue will be reported (e.g. `Severity.error`).

Expand Down
16 changes: 12 additions & 4 deletions packages/hint/src/lib/hint-context.ts
Expand Up @@ -25,7 +25,10 @@ export type ReportOptions = {
content?: string;
/** The `HTMLElement` where the issue was found (used to get a `ProblemLocation`). */
element?: HTMLElement | null;
/** The `ProblemLocation` where the issue was found. */
/**
* The `ProblemLocation` where the issue was found.
* If specified with `element`, represents an offset in the element's content (e.g. for inline CSS in HTML).
*/
location?: ProblemLocation | null;
/** The `Severity` to report the issue as (overrides default settings for a hint). */
severity?: Severity;
Expand Down Expand Up @@ -102,18 +105,23 @@ export class HintContext<E extends Events = Events> {
}

/** Finds the approximative location in the page's HTML for a match in an element. */
public findProblemLocation(element: HTMLElement): ProblemLocation | null {
public findProblemLocation(element: HTMLElement, offset: ProblemLocation | null): ProblemLocation | null {
if (offset) {
return element.getContentLocation(offset);
}

return element.getLocation();
}

/** Reports a problem with the resource. */
public report(resource: string, message: string, options: ReportOptions = {}) {
const { codeSnippet, element, severity } = options;
let position: ProblemLocation | null = options.location || null;
let position = options.location || null;
let sourceCode: string | null = null;

if (element) {
position = this.findProblemLocation(element);
// When element is provided, position is an offset in the content.
position = this.findProblemLocation(element, position);
sourceCode = element.outerHTML().replace(/[\t]/g, ' ');
}

Expand Down
66 changes: 58 additions & 8 deletions packages/hint/src/lib/types/html.ts
Expand Up @@ -79,30 +79,80 @@ export class HTMLElement {
}

/**
* Zero-based location of the element.
* Helper to find the original location in source of an element.
* Used when this element is part of a DOM snapshot to search the
* original fetched document a similar element and use the location
* of that element instead.
*/
public getLocation(): ProblemLocation | null {
private _getOriginalLocation(): parse5.ElementLocation | null {
const location = this._element.sourceCodeLocation;

// Use direct location information when available.
if (location) {
return {
// Column is zero-based, but pointing to the tag name, not the character <
column: location.startCol,
line: location.startLine - 1
};
return location;
}

// If not, try to match an element in the original document to use it's location.
if (this.ownerDocument && this.ownerDocument.originalDocument) {
const match = findOriginalElement(this.ownerDocument.originalDocument, this);

return match ? match.getLocation() : null;
if (match) {
return match._element.sourceCodeLocation;
}
}

// Otherwise we don't have a location (element may have been dynamically generated).
return null;
}

/**
* Zero-based location of the element.
*/
public getLocation(): ProblemLocation | null {
const location = this._getOriginalLocation();

if (!location) {
return null;
}

// Column is zero-based, but pointing to the tag name, not the character <
return {
column: location.startCol,
line: location.startLine - 1
};
}

/**
* Calculate the document location of content within this element.
* Used to determine offsets for CSS-in-HTML and JS-in-HTML reports.
*/
public getContentLocation(offset: ProblemLocation): ProblemLocation | null {
const location = this._getOriginalLocation();

if (!location) {
return null;
}

// Get the end of the start tag from `parse5`, converting to be zero-based.
const startTag = location.startTag;
const column = startTag.endCol - 1;
const line = startTag.endLine - 1;

// Adjust resulting column when content is on the same line as the tag.
if (offset.line === 0) {
return {
column: column + offset.column,
line
};
}

// Otherwise adjust just the resulting line.
return {
column: offset.column,
line: line + offset.line
};
}

public isSame(element: HTMLElement): boolean {
return this._element === element._element;
}
Expand Down

0 comments on commit b9708ae

Please sign in to comment.