Skip to content

Commit

Permalink
feat: detect API and manifest key incompatibilities with strict_min_v…
Browse files Browse the repository at this point in the history
…ersion (mozilla#1493)
  • Loading branch information
freaktechnik committed Dec 5, 2018
1 parent 15b841f commit f531293
Show file tree
Hide file tree
Showing 16 changed files with 865 additions and 78 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ Dependencies are automatically kept up-to-date using [greenkeeper](http://greenk
| npm run test-coverage-once | Runs the tests once with coverage |
| npm run test-integration-linter | Runs our integration test-suite |
| npm run prettier | Automatically format the whole code-base with Prettier |
| npm run prettier-dev | Automatically compare and format modified source files against the master branch |
| npm run prettier-dev | Automatically compare and format modified source files against the master branch |

### Building

Expand Down
4 changes: 2 additions & 2 deletions docs/import-firefox-schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,5 @@ And import the schema.

## Things to check for further updates

* Review the schema update carefully and see if there are any updates that require additional linting / warning from the linter (e.g properties that are meant for internal add-ons and shouldn't be used by regular add-ons, ask around if unsure)
* Check for custom format validations in ``src/schema/formats.js`` and update accordingly with upstream code (e.g ``manifestShortcutKey``)
- Review the schema update carefully and see if there are any updates that require additional linting / warning from the linter (e.g properties that are meant for internal add-ons and shouldn't be used by regular add-ons, ask around if unsure)
- Check for custom format validations in `src/schema/formats.js` and update accordingly with upstream code (e.g `manifestShortcutKey`)
150 changes: 78 additions & 72 deletions docs/rules.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"glob": "7.1.3",
"is-mergeable-object": "1.1.0",
"jed": "1.1.1",
"mdn-browser-compat-data": "0.0.58",
"os-locale": "3.0.1",
"pino": "5.9.0",
"po2json": "0.4.5",
Expand Down
2 changes: 2 additions & 0 deletions src/const.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export const ESLINT_RULE_MAPPING = Object.assign(
'webextension-api': ESLINT_WARNING,
'webextension-unsupported-api': ESLINT_WARNING,
'content-scripts-file-absent': ESLINT_ERROR,
'webextension-api-compat': ESLINT_WARNING,
'webextension-api-compat-android': ESLINT_WARNING,
},
EXTERNAL_RULE_MAPPING
);
Expand Down
24 changes: 24 additions & 0 deletions src/messages/javascript.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,34 @@ export const STORAGE_LOCAL = temporaryAPI('storage.local');
export const STORAGE_SYNC = temporaryAPI('storage.sync');
export const IDENTITY_GETREDIRECTURL = temporaryAPI('identity.getRedirectURL');

export const INCOMPATIBLE_API = {
code: 'INCOMPATIBLE_API',
message: null,
messageFormat: i18n._(
'{{api}} is not supported in Firefox version {{minVersion}}'
),
description: i18n._(
'This API is not implemented by the given minimum Firefox version'
),
};

export const ANDROID_INCOMPATIBLE_API = {
code: 'ANDROID_INCOMPATIBLE_API',
message: null,
messageFormat: i18n._(
'{{api}} is not supported in Firefox for Android version {{minVersion}}'
),
description: i18n._(
'This API is not implemented by the given minimum Firefox for Android version'
),
};

export const ESLINT_OVERWRITE_MESSAGE = {
'no-eval': DANGEROUS_EVAL,
'no-implied-eval': NO_IMPLIED_EVAL,
'no-new-func': DANGEROUS_EVAL,
'no-unsafe-innerhtml/no-unsafe-innerhtml': UNSAFE_DYNAMIC_VARIABLE_ASSIGNMENT,
'webextension-unsupported-api': UNSUPPORTED_API,
'webextension-api-compat': INCOMPATIBLE_API,
'webextension-api-compat-android': ANDROID_INCOMPATIBLE_API,
};
88 changes: 88 additions & 0 deletions src/messages/manifestjson.js
Original file line number Diff line number Diff line change
Expand Up @@ -418,3 +418,91 @@ export function noMessagesFileInLocales(path) {
file: MANIFEST_JSON,
};
}

export const KEY_FIREFOX_UNSUPPORTED_BY_MIN_VERSION =
'KEY_FIREFOX_UNSUPPORTED_BY_MIN_VERSION';
export function keyFirefoxUnsupportedByMinVersion(
key,
minVersion,
versionAdded
) {
return {
code: KEY_FIREFOX_UNSUPPORTED_BY_MIN_VERSION,
message: i18n._(
'Manifest key not supported by the specified minimum Firefox version'
),
description: i18n.sprintf(
i18n._(oneLine`"strict_min_version" requires Firefox %(minVersion)s, which
was released before version %(versionAdded)s introduced support for
"%(key)s".`),
{ key, minVersion, versionAdded }
),
file: MANIFEST_JSON,
};
}

export const PERMISSION_FIREFOX_UNSUPPORTED_BY_MIN_VERSION =
'èERMISSION_FIREFOX_UNSUPPORTED_BY_MIN_VERSION';
export function permissionFirefoxUnsupportedByMinVersion(
key,
minVersion,
versionAdded
) {
return {
code: PERMISSION_FIREFOX_UNSUPPORTED_BY_MIN_VERSION,
message: i18n._(
'Permission not supported by the specified minimum Firefox version'
),
description: i18n.sprintf(
i18n._(oneLine`"strict_min_version" requires Firefox %(minVersion)s, which
was released before version %(versionAdded)s introduced support for
"%(key)s".`),
{ key, minVersion, versionAdded }
),
file: MANIFEST_JSON,
};
}

export const KEY_FIREFOX_ANDROID_UNSUPPORTED_BY_MIN_VERSION =
'KEY_FIREFOX_ANDROID_UNSUPPORTED_BY_MIN_VERSION';
export function keyFirefoxAndroidUnsupportedByMinVersion(
key,
minVersion,
versionAdded
) {
return {
code: KEY_FIREFOX_ANDROID_UNSUPPORTED_BY_MIN_VERSION,
message: i18n._(
'Manifest key not supported by the specified minimum Firefox for Android version'
),
description: i18n.sprintf(
i18n._(oneLine`"strict_min_version" requires Firefox for Android
%(minVersion)s, which was released before version %(versionAdded)s
introduced support for "%(key)s".`),
{ key, minVersion, versionAdded }
),
file: MANIFEST_JSON,
};
}

export const PERMISSION_FIREFOX_ANDROID_UNSUPPORTED_BY_MIN_VERSION =
'PERMISSION_FIREFOX_ANDROID_UNSUPPORTED_BY_MIN_VERSION';
export function permissionFirefoxAndroidUnsupportedByMinVersion(
key,
minVersion,
versionAdded
) {
return {
code: PERMISSION_FIREFOX_ANDROID_UNSUPPORTED_BY_MIN_VERSION,
message: i18n._(
'Permission not supported by the specified minimum Firefox for Android version'
),
description: i18n.sprintf(
i18n._(oneLine`"strict_min_version" requires Firefox for Android
%(minVersion)s, which was released before version %(versionAdded)s
introduced support for "%(key)s".`),
{ key, minVersion, versionAdded }
),
file: MANIFEST_JSON,
};
}
105 changes: 104 additions & 1 deletion src/parsers/manifestjson.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import RJSON from 'relaxed-json';
import { oneLine } from 'common-tags';
import probeImageSize from 'probe-image-size';
import upath from 'upath';
import bcd from 'mdn-browser-compat-data';

import { getDefaultConfigValue } from 'yargs-options';
import {
Expand All @@ -28,7 +29,12 @@ import log from 'logger';
import * as messages from 'messages';
import JSONParser from 'parsers/json';
import { isToolkitVersionString } from 'schema/formats';
import { parseCspPolicy, normalizePath } from 'utils';
import {
parseCspPolicy,
normalizePath,
firefoxStrictMinVersion,
basicCompatVersionComparison,
} from 'utils';
import BLOCKED_CONTENT_SCRIPT_HOSTS from 'blocked_content_script_hosts.txt';

async function getImageMetadata(io, iconPath) {
Expand Down Expand Up @@ -88,6 +94,84 @@ export default class ManifestJSONParser extends JSONParser {
}
}

checkKeySupport(support, minVersion, key, isPermission = false) {
if (support.firefox) {
const versionAdded = support.firefox.version_added;
if (basicCompatVersionComparison(versionAdded, minVersion)) {
if (!isPermission) {
this.collector.addWarning(
messages.keyFirefoxUnsupportedByMinVersion(
key,
this.parsedJSON.applications.gecko.strict_min_version,
versionAdded
)
);
} else {
this.collector.addNotice(
messages.permissionFirefoxUnsupportedByMinVersion(
key,
this.parsedJSON.applications.gecko.strict_min_version,
versionAdded
)
);
}
}
}
if (support.firefox_android) {
const versionAddedAndroid = support.firefox_android.version_added;
if (basicCompatVersionComparison(versionAddedAndroid, minVersion)) {
if (!isPermission) {
this.collector.addWarning(
messages.keyFirefoxAndroidUnsupportedByMinVersion(
key,
this.parsedJSON.applications.gecko.strict_min_version,
versionAddedAndroid
)
);
} else {
this.collector.addNotice(
messages.permissionFirefoxAndroidUnsupportedByMinVersion(
key,
this.parsedJSON.applications.gecko.strict_min_version,
versionAddedAndroid
)
);
}
}
}
}

checkCompatInfo(compatInfo, minVersion, key, manifestKeyValue) {
for (const subkey in compatInfo) {
if (Object.prototype.hasOwnProperty.call(compatInfo, subkey)) {
const subkeyInfo = compatInfo[subkey];
if (subkey === '__compat') {
this.checkKeySupport(subkeyInfo.support, minVersion, key);
} else if (
typeof manifestKeyValue === 'object' &&
Object.prototype.hasOwnProperty.call(manifestKeyValue, subkey)
) {
this.checkCompatInfo(
subkeyInfo,
minVersion,
`${key}.${subkey}`,
manifestKeyValue[subkey]
);
} else if (
(key === 'permissions' || key === 'optional_permissions') &&
manifestKeyValue.includes(subkey)
) {
this.checkKeySupport(
subkeyInfo.__compat.support,
minVersion,
`${key}:${subkey}`,
true
);
}
}
}
}

errorLookup(error) {
// This is the default message.
let baseObject = messages.JSON_INVALID;
Expand Down Expand Up @@ -287,6 +371,21 @@ export default class ManifestJSONParser extends JSONParser {
}
}

const minVersion = firefoxStrictMinVersion(this.parsedJSON);
if (!this.isLanguagePack && !this.isDictionary && minVersion) {
for (const key in bcd.webextensions.manifest) {
if (Object.prototype.hasOwnProperty.call(this.parsedJSON, key)) {
const compatInfo = bcd.webextensions.manifest[key];
this.checkCompatInfo(
compatInfo,
minVersion,
key,
this.parsedJSON[key]
);
}
}
}

if (isToolkitVersionString(this.parsedJSON.version)) {
this.collector.addNotice(messages.PROP_VERSION_TOOLKIT_ONLY);
}
Expand Down Expand Up @@ -605,6 +704,10 @@ export default class ManifestJSONParser extends JSONParser {
name: this.parsedJSON.name,
type: PACKAGE_EXTENSION,
version: this.parsedJSON.version,
firefoxMinVersion:
this.parsedJSON.applications &&
this.parsedJSON.applications.gecko &&
this.parsedJSON.applications.gecko.strict_min_version,
};
}
}
3 changes: 3 additions & 0 deletions src/rules/javascript/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,8 @@ module.exports = {
.default,
'webextension-unsupported-api': require('./webextension-unsupported-api')
.default,
'webextension-api-compat': require('./webextension-api-compat').default,
'webextension-api-compat-android': require('./webextension-api-compat-android')
.default,
},
};
49 changes: 49 additions & 0 deletions src/rules/javascript/webextension-api-compat-android.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import bcd from 'mdn-browser-compat-data';

import { ANDROID_INCOMPATIBLE_API } from 'messages/javascript';
import {
isBrowserNamespace,
firefoxStrictMinVersion,
isCompatible,
} from 'utils';
import { hasBrowserApi } from 'schema/browser-apis';

export default {
create(context) {
const minVersion =
context.settings.addonMetadata &&
firefoxStrictMinVersion({
applications: {
gecko: {
strict_min_version:
context.settings.addonMetadata.firefoxMinVersion,
},
},
});
if (minVersion) {
return {
MemberExpression(node) {
if (
!node.computed &&
node.object.object &&
isBrowserNamespace(node.object.object.name)
) {
const namespace = node.object.property.name;
const property = node.property.name;
const api = `${namespace}.${property}`;
if (
hasBrowserApi(namespace, property) &&
!isCompatible(bcd, api, minVersion, 'firefox_android')
) {
context.report(node, ANDROID_INCOMPATIBLE_API.messageFormat, {
api,
minVersion: context.settings.addonMetadata.firefoxMinVersion,
});
}
}
},
};
}
return {};
},
};
Loading

0 comments on commit f531293

Please sign in to comment.