fix(internal): Add locus resloving capabilities

Resolve all Meteor.isClient, Meteor.isServer and Meteor.isCordova LogicalExpressions
This commit is contained in:
Dominik Ferber
2015-10-13 18:42:31 +02:00
parent 93498f368b
commit 0b9622dedd
48 changed files with 1004 additions and 276 deletions

View File

@@ -1,4 +1,4 @@
import getMeta from './util/getMeta'
import {getMeta} from './util'
function unpack (rule) {
const packedRule = require(rule)

View File

@@ -10,7 +10,9 @@ import {isMeteorCall, isFunction} from '../util/ast'
// -----------------------------------------------------------------------------
module.exports = () => (context) => {
module.exports = getMeta => context => {
const {isLintedEnv} = getMeta(context.getFilename())
// ---------------------------------------------------------------------------
// Helpers
@@ -57,6 +59,10 @@ module.exports = () => (context) => {
// Public
// ---------------------------------------------------------------------------
if (!isLintedEnv) {
return {}
}
return {
CallExpression: (node) => {

View File

@@ -9,16 +9,11 @@
// Rule Definition
// -----------------------------------------------------------------------------
import {CLIENT, SERVER, UNIVERSAL} from '../util/environment'
import {isMeteorProp, isMeteorCall} from '../util/ast'
module.exports = getMeta => context => {
const {env} = getMeta(context.getFilename())
if (env !== CLIENT && env !== SERVER && env !== UNIVERSAL) {
return {}
}
const {isLintedEnv} = getMeta(context.getFilename())
// ---------------------------------------------------------------------------
// Helpers
@@ -43,6 +38,10 @@ module.exports = getMeta => context => {
// Public
// ---------------------------------------------------------------------------
if (!isLintedEnv) {
return {}
}
return {
AssignmentExpression: function (node) {

View File

@@ -7,7 +7,9 @@
// Rule Definition
// -----------------------------------------------------------------------------
module.exports = () => (context) => {
module.exports = getMeta => context => {
const {isLintedEnv} = getMeta(context.getFilename())
// ---------------------------------------------------------------------------
// Helpers
@@ -41,6 +43,10 @@ module.exports = () => (context) => {
// Public
// ---------------------------------------------------------------------------
if (!isLintedEnv) {
return {}
}
return {
AssignmentExpression: (node) => {

View File

@@ -6,18 +6,23 @@
// -----------------------------------------------------------------------------
// Rule Definition
// -----------------------------------------------------------------------------
module.exports = getMeta => context => {
module.exports = () => context => {
const {isLintedEnv} = getMeta(context.getFilename())
// -------------------------------------------------------------------------
// Public
// -------------------------------------------------------------------------
if (!isLintedEnv) {
return {}
}
return {
MemberExpression (node) {
if (node.object.name === 'Session') {
context.report(node, 'Unexpected Session statement.')
context.report(node, 'Unexpected Session statement')
}
}

View File

@@ -9,12 +9,18 @@ import {isMeteorCall} from '../util/ast'
// Rule Definition
// -----------------------------------------------------------------------------
module.exports = () => (context) => {
module.exports = getMeta => context => {
const {isLintedEnv} = getMeta(context.getFilename())
// -------------------------------------------------------------------------
// Public
// -------------------------------------------------------------------------
if (!isLintedEnv) {
return {}
}
return {
CallExpression: function (node) {

View File

@@ -5,19 +5,16 @@
* See LICENSE file in root directory for full license.
*/
import {CLIENT, SERVER, UNIVERSAL} from '../util/environment'
import {isMeteorCall, isInServerBlock, isInClientBlock} from '../util/ast'
import {isMeteorCall} from '../util/ast'
import {getExecutors} from '../util'
// -----------------------------------------------------------------------------
// Rule Definition
// -----------------------------------------------------------------------------
module.exports = getMeta => context => {
const {env} = getMeta(context.getFilename())
if (env !== CLIENT && env !== SERVER && env !== UNIVERSAL) {
return {}
}
const {isLintedEnv, env} = getMeta(context.getFilename())
// -------------------------------------------------------------------------
// Helpers
@@ -43,62 +40,50 @@ module.exports = getMeta => context => {
context.report(node, 'Allowed on client only')
}
function checkMeteorPublish (node, ancestors) {
function checkMeteorPublish (node, executors) {
if (!isMeteorCall(node, 'publish')) {
return
}
switch (env) {
case CLIENT:
noPublishOnClient(node)
break
case SERVER:
expectTwoArguments(node)
break
case UNIVERSAL:
if (isInServerBlock(ancestors)) {
expectTwoArguments(node)
} else {
noPublishOnClient(node)
}
break
if (executors.has('browser')) {
noPublishOnClient(node)
}
if (executors.has('server')) {
expectTwoArguments(node)
}
}
function checkMeteorSubscribe (node, ancestors) {
function checkMeteorSubscribe (node, executors) {
if (!isMeteorCall(node, 'subscribe')) {
return
}
switch (env) {
case CLIENT:
atLeastOneArgument(node)
break
case SERVER:
noSubscribeOnServer(node)
break
case UNIVERSAL:
if (isInClientBlock(ancestors)) {
atLeastOneArgument(node)
} else {
noSubscribeOnServer(node)
}
break
if (executors.has('browser')) {
atLeastOneArgument(node)
}
if (executors.has('server')) {
noSubscribeOnServer(node)
}
}
// -------------------------------------------------------------------------
// Public
// -------------------------------------------------------------------------
if (!isLintedEnv) {
return {}
}
return {
CallExpression: function (node) {
const ancestors = context.getAncestors()
const executors = getExecutors(env, ancestors)
// Meteor.publish
checkMeteorPublish(node, ancestors)
checkMeteorSubscribe(node, ancestors)
checkMeteorPublish(node, executors)
checkMeteorSubscribe(node, executors)
}
}

View File

@@ -1,4 +1,3 @@
export {default as isMeteorCall} from './isMeteorCall'
export {default as isMeteorProp} from './isMeteorProp'
export {default as isInBlock, isInClientBlock, isInServerBlock} from './isInBlock'
export {default as isFunction} from './isFunction'

View File

@@ -1,44 +0,0 @@
import {CLIENT, SERVER} from '../environment'
import isMeteorProp from './isMeteorProp'
import invariant from 'invariant'
export function isInBlock (ancestors, env) {
invariant(Array.isArray(ancestors), 'isInBlock: ancestors is not an array')
invariant(!!env, 'isInBlock: called without environment')
invariant(env === CLIENT || env === SERVER, 'isInBlock: unkown environment')
if (ancestors.length === 0) {
return false
}
let isInServer = false
let isInClient = false
for (let i = 0; i < ancestors.length; i++) {
const ancestor = ancestors[i]
if (ancestor.type === 'IfStatement' && ancestor.test.type === 'MemberExpression') {
if (isMeteorProp(ancestor.test, 'isServer')) {
isInServer = true
}
if (isMeteorProp(ancestor.test, 'isClient')) {
isInClient = true
}
}
}
switch (env) {
case SERVER:
return isInServer && !isInClient
case CLIENT:
return !isInServer && isInClient
}
}
export function isInServerBlock (ancestors) {
return isInBlock(ancestors, SERVER)
}
export function isInClientBlock (ancestors) {
return isInBlock(ancestors, CLIENT)
}

View File

@@ -0,0 +1,33 @@
import invariant from 'invariant'
import isMeteorBlockOnlyTest from './isMeteorBlockOnlyTest'
import getExecutorsFromTest from './getExecutorsFromTest'
import {intersection, difference} from './sets'
// Set -> Array -> Set
export default function filterExecutorsByAncestors (originalExecutors, ancestors) {
let executors = new Set([...originalExecutors])
for (let i = ancestors.length - 1; i > 0; i--) {
const current = ancestors[i]
const parent = ancestors[i - 1]
if (parent.type === 'IfStatement') {
if (isMeteorBlockOnlyTest(parent.test)) {
const executorsFromTest = getExecutorsFromTest(parent.test)
if (parent.consequent === current) {
executors = intersection(executors, executorsFromTest)
} else if (parent.alternate === current) {
executors = difference(executors, executorsFromTest)
} else {
invariant(false, 'Block is neither consequent nor alternate of parent')
}
} else {
// can not determine executors, because of unresolvable if-statement
return new Set()
}
}
}
return executors
}

View File

@@ -0,0 +1,10 @@
import filterExecutorsByAncestors from './filterExecutorsByAncestors'
import getExecutorsByEnv from './getExecutorsByEnv'
// ENVIRONMENT -> Nodes -> Set
export default function getExecutors (env, ancestors) {
return filterExecutorsByAncestors(
getExecutorsByEnv(env),
ancestors
)
}

View File

@@ -0,0 +1,51 @@
import {
PUBLIC,
PRIVATE,
CLIENT,
SERVER,
PACKAGE,
TEST,
NODE_MODULE,
UNIVERSAL,
PACKAGE_CONFIG,
MOBILE_CONFIG,
COMPATIBILITY,
NON_METEOR
} from '../environment'
/**
* Transforms an environment into executors
* @param {ENVIRONMENT} env An Environment
* @return {Set} A Set of executors
*/
export default function getExecutorsByEnv (env) {
const executors = new Set()
switch (env) {
case CLIENT:
case COMPATIBILITY:
executors.add('browser')
executors.add('cordova')
break
case SERVER:
executors.add('server')
break
case UNIVERSAL:
executors.add('server')
executors.add('browser')
executors.add('cordova')
break
case PACKAGE_CONFIG:
case MOBILE_CONFIG:
executors.add('isobuild')
break
case PUBLIC:
case PRIVATE:
case TEST:
case NODE_MODULE:
case NON_METEOR:
case PACKAGE:
default:
break
}
return executors
}

View File

@@ -0,0 +1,28 @@
import invariant from 'invariant'
import isMeteorProp from '../ast/isMeteorProp'
import {union, intersection} from './sets'
import invert from './invert'
// Nodes -> Set
export default function getExecutorsFromTest (test) {
switch (test.type) {
case 'MemberExpression':
if (isMeteorProp(test, 'isClient')) {
return new Set(['browser', 'cordova'])
} else if (isMeteorProp(test, 'isServer')) {
return new Set(['server'])
} else if (isMeteorProp(test, 'isCordova')) {
return new Set(['cordova'])
}
return invariant(false, 'Unkown Meteor prop should never be reached')
case 'UnaryExpression':
return invert(getExecutorsFromTest(test.argument))
case 'LogicalExpression':
if (test.operator === '&&') {
return intersection(getExecutorsFromTest(test.left), getExecutorsFromTest(test.right))
} else if (test.operator === '||') {
return union(getExecutorsFromTest(test.left), getExecutorsFromTest(test.right))
}
return invariant(false, 'Unkown operator should never be reached')
}
}

View File

@@ -0,0 +1,5 @@
import {difference} from './sets'
export default function invert (executors) {
return difference(new Set(['browser', 'server', 'cordova']), executors)
}

View File

@@ -0,0 +1,28 @@
import {isMeteorProp} from '../ast'
/**
* Verifies a test of an IfStatement contains only checks with
* Meteor.isClient, Meteor.isServer and Meteor.isCordova.
*
* @param {node} test Test of an IfStatement (MemberExpression, LogicalExpression, UnaryExpression)
* @return {Boolean} True if test contains only Meteor locus checks
*/
export default function isMeteorBlockOnlyTest (test) {
switch (test.type) {
case 'MemberExpression':
return (
isMeteorProp(test, 'isClient') ||
isMeteorProp(test, 'isServer') ||
isMeteorProp(test, 'isCordova')
)
case 'UnaryExpression':
if (test.operator === '!') {
return isMeteorBlockOnlyTest(test.argument)
}
return false
case 'LogicalExpression':
return isMeteorBlockOnlyTest(test.left) && isMeteorBlockOnlyTest(test.right)
default:
return false
}
}

View File

@@ -0,0 +1,26 @@
import invariant from 'invariant'
// Set -> Set -> Set
export function difference (a, b) {
invariant(!!a, 'difference: Set a is not defined')
invariant(!!b, 'difference: Set b is not defined')
return new Set(
[...a].filter(x => !b.has(x))
)
}
// Set -> Set -> Set
export function union (a, b) {
invariant(!!a, 'union: Set a is not defined')
invariant(!!b, 'union: Set b is not defined')
return new Set([...a, ...b])
}
// Set -> Set -> Set
export function intersection (a, b) {
invariant(!!a, 'intersection: Set a is not defined')
invariant(!!b, 'intersection: Set b is not defined')
return new Set(
[...a].filter(element => b.has(element))
)
}

View File

@@ -1,6 +0,0 @@
import getMeteorMeta from './internal/getMeteorMeta'
const getRelativePath = require('./internal/getRelativePath')
export default function (filename) {
return getMeteorMeta(getRelativePath(filename))
}

2
lib/util/index.js Normal file
View File

@@ -0,0 +1,2 @@
export {default as getMeta} from './meta/getMeta'
export {default as getExecutors} from './executors/getExecutors'

6
lib/util/meta/getMeta.js Normal file
View File

@@ -0,0 +1,6 @@
import getMeteorMeta from './getMeteorMeta'
const getRelativePath = require('./getRelativePath')
export default function (filename) {
return getMeteorMeta(getRelativePath(filename))
}

View File

@@ -1,6 +1,7 @@
import path from 'path'
import ENVIRONMENT from '../environment.js'
import folderNames from '../folderNames.js'
import ENVIRONMENT from '../environment'
import folderNames from '../folderNames'
import isLintedEnv from './isLintedEnv'
function matchLeft (dirs, list) {
for (let i = 0; i < dirs.length; i++) {
@@ -83,14 +84,14 @@ function determineEnvironment (pathInProjectList) {
}
function getMeteorFileInfo (relativeFilename) {
const pathInProjectList = relativeFilename.split(path.sep)
const environment = determineEnvironment(pathInProjectList)
return {
path: relativeFilename,
env: environment
env: environment,
isLintedEnv: isLintedEnv(environment)
}
}

View File

@@ -0,0 +1,5 @@
import {CLIENT, SERVER, UNIVERSAL, COMPATIBILITY} from '../environment'
export default function (env) {
return env === CLIENT || env === SERVER || env === UNIVERSAL || env === COMPATIBILITY
}

View File

@@ -4,7 +4,7 @@
"description": "Meteor specific linting rules for ESLint",
"main": "dist/index.js",
"scripts": {
"build": "babel lib -d dist --auxiliary-comment-before \"istanbul ignore next\"",
"build": "babel lib -d dist --optional runtime --auxiliary-comment-before \"istanbul ignore next\"",
"build:w": "npm run build -- -w",
"check-coverage": "istanbul check-coverage",
"clean": "rimraf dist",
@@ -36,6 +36,7 @@
"devDependencies": {
"babel": "5.8.23",
"babel-eslint": "4.1.3",
"babel-runtime": "5.8.25",
"colors": "1.1.2",
"coveralls": "2.11.4",
"cz-conventional-changelog": "1.1.4",

View File

@@ -66,11 +66,7 @@ import {CLIENT, SERVER, UNIVERSAL} from '../util/environment'
module.exports = getMeta => context => {
const {env} = getMeta(context.getFilename())
if (env !== CLIENT && env !== SERVER && env !== UNIVERSAL) {
return {}
}
const {isLintedEnv} = getMeta(context.getFilename())
// ---------------------------------------------------------------------------
// Helpers
@@ -82,6 +78,10 @@ module.exports = getMeta => context => {
// Public
// ---------------------------------------------------------------------------
if (!isLintedEnv) {
return {}
}
return {
// give me methods
@@ -107,7 +107,7 @@ const test = `/**
// Requirements
// -----------------------------------------------------------------------------
import {CLIENT, SERVER} from '../../../dist/util/environment.js'
import {CLIENT, SERVER} from '../../../dist/util/environment'
const rule = require('../../../dist/rules/${ruleId}')
const RuleTester = require('eslint').RuleTester
@@ -117,7 +117,7 @@ const RuleTester = require('eslint').RuleTester
// -----------------------------------------------------------------------------
const ruleTester = new RuleTester()
ruleTester.run('${ruleId}', rule(() => ({env: SERVER})), {
ruleTester.run('${ruleId}', rule(() => ({env: SERVER, isLintedEnv: true})), {
valid: [
// fill me in
@@ -127,14 +127,14 @@ ruleTester.run('${ruleId}', rule(() => ({env: SERVER})), {
{
code: '${escapedFailingExample}',
errors: [
{message: 'Unexpected Session statement.', type: 'MemberExpression'}
{message: 'The error message', type: 'MemberExpression'}
]
}
]
})
ruleTester.run('${ruleId}', rule(() => ({env: CLIENT})), {
ruleTester.run('${ruleId}', rule(() => ({env: CLIENT, isLintedEnv: true})), {
valid: [
// fill me in

View File

@@ -16,7 +16,7 @@ const RuleTester = require('eslint').RuleTester
// -----------------------------------------------------------------------------
const ruleTester = new RuleTester()
ruleTester.run('audit-argument-checks', rule(), {
ruleTester.run('audit-argument-checks', rule(() => ({isLintedEnv: true})), {
valid: [
'foo()',
@@ -123,3 +123,10 @@ ruleTester.run('audit-argument-checks', rule(), {
}
]
})
ruleTester.run('audit-argument-checks', rule(() => ({isLintedEnv: false})), {
valid: [
'Meteor.publish("foo", function (bar) { foo(); })'
],
invalid: []
})

View File

@@ -9,15 +9,6 @@
// Requirements
// -----------------------------------------------------------------------------
import {
CLIENT,
SERVER,
UNIVERSAL,
MOBILE_CONFIG,
PACKAGE_CONFIG,
NON_METEOR
} from '../../../dist/util/environment.js'
const rule = require('../../../dist/rules/core')
const RuleTester = require('eslint').RuleTester
@@ -126,10 +117,5 @@ const errorFreeTests = {
}
const ruleTester = new RuleTester()
ruleTester.run('core', rule(() => ({env: SERVER})), tests)
ruleTester.run('core', rule(() => ({env: CLIENT})), tests)
ruleTester.run('core', rule(() => ({env: UNIVERSAL})), tests)
ruleTester.run('core', rule(() => ({env: NON_METEOR})), errorFreeTests)
ruleTester.run('core', rule(() => ({env: PACKAGE_CONFIG})), errorFreeTests)
ruleTester.run('core', rule(() => ({env: MOBILE_CONFIG})), errorFreeTests)
ruleTester.run('core', rule(() => ({isLintedEnv: true})), tests)
ruleTester.run('core', rule(() => ({isLintedEnv: false})), errorFreeTests)

View File

@@ -18,7 +18,7 @@ const RuleTester = require('eslint').RuleTester
// -----------------------------------------------------------------------------
const ruleTester = new RuleTester()
ruleTester.run('no-blaze-lifecycle-assignment', rule(), {
ruleTester.run('no-blaze-lifecycle-assignment', rule(() => ({isLintedEnv: true})), {
valid: [
'x += 1',
@@ -74,3 +74,10 @@ ruleTester.run('no-blaze-lifecycle-assignment', rule(), {
}
]
})
ruleTester.run('no-blaze-lifecycle-assignment', rule(() => ({isLintedEnv: false})), {
valid: [
'Template.foo.created = function () {}'
],
invalid: []
})

View File

@@ -9,37 +9,16 @@
// Requirements
// -----------------------------------------------------------------------------
import {SERVER, CLIENT} from '../../../dist/util/environment.js'
const rule = require('../../../dist/rules/no-session')
const RuleTester = require('eslint').RuleTester
// -----------------------------------------------------------------------------
// Environments
// -----------------------------------------------------------------------------
const serverEnv = {
path: 'server/methods.js',
env: SERVER,
isCompatibilityFile: false,
isInMeteorProject: true
}
const clientEnv = {
path: 'client/methods.js',
env: CLIENT,
isCompatibilityFile: false,
isInMeteorProject: true
}
// -----------------------------------------------------------------------------
// Tests
// -----------------------------------------------------------------------------
const ruleTester = new RuleTester()
ruleTester.run('no-session', rule(() => serverEnv), {
ruleTester.run('no-session', rule(() => ({isLintedEnv: true})), {
valid: [
'session.get("foo")',
@@ -47,26 +26,17 @@ ruleTester.run('no-session', rule(() => serverEnv), {
],
invalid: [
{code: 'Session.set("foo", true)', errors: [{message: 'Unexpected Session statement.', type: 'MemberExpression'}]},
{code: 'Session.get("foo")', errors: [{message: 'Unexpected Session statement.', type: 'MemberExpression'}]},
{code: 'Session.clear("foo")', errors: [{message: 'Unexpected Session statement.', type: 'MemberExpression'}]},
{code: 'Session.all()', errors: [{message: 'Unexpected Session statement.', type: 'MemberExpression'}]}
{code: 'Session.set("foo", true)', errors: [{message: 'Unexpected Session statement', type: 'MemberExpression'}]},
{code: 'Session.get("foo")', errors: [{message: 'Unexpected Session statement', type: 'MemberExpression'}]},
{code: 'Session.clear("foo")', errors: [{message: 'Unexpected Session statement', type: 'MemberExpression'}]},
{code: 'Session.all()', errors: [{message: 'Unexpected Session statement', type: 'MemberExpression'}]}
]
})
ruleTester.run('no-session', rule(() => clientEnv), {
ruleTester.run('no-session', rule(() => ({isLintedEnv: false})), {
valid: [
'session.get("foo")',
'foo(Session)'
'Session.set("foo", true)',
'Session.get("foo")'
],
invalid: [
{code: 'Session.set("foo", true)', errors: [{message: 'Unexpected Session statement.', type: 'MemberExpression'}]},
{code: 'Session.get("foo")', errors: [{message: 'Unexpected Session statement.', type: 'MemberExpression'}]},
{code: 'Session.clear("foo")', errors: [{message: 'Unexpected Session statement.', type: 'MemberExpression'}]},
{code: 'Session.all()', errors: [{message: 'Unexpected Session statement.', type: 'MemberExpression'}]}
]
invalid: []
})

View File

@@ -16,7 +16,7 @@ const RuleTester = require('eslint').RuleTester
// -----------------------------------------------------------------------------
const ruleTester = new RuleTester()
ruleTester.run('no-zero-timeout', rule(), {
ruleTester.run('no-zero-timeout', rule(() => ({isLintedEnv: true})), {
valid: [
'Meteor.setTimeout()',
@@ -66,3 +66,10 @@ ruleTester.run('no-zero-timeout', rule(), {
}
]
})
ruleTester.run('no-zero-timeout', rule(() => ({isLintedEnv: false})), {
valid: [
'Meteor.setTimeout(function () {}, 0)'
],
invalid: []
})

View File

@@ -13,49 +13,12 @@ import {CLIENT, SERVER, UNIVERSAL} from '../../../dist/util/environment.js'
const rule = require('../../../dist/rules/pubsub')
const RuleTester = require('eslint').RuleTester
// -----------------------------------------------------------------------------
// Environments
// -----------------------------------------------------------------------------
const serverEnv = {
path: 'server/pubsub.js',
env: SERVER,
isCompatibilityFile: false,
isInMeteorProject: true,
isPackageConfig: false,
isMobileConfig: false
}
const clientEnv = {
path: 'server/pubsub.js',
env: CLIENT,
isCompatibilityFile: false,
isInMeteorProject: true,
isPackageConfig: false,
isMobileConfig: false
}
const universalEnv = {
path: 'pubsub.js',
env: UNIVERSAL,
isCompatibilityFile: false,
isInMeteorProject: true,
isPackageConfig: false,
isMobileConfig: false
}
const notInMeteorProject = {
isInMeteorProject: false
}
// -----------------------------------------------------------------------------
// Tests
// -----------------------------------------------------------------------------
const ruleTester = new RuleTester()
ruleTester.run('pubsub', rule(() => serverEnv), {
ruleTester.run('pubsub', rule(() => ({env: SERVER, isLintedEnv: true})), {
valid: [
'Meteor.publish("foo", function () {})'
],
@@ -76,7 +39,7 @@ ruleTester.run('pubsub', rule(() => serverEnv), {
]
})
ruleTester.run('pubsub', rule(() => clientEnv), {
ruleTester.run('pubsub', rule(() => ({env: CLIENT, isLintedEnv: true})), {
valid: [
'Meteor.subscribe("foo")'
],
@@ -97,10 +60,20 @@ ruleTester.run('pubsub', rule(() => clientEnv), {
]
})
ruleTester.run('pubsub', rule(() => universalEnv), {
ruleTester.run('pubsub', rule(() => ({env: UNIVERSAL, isLintedEnv: true})), {
valid: [
'if (Meteor.isClient) { Meteor.subscribe("foo") }',
'if (Meteor.isServer) { Meteor.publish("foo", function () {}) }'
'if (Meteor.isServer) { Meteor.publish("foo", function () {}) }',
`
if (Meteor.isClient) {
if (Meteor.isServer) {
// valid because it is unreachable
Meteor.publish("foo", function () {})
Meteor.subscribe("foo")
}
}
`
],
invalid: [
@@ -109,39 +82,11 @@ ruleTester.run('pubsub', rule(() => universalEnv), {
errors: [
{message: 'Allowed on client only', type: 'CallExpression'}
]
},
{
code: `
if (Meteor.isClient) {
if (Meteor.isServer) {
Meteor.publish("foo", function () {})
Meteor.subscribe("foo")
}
}
`,
errors: [
{message: 'Allowed on server only', type: 'CallExpression'},
{message: 'Allowed on client only', type: 'CallExpression'}
]
},
{
code: `
if (Meteor.isServer) {
if (Meteor.isClient) {
Meteor.publish("foo", function () {})
Meteor.subscribe("foo")
}
}
`,
errors: [
{message: 'Allowed on server only', type: 'CallExpression'},
{message: 'Allowed on client only', type: 'CallExpression'}
]
}
]
})
ruleTester.run('pubsub', rule(() => notInMeteorProject), {
ruleTester.run('pubsub', rule(() => ({isLintedEnv: false})), {
valid: [
'foo()'
],

View File

@@ -1,25 +0,0 @@
/* eslint-env mocha */
import assert from 'assert'
import {CLIENT, SERVER, UNIVERSAL} from '../../../../dist/util/environment'
import {isInBlock, isInServerBlock, isInClientBlock} from '../../../../dist/util/ast/isInBlock'
describe('isInBlock', function () {
it('returns false if no ancestors are present', function () {
assert.equal(isInBlock([], CLIENT), false)
assert.equal(isInBlock([], SERVER), false)
})
it('throws if no ancestors are given', function () {
assert.throws(isInClientBlock, Error)
assert.throws(isInServerBlock, Error)
})
it('throws if no environment is given', function () {
assert.throws(isInBlock.bind(null, []), Error)
})
it('throws when called with unhandeled environment', function () {
assert.throws(isInBlock.bind(null, [], UNIVERSAL))
})
})

View File

@@ -0,0 +1,190 @@
/* eslint-env mocha */
import assert from 'assert'
import filterExecutorsByAncestors from '../../../../dist/util/executors/filterExecutorsByAncestors'
describe('filterExecutorsByAncestors', function () {
it('filters on MemberExpression for isClient', function () {
const consequent = {type: 'BlockStatement'}
const result = filterExecutorsByAncestors(new Set(['browser', 'server']), [
{type: 'Program'},
{
type: 'IfStatement',
test: {
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'Meteor'
},
property: {
type: 'Identifier',
name: 'isClient'
}
},
consequent: consequent
},
consequent
])
assert.equal(result.size, 1)
assert.ok(result.has('browser'))
})
it('filters on MemberExpression for else-block of isClient', function () {
const alternate = {type: 'BlockStatement'}
const result = filterExecutorsByAncestors(new Set(['browser', 'server']), [
{type: 'Program'},
{
type: 'IfStatement',
test: {
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'Meteor'
},
property: {
type: 'Identifier',
name: 'isClient'
}
},
alternate: alternate
},
alternate
])
assert.equal(result.size, 1)
assert.ok(result.has('server'))
})
it('warns on hierarchical error', function () {
assert.throws(() => {
const consequent = {type: 'BlockStatement'}
filterExecutorsByAncestors(new Set(['browser', 'server']), [
{type: 'Program'},
{
type: 'IfStatement',
test: {
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'Meteor'
},
property: {
type: 'Identifier',
name: 'isClient'
}
}
},
consequent
])
})
})
it('filters on MemberExpression for isServer', function () {
const consequent = {type: 'BlockStatement'}
const result = filterExecutorsByAncestors(new Set(['server', 'cordova']), [
{type: 'Program'},
{
type: 'IfStatement',
test: {
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'Meteor'
},
property: {
type: 'Identifier',
name: 'isServer'
}
},
consequent: consequent
},
consequent
])
assert.equal(result.size, 1)
assert.ok(result.has('server'))
})
it('filters on MemberExpression for isCordova', function () {
const consequent = {type: 'BlockStatement'}
const result = filterExecutorsByAncestors(new Set(['browser', 'cordova']), [
{type: 'Program'},
{
type: 'IfStatement',
test: {
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'Meteor'
},
property: {
type: 'Identifier',
name: 'isCordova'
}
},
consequent: consequent
},
consequent
])
assert.equal(result.size, 1)
assert.ok(result.has('cordova'))
})
it('filters on UnaryExpression', function () {
const consequent = {type: 'BlockStatement'}
const result = filterExecutorsByAncestors(new Set(['browser', 'server', 'cordova']), [
{type: 'Program'},
{
type: 'IfStatement',
test: {
type: 'UnaryExpression',
operator: '!',
argument: {
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'Meteor'
},
property: {
type: 'Identifier',
name: 'isClient'
}
}
},
consequent: consequent
},
consequent
])
assert.equal(result.size, 1)
assert.ok(result.has('server'))
})
it('returns no executors when an unresolvable IfStatement is in ancestors', function () {
const consequent = {type: 'BlockStatement'}
const ifConsequent = {
test: {
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'Meteor'
},
property: {
type: 'Identifier',
name: 'isClient'
}
},
consequent: consequent
}
const result = filterExecutorsByAncestors(new Set(['browser', 'server']), [
{type: 'Program'},
{
type: 'IfStatement',
test: {type: 'Identifier'},
consequent: ifConsequent
},
ifConsequent,
consequent
])
assert.equal(result.size, 0)
})
})

View File

@@ -0,0 +1,15 @@
/* eslint-env mocha */
import assert from 'assert'
import getExecutors from '../../../../dist/util/executors/getExecutors'
import {UNIVERSAL} from '../../../../dist/util/environment'
describe('getExecutors', function () {
it('returns executors for no ancestors', function () {
const result = getExecutors(UNIVERSAL, [])
assert.equal(result.size, 3)
assert.ok(result.has('browser'))
assert.ok(result.has('server'))
assert.ok(result.has('cordova'))
})
})

View File

@@ -0,0 +1,80 @@
/* eslint-env mocha */
import assert from 'assert'
import getExecutorsByEnv from '../../../../dist/util/executors/getExecutorsByEnv'
import {
PUBLIC,
PRIVATE,
CLIENT,
SERVER,
PACKAGE,
TEST,
NODE_MODULE,
UNIVERSAL,
PACKAGE_CONFIG,
MOBILE_CONFIG,
COMPATIBILITY,
NON_METEOR
} from '../../../../dist/util/environment'
describe('getExecutorsByEnv', function () {
it('public', function () {
const result = getExecutorsByEnv(PUBLIC)
assert.equal(result.size, 0)
})
it('private', function () {
const result = getExecutorsByEnv(PRIVATE)
assert.equal(result.size, 0)
})
it('client', function () {
const result = getExecutorsByEnv(CLIENT)
assert.equal(result.size, 2)
assert.ok(result.has('browser'))
assert.ok(result.has('cordova'))
})
it('server', function () {
const result = getExecutorsByEnv(SERVER)
assert.equal(result.size, 1)
assert.ok(result.has('server'))
})
it('package', function () {
const result = getExecutorsByEnv(PACKAGE)
assert.equal(result.size, 0)
})
it('test', function () {
const result = getExecutorsByEnv(TEST)
assert.equal(result.size, 0)
})
it('node_module', function () {
const result = getExecutorsByEnv(NODE_MODULE)
assert.equal(result.size, 0)
})
it('universal', function () {
const result = getExecutorsByEnv(UNIVERSAL)
assert.equal(result.size, 3)
assert.ok(result.has('browser'))
assert.ok(result.has('server'))
assert.ok(result.has('cordova'))
})
it('packageConfig', function () {
const result = getExecutorsByEnv(PACKAGE_CONFIG)
assert.equal(result.size, 1)
assert.ok(result.has('isobuild'))
})
it('mobileConfig', function () {
const result = getExecutorsByEnv(MOBILE_CONFIG)
assert.equal(result.size, 1)
assert.ok(result.has('isobuild'))
})
it('compatibility', function () {
const result = getExecutorsByEnv(COMPATIBILITY)
assert.equal(result.size, 2)
assert.ok(result.has('cordova'))
assert.ok(result.has('browser'))
})
it('nonMeteor', function () {
const result = getExecutorsByEnv(NON_METEOR)
assert.equal(result.size, 0)
})
})

View File

@@ -0,0 +1,149 @@
/* eslint-env mocha */
import assert from 'assert'
import getExecutorsFromTest from '../../../../dist/util/executors/getExecutorsFromTest'
describe('getExecutorsFromTest', function () {
describe('MemberExpression', function () {
it('isClient', function () {
const result = getExecutorsFromTest({
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'Meteor'
},
property: {
type: 'Identifier',
name: 'isClient'
}
})
assert.equal(result.size, 2)
assert.ok(result.has('browser'))
assert.ok(result.has('cordova'))
})
it('isServer', function () {
const result = getExecutorsFromTest({
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'Meteor'
},
property: {
type: 'Identifier',
name: 'isServer'
}
})
assert.equal(result.size, 1)
assert.ok(result.has('server'))
})
it('isCordova', function () {
const result = getExecutorsFromTest({
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'Meteor'
},
property: {
type: 'Identifier',
name: 'isCordova'
}
})
assert.equal(result.size, 1)
assert.ok(result.has('cordova'))
})
it('throws on unkown Meteor prop', function () {
assert.throws(
() => {
getExecutorsFromTest({
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'Meteor'
},
property: {
type: 'Identifier',
name: 'isNotAMeteorProp'
}
})
}
)
})
})
describe('LogicalExpression', function () {
it('resolves isServer AND isClient', function () {
const result = getExecutorsFromTest({
type: 'LogicalExpression',
operator: '&&',
left: {
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'Meteor'
},
property: {
type: 'Identifier',
name: 'isServer'
}
},
right: {
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'Meteor'
},
property: {
type: 'Identifier',
name: 'isClient'
}
}
})
assert.equal(result.size, 0)
})
it('resolves isServer OR isClient', function () {
const result = getExecutorsFromTest({
type: 'LogicalExpression',
operator: '||',
left: {
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'Meteor'
},
property: {
type: 'Identifier',
name: 'isServer'
}
},
right: {
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'Meteor'
},
property: {
type: 'Identifier',
name: 'isClient'
}
}
})
assert.equal(result.size, 3)
assert.ok(result.has('browser'))
assert.ok(result.has('server'))
assert.ok(result.has('cordova'))
})
it('throws for unkown operator in LogicalExpression', function () {
assert.throws(() => {
getExecutorsFromTest({
type: 'LogicalExpression',
operator: 'XY',
left: {},
right: {}
})
})
})
})
})

View File

@@ -0,0 +1,206 @@
/* eslint-env mocha */
import assert from 'assert'
import isMeteorBlockOnlyTest from '../../../../dist/util/executors/isMeteorBlockOnlyTest'
describe('isMeteorBlockOnlyTest', function () {
it('accepts a valid MemberExpression', function () {
const result = isMeteorBlockOnlyTest({
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'Meteor'
},
property: {
type: 'Identifier',
name: 'isClient'
}
})
assert.ok(result)
})
it('accepts a valid computed MemberExpression', function () {
const result = isMeteorBlockOnlyTest({
type: 'MemberExpression',
computed: true,
object: {
type: 'Identifier',
name: 'Meteor'
},
property: {
type: 'Literal',
value: 'isCordova'
}
})
assert.ok(result)
})
it('does not accept an invalid MemberExpression', function () {
const result = isMeteorBlockOnlyTest({
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'Foo'
},
property: {
type: 'Identifier',
name: 'isClient'
}
})
assert.ok(!result)
})
it('accepts a valid UnaryExpression', function () {
const result = isMeteorBlockOnlyTest({
type: 'UnaryExpression',
operator: '!',
argument: {
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'Meteor'
},
property: {
type: 'Identifier',
name: 'isServer'
}
}
})
assert.ok(result)
})
it('does not accept an invalid UnaryExpression', function () {
const result = isMeteorBlockOnlyTest({
type: 'UnaryExpression',
operator: '!',
argument: {
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'Foo'
},
property: {
type: 'Identifier',
name: 'isClient'
}
}
})
assert.ok(!result)
})
it('accepts a valid LogicalExpression', function () {
const result = isMeteorBlockOnlyTest({
type: 'LogicalExpression',
operator: '||',
left: {
type: 'LogicalExpression',
operator: '&&',
left: {
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'Meteor'
},
property: {
type: 'Identifier',
name: 'isClient'
}
},
right: {
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'Meteor'
},
property: {
type: 'Identifier',
name: 'isServer'
}
}
},
right: {
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'Meteor'
},
property: {
type: 'Identifier',
name: 'isCordova'
}
}
})
assert.ok(result)
})
it('does not accept an invalid LogicalExpression', function () {
const result = isMeteorBlockOnlyTest({
type: 'LogicalExpression',
operator: '||',
left: {
type: 'LogicalExpression',
operator: '&&',
left: {
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'Foo'
},
property: {
type: 'Identifier',
name: 'isClient'
}
},
right: {
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'Meteor'
},
property: {
type: 'Identifier',
name: 'isServer'
}
}
},
right: {
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'Meteor'
},
property: {
type: 'Identifier',
name: 'isCordova'
}
}
})
assert.ok(!result)
})
it('returns false for unresolvable expressions', function () {
const result = isMeteorBlockOnlyTest({type: 'Identifier'})
assert.ok(!result)
})
it('returns false for invalid unary expressions', function () {
const result = isMeteorBlockOnlyTest({
type: 'UnaryExpression',
operator: '-',
argument: {
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'Foo'
},
property: {
type: 'Identifier',
name: 'isClient'
}
}
})
assert.ok(!result)
})
})

View File

@@ -0,0 +1,39 @@
/* eslint-env mocha */
import assert from 'assert'
import {difference, union, intersection} from '../../../../dist/util/executors/sets'
describe('executors', function () {
describe('union', function () {
it('unifies two sets', function () {
const result = union(new Set(['cordova']), new Set(['client', 'server']))
assert.equal(result.size, 3)
assert.ok(result.has('client'))
assert.ok(result.has('cordova'))
assert.ok(result.has('server'))
})
})
describe('difference', function () {
it('returns the difference when b contains nothing from a', function () {
const result = difference(new Set(['cordova']), new Set(['client', 'server']))
assert.equal(result.size, 1)
assert.ok(result.has('cordova'))
})
it('returns the difference when b contains one value from a', function () {
const result = difference(new Set(['client', 'cordova']), new Set(['client', 'server']))
assert.equal(result.size, 1)
assert.ok(result.has('cordova'))
})
})
describe('intersection', function () {
it('returns the intersection', function () {
const result = intersection(new Set(['client', 'cordova']), new Set(['client', 'server']))
assert.equal(result.size, 1)
assert.ok(result.has('client'))
})
})
})

View File

@@ -13,7 +13,7 @@ function matcher (filename) {
)
}
const findOneUpwards = require('../../../../dist/util/internal/findOneUpwards.js')
const findOneUpwards = require('../../../../dist/util/meta/findOneUpwards.js')
describe('findOneUpwards', function () {
it('returns false when no project is found', function () {

View File

@@ -1,10 +1,10 @@
/* eslint-env mocha */
import assert from 'assert'
import {NON_METEOR} from '../../../dist/util/environment'
import {NON_METEOR} from '../../../../dist/util/environment'
const rewire = require('rewire')
const getMeta = rewire('../../../dist/util/getMeta')
const getMeta = rewire('../../../../dist/util/meta/getMeta')
getMeta.__set__('getRelativePath', function (path) {
return path
})

View File

@@ -4,7 +4,7 @@ import assert from 'assert'
import path from 'path'
import ENVIRONMENT from '../../../../dist/util/environment.js'
import getMeteorMeta from '../../../../dist/util/internal/getMeteorMeta.js'
import getMeteorMeta from '../../../../dist/util/meta/getMeteorMeta.js'
describe('getMeteorMeta', function () {

View File

@@ -2,7 +2,7 @@
import assert from 'assert'
const rewire = require('rewire')
const getRelativePath = rewire('../../../../dist/util/internal/getRelativePath.js')
const getRelativePath = rewire('../../../../dist/util/meta/getRelativePath.js')
getRelativePath.__set__('getRootPath', function (filename) {
if (filename === '/Users/anon/git/meteor-project/client/file.js') {

View File

@@ -2,7 +2,7 @@
import assert from 'assert'
const rewire = require('rewire')
const getRootPath = rewire('../../../../dist/util/internal/getRootPath.js')
const getRootPath = rewire('../../../../dist/util/meta/getRootPath.js')
getRootPath.__set__('findOneUpwards', function (filename) {
if (filename === '/Users/anon/git/meteor-project/file.js') {

View File

@@ -2,7 +2,7 @@
import assert from 'assert'
const rewire = require('rewire')
const isMeteorProject = rewire('../../../../dist/util/internal/isMeteorProject')
const isMeteorProject = rewire('../../../../dist/util/meta/isMeteorProject')
isMeteorProject.__set__('pathExists', {
sync: function (path) {

View File

@@ -1,7 +1,7 @@
/* eslint-env mocha */
import assert from 'assert'
import stripPathPrefix from '../../../../dist/util/internal/stripPathPrefix.js'
import stripPathPrefix from '../../../../dist/util/meta/stripPathPrefix.js'
describe('stripPathPrefix', function () {
it('strips path correctly', function () {