/**
* @license
* Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at
* http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at
* http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at
* http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at
* http://polymer.github.io/PATENTS.txt
*/
import * as dom5 from 'dom5/lib/index-next';
import {Document, ParsedHtmlDocument, Replacement, Severity, Warning} from 'prax-analyzer';
import {HtmlRule} from '../../html/rule';
import {getIndentationInside, insertContentAfter, removeNode} from '../../html/util';
import {registry} from '../../registry';
import {stripIndentation} from '../../util';
// Capture any import file in the `classes` folder.
const deprecatedImports = /iron-flex-layout\/classes\/.*/;
const replacementImport = 'iron-flex-layout/iron-flex-layout-classes.html';
const polymerLikeImport = /(iron|paper|app)-.*\/(iron|paper|app)-.*\.html$/;
const p = dom5.predicates;
const isImport = p.AND(
p.hasTagName('link'), p.hasAttrValue('rel', 'import'), p.hasAttr('href'));
const styleModulesRegex = /iron-(flex|positioning)/;
const usesIronFlexStyleIncludes = p.AND(
p.hasTagName('style'),
p.hasAttr('include'),
(node: dom5.Node) =>
styleModulesRegex.test(dom5.getAttribute(node, 'include')!));
class IronFlexLayoutImport extends HtmlRule {
code = 'iron-flex-layout-import';
description = stripIndentation(`
Warns when the deprecated iron-flex-layout/classes/*.html files are imported.
This:
Should instead be written as:
`);
async checkDocument(parsedDocument: ParsedHtmlDocument, document: Document) {
const warnings: Warning[] = [];
this.convertDeclarations(parsedDocument, document, warnings);
return warnings;
}
convertDeclarations(
parsedDocument: ParsedHtmlDocument, _: Document, warnings: Warning[]) {
const imports = [...dom5.queryAll(parsedDocument.ast, isImport)];
// Assume base path to be current folder.
let polymerElementsBasePath: string = './';
let goodImport: dom5.Node|null = null;
const badImports: dom5.Node[] = [];
for (const imp of imports) {
const href = dom5.getAttribute(imp, 'href')!;
if (href.endsWith(replacementImport)) {
goodImport = imp;
} else if (deprecatedImports.test(href)) {
badImports.push(imp);
}
if (polymerLikeImport.test(href)) {
polymerElementsBasePath = href.replace(polymerLikeImport, '');
}
}
const styleNode = dom5.query(
parsedDocument.ast,
usesIronFlexStyleIncludes,
dom5.childNodesIncludeTemplate);
// Style modules are used, but not imported!
if (!badImports.length && !goodImport && styleNode) {
let fix = undefined;
const correctImport = polymerElementsBasePath + replacementImport;
if (imports.length) {
const lastImport = imports[imports.length - 1];
const indent = getIndentationInside(lastImport.parentNode!);
fix = [insertContentAfter(parsedDocument, lastImport, `
${indent}`)];
} else {
// If no imports present, assume we are in a file with only
// s as it doesn't make sense to be in a entry-point
// document w/o imports.
const range = {
file: parsedDocument.sourceRangeForStartTag(styleNode)!.file,
start: {line: 0, column: 0},
end: {line: 0, column: 0}
};
const replacementText = `\n`;
fix = [{replacementText, range}];
}
warnings.push(new Warning({
code: 'iron-flex-layout-import',
message: `iron-flex-layout style modules are used but not imported.
Import iron-flex-layout/iron-flex-layout-classes.html`,
parsedDocument,
severity: Severity.WARNING,
sourceRange:
parsedDocument.sourceRangeForAttributeValue(styleNode, 'include')!,
fix
}));
}
// If there is a good import that is not used, remove it.
if (!styleNode && goodImport) {
warnings.push(new Warning({
code: 'iron-flex-layout-import',
message:
`This import defines style modules that are not being used. It can be removed.`,
parsedDocument,
severity: Severity.WARNING,
sourceRange: parsedDocument.sourceRangeForStartTag(goodImport)!,
fix: removeNode(parsedDocument, goodImport)
}));
}
// If there are bad imports, ensure they're either fixed or removed.
badImports.forEach((imp, i) => {
const href = dom5.getAttribute(imp, 'href')!;
const correctImport = href.replace(deprecatedImports, replacementImport);
const suggestedFix = styleNode ?
`Replace it with ${correctImport} import.` :
'Remove it as it is not used.';
const fix: Replacement[] = [];
if (!styleNode || goodImport || i > 0) {
fix.push(...removeNode(parsedDocument, imp));
} else {
fix.push({
replacementText: `"${correctImport}"`,
range: parsedDocument.sourceRangeForAttributeValue(imp, 'href')!
});
}
warnings.push(new Warning({
code: 'iron-flex-layout-import',
message: `${href} import is deprecated in iron-flex-layout v1, ` +
`and not shipped in iron-flex-layout v2.
${suggestedFix}
Run the lint rule \`iron-flex-layout-classes\` with \`--fix\` to include the required style modules.`,
parsedDocument,
severity: Severity.WARNING,
sourceRange: parsedDocument.sourceRangeForAttributeValue(imp, 'href')!,
fix
}));
});
}
}
registry.register(new IronFlexLayoutImport());