"use strict";
var __extends = (this && this.__extends) || (function () {
    var extendStatics = Object.setPrototypeOf ||
        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
var compiler_1 = require("@angular/compiler");
var Lint = require("tslint");
var ngWalker_1 = require("./angular/ngWalker");
var basicCssAstVisitor_1 = require("./angular/styles/basicCssAstVisitor");
var basicTemplateAstVisitor_1 = require("./angular/templates/basicTemplateAstVisitor");
var templateParser_1 = require("./angular/templates/templateParser");
var utils_1 = require("./util/utils");
var logger_1 = require("./util/logger");
var ngVersion_1 = require("./util/ngVersion");
var CssSelectorTokenizer = require('css-selector-tokenizer');
var isEncapsulationEnabled = function (encapsulation) {
    if (!encapsulation) {
        return true;
    }
    if (utils_1.getSymbolName(encapsulation) !== 'ViewEncapsulation') {
        return false;
    }
    var encapsulationType = encapsulation.name.text;
    return /^(Emulated|Native)$/.test(encapsulationType);
};
var lang = require('cssauron')({
    tag: function (node) {
        return (node.name || '').toLowerCase();
    },
    contents: function (node) {
        return '';
    },
    id: function (node) {
        return this.attr(node, 'id');
    },
    class: function (node) {
        var classBindings = (node.inputs || [])
            .filter(function (b) { return b.type === 2; })
            .map(function (b) { return b.name; })
            .join(' ');
        var classAttr = node.attrs.find(function (a) { return a.name.toLowerCase() === 'class'; });
        return classAttr ? classAttr.value + " " + classBindings : classBindings;
    },
    parent: function (node) {
        return node.parentNode;
    },
    children: function (node) {
        return node.children;
    },
    attr: function (node, attr) {
        var targetAttr = node.attrs.find(function (a) { return a.name === attr; });
        return targetAttr ? targetAttr.value : undefined;
    }
});
var ElementVisitor = (function (_super) {
    __extends(ElementVisitor, _super);
    function ElementVisitor() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    ElementVisitor.prototype.visitElement = function (ast, fn) {
        var _this = this;
        fn(ast);
        ast.children.forEach(function (c) {
            if (c instanceof compiler_1.ElementAst) {
                c.parentNode = ast;
            }
            _this.visit(c, fn);
        });
    };
    return ElementVisitor;
}(basicTemplateAstVisitor_1.BasicTemplateAstVisitor));
var hasSelector = function (s, type) {
    if (!s) {
        return false;
    }
    return s.type === 'selector' || s.type === 'selectors' ? (s.nodes || []).some(function (n) { return hasSelector(n, type); }) : s.type === type;
};
var dynamicFilters = {
    id: function (ast) {
        return (ast.inputs || []).some(function (i) { return i.name === 'id'; });
    },
    attribute: function (ast) {
        return (ast.inputs || []).some(function (i) { return i.type === 1; });
    },
    class: function (ast) {
        return (ast.inputs || []).some(function (i) { return i.name === 'className' || i.name === 'ngClass'; });
    }
};
var ElementFilterVisitor = (function (_super) {
    __extends(ElementFilterVisitor, _super);
    function ElementFilterVisitor() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    ElementFilterVisitor.prototype.shouldVisit = function (ast, strategies, selectorTypes) {
        var _this = this;
        return (Object.keys(strategies).every(function (s) {
            var strategy = strategies[s];
            return !selectorTypes[s] || !strategy(ast);
        }) &&
            (ast.children || []).every(function (c) {
                return (ast instanceof compiler_1.ElementAst && _this.shouldVisit(c, strategies, selectorTypes)) ||
                    (ast instanceof compiler_1.EmbeddedTemplateAst &&
                        (ast.children || []).every(function (c) { return _this.shouldVisit(c, strategies, selectorTypes); }));
            }));
    };
    return ElementFilterVisitor;
}(basicTemplateAstVisitor_1.BasicTemplateAstVisitor));
var Rule = (function (_super) {
    __extends(Rule, _super);
    function Rule() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    Rule.prototype.apply = function (sourceFile) {
        return this.applyWithWalker(new UnusedCssNgVisitor(sourceFile, this.getOptions(), {
            cssVisitorCtrl: UnusedCssVisitor
        }));
    };
    Rule.metadata = {
        ruleName: 'no-unused-css',
        type: 'maintainability',
        description: "Disallows having an unused CSS rule in the component's stylesheet.",
        options: null,
        optionsDescription: 'Not configurable.',
        typescriptOnly: true,
        hasFix: true
    };
    return Rule;
}(Lint.Rules.AbstractRule));
exports.Rule = Rule;
var UnusedCssVisitor = (function (_super) {
    __extends(UnusedCssVisitor, _super);
    function UnusedCssVisitor(sourceFile, originalOptions, context, style, templateStart) {
        var _this = _super.call(this, sourceFile, originalOptions, context, style, templateStart) || this;
        _this.style = style;
        return _this;
    }
    UnusedCssVisitor.prototype.visitCssSelectorRule = function (ast) {
        var _this = this;
        try {
            var match = ast.selectors.some(function (s) { return _this.visitCssSelector(s); });
            if (!match) {
                var endOffset = ast.end.offset, startOffset = ast.start.offset;
                var length_1 = endOffset - startOffset + 1;
                this.addFailureAt(startOffset, length_1, 'Unused styles', Lint.Replacement.deleteText(startOffset - 1, length_1 + 1));
            }
        }
        catch (e) {
            logger_1.logger.error(e);
        }
        return true;
    };
    UnusedCssVisitor.prototype.visitCssSelector = function (ast) {
        var parts = [];
        for (var i = 0; i < ast.selectorParts.length; i += 1) {
            var c = ast.selectorParts[i];
            c.strValue = c.strValue.split('::').shift();
            if (c.strValue.endsWith('/') || c.strValue.endsWith('>')) {
                parts.push(c.strValue);
                break;
            }
            else if (!c.strValue.startsWith(':')) {
                parts.push(c.strValue);
            }
        }
        if (!parts.length || !this.templateAst) {
            return true;
        }
        var strippedSelector = parts.map(function (s) { return s.replace(/\/|>$/, '').trim(); }).join(' ');
        var elementFilterVisitor = new ElementFilterVisitor(this.getSourceFile(), this._originalOptions, this.context, 0);
        var tokenized = CssSelectorTokenizer.parse(strippedSelector);
        var selectorTypesCache = Object.keys(dynamicFilters).reduce(function (a, key) {
            a[key] = hasSelector(tokenized, key);
            return a;
        }, {});
        if (!elementFilterVisitor.shouldVisit(this.templateAst, dynamicFilters, selectorTypesCache)) {
            return true;
        }
        var matchFound = false;
        var selector = function (element) {
            if (lang(strippedSelector)(element)) {
                matchFound = true;
                return true;
            }
            return false;
        };
        var visitor = new ElementVisitor(this.getSourceFile(), this._originalOptions, this.context, 0);
        visitor.visit(this.templateAst, selector);
        return matchFound;
    };
    return UnusedCssVisitor;
}(basicCssAstVisitor_1.BasicCssAstVisitor));
var UnusedCssNgVisitor = (function (_super) {
    __extends(UnusedCssNgVisitor, _super);
    function UnusedCssNgVisitor() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    UnusedCssNgVisitor.prototype.visitClassDeclaration = function (declaration) {
        var _this = this;
        var d = utils_1.getComponentDecorator(declaration);
        if (d) {
            var meta_1 = this._metadataReader.read(declaration);
            this.visitNgComponent(meta_1);
            if (meta_1.template && meta_1.template.template) {
                try {
                    var ElementAstCtr_1 = compiler_1.ElementAst;
                    ngVersion_1.SemVerDSL.gte('4.0.0-beta.8', function () {
                        _this.templateAst = new ElementAstCtr_1('*', [], [], [], [], [], [], false, [], templateParser_1.parseTemplate(meta_1.template.template.code), 0, null, null);
                    }).else(function () {
                        _this.templateAst = new ElementAstCtr_1('*', [], [], [], [], [], [], false, templateParser_1.parseTemplate(meta_1.template.template.code), 0, null, null);
                    });
                }
                catch (e) {
                    logger_1.logger.error('Cannot parse the template', e);
                }
            }
        }
        _super.prototype.visitClassDeclaration.call(this, declaration);
    };
    UnusedCssNgVisitor.prototype.visitNgStyleHelper = function (style, context, styleMetadata, baseStart) {
        this.validateStyles(style, context, styleMetadata, baseStart);
        _super.prototype.visitNgStyleHelper.call(this, style, context, styleMetadata, baseStart);
    };
    UnusedCssNgVisitor.prototype.validateStyles = function (style, context, styleMetadata, baseStart) {
        var _this = this;
        if (!style) {
            return;
        }
        var file = this.getContextSourceFile(styleMetadata.url, styleMetadata.style.source);
        var visitor = new UnusedCssVisitor(file, this._originalOptions, context, styleMetadata, baseStart);
        visitor.templateAst = this.templateAst;
        var d = utils_1.getComponentDecorator(context.controller);
        var encapsulation = utils_1.getDecoratorPropertyInitializer(d, 'encapsulation');
        if (isEncapsulationEnabled(encapsulation)) {
            style.visit(visitor);
            visitor.getFailures().forEach(function (f) { return _this.addFailure(f); });
        }
    };
    return UnusedCssNgVisitor;
}(ngWalker_1.NgWalker));
exports.UnusedCssNgVisitor = UnusedCssNgVisitor;
