/** * @license * Copyright (C) 2020 The Android Open Source Project * * Licensed 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. */ const fs = require('fs'); const path = require('path'); const jsExt = '.js'; class NonJsValidator { onProgramEnd(context, node) { } onGoogDeclareModuleId(context, node) { context.report({ message: 'goog.declareModuleId is allowed only in .js files', node: node, }); } } class JsOnlyValidator { onProgramEnd(context, node) { } onGoogDeclareModuleId(context, node) { context.report({ message: 'goog.declareModuleId present, but .d.ts file doesn\'t exist. ' + 'Either remove goog.declareModuleId or add the .d.ts file.', node: node, }); } } class JsWithDtsValidator { constructor() { this._googDeclareModuleIdExists = false; } onProgramEnd(context, node) { if(!this._googDeclareModuleIdExists) { context.report({ message: 'goog.declareModuleId(...) is missed. ' + 'Either add it or remove the associated .d.ts file.', node: node, }) } } onGoogDeclareModuleId(context, node) { if(this._googDeclareModuleIdExists) { context.report({ message: 'Duplicated goog.declareModuleId.', node: node, }); return; } const filename = context.getFilename(); this._googDeclareModuleIdExists = true; const scope = context.getScope(); if(scope.type !== 'global' && scope.type !== 'module') { context.report({ message: 'goog.declareModuleId is allowed only at the root level.', node: node, }); // no return - other problems are possible } if(node.arguments.length !== 1) { context.report({ message: 'goog.declareModuleId must have exactly one parameter.', node: node, }); if(node.arguments.length === 0) { return; } } const argument = node.arguments[0]; if(argument.type !== 'Literal') { context.report({ message: 'The argument for the declareModuleId method ' + 'must be a string literal.', node: argument, }); return; } const pathStart = '/polygerrit-ui/app/'; const index = filename.lastIndexOf(pathStart); if(index < 0) { context.report({ message: 'The file located outside of polygerrit-ui/app directory. ' + 'Please check eslint config.', node: argument, }); return; } const expectedName = 'polygerrit.' + filename.slice(index + pathStart.length, -jsExt.length) .replace(/\//g, '.') // Replace all occurrences of '/' with '.' .replace(/-/g, '$2d'); // Replace all occurrences of '-' with '$2d' if(argument.value !== expectedName) { context.report({ message: `Invalid module id. It must be '${expectedName}'.`, node: argument, fix: function(fixer) { return fixer.replaceText(argument, `'${expectedName}'`); }, }); } } } module.exports = { meta: { type: 'problem', docs: { description: 'Check that goog.declareModuleId is valid', category: 'TS imports JS errors', recommended: false, }, fixable: "code", schema: [], }, create: function (context) { let fileValidator; return { Program: function(node) { const filename = context.getFilename(); if(filename.endsWith(jsExt)) { const dtsFilename = filename.slice(0, -jsExt.length) + ".d.ts"; if(fs.existsSync(dtsFilename)) { fileValidator = new JsWithDtsValidator(); } else { fileValidator = new JsOnlyValidator(); } } else { fileValidator = new NonJsValidator(); } }, "Program:exit": function(node) { fileValidator.onProgramEnd(context, node); fileValidator = null; }, 'ExpressionStatement > CallExpression[callee.property.name="declareModuleId"][callee.object.name="goog"]': function(node) { fileValidator.onGoogDeclareModuleId(context, node); } }; }, };