/* * Copyright 2015 The Closure Compiler Authors. * * 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. */ package com.google.javascript.jscomp; /** Unit tests for {@link ConvertToTypedInterface}. */ public final class ConvertToTypedInterfaceTest extends Es6CompilerTestCase { @Override protected CompilerPass getProcessor(final Compiler compiler) { return new ConvertToTypedInterface(compiler); } @Override protected int getNumRepetitions() { return 1; } public void testInferAnnotatedTypeFromTypeInference() { enableTypeCheck(); test("/** @const */ var x = 5;", "/** @const {number} */ var x;"); test( "/** @constructor */ function Foo() { /** @const */ this.x = 5; }", "/** @constructor */ function Foo() {} \n /** @const {number} */ Foo.prototype.x;"); } public void testExternsDefinitionsRespected() { test("/** @type {number} */ var x;", "x = 7;", "", null, null); } public void testSimpleConstJsdocPropagation() { test("/** @const */ var x = 5;", "/** @const {number} */ var x;"); test("/** @const */ var x = true;", "/** @const {boolean} */ var x;"); test("/** @const */ var x = 'str';", "/** @const {string} */ var x;"); test("/** @const */ var x = null;", "/** @const {null} */ var x;"); test("/** @const */ var x = void 0;", "/** @const {void} */ var x;"); test("/** @const */ var x = /a/;", "/** @const {!RegExp} */ var x;"); test( "/** @constructor */ function Foo() { /** @const */ this.x = 5; }", "/** @constructor */ function Foo() {} \n /** @const {number} */ Foo.prototype.x;"); testWarning( "/** @const */ var x = cond ? true : 5;", "/** @const {*} */ var x;", ConvertToTypedInterface.CONSTANT_WITHOUT_EXPLICIT_TYPE); } public void testConstKeywordJsdocPropagation() { testEs6("const x = 5;", "/** @const {number} */ var x;"); } public void testThisPropertiesInConstructors() { test( "/** @constructor */ function Foo() { /** @const {number} */ this.x; }", "/** @constructor */ function Foo() {} \n /** @const {number} */ Foo.prototype.x;"); test( "/** @constructor */ function Foo() { this.x; }", "/** @constructor */ function Foo() {} \n /** @const {*} */ Foo.prototype.x;"); test( "/** @constructor */ function Foo() { /** @type {?number} */ this.x = null; this.x = 5; }", "/** @constructor */ function Foo() {} \n /** @type {?number} */ Foo.prototype.x;"); testWarning( "/** @constructor */ function Foo() { /** @const */ this.x = cond ? true : 5; }", "/** @constructor */ function Foo() {} /** @const {*} */ Foo.prototype.x; ", ConvertToTypedInterface.CONSTANT_WITHOUT_EXPLICIT_TYPE); } public void testThisPropertiesInConstructorsAndPrototype() { test( LINE_JOINER.join( "/** @constructor */ function Foo() { this.x = null; }", "/** @type {?number} */ Foo.prototype.x = 5;"), "/** @constructor */ function Foo() {} \n /** @type {?number} */ Foo.prototype.x;"); test( LINE_JOINER.join( "/** @constructor */ function Foo() { this.x = null; }", "/** @type {?number} */ Foo.prototype.x;"), "/** @constructor */ function Foo() {} \n /** @type {?number} */ Foo.prototype.x;"); test( LINE_JOINER.join( "/** @constructor */ function Foo() { this.x = null; }", "Foo.prototype.x = 5;"), "/** @constructor */ function Foo() {} \n /** @const {*} */ Foo.prototype.x;"); } public void testConstJsdocPropagationForNames() { test( "/** @type {!Array<string>} */ var x = []; /** @const */ var y = x;", "/** @type {!Array<string>} */ var x; /** @const {!Array<string>} */ var y;"); test( LINE_JOINER.join( "/** @constructor */", "function Foo(/** number */ x) {", " /** @const */ this.x = x;", "}"), LINE_JOINER.join( "/** @constructor */ function Foo(/** number */ x) {}", "/** @const {number} */ Foo.prototype.x;")); test( LINE_JOINER.join( "/** @constructor @param {!Array<string>} arr */", "function Foo(arr) {", " /** @const */ this.arr = arr;", "}"), LINE_JOINER.join( "/** @constructor @param {!Array<string>} arr */ function Foo(arr) {}", "/** @const {!Array<string>} */ Foo.prototype.arr;")); testEs6( LINE_JOINER.join( "class Foo {", " constructor(/** number */ x) {", " /** @const */ this.x = x;", " }", "}"), LINE_JOINER.join( "class Foo {", " constructor(/** number */ x) {}", "}", "/** @const {number} */ Foo.prototype.x;")); testEs6( LINE_JOINER.join( "class Foo {", " /** @param {number} x */", " constructor(x) {", " /** @const */ this.x = x;", " }", "}"), LINE_JOINER.join( "class Foo {", " /** @param {number} x */", " constructor(x) {}", "}", "/** @const {number} */ Foo.prototype.x;")); } public void testConstPropagationPrivateProperties1() { test( LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @const @private */ this.x = someComplicatedExpression();", "}"), LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @const @private {*} */ Foo.prototype.x;")); } public void testConstPropagationPrivateProperties2() { testEs6( LINE_JOINER.join( "goog.provide('a.b.c');", "", "/** @private @const */", "a.b.c.helper_ = someComplicatedExpression();"), "goog.provide('a.b.c'); /** @private @const {*} */ a.b.c.helper_;"); } public void testOverrideAnnotationCountsAsDeclaration() { testSame( LINE_JOINER.join( "goog.provide('x.y.z.Bar');", "", "goog.require('a.b.c.Foo');", "", "/** @constructor @extends {a.b.c.Foo} */", "x.y.z.Bar = function() {};", "", "/** @override */", "x.y.z.Bar.prototype.method = function(a, b, c) {};")); testSameEs6( LINE_JOINER.join( "goog.module('x.y.z');", "", "const {Foo} = goog.require('a.b.c');", "", "/** @constructor @extends {Foo} */", "const Bar = class {", " /** @override */", " method(a, b, c) {}", "}", "exports.Bar = Bar;")); } public void testConstJsdocPropagationForNames_optional() { test( LINE_JOINER.join( "/** @constructor */", "function Foo(/** number= */ opt_x) {", " /** @const */ this.x = opt_x;", "}"), LINE_JOINER.join( "/** @constructor */ function Foo(/** number= */ opt_x) {}", "/** @const {number|undefined} */ Foo.prototype.x;")); } public void testConstJsdocPropagationForNames_rest() { testEs6( LINE_JOINER.join( "/**", " * @constructor", " * @param {...number} nums", " */", "function Foo(...nums) {", " /** @const */ this.nums = nums;", "}"), LINE_JOINER.join( "/**", " * @constructor", " * @param {...number} nums", " */", "function Foo(...nums) {}", "/** @const {!Array<number>} */ Foo.prototype.nums;")); } public void testOptionalRestParamFunction() { testEs6( LINE_JOINER.join( "/**", " * @param {?Object} o", " * @param {string=} str", " * @param {number=} num", " * @param {...!Object} rest", " */", "function foo(o, str = '', num = 5, ...rest) {}"), LINE_JOINER.join( "/**", " * @param {?Object} o", " * @param {string=} str", " * @param {number=} num", " * @param {...!Object} rest", " */", "function foo(o, str, num, ...rest) {}")); } public void testConstJsdocPropagationForNames_defaultValue() { testEs6( LINE_JOINER.join( "/**", " * @constructor", " * @param {string=} str", " */", "function Foo(str = '') {", " /** @const */ this.s = str;", "}"), LINE_JOINER.join( "/**", " * @constructor", " * @param {string=} str", " */", "function Foo(str) {}", "/** @const {string} */ Foo.prototype.s;")); testEs6( LINE_JOINER.join( "class Foo {", " /** @param {string=} str */", " constructor(str = '') {", " /** @const */ this.s = str;", " }", "}"), LINE_JOINER.join( "class Foo {", " /** @param {string=} str */", " constructor(str) {}", "}", "/** @const {string} */ Foo.prototype.s;")); } public void testConstWithDeclaredTypes() { test("/** @const @type {number} */ var n = compute();", "/** @const @type {number} */ var n;"); test("/** @const {number} */ var n = compute();", "/** @const @type {number} */ var n;"); test("/** @const @return {void} */ var f = compute();", "/** @const @return {void} */ var f;"); test("/** @const @this {Array} */ var f = compute();", "/** @const @this {Array} x */ var f;"); test( "/** @const @param {number} x */ var f = compute();", "/** @const @param {number} x */ var f;"); test( "/** @const @constructor x */ var Foo = createConstructor();", "/** @const @constructor x */ var Foo;"); } public void testRemoveUselessStatements() { test("34", ""); test("'str'", ""); test("({x:4})", ""); test("debugger;", ""); test("throw 'error';", ""); test("label: debugger;", ""); } public void testRemoveUnnecessaryBodies() { test("function f(x,y) { /** @type {number} */ z = x + y; return z; }", "function f(x,y) {}"); test( "/** @return {number} */ function f(/** number */ x, /** number */ y) { return x + y; }", "/** @return {number} */ function f(/** number */ x, /** number */ y) {}"); testEs6( "class Foo { method(/** string */ s) { return s.split(','); } }", "class Foo { method(/** string */ s) {} }"); } public void testGoogModules() { testSame( LINE_JOINER.join( "goog.module('x.y.z');", "", "/** @constructor */ function Foo() {};", "", "exports = Foo;")); testSameEs6( LINE_JOINER.join( "goog.module('x.y.z');", "", "const Baz = goog.require('a.b.c');", "", "/** @constructor */ function Foo() {};", "/** @type {!Baz} */ Foo.prototype.baz", "", "exports = Foo;")); testSameEs6( LINE_JOINER.join( "goog.module('x.y.z');", "", "const {Bar, Baz} = goog.require('a.b.c');", "", "/** @constructor */ function Foo() {};", "/** @type {!Baz} */ Foo.prototype.baz", "", "exports = Foo;")); testSame( new String[] { LINE_JOINER.join( "goog.module('a.b.c');", "/** @constructor */ function Foo() {}", "Foo.prototoype.display = function() {};", "exports = Foo;"), LINE_JOINER.join( "goog.module('x.y.z');", "/** @constructor */ function Foo() {}", "Foo.prototoype.display = function() {};", "exports = Foo;"), }); testSame( new String[] { LINE_JOINER.join( "/** @constructor */ function Foo() {}", "Foo.prototoype.display = function() {};"), LINE_JOINER.join( "goog.module('x.y.z');", "/** @constructor */ function Foo() {}", "Foo.prototoype.display = function() {};", "exports = Foo;"), }); test( new String[] { LINE_JOINER.join( "goog.module('a.b.c');", "/** @constructor */ function Foo() {", " /** @type {number} */ this.x = 5;", "}", "exports = Foo;"), LINE_JOINER.join( "goog.module('x.y.z');", "/** @constructor */ function Foo() {", " /** @type {number} */ this.x = 99;", "}", "exports = Foo;"), }, new String[] { LINE_JOINER.join( "goog.module('a.b.c');", "/** @constructor */ function Foo() {}", "/** @type {number} */ Foo.prototype.x;", "exports = Foo;"), LINE_JOINER.join( "goog.module('x.y.z');", "/** @constructor */ function Foo() {}", "/** @type {number} */ Foo.prototype.x;", "exports = Foo;"), }); } public void testGoogModulesWithUndefinedExports() { testEs6( LINE_JOINER.join( "goog.module('x.y.z');", "", "const Baz = goog.require('x.y.z.Baz');", "const Foobar = goog.require('f.b.Foobar');", "", "/** @type {Foobar} */", "exports.foobar = (new Baz).getFoobar();"), LINE_JOINER.join( "goog.module('x.y.z');", "", "const Baz = goog.require('x.y.z.Baz');", "const Foobar = goog.require('f.b.Foobar');", "", "/** @type {Foobar} */", "exports.foobar;")); testEs6( LINE_JOINER.join( "goog.module('x.y.z');", "", "const Baz = goog.require('x.y.z.Baz');", "const Foobar = goog.require('f.b.Foobar');", "", "/** @type {Foobar} */", "exports = (new Baz).getFoobar();"), LINE_JOINER.join( "goog.module('x.y.z');", "", "const Baz = goog.require('x.y.z.Baz');", "const Foobar = goog.require('f.b.Foobar');", "", "/** @type {Foobar} */", "exports = /** @const {?} */ (0);")); } public void testCrossFileModifications() { test( LINE_JOINER.join( "goog.module('a.b.c');", "othermodule.modify.something = othermodule.modify.something + 1;"), "goog.module('a.b.c');"); testEs6( LINE_JOINER.join( "goog.module('a.b.c');", "class Foo {}", "Foo.something = othermodule.modify.something + 1;", "exports = Foo;"), LINE_JOINER.join( "goog.module('a.b.c');", "class Foo {}", "/** @const {*} */ Foo.something", "exports = Foo;")); test( LINE_JOINER.join( "goog.provide('a.b.c');", "otherfile.modify.something = otherfile.modify.something + 1;"), "goog.provide('a.b.c');"); test( LINE_JOINER.join( "goog.provide('a.b.c');", "a.b.c.something = otherfile.modify.something + 1;"), LINE_JOINER.join( "goog.provide('a.b.c');", "/** @const {*} */ a.b.c.something;")); } public void testRemoveCalls() { test("alert('hello'); window.clearTimeout();", ""); testSame("goog.provide('a.b.c');"); testSame("goog.provide('a.b.c'); goog.require('x.y.z');"); } public void testEnums() { test( "/** @const @enum {number} */ var E = { A: 1, B: 2, C: 3};", "/** @const @enum {number} */ var E = { A: 0, B: 0, C: 0};"); test( "/** @enum {number} */ var E = { A: foo(), B: bar(), C: baz()};", "/** @enum {number} */ var E = { A: 0, B: 0, C: 0};"); // NOTE: This pattern typechecks when found in externs, but not for code. // Since the goal of this pass is intended to be used as externs, this is acceptable. test( "/** @enum {string} */ var E = { A: 'hello', B: 'world'};", "/** @enum {string} */ var E = { A: 0, B: 0};"); test( "/** @enum {Object} */ var E = { A: {b: 'c'}, D: {e: 'f'} };", "/** @enum {Object} */ var E = { A: 0, D: 0};"); test( "var x = 7; /** @enum {number} */ var E = { A: x };", "/** @const {*} */ var x; /** @enum {number} */ var E = { A: 0 };"); } public void testTryCatch() { test( "try { /** @type {number} */ var n = foo(); } catch (e) { console.log(e); }", "{ /** @type {number} */ var n; }"); } public void testTemplatedClass() { testEs6( LINE_JOINER.join( "/** @template T */", "const Foo = goog.defineClass(null, {", " /** @param {T} x */", " constructor: function(x) { /** @const */ this.x = x;},", "});"), LINE_JOINER.join( "/** @template T */", "const Foo = goog.defineClass(null, {", " /** @param {T} x */", " constructor: function(x) {},", "});", "/** @const {T} */ Foo.prototype.x;")); testEs6( LINE_JOINER.join( "/** @template T */", "class Foo {", " /** @param {T} x */", " constructor(x) { /** @const */ this.x = x;}", "}"), LINE_JOINER.join( "/** @template T */", "class Foo {", " /** @param {T} x */", " constructor(x) {}", "}", "/** @const {T} */ Foo.prototype.x;")); } public void testConstructorBodyWithThisDeclaration() { test( "/** @constructor */ function Foo() { /** @type {number} */ this.num = 5;}", "/** @constructor */ function Foo() {} /** @type {number} */ Foo.prototype.num;"); test( "/** @constructor */ function Foo(b) { if (b) { /** @type {number} */ this.num = 5; } }", "/** @constructor */ function Foo(b) {} /** @type {number} */ Foo.prototype.num;"); testEs6( "/** @constructor */ let Foo = function() { /** @type {number} */ this.num = 5;}", "/** @constructor */ let Foo = function() {}; /** @type {number} */ Foo.prototype.num;"); testEs6( "class Foo { constructor() { /** @type {number} */ this.num = 5;} }", "class Foo { constructor() {} } /** @type {number} */ Foo.prototype.num;"); testEs6( LINE_JOINER.join( "const Foo = goog.defineClass(null, {", " constructor: function() { /** @type {number} */ this.num = 5;},", "});"), LINE_JOINER.join( "const Foo = goog.defineClass(null, {", " constructor: function() {},", "});", "/** @type {number} */ Foo.prototype.num;")); testEs6( LINE_JOINER.join( "ns.Foo = goog.defineClass(null, {", " constructor: function() { /** @type {number} */ this.num = 5;},", "});"), LINE_JOINER.join( "ns.Foo = goog.defineClass(null, {", " constructor: function() {},", "});", "/** @type {number} */ ns.Foo.prototype.num;")); testEs6( LINE_JOINER.join( "const Foo = goog.defineClass(null, {", " /** @return {number} */", " foo: function() { return 5;},", "});"), LINE_JOINER.join( "const Foo = goog.defineClass(null, {", " /** @return {number} */", " foo: function() {},", "});")); testEs6( LINE_JOINER.join( "const Foo = goog.defineClass(null, {", " /** @return {number} */", " foo() { return 5;},", "});"), LINE_JOINER.join( "const Foo = goog.defineClass(null, {", " /** @return {number} */", " foo() {},", "});")); } public void testConstructorBodyWithoutThisDeclaration() { test( "/** @constructor */ function Foo(o) { o.num = 8; var x = 'str'; }", "/** @constructor */ function Foo(o) {}"); } public void testIIFE() { test("(function(){ /** @type {number} */ var n = 99; })();", ""); } public void testConstants() { test("/** @const {number} */ var x = 5;", "/** @const {number} */ var x;"); } public void testDefines() { // NOTE: This is another pattern that is only allowed in externs. test("/** @define {number} */ var x = 5;", "/** @define {number} */ var x;"); test("/** @define {number} */ goog.define('goog.BLAH', 5);", "/** @define {number} */ goog.define('goog.BLAH');"); } public void testIfs() { test( "if (true) { var /** number */ x = 5; }", "{/** @type {number} */ var x;}"); test( "if (true) { var /** number */ x = 5; } else { var /** string */ y = 'str'; }", "{/** @type {number} */ var x;} {/** @type {string} */ var y; }"); test( "if (true) { if (false) { var /** number */ x = 5; } }", "{{/** @type {number} */ var x;}}"); test( "if (true) {} else { if (false) {} else { var /** number */ x = 5; } }", "{}{{}{/** @type {number} */ var x;}}"); } public void testLoops() { test("while (true) { foo(); break; }", "{}"); test("for (var i = 0; i < 10; i++) { var field = 88; }", "/** @const {*} */ var i; {/** @const {*} */ var field;}"); test( "while (i++ < 10) { var /** number */ field = i; }", "{ /** @type {number } */ var field; }"); test( "do { var /** number */ field = i; } while (i++ < 10);", "{ /** @type {number } */ var field; }"); test( "for (var /** number */ i = 0; i < 10; i++) { var /** number */ field = i; }", "/** @type {number} */ var i; { /** @type {number } */ var field; }"); test( "for (i = 0; i < 10; i++) { var /** number */ field = i; }", "{ /** @type {number } */ var field; }"); test( "for (var i = 0; i < 10; i++) { var /** number */ field = i; }", "/** @const {*} */ var i; { /** @type {number } */ var field; }"); } public void testNamespaces() { testSame("/** @const */ var ns = {}; /** @return {number} */ ns.fun = function(x,y,z) {}"); testSame("/** @const */ var ns = {}; ns.fun = function(x,y,z) {}"); } public void testRemoveIgnoredProperties() { test( "/** @const */ var ns = {}; /** @return {number} */ ns['fun'] = function(x,y,z) {}", "/** @const */ var ns = {};"); test( "/** @constructor */ function Foo() {} Foo.prototype['fun'] = function(x,y,z) {}", "/** @constructor */ function Foo() {}"); } public void testRemoveRepeatedProperties() { test( "/** @const */ var ns = {}; /** @type {number} */ ns.x = 5; ns.x = 7;", "/** @const */ var ns = {}; /** @type {number} */ ns.x;"); test( "/** @const */ var ns = {}; ns.x = 5; ns.x = 7;", "/** @const */ var ns = {}; /** @const {*} */ ns.x;"); testEs6( "const ns = {}; /** @type {number} */ ns.x = 5; ns.x = 7;", "const ns = {}; /** @type {number} */ ns.x;"); } public void testRemoveRepeatedDeclarations() { test("/** @type {number} */ var x = 4; var x = 7;", "/** @type {number} */ var x;"); test("/** @type {number} */ var x = 4; x = 7;", "/** @type {number} */ var x;"); test("var x = 4; var x = 7;", "/** @const {*} */ var x;"); test("var x = 4; x = 7;", "/** @const {*} */ var x;"); } public void testDontRemoveGoogModuleContents() { testWarning( "goog.module('x.y.z'); var C = goog.require('a.b.C'); exports = new C;", ConvertToTypedInterface.CONSTANT_WITHOUT_EXPLICIT_TYPE); testSameEs6("goog.module('x.y.z.Foo'); exports = class {};"); testSameEs6("goog.module('x.y.z'); exports.Foo = class {};"); testSameEs6("goog.module('x.y.z.Foo'); class Foo {}; exports = Foo;"); testSameEs6("goog.module('x.y.z'); class Foo {}; exports.Foo = Foo;"); testSameEs6("goog.module('x.y.z'); class Foo {}; exports = {Foo};"); testSameEs6( LINE_JOINER.join( "goog.module('x.y.z');", "const C = goog.require('a.b.C');", "class Foo extends C {}", "exports = Foo;")); } public void testDontPreserveUnknownTypeDeclarations() { test( "goog.forwardDeclare('MyType'); /** @type {MyType} */ var x;", "/** @type {MyType} */ var x;"); test( "goog.addDependency('zzz.js', ['MyType'], []); /** @type {MyType} */ var x;", "/** @type {MyType} */ var x;"); // This is OK, because short-import goog.forwardDeclares don't declare a type. testSame("goog.module('x.y.z'); var C = goog.forwardDeclare('a.b.C'); /** @type {C} */ var c;"); } public void testAliasOfRequirePreserved() { testSame( LINE_JOINER.join( "goog.provide('a.b.c');", "", "goog.require('ns.Foo');", "", "/** @const */", "a.b.c.FooAlias = ns.Foo;")); testSame( LINE_JOINER.join( "goog.provide('a.b.c');", "", "goog.require('ns.Foo');", "", "/** @constructor */", "a.b.c.FooAlias = ns.Foo;")); testSameEs6( LINE_JOINER.join( "goog.module('mymod');", "", "const {Foo} = goog.require('ns.Foo');", "", "/** @const */", "var FooAlias = Foo;", "", "/** @param {!FooAlias} f */", "exports = function (f) {};")); testSame( LINE_JOINER.join( "goog.module('mymod');", "", "var Foo = goog.require('ns.Foo');", "", "/** @constructor */", "var FooAlias = Foo;", "", "/** @param {!FooAlias} f */", "exports = function (f) {};")); } public void testAliasOfNonRequiredName() { testWarning( LINE_JOINER.join( "goog.provide('a.b.c');", "", "/** @const */", "a.b.c.FooAlias = ns.Foo;"), ConvertToTypedInterface.CONSTANT_WITHOUT_EXPLICIT_TYPE); testWarning( LINE_JOINER.join( "goog.provide('a.b.c');", "", "/** @constructor */", "a.b.c.Bar = function() {", " /** @const */", " this.FooAlias = ns.Foo;", "};"), ConvertToTypedInterface.CONSTANT_WITHOUT_EXPLICIT_TYPE); testWarningEs6( LINE_JOINER.join( "goog.module('a.b.c');", "", "class FooAlias {", " constructor() {", " /** @const */", " this.FooAlias = window.Foo;", " }", "};"), ConvertToTypedInterface.CONSTANT_WITHOUT_EXPLICIT_TYPE); } public void testGoogScopeNotSupported() { testSameWarning( new String[] { LINE_JOINER.join( "goog.provide('a.b.c.MyFoo');", "", "goog.require('x.y.Foo');", "", "goog.scope(function() {", " var Foo = x.y.Foo;", " var ns = a.b.c;", " ns.MyFoo = new Foo;", "});") }, ConvertToTypedInterface.UNSUPPORTED_GOOG_SCOPE); } }